On Fri, Nov 10, 2023 at 2:46 PM Naveen Yerramneni <naveen.yerramn...@nutanix.com> wrote: > > > > > On 10-Nov-2023, at 10:52 PM, Numan Siddique <num...@ovn.org> wrote: > > > > On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni > > <naveen.yerramn...@nutanix.com> wrote: > >> > >> This patch contains changes to enable DHCP Relay Agent support for > >> overlay subnets. > >> > >> NOTE: > >> ----- > >> - This patch has required changes to enable basic DHCP Relay > >> functionality for overlay subnets. Sending this for review to get the > >> initial feedback about the approach taken. > >> > >> POST RFC REVIEW > >> ---------------- > >> 1. Address review comments/suggestions > >> 2. Address TODOs > >> 3. Add unit tests > >> 4. Complete testing > >> > >> USE CASE: > >> ---------- > >> - Enable IP address assignment for overlay subnets from the > >> centralized DHCP server present in the underlay network. > >> > >> PREREQUISITES > >> -------------- > >> - Logical Router Port IP should be assigned (statically) from the > >> same overlay subnet which is managed by DHCP server. > >> - LRP IP is used for GIADRR field when relaying the DHCP packets and > >> also same IP needs to be configured as default gateway for the overlay > >> subnet. > >> - Overlay subnets managed by external DHCP server are expected to be > >> directly reachable from the underlay network. > >> > >> EXPECTED PACKET FLOW: > >> ---------------------- > >> Following is the expected packet flow inorder to support DHCP rleay > >> functionality in OVN. > >> 1. DHCP client originates DHCP discovery (broadcast). > >> 2. DHCP relay (running on the OVN) receives the broadcast and > >> forwards the packet to the DHCP server by converting it to unicast. While > >> forwarding the packet, it updates the GIADDR in DHCP header to its > >> interface IP on which DHCP packet is received. > >> 3. DHCP server uses GIADDR field to decide the IP address pool from > >> which IP has to be assigned and DHCP offer is sent to the same IP (GIADDR). > >> 4. DHCP relay agent forwards the offer to the client, it resets the > >> GIADDR field when forwarding the offer to the client. > >> 5. DHCP client sends DHCP request (broadcast) packet. > >> 6. DHCP relay (running on the OVN) receives the broadcast and > >> forwards the packet to the DHCP server by converting it to unicast. While > >> forwarding the packet, it updates the GIADDR in DHCP header to its > >> interface IP on which DHCP packet is received. > >> 7. DHCP Server sends the ACK packet. > >> 8. DHCP relay agent forwards the ACK packet to the client, it resets > >> the GIADDR field when forwarding the ACK to the client. > >> 9. All the future renew/release packets are directly exchanged > >> between DHCP client and DHCP server. > >> > >> OVN DHCP RELAY PACKET FLOW: > >> ---------------------------- > >> To add DHCP Relay support on OVN, we need to replicate all the behavior > >> described above using distributed logical switch and logical router. > >> At, highlevel packet flow is distributed among Logical Switch and > >> Logical Router on source node (where VM is deployed) and redirect > >> chassis(RC) node. > >> 1. Request packet gets processed on the source node where VM is > >> deployed and relays the packet to DHCP server. > >> 2. Response packet is first processed on RC node (which first > >> recieves the packet from underlay network). RC node forwards the packet to > >> the right node by filling in the dest MAC and IP. > >> > >> OVN Packet flow with DHCP relay is explained below. > >> 1. DHCP client (VM) sends the DHCP discover packet (broadcast). > >> 2. Logical switch converts the packet to L2 unicast by setting the > >> destination MAC to LRP's MAC > >> 3. Logical Router receives the packet and redirects it to the OVN > >> controller. > >> 4. OVN controller updates the required information(GIADDR) in the > >> DHCP payload after doing the required checks. If any check fails, packet > >> is dropped. > >> 5. Logical Router converts the packet to L3 unicast and forwards it > >> to the server. This packets gets routed like any other packet (via RC > >> node). > >> 6. Server replies with DHCP offer. > >> 7. RC node processes the DHCP offer and forwards it to the OVN > >> controller. > >> 8. OVN controller does sanity checks and updates the destination MAC > >> (available in DHCP header), destination IP (available in DHCP header), > >> resets GIADDR and reinjects the packet to datapath. > >> If any check fails, packet is dropped. > >> 9. Logical router updates the source IP and port and forwards the > >> packet to logical switch. > >> 10. Logical switch delivers the packet to the DHCP client. > >> 11. Similar steps are performed for Request and Ack packets. > >> 12. All the future renew/release packets are directly exchanged > >> between DHCP client and DHCP server > >> > >> NEW OVN ACTIONS > >> --------------- > >> > >> 1. dhcp_relay_req(<relay-ip>, <server-ip>) > >> - This action executes on the source node on which the DHCP > >> request originated. > >> - This action relays the DHCP request coming from client to the > >> server. Relay-ip is used to update GIADDR in the DHCP header. > >> 2. dhcp_relay_resp_fwd(<relay-ip>, <server-ip>) > >> - This action executes on the first node (RC node) which > >> processes the DHCP response from the server. > >> - This action updates the destination MAC and destination IP so > >> that the response can be forwarded to the appropriate node from which > >> request was originated. > >> - Relay-ip, server-ip are used to validate GIADDR and SERVER ID > >> in the DHCP payload. > >> > >> FLOWS > >> ----- > >> Following are the flows required for one overlay subnet. > >> > >> 1. table=27(ls_in_l2_lkup ), priority=100 , match=(inport == > >> <vm_port> && eth.src == <vm_mac> && ip4.src == 0.0.0.0 && ip4.dst == > >> 255.255.255.255 && udp.src == 68 && udp.dst == 67), > >> action=(eth.dst=<lrp_mac>;outport=<lrp-port>;next;/* DHCP_RELAY_REQ */) > >> 2. table=3 (lr_in_ip_input ), priority=110 , match=(inport == > >> <lrp_port> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src > >> == 68 && udp.dst == 67), > >> action=(dhcp_relay_req(<lrp_ip>,<dhcp_server_ip>);ip4.src=<lrp_ip>;ip4.dst=<dhcp_server_ip>;udp.src=67;next; > >> /* DHCP_RELAY_REQ */) > >> 3. table=3 (lr_in_ip_input ), priority=110 , match=(ip4.src == > >> <dhcp_server_ip> && ip4.dst ==<lrp_ip> && udp.src == 67 && udp.dst == 67), > >> action=(next;/* DHCP_RELAY_RESP */) > >> 4. table=17(lr_in_dhcp_relay_resp_fwd), priority=110 , > >> match=(ip4.src == <dhcp_server_ip> && ip4.dst == <lrp_ip> && udp.src == 67 > >> && udp.dst == 67), > >> action=(dhcp_relay_resp_fwd();ip4.src=<lrp_ip>;udp.dst=68;outport=<lrp_port>;output; > >> /* DHCP_RELAY_RESP */) > >> > >> NEW PIPELINE STAGES > >> ------------------- > >> Following stage is added for DHCP relay feature. Some of the flows are > >> fitted into the existing pipeline tages. > >> 1. lr_in_dhcp_relay_resp_fwd > >> - Forward teh DHCP response to the appropriate node > >> > >> NB SCHEMA CHANGES > >> ---------------- > >> 1. New DHCP_Relay table > >> "DHCP_Relay": { > >> "columns": { > >> "name": {"type": "string"}, > >> "servers": {"type": {"key": "string", > >> "min": 0, > >> "max": 1}}, > >> "external_ids": { > >> "type": {"key": "string", "value": "string", > >> "min": 0, "max": "unlimited"}}}, > >> "isRoot": true}, > >> 2. New column to Logical_Router_Port table > >> "dhcp_relay": {"type": {"key": {"type": "uuid", > >> "refTable": "DHCP_Relay", > >> "refType": "weak"}, > >> "min": 0, > >> "max": 1}}, > >> 3. New column to Logical_Switch_table > >> "dhcp_relay_port": {"type": {"key": {"type": "uuid", > >> "refTable": "Logical_Router_Port", > >> "refType": "weak"}, > >> "min": 0, > >> "max": 1}}}, > >> Commands to enable the feature: > >> ------------------------------ > >> - ovn-nbctl create DHCP_Relay servers=<ip> > >> - ovn-nbctl set Logical_Router_port <lrp_uuid> > >> dhcp_relay=<dhcp_relay_uuid> > >> - ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid> > >> > >> Example: > >> ------- > >> ovn-nbctl ls-add sw1 > >> ovn-nbctl lsp-add sw1 sw1-port1 > >> ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to > >> be specified when logical ports are created. > >> ovn-nbctl lr-add lr1 > >> ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP > >> is set in GIADDR field when relaying the DHCP requests to server. > >> ovn-nbctl lsp-add sw1 lr1-attachment > >> ovn-nbctl lsp-set-type lr1-attachment router > >> ovn-nbctl lsp-set-addresses lr1-attachment <MAC> > >> ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1 > >> ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP> > >> ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid> > >> ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid> > >> > >> Limitations: > >> ------------ > >> - All OVN features that needs IP address to be configured on logical > >> port (like proxy arp, etc) will not be supported for overlay subnets on > >> which DHCP relay is enabled. > >> > >> References: > >> ---------- > >> - rfc1541, rfc1542, rfc2131 > >> > >> Signed-off-by: Naveen Yerramneni <naveen.yerramn...@nutanix.com> > >> Co-authored-by: Huzaifa Calcuttawala <huzaif...@nutanix.com> > >> Signed-off-by: Huzaifa Calcuttawala <huzaif...@nutanix.com> > >> CC: Mary Manohar <mary.mano...@nutanix.com> > >> CC: Abhiram Sangana <sangana.abhi...@nutanix.com> > > > > > > Hi Naveen, > > > > I had a couple of questions in your first RFC patch. Can you please > > answer those ? Please see below > > > > > > 2. Can you please provide a few examples on how a logical port is > > created ? What address would be set for the logical port ? > > And once a VM gets IP using dhcp proxy, is this IP address > > stored in OVN Northbound db Logical_Switch_Port ? > > How does OVN learn about this mac-ip binding for a VM and forward > > the packet later for any E-W or N-S traffic ? > > We had updated the implementation to DHCP Relay Agent in this patch. > I have shared the example in this patch on how to enable the feature > and also prerequisites, limitations (copy-pasting them below) > > Example: > ------------ > ovn-nbctl ls-add sw1 > ovn-nbctl lsp-add sw1 sw1-port1 > ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to be > specified when logical ports are created. > ovn-nbctl lr-add lr1 > ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP is > set in GIADDR field when relaying the DHCP requests to server. > ovn-nbctl lsp-add sw1 lr1-attachment > ovn-nbctl lsp-set-type lr1-attachment router > ovn-nbctl lsp-set-addresses lr1-attachment <MAC> > ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1 > ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP> > ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid> > ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid> > > PREREQUISITES > -------------- > - Logical Router Port (LRP) IP should be assigned (statically) from the > same overlay subnet which is managed by DHCP server. > - LRP IP is used for GIADRR field when relaying the DHCP packets and also > same IP needs to be configured as default gateway for the overlay subnet. > - Overlay subnets managed by external DHCP server are expected to be > directly reachable from the underlay network. > > Limitations: > -------------- > - All OVN features that needs IP address to be configured on logical port > (like proxy arp, etc) will not be supported for overlay subnets on which DHCP > relay is enabled. > > IP address is NOT updated in the OVN northbound DB. > OVN would be learning the VM MAC by sending ARP requests if entry is not > found in Mac binding table for DHCP relay subnets. > > > 3. Is it possible to handle all this DHCP proxy in the logical switch > > pipeline itself ? In a typical deployment where DHCP proxy is used, > > Who does the DHCP proxy ? Is it the router ? >) > We had updated the implementation to DHCP Relay Agent in this patch. > Generally, DHCP Relay agent functionality is handled on Routers. > I thought about possibility of implementing DHCP relay agent functionality in > the logical switch pipeline but couldn’t find a way. One of the main reasons > is: > - DHCP server response messages (OFFER, ACK) are destined to LRP IP > (GIADDR field is set to LRP IP in the DHCP discovery/request packets) and > these packets first lands on RC node. > Response needs to be parsed first on the RC node (logical router > pipeline) in order find out which destination (using client MAC in DHCP > response payload) response has to be forwarded. > >
Thanks. No objections from me for this patch. Perhaps you can resubmit with the RFC tag removed. This would encourage more reviews. I'd strongly suggest adding system tests in system-ovn.at or system tests based on ovn-fake-multinode (multinode.at) system tests that can run a real dhcp relay server and test out the functionality. Thanks Numan > > > > Thanks > > Numan > > > > > >> --- > >> controller/pinctrl.c | 436 ++++++++++++++++++++++++++++++++++++++++++ > >> include/ovn/actions.h | 26 +++ > >> lib/actions.c | 114 +++++++++++ > >> lib/ovn-l7.h | 1 + > >> northd/northd.c | 174 ++++++++++++++++- > >> ovn-nb.ovsschema | 23 ++- > >> ovn-nb.xml | 27 +++ > >> ovs | 2 +- > >> tests/ovn-northd.at | 6 +- > >> tests/ovn.at | 12 +- > >> utilities/ovn-trace.c | 8 + > >> 11 files changed, 812 insertions(+), 17 deletions(-) > >> mode change 160000 => 120000 ovs > >> > >> diff --git a/controller/pinctrl.c b/controller/pinctrl.c > >> index 3c1cecfde..ee68d0088 100644 > >> --- a/controller/pinctrl.c > >> +++ b/controller/pinctrl.c > >> @@ -383,6 +383,7 @@ static void pinctrl_handle_put_fdb(const struct flow > >> *md, > >> const struct flow *headers) > >> OVS_REQUIRES(pinctrl_mutex); > >> > >> + > >> COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); > >> COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map); > >> COVERAGE_DEFINE(pinctrl_drop_controller_event); > >> @@ -1888,6 +1889,431 @@ is_dhcp_flags_broadcast(ovs_be16 flags) > >> return flags & htons(DHCP_BROADCAST_FLAG); > >> } > >> > >> + > >> +static const char *dhcp_msg_str[] = { > >> +[0] = "INVALID", > >> +[DHCP_MSG_DISCOVER] = "DISCOVER", > >> +[DHCP_MSG_OFFER] = "OFFER", > >> +[DHCP_MSG_REQUEST] = "REQUEST", > >> +[OVN_DHCP_MSG_DECLINE] = "DECLINE", > >> +[DHCP_MSG_ACK] = "ACK", > >> +[DHCP_MSG_NAK] = "NAK", > >> +[OVN_DHCP_MSG_RELEASE] = "RELEASE", > >> +[OVN_DHCP_MSG_INFORM] = "INFORM" > >> +}; > >> + > >> +static bool > >> +dhcp_relay_is_msg_type_supported(uint8_t msg_type) > >> +{ > >> + return (msg_type >= DHCP_MSG_DISCOVER && msg_type <= > >> OVN_DHCP_MSG_RELEASE); > >> +} > >> + > >> +static const char *dhcp_msg_str_get(uint8_t msg_type) > >> +{ > >> + if (!dhcp_relay_is_msg_type_supported(msg_type)) { > >> + return "INVALID"; > >> + } > >> + return dhcp_msg_str[msg_type]; > >> +} > >> + > >> +/* Called with in the pinctrl_handler thread context. */ > >> +static void > >> +pinctrl_handle_dhcp_relay_req( > >> + struct rconn *swconn, > >> + struct dp_packet *pkt_in, struct ofputil_packet_in *pin, > >> + struct ofpbuf *userdata, > >> + struct ofpbuf *continuation) > >> +{ > >> + enum ofp_version version = rconn_get_version(swconn); > >> + enum ofputil_protocol proto = > >> ofputil_protocol_from_ofp_version(version); > >> + struct dp_packet *pkt_out_ptr = NULL; > >> + > >> + /* Parse relay IP and server IP. */ > >> + ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip); > >> + ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip); > >> + if (!relay_ip || !server_ip) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: relay ip or server ip not > >> present in the userdata"); > >> + return; > >> + } > >> + > >> + /* Validate the DHCP request packet. > >> + * Format of the DHCP packet is > >> + * > >> ------------------------------------------------------------------------ > >> + *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP > >> OPTIONS(var len)| > >> + * > >> ------------------------------------------------------------------------ > >> + */ > >> + > >> + size_t in_l4_size = dp_packet_l4_size(pkt_in); > >> + const char *end = (char *)dp_packet_l4(pkt_in) + in_l4_size; > >> + const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in); > >> + if (!in_dhcp_ptr) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid or incomplete DHCP > >> packet received"); > >> + return; > >> + } > >> + > >> + const struct dhcp_header *in_dhcp_data > >> + = (const struct dhcp_header *) in_dhcp_ptr; > >> + in_dhcp_ptr += sizeof *in_dhcp_data; > >> + if (in_dhcp_ptr > end) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid or incomplete DHCP > >> packet received, " > >> + "bad data length"); > >> + return; > >> + } > >> + if (in_dhcp_data->op != DHCP_OP_REQUEST) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid opcode in the DHCP > >> packet: %d", > >> + in_dhcp_data->op); > >> + return; > >> + } > >> + > >> + /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP > >> + * options is the DHCP magic cookie followed by the actual DHCP > >> options. > >> + */ > >> + ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE); > >> + if (in_dhcp_ptr + sizeof magic_cookie > end || > >> + get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: magic cookie not present in > >> the packet"); > >> + return; > >> + } > >> + > >> + if (in_dhcp_data->giaddr) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: giaddr is already set"); > >> + return; > >> + } > >> + > >> + if (in_dhcp_data->htype != 0x1) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: packet is recieved with > >> unsupported hardware type"); > >> + return; > >> + } > >> + > >> + ovs_be32 *server_id_ptr = NULL; > >> + const uint8_t *in_dhcp_msg_type = NULL; > >> + > >> + in_dhcp_ptr += sizeof magic_cookie; > >> + ovs_be32 request_ip = in_dhcp_data->ciaddr; > >> + while (in_dhcp_ptr < end) { > >> + const struct dhcp_opt_header *in_dhcp_opt = > >> + (const struct dhcp_opt_header *)in_dhcp_ptr; > >> + if (in_dhcp_opt->code == DHCP_OPT_END) { > >> + break; > >> + } > >> + if (in_dhcp_opt->code == DHCP_OPT_PAD) { > >> + in_dhcp_ptr += 1; > >> + continue; > >> + } > >> + in_dhcp_ptr += sizeof *in_dhcp_opt; > >> + if (in_dhcp_ptr > end) { > >> + break; > >> + } > >> + in_dhcp_ptr += in_dhcp_opt->len; > >> + if (in_dhcp_ptr > end) { > >> + break; > >> + } > >> + > >> + switch (in_dhcp_opt->code) { > >> + case DHCP_OPT_MSG_TYPE: > >> + if (in_dhcp_opt->len == 1) { > >> + in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt); > >> + } > >> + break; > >> + case DHCP_OPT_REQ_IP: > >> + if (in_dhcp_opt->len == 4) { > >> + request_ip = > >> get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); > >> + } > >> + break; > >> + case OVN_DHCP_OPT_CODE_SERVER_ID: //Server Identifier > >> + if (in_dhcp_opt->len == 4) { > >> + server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt); > >> + } > >> + break; > >> + default: > >> + break; > >> + } > >> + } > >> + > >> + /* Check whether the DHCP Message Type (opt 53) is present or not */ > >> + if (!in_dhcp_msg_type) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: missing message type"); > >> + return; > >> + } > >> + > >> + /* Relay the DHCP request packet */ > >> + uint16_t new_l4_size = in_l4_size; > >> + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; > >> + > >> + struct dp_packet pkt_out; > >> + dp_packet_init(&pkt_out, new_packet_size); > >> + dp_packet_clear(&pkt_out); > >> + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); > >> + pkt_out_ptr = &pkt_out; > >> + > >> + /* Copy the L2 and L3 headers from the pkt_in as they would remain > >> same*/ > >> + dp_packet_put( > >> + &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); > >> + > >> + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; > >> + pkt_out.l2_pad_size = pkt_in->l2_pad_size; > >> + pkt_out.l3_ofs = pkt_in->l3_ofs; > >> + pkt_out.l4_ofs = pkt_in->l4_ofs; > >> + > >> + struct ip_header *out_ip = dp_packet_l3(&pkt_out); > >> + > >> + struct udp_header *udp = dp_packet_put( > >> + &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); > >> + > >> + struct dhcp_header *dhcp_data = dp_packet_put( > >> + &pkt_out, dp_packet_pull(pkt_in, new_l4_size-UDP_HEADER_LEN), > >> new_l4_size-UDP_HEADER_LEN); > >> + dhcp_data->giaddr = *relay_ip; > >> + //TODO: incremental checkcum > >> + if (udp->udp_csum) { > >> + udp->udp_csum = 0; > >> + uint32_t p_csum = packet_csum_pseudoheader(out_ip); > >> + udp->udp_csum = csum_finish(csum_continue(p_csum, udp, > >> new_l4_size)); > >> + } > >> + pin->packet = dp_packet_data(&pkt_out); > >> + pin->packet_len = dp_packet_size(&pkt_out); > >> + > >> + /* Log the DHCP message. */ > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); > >> + const struct eth_header *l2 = dp_packet_eth(&pkt_out); > >> + VLOG_INFO_RL(&rl, "DHCP_RELAY_REQ:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT > >> + " XID:%u" > >> + " REQ_IP:"IP_FMT > >> + " GIADDR:"IP_FMT > >> + " SERVER_ADDR:"IP_FMT, > >> + dhcp_msg_str_get(*in_dhcp_msg_type), > >> + ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), > >> ntohl(dhcp_data->xid), > >> + IP_ARGS(request_ip), IP_ARGS(dhcp_data->giaddr), > >> + IP_ARGS(*server_ip)); > >> + queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); > >> + if (pkt_out_ptr) { > >> + dp_packet_uninit(pkt_out_ptr); > >> + } > >> +} > >> + > >> +/* Called with in the pinctrl_handler thread context. */ > >> +static void > >> +pinctrl_handle_dhcp_relay_resp_fwd( > >> + struct rconn *swconn, > >> + struct dp_packet *pkt_in, struct ofputil_packet_in *pin, > >> + struct ofpbuf *userdata, > >> + struct ofpbuf *continuation) > >> +{ > >> + enum ofp_version version = rconn_get_version(swconn); > >> + enum ofputil_protocol proto = > >> ofputil_protocol_from_ofp_version(version); > >> + struct dp_packet *pkt_out_ptr = NULL; > >> + > >> + /* Parse relay IP and server IP. */ > >> + ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip); > >> + ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip); > >> + if (!relay_ip || !server_ip) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: relay ip or server ip not > >> present in the userdata"); > >> + return; > >> + } > >> + > >> + /* Validate the DHCP request packet. > >> + * Format of the DHCP packet is > >> + * > >> ------------------------------------------------------------------------ > >> + *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP > >> OPTIONS(var len)| > >> + * > >> ------------------------------------------------------------------------ > >> + */ > >> + > >> + size_t in_l4_size = dp_packet_l4_size(pkt_in); > >> + const char *end = (char *)dp_packet_l4(pkt_in) + in_l4_size; > >> + const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in); > >> + if (!in_dhcp_ptr) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid or incomplete > >> packet received"); > >> + return; > >> + } > >> + > >> + const struct dhcp_header *in_dhcp_data > >> + = (const struct dhcp_header *) in_dhcp_ptr; > >> + in_dhcp_ptr += sizeof *in_dhcp_data; > >> + if (in_dhcp_ptr > end) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid or incomplete > >> packet received, " > >> + "bad data length"); > >> + return; > >> + } > >> + if (in_dhcp_data->op != DHCP_OP_REPLY) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid opcode in the > >> packet: %d", > >> + in_dhcp_data->op); > >> + return; > >> + } > >> + > >> + /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP > >> + * options is the DHCP magic cookie followed by the actual DHCP > >> options. > >> + */ > >> + ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE); > >> + if (in_dhcp_ptr + sizeof magic_cookie > end || > >> + get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: magic cookie not present > >> in the packet"); > >> + return; > >> + } > >> + > >> + if (!in_dhcp_data->giaddr) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: giaddr is not set in > >> request"); > >> + return; > >> + } > >> + ovs_be32 giaddr = in_dhcp_data->giaddr; > >> + > >> + ovs_be32 *server_id_ptr = NULL; > >> + ovs_be32 lease_time = 0; > >> + const uint8_t *in_dhcp_msg_type = NULL; > >> + > >> + in_dhcp_ptr += sizeof magic_cookie; > >> + while (in_dhcp_ptr < end) { > >> + const struct dhcp_opt_header *in_dhcp_opt = > >> + (const struct dhcp_opt_header *)in_dhcp_ptr; > >> + if (in_dhcp_opt->code == DHCP_OPT_END) { > >> + break; > >> + } > >> + if (in_dhcp_opt->code == DHCP_OPT_PAD) { > >> + in_dhcp_ptr += 1; > >> + continue; > >> + } > >> + in_dhcp_ptr += sizeof *in_dhcp_opt; > >> + if (in_dhcp_ptr > end) { > >> + break; > >> + } > >> + in_dhcp_ptr += in_dhcp_opt->len; > >> + if (in_dhcp_ptr > end) { > >> + break; > >> + } > >> + > >> + switch (in_dhcp_opt->code) { > >> + case DHCP_OPT_MSG_TYPE: > >> + if (in_dhcp_opt->len == 1) { > >> + in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt); > >> + } > >> + break; > >> + case OVN_DHCP_OPT_CODE_SERVER_ID: //Server Identifier > >> + if (in_dhcp_opt->len == 4) { > >> + server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt); > >> + } > >> + break; > >> + case OVN_DHCP_OPT_CODE_LEASE_TIME: > >> + if (in_dhcp_opt->len == 4) { > >> + lease_time = > >> get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); > >> + } > >> + break; > >> + default: > >> + break; > >> + } > >> + } > >> + > >> + /* Check whether the DHCP Message Type (opt 53) is present or not */ > >> + if (!in_dhcp_msg_type) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing message type"); > >> + return; > >> + } > >> + > >> + if (!server_id_ptr) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing server identifier"); > >> + return; > >> + } > >> + > >> + if (*server_id_ptr != *server_ip) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: server identifier mismatch"); > >> + return; > >> + } > >> + > >> + if (giaddr != *relay_ip) { > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > >> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: giaddr mismatch"); > >> + return; > >> + } > >> + > >> + > >> + /* Update destination MAC & IP so that the packet is forward to the > >> + * right destination node. > >> + */ > >> + uint16_t new_l4_size = in_l4_size; > >> + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; > >> + > >> + struct dp_packet pkt_out; > >> + dp_packet_init(&pkt_out, new_packet_size); > >> + dp_packet_clear(&pkt_out); > >> + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); > >> + pkt_out_ptr = &pkt_out; > >> + > >> + /* Copy the L2 and L3 headers from the pkt_in as they would remain > >> same*/ > >> + struct eth_header *eth = dp_packet_put( > >> + &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); > >> + > >> + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; > >> + pkt_out.l2_pad_size = pkt_in->l2_pad_size; > >> + pkt_out.l3_ofs = pkt_in->l3_ofs; > >> + pkt_out.l4_ofs = pkt_in->l4_ofs; > >> + > >> + struct udp_header *udp = dp_packet_put( > >> + &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); > >> + > >> + struct dhcp_header *dhcp_data = dp_packet_put( > >> + &pkt_out, dp_packet_pull(pkt_in, new_l4_size-UDP_HEADER_LEN), > >> new_l4_size-UDP_HEADER_LEN); > >> + memcpy(ð->eth_dst, dhcp_data->chaddr, sizeof(eth->eth_dst)); > >> + > >> + > >> + /* Send a broadcast IP frame when BROADCAST flag is set. */ > >> + struct ip_header *out_ip = dp_packet_l3(&pkt_out); > >> + ovs_be32 ip_dst; > >> + ovs_be32 ip_dst_orig = get_16aligned_be32(&out_ip->ip_dst); > >> + if (!is_dhcp_flags_broadcast(dhcp_data->flags)) { > >> + ip_dst = dhcp_data->yiaddr; > >> + } else { > >> + ip_dst = htonl(0xffffffff); > >> + } > >> + put_16aligned_be32(&out_ip->ip_dst, ip_dst); > >> + out_ip->ip_csum = recalc_csum32(out_ip->ip_csum, > >> + ip_dst_orig, ip_dst); > >> + if (udp->udp_csum) > >> + { > >> + udp->udp_csum = recalc_csum32(udp->udp_csum, > >> + ip_dst_orig, ip_dst); > >> + } > >> + /* Reset giaddr */ > >> + dhcp_data->giaddr = htonl(0x0); > >> + if (udp->udp_csum) > >> + { > >> + udp->udp_csum = recalc_csum32(udp->udp_csum, > >> + giaddr, 0); > >> + } > >> + pin->packet = dp_packet_data(&pkt_out); > >> + pin->packet_len = dp_packet_size(&pkt_out); > >> + > >> + /* Log the DHCP message. */ > >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); > >> + const struct eth_header *l2 = dp_packet_eth(&pkt_out); > >> + VLOG_INFO_RL(&rl, "DHCP_RELAY_RESP_FWD:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT > >> + " XID:%u" > >> + " YIADDR:"IP_FMT > >> + " GIADDR:"IP_FMT > >> + " SERVER_ADDR:"IP_FMT, > >> + dhcp_msg_str_get(*in_dhcp_msg_type), > >> + ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), > >> ntohl(dhcp_data->xid), > >> + IP_ARGS(dhcp_data->yiaddr), > >> + IP_ARGS(giaddr), IP_ARGS(*server_id_ptr)); > >> + queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); > >> + if (pkt_out_ptr) { > >> + dp_packet_uninit(pkt_out_ptr); > >> + } > >> +} > >> + > >> /* Called with in the pinctrl_handler thread context. */ > >> static void > >> pinctrl_handle_put_dhcp_opts( > >> @@ -3158,6 +3584,16 @@ process_packet_in(struct rconn *swconn, const > >> struct ofp_header *msg) > >> ovs_mutex_unlock(&pinctrl_mutex); > >> break; > >> > >> + case ACTION_OPCODE_DHCP_RELAY_REQ: > >> + pinctrl_handle_dhcp_relay_req(swconn, &packet, &pin, > >> + &userdata, &continuation); > >> + break; > >> + > >> + case ACTION_OPCODE_DHCP_RELAY_RESP_FWD: > >> + pinctrl_handle_dhcp_relay_resp_fwd(swconn, &packet, &pin, > >> + &userdata, &continuation); > >> + break; > >> + > >> case ACTION_OPCODE_PUT_DHCP_OPTS: > >> pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &headers, > >> &userdata, &continuation); > >> diff --git a/include/ovn/actions.h b/include/ovn/actions.h > >> index 04bb6ffd0..e97ae83b8 100644 > >> --- a/include/ovn/actions.h > >> +++ b/include/ovn/actions.h > >> @@ -95,6 +95,8 @@ struct collector_set_ids; > >> OVNACT(LOOKUP_ND_IP, ovnact_lookup_mac_bind_ip) \ > >> OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \ > >> OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ > >> + OVNACT(DHCPV4_RELAY_REQ, ovnact_dhcp_relay) \ > >> + OVNACT(DHCPV4_RELAY_RESP_FWD, ovnact_dhcp_relay) \ > >> OVNACT(SET_QUEUE, ovnact_set_queue) \ > >> OVNACT(DNS_LOOKUP, ovnact_result) \ > >> OVNACT(LOG, ovnact_log) \ > >> @@ -387,6 +389,14 @@ struct ovnact_put_opts { > >> size_t n_options; > >> }; > >> > >> +/* OVNACT_DHCP_RELAY. */ > >> +struct ovnact_dhcp_relay { > >> + struct ovnact ovnact; > >> + int family; > >> + ovs_be32 relay_ipv4; > >> + ovs_be32 server_ipv4; > >> +}; > >> + > >> /* Valid arguments to SET_QUEUE action. > >> * > >> * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should > >> @@ -747,6 +757,22 @@ enum action_opcode { > >> > >> /* activation_strategy_rarp() */ > >> ACTION_OPCODE_ACTIVATION_STRATEGY_RARP, > >> + > >> + /* "dhcp_relay_req(relay_ip, server_ip)". > >> + * > >> + * Arguments follow the action_header, in this format: > >> + * - The 32-bit DHCP relay IP. > >> + * - The 32-bit DHCP server IP. > >> + */ > >> + ACTION_OPCODE_DHCP_RELAY_REQ, > >> + > >> + /* "dhcp_relay_resp_fwd(relay_ip, server_ip)". > >> + * > >> + * Arguments follow the action_header, in this format: > >> + * - The 32-bit DHCP relay IP. > >> + * - The 32-bit DHCP server IP. > >> + */ > >> + ACTION_OPCODE_DHCP_RELAY_RESP_FWD, > >> }; > >> > >> /* Header. */ > >> diff --git a/lib/actions.c b/lib/actions.c > >> index b880927b6..4b63722c5 100644 > >> --- a/lib/actions.c > >> +++ b/lib/actions.c > >> @@ -2629,6 +2629,116 @@ ovnact_controller_event_free(struct > >> ovnact_controller_event *event) > >> free_gen_options(event->options, event->n_options); > >> } > >> > >> +static void > >> +format_DHCPV4_RELAY_REQ(const struct ovnact_dhcp_relay *dhcp_relay, > >> struct ds *s) > >> +{ > >> + ds_put_format(s, "dhcp_relay_req("IP_FMT","IP_FMT");", > >> + IP_ARGS(dhcp_relay->relay_ipv4), > >> + IP_ARGS(dhcp_relay->server_ipv4)); > >> +} > >> + > >> +static void > >> +parse_dhcp_relay_req(struct action_context *ctx, > >> + struct ovnact_dhcp_relay *dhcp_relay) > >> +{ > >> + //lexer_get(ctx->lexer); /* Skip dhcp_relay_req. */ > >> + lexer_force_match(ctx->lexer, LEX_T_LPAREN); > >> + > >> + /* Parse relay ip and server ip. */ > >> + if (ctx->lexer->token.format == LEX_F_IPV4) { > >> + dhcp_relay->family = AF_INET; > >> + dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4; > >> + lexer_get(ctx->lexer); > >> + lexer_match(ctx->lexer, LEX_T_COMMA); > >> + if (ctx->lexer->token.format == LEX_F_IPV4) { > >> + dhcp_relay->family = AF_INET; > >> + dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4; > >> + lexer_get(ctx->lexer); > >> + } > >> + else > >> + { > >> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server > >> ip"); > >> + return; > >> + } > >> + } > >> + else > >> + { > >> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and > >> server ips"); > >> + return; > >> + } > >> + lexer_force_match(ctx->lexer, LEX_T_RPAREN); > >> +} > >> + > >> +static void > >> +encode_DHCPV4_RELAY_REQ(const struct ovnact_dhcp_relay *dhcp_relay, > >> + const struct ovnact_encode_params *ep, > >> + struct ofpbuf *ofpacts) > >> +{ > >> + size_t oc_offset = > >> encode_start_controller_op(ACTION_OPCODE_DHCP_RELAY_REQ, > >> + true, ep->ctrl_meter_id, > >> + ofpacts); > >> + ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4, > >> sizeof(dhcp_relay->relay_ipv4)); > >> + ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4, > >> sizeof(dhcp_relay->server_ipv4)); > >> + encode_finish_controller_op(oc_offset, ofpacts); > >> +} > >> + > >> +static void > >> +format_DHCPV4_RELAY_RESP_FWD(const struct ovnact_dhcp_relay *dhcp_relay, > >> struct ds *s) > >> +{ > >> + ds_put_format(s, "dhcp_relay_resp("IP_FMT","IP_FMT");", > >> + IP_ARGS(dhcp_relay->relay_ipv4), > >> + IP_ARGS(dhcp_relay->server_ipv4)); > >> +} > >> + > >> +static void > >> +parse_dhcp_relay_resp_fwd(struct action_context *ctx, > >> + struct ovnact_dhcp_relay *dhcp_relay) > >> +{ > >> + //lexer_get(ctx->lexer); /* Skip dhcp_relay_resp. */ > >> + lexer_force_match(ctx->lexer, LEX_T_LPAREN); > >> + > >> + /* Parse relay ip and server ip. */ > >> + if (ctx->lexer->token.format == LEX_F_IPV4) { > >> + dhcp_relay->family = AF_INET; > >> + dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4; > >> + lexer_get(ctx->lexer); > >> + lexer_match(ctx->lexer, LEX_T_COMMA); > >> + if (ctx->lexer->token.format == LEX_F_IPV4) { > >> + dhcp_relay->family = AF_INET; > >> + dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4; > >> + lexer_get(ctx->lexer); > >> + } > >> + else > >> + { > >> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server > >> ip"); > >> + return; > >> + } > >> + } > >> + else > >> + { > >> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and > >> server ips"); > >> + return; > >> + } > >> + lexer_force_match(ctx->lexer, LEX_T_RPAREN); > >> +} > >> + > >> +static void > >> +encode_DHCPV4_RELAY_RESP_FWD(const struct ovnact_dhcp_relay *dhcp_relay, > >> + const struct ovnact_encode_params *ep, > >> + struct ofpbuf *ofpacts) > >> +{ > >> + size_t oc_offset = > >> encode_start_controller_op(ACTION_OPCODE_DHCP_RELAY_RESP_FWD, > >> + true, ep->ctrl_meter_id, > >> + ofpacts); > >> + ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4, > >> sizeof(dhcp_relay->relay_ipv4)); > >> + ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4, > >> sizeof(dhcp_relay->server_ipv4)); > >> + encode_finish_controller_op(oc_offset, ofpacts); > >> +} > >> + > >> +static void ovnact_dhcp_relay_free(struct ovnact_dhcp_relay *dhcp_relay > >> OVS_UNUSED) > >> +{ > >> +} > >> + > >> static void > >> parse_put_opts(struct action_context *ctx, const struct expr_field *dst, > >> struct ovnact_put_opts *po, const struct hmap *gen_opts, > >> @@ -5451,6 +5561,10 @@ parse_action(struct action_context *ctx) > >> parse_sample(ctx); > >> } else if (lexer_match_id(ctx->lexer, "mac_cache_use")) { > >> ovnact_put_MAC_CACHE_USE(ctx->ovnacts); > >> + } else if (lexer_match_id(ctx->lexer, "dhcp_relay_req")) { > >> + parse_dhcp_relay_req(ctx, > >> ovnact_put_DHCPV4_RELAY_REQ(ctx->ovnacts)); > >> + } else if (lexer_match_id(ctx->lexer, "dhcp_relay_resp_fwd")) { > >> + parse_dhcp_relay_resp_fwd(ctx, > >> ovnact_put_DHCPV4_RELAY_RESP_FWD(ctx->ovnacts)); > >> } else { > >> lexer_syntax_error(ctx->lexer, "expecting action"); > >> } > >> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h > >> index ad514a922..e08581123 100644 > >> --- a/lib/ovn-l7.h > >> +++ b/lib/ovn-l7.h > >> @@ -69,6 +69,7 @@ struct gen_opts_map { > >> */ > >> #define OVN_DHCP_OPT_CODE_NETMASK 1 > >> #define OVN_DHCP_OPT_CODE_LEASE_TIME 51 > >> +#define OVN_DHCP_OPT_CODE_SERVER_ID 54 > >> #define OVN_DHCP_OPT_CODE_T1 58 > >> #define OVN_DHCP_OPT_CODE_T2 59 > >> > >> diff --git a/northd/northd.c b/northd/northd.c > >> index f8b046d83..654c23da5 100644 > >> --- a/northd/northd.c > >> +++ b/northd/northd.c > >> @@ -181,11 +181,12 @@ enum ovn_stage { > >> PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 14, > >> "lr_in_ip_routing_ecmp") \ > >> PIPELINE_STAGE(ROUTER, IN, POLICY, 15, "lr_in_policy") > >> \ > >> PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 16, "lr_in_policy_ecmp") > >> \ > >> - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 17, "lr_in_arp_resolve") > >> \ > >> - PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 18, "lr_in_chk_pkt_len") > >> \ > >> - PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 19, "lr_in_larger_pkts") > >> \ > >> - PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 20, "lr_in_gw_redirect") > >> \ > >> - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 21, "lr_in_arp_request") > >> \ > >> + PIPELINE_STAGE(ROUTER, IN, DHCP_RELAY_RESP_FWD, 17, > >> "lr_in_dhcp_relay_resp_fwd") \ > >> + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 18, "lr_in_arp_resolve") > >> \ > >> + PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 19, "lr_in_chk_pkt_len") > >> \ > >> + PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 20, "lr_in_larger_pkts") > >> \ > >> + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 21, "lr_in_gw_redirect") > >> \ > >> + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 22, "lr_in_arp_request") > >> \ > >> \ > >> /* Logical router egress stages. */ \ > >> PIPELINE_STAGE(ROUTER, OUT, CHECK_DNAT_LOCAL, 0, > >> \ > >> @@ -9626,6 +9627,80 @@ build_dhcpv6_options_flows(struct ovn_port *op, > >> ds_destroy(&match); > >> } > >> > >> +static void > >> +build_lswitch_dhcp_relay_flows(struct ovn_port *op, > >> + const struct hmap *lr_ports, > >> + const struct hmap *lflows, > >> + const struct shash *meter_groups OVS_UNUSED) > >> +{ > >> + if (op->nbrp || !op->nbsp) { > >> + return; > >> + } > >> + //consider only ports attached to VMs > >> + if (strcmp(op->nbsp->type, "")) { > >> + return; > >> + } > >> + > >> + if (!op->od || !op->od->n_router_ports || > >> + !op->od->nbs || !op->od->nbs->dhcp_relay_port) { > >> + return; > >> + } > >> + > >> + struct ds match = DS_EMPTY_INITIALIZER; > >> + struct ds action = DS_EMPTY_INITIALIZER; > >> + struct nbrec_logical_router_port *lrp = op->od->nbs->dhcp_relay_port; > >> + struct ovn_port *rp = ovn_port_find(lr_ports, lrp->name); > >> + > >> + if (!rp || !rp->nbrp || !rp->nbrp->dhcp_relay) { > >> + return; > >> + } > >> + > >> + struct ovn_port *sp = NULL; > >> + struct nbrec_dhcp_relay *dhcp_relay = rp->nbrp->dhcp_relay; > >> + > >> + for (int i=0; i<op->od->n_router_ports; i++) { > >> + struct ovn_port *sp_tmp = op->od->router_ports[i]; > >> + if (sp_tmp->peer == rp) { > >> + sp = sp_tmp; > >> + break; > >> + } > >> + } > >> + if (!sp) { > >> + return; > >> + } > >> + > >> + char *server_ip_str = NULL; > >> + uint16_t port; > >> + int addr_family; > >> + struct in6_addr server_ip; > >> + > >> + if (!ip_address_and_port_from_lb_key(dhcp_relay->servers, > >> &server_ip_str, > >> + &server_ip, &port, > >> &addr_family)) { > >> + return; > >> + } > >> + > >> + if (server_ip_str == NULL) { > >> + return; > >> + } > >> + > >> + ds_put_format( > >> + &match, "inport == %s && eth.src == %s && " > >> + "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && " > >> + "udp.src == 68 && udp.dst == 67", > >> + op->json_key, op->lsp_addrs[0].ea_s); > >> + ds_put_format(&action, > >> + "eth.dst=%s;outport=%s;next;/* DHCP_RELAY_REQ */", > >> + rp->lrp_networks.ea_s,sp->json_key); > >> + ovn_lflow_add_with_hint__(lflows, op->od, > >> + S_SWITCH_IN_L2_LKUP, 100, > >> + ds_cstr(&match), > >> + ds_cstr(&action), > >> + op->key, > >> + NULL, > >> + &lrp->header_); > >> + free(server_ip_str); > >> +} > >> + > >> static void > >> build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op, > >> const struct ovn_port > >> *port, > >> @@ -10197,6 +10272,13 @@ build_lswitch_dhcp_options_and_response(struct > >> ovn_port *op, > >> return; > >> } > >> > >> + if (op->od && op->od->nbs > >> + && op->od->nbs->dhcp_relay_port) { > >> + /* Don't add the DHCP server flows if DHCP Relay is enabled on the > >> + * logical switch. */ > >> + return; > >> + } > >> + > >> bool is_external = lsp_is_external(op->nbsp); > >> if (is_external && (!op->od->n_localnet_ports || > >> !op->nbsp->ha_chassis_group)) { > >> @@ -14452,6 +14534,85 @@ build_dhcpv6_reply_flows_for_lrouter_port( > >> } > >> } > >> > >> +static void > >> +build_dhcp_relay_flows_for_lrouter_port( > >> + struct ovn_port *op, struct hmap *lflows, > >> + struct ds *match) > >> +{ > >> + if (!op->nbrp || !op->nbrp->dhcp_relay) { > >> + return; > >> + } > >> + struct nbrec_dhcp_relay *dhcp_relay = op->nbrp->dhcp_relay; > >> + if (!dhcp_relay->servers) { > >> + return; > >> + } > >> + > >> + int addr_family; > >> + uint16_t port; > >> + char *server_ip_str = NULL; > >> + struct in6_addr server_ip; > >> + > >> + if (!ip_address_and_port_from_lb_key(dhcp_relay->servers, > >> &server_ip_str, > >> + &server_ip, &port, > >> &addr_family)) { > >> + return; > >> + } > >> + > >> + if (server_ip_str == NULL) { > >> + return; > >> + } > >> + > >> + struct ds dhcp_action = DS_EMPTY_INITIALIZER; > >> + ds_clear(match); > >> + ds_put_format( > >> + match, "inport == %s && " > >> + "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && " > >> + "udp.src == 68 && udp.dst == 67", > >> + op->json_key); > >> + ds_put_format(&dhcp_action, > >> + "dhcp_relay_req(%s,%s);" > >> + "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_RELAY_REQ > >> */", > >> + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str, > >> + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str); > >> + > >> + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, > >> + ds_cstr(match), ds_cstr(&dhcp_action), > >> + &op->nbrp->header_); > >> + > >> + ds_clear(match); > >> + ds_clear(&dhcp_action); > >> + > >> + ds_put_format( > >> + match, "ip4.src == %s && ip4.dst == %s && " > >> + "udp.src == 67 && udp.dst == 67", > >> + server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s); > >> + ds_put_format(&dhcp_action, "next;/* DHCP_RELAY_RESP */"); > >> + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, > >> + ds_cstr(match), ds_cstr(&dhcp_action), > >> + &op->nbrp->header_); > >> + > >> + ds_clear(match); > >> + ds_clear(&dhcp_action); > >> + > >> + ds_put_format( > >> + match, "ip4.src == %s && ip4.dst == %s && " > >> + "udp.src == 67 && udp.dst == 67", > >> + server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s); > >> + ds_put_format(&dhcp_action, > >> + "dhcp_relay_resp_fwd(%s,%s);ip4.src=%s;udp.dst=68;" > >> + "outport=%s;output; /* DHCP_RELAY_RESP */", > >> + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str, > >> + op->lrp_networks.ipv4_addrs[0].addr_s, op->json_key); > >> + ovn_lflow_add_with_hint(lflows, op->od, > >> S_ROUTER_IN_DHCP_RELAY_RESP_FWD, > >> + 110, > >> + ds_cstr(match), ds_cstr(&dhcp_action), > >> + &op->nbrp->header_); > >> + > >> + ds_clear(match); > >> + ds_clear(&dhcp_action); > >> + > >> + free(server_ip_str); > >> +} > >> + > >> static void > >> build_ipv6_input_flows_for_lrouter_port( > >> struct ovn_port *op, struct hmap *lflows, > >> @@ -15667,6 +15828,7 @@ build_lrouter_nat_defrag_and_lb(struct > >> ovn_datapath *od, struct hmap *lflows, > >> ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, 0, "1", "next;"); > >> ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); > >> ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); > >> + ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_RESP_FWD, 0, "1", > >> "next;"); > >> > >> const char *ct_flag_reg = features->ct_no_masked_label > >> ? "ct_mark" > >> @@ -16148,6 +16310,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct > >> ovn_port *op, > >> build_lswitch_dhcp_options_and_response(op, lflows, meter_groups); > >> build_lswitch_external_port(op, lflows); > >> build_lswitch_ip_unicast_lookup(op, lflows, actions, match); > >> + build_lswitch_dhcp_relay_flows(op, lr_ports, lflows, meter_groups); > >> > >> /* Build Logical Router Flows. */ > >> build_ip_routing_flows_for_router_type_lsp(op, lr_ports, lflows); > >> @@ -16177,6 +16340,7 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct > >> ovn_port *op, > >> build_egress_delivery_flows_for_lrouter_port(op, lsi->lflows, > >> &lsi->match, > >> &lsi->actions); > >> build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, > >> &lsi->match); > >> + build_dhcp_relay_flows_for_lrouter_port(op, lsi->lflows, &lsi->match); > >> build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows, > >> &lsi->match, &lsi->actions, > >> lsi->meter_groups); > >> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema > >> index e103360ec..7d7e680e0 100644 > >> --- a/ovn-nb.ovsschema > >> +++ b/ovn-nb.ovsschema > >> @@ -1,7 +1,7 @@ > >> { > >> "name": "OVN_Northbound", > >> "version": "7.1.0", > >> - "cksum": "217362582 33949", > >> + "cksum": "1797404008 34972", > >> "tables": { > >> "NB_Global": { > >> "columns": { > >> @@ -89,7 +89,12 @@ > >> "type": {"key": {"type": "uuid", > >> "refTable": "Forwarding_Group", > >> "refType": "strong"}, > >> - "min": 0, "max": "unlimited"}}}, > >> + "min": 0, "max": "unlimited"}}, > >> + "dhcp_relay_port": {"type": {"key": {"type": "uuid", > >> + "refTable": > >> "Logical_Router_Port", > >> + "refType": "weak"}, > >> + "min": 0, > >> + "max": 1}}}, > >> "isRoot": true}, > >> "Logical_Switch_Port": { > >> "columns": { > >> @@ -436,6 +441,11 @@ > >> "ipv6_prefix": {"type": {"key": "string", > >> "min": 0, > >> "max": "unlimited"}}, > >> + "dhcp_relay": {"type": {"key": {"type": "uuid", > >> + "refTable": "DHCP_Relay", > >> + "refType": "weak"}, > >> + "min": 0, > >> + "max": 1}}, > >> "external_ids": { > >> "type": {"key": "string", "value": "string", > >> "min": 0, "max": "unlimited"}}, > >> @@ -529,6 +539,15 @@ > >> "type": {"key": "string", "value": "string", > >> "min": 0, "max": "unlimited"}}}, > >> "isRoot": true}, > >> + "DHCP_Relay": { > >> + "columns": { > >> + "servers": {"type": {"key": "string", > >> + "min": 0, > >> + "max": 1}}, > >> + "external_ids": { > >> + "type": {"key": "string", "value": "string", > >> + "min": 0, "max": "unlimited"}}}, > >> + "isRoot": true}, > >> "Connection": { > >> "columns": { > >> "target": {"type": "string"}, > >> diff --git a/ovn-nb.xml b/ovn-nb.xml > >> index 1de0c3041..ca3085e93 100644 > >> --- a/ovn-nb.xml > >> +++ b/ovn-nb.xml > >> @@ -608,6 +608,11 @@ > >> Please see the <ref table="DNS"/> table. > >> </column> > >> > >> + <column name="dhcp_relay_port"> > >> + This column defines the <ref table="Logical_Router_Port"/> on which > >> + DHCP relay is enabled. > >> + </column> > >> + > >> <column name="forwarding_groups"> > >> Groups a set of logical port endpoints for traffic going out of the > >> logical switch. > >> @@ -2980,6 +2985,10 @@ or > >> port has all ingress and egress traffic dropped. > >> </column> > >> > >> + <column name="dhcp_relay"> > >> + This column is used to enabled DHCP Relay. Please refer to <ref > >> table="DHCP_Relay"/> table. > >> + </column> > >> + > >> <group title="Distributed Gateway Ports"> > >> <p> > >> Gateways, as documented under <code>Gateways</code> in the OVN > >> @@ -4286,6 +4295,24 @@ or > >> </group> > >> </table> > >> > >> + <table name="DHCP_Relay" title="DHCP Relay"> > >> + <p> > >> + OVN implements native DHCPv4 relay support which caters to the > >> common > >> + use case of relaying the DHCP requests to external DHCP server. > >> + </p> > >> + > >> + <column name="servers"> > >> + <p> > >> + The DHCPv4 server IP address. > >> + </p> > >> + </column> > >> + <group title="Common Columns"> > >> + <column name="external_ids"> > >> + See <em>External IDs</em> at the beginning of this document. > >> + </column> > >> + </group> > >> + </table> > >> + > >> <table name="Connection" title="OVSDB client connections."> > >> <p> > >> Configuration for a database connection to an Open vSwitch database > >> diff --git a/ovs b/ovs > >> deleted file mode 160000 > >> index 1d78a3f31..000000000 > >> --- a/ovs > >> +++ /dev/null > >> @@ -1 +0,0 @@ > >> -Subproject commit 1d78a3f3164a6bf651b34f52812f38655b28a9ce > >> diff --git a/ovs b/ovs > >> new file mode 120000 > >> index 000000000..7be8871aa > >> --- /dev/null > >> +++ b/ovs > >> @@ -0,0 +1 @@ > >> +/home/naveen.yerramneni/development/ghub/ovs > >> \ No newline at end of file > >> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at > >> index 196fe01fb..7f4ef6152 100644 > >> --- a/tests/ovn-northd.at > >> +++ b/tests/ovn-northd.at > >> @@ -8774,9 +8774,9 @@ ovn-nbctl --wait=sb set logical_router_port R1-PUB > >> options:redirect-type=bridged > >> ovn-sbctl dump-flows R1 > R1flows > >> AT_CAPTURE_FILE([R1flows]) > >> > >> -AT_CHECK([grep "lr_in_arp_resolve" R1flows | grep priority=90 | sort], > >> [0], [dnl > >> - table=17(lr_in_arp_resolve ), priority=90 , match=(outport == > >> "R1-PUB" && ip4.src == 10.0.0.3 && is_chassis_resident("S0-P0")), > >> action=(get_arp(outport, reg0); next;) > >> - table=17(lr_in_arp_resolve ), priority=90 , match=(outport == > >> "R1-PUB" && ip6.src == 1000::3 && is_chassis_resident("S0-P0")), > >> action=(get_nd(outport, xxreg0); next;) > >> +AT_CHECK([grep "lr_in_arp_resolve" R1flows | grep priority=90 | sed > >> 's/table=../table=??/' | sort], [0], [dnl > >> + table=??(lr_in_arp_resolve ), priority=90 , match=(outport == > >> "R1-PUB" && ip4.src == 10.0.0.3 && is_chassis_resident("S0-P0")), > >> action=(get_arp(outport, reg0); next;) > >> + table=??(lr_in_arp_resolve ), priority=90 , match=(outport == > >> "R1-PUB" && ip6.src == 1000::3 && is_chassis_resident("S0-P0")), > >> action=(get_nd(outport, xxreg0); next;) > >> ]) > >> > >> AT_CLEANUP > >> diff --git a/tests/ovn.at b/tests/ovn.at > >> index 637d92bed..2306d7e7d 100644 > >> --- a/tests/ovn.at > >> +++ b/tests/ovn.at > >> @@ -21865,7 +21865,7 @@ eth_dst=00000000ff01 > >> ip_src=$(ip_to_hex 10 0 0 10) > >> ip_dst=$(ip_to_hex 172 168 0 101) > >> send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 > >> 0000000000000000000000 > >> -AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int > >> metadata=0x$lr0_dp_key | awk '/table=28, n_packets=1, n_bytes=45/{print > >> $7" "$8}'],[0],[dnl > >> +AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int > >> metadata=0x$lr0_dp_key | awk '/table=29, n_packets=1, n_bytes=45/{print > >> $7" "$8}'],[0],[dnl > >> priority=80,ip,reg15=0x$lr0_public_dp_key,metadata=0x$lr0_dp_key,nw_src=10.0.0.10 > >> actions=drop > >> ]) > >> > >> @@ -28918,7 +28918,7 @@ AT_CHECK([ > >> grep "priority=100" | \ > >> grep -c > >> "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_MARK\\[[16..31\\]]))" > >> > >> - grep table=25 hv${hv}flows | \ > >> + grep table=26 hv${hv}flows | \ > >> grep "priority=200" | \ > >> grep -c > >> "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST" > >> done; :], [0], [dnl > >> @@ -29043,7 +29043,7 @@ AT_CHECK([ > >> grep "priority=100" | \ > >> grep -c > >> "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_MARK\\[[16..31\\]]))" > >> > >> - grep table=25 hv${hv}flows | \ > >> + grep table=26 hv${hv}flows | \ > >> grep "priority=200" | \ > >> grep -c > >> "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST" > >> done; :], [0], [dnl > >> @@ -29540,7 +29540,7 @@ if test X"$1" = X"DGP"; then > >> else > >> prio=2 > >> fi > >> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, > >> n_packets=1,.* > >> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 > >> actions=drop" -c], [0], [dnl > >> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, > >> n_packets=1,.* > >> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 > >> actions=drop" -c], [0], [dnl > >> 1 > >> ]) > >> > >> @@ -29559,13 +29559,13 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | > >> grep "actions=controller" | grep > >> > >> if test X"$1" = X"DGP"; then > >> # The packet dst should be resolved once for E/W centralized NAT > >> purpose. > >> - AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, > >> n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} > >> actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl > >> + AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, > >> n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} > >> actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl > >> 1 > >> ]) > >> fi > >> > >> # The packet should've been finally dropped in the lr_in_arp_resolve stage. > >> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, > >> n_packets=2,.* > >> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 > >> actions=drop" -c], [0], [dnl > >> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, > >> n_packets=2,.* > >> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 > >> actions=drop" -c], [0], [dnl > >> 1 > >> ]) > >> OVN_CLEANUP([hv1]) > >> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c > >> index 0b86eae7b..3253fc11f 100644 > >> --- a/utilities/ovn-trace.c > >> +++ b/utilities/ovn-trace.c > >> @@ -3205,6 +3205,14 @@ trace_actions(const struct ovnact *ovnacts, size_t > >> ovnacts_len, > >> super); > >> break; > >> > >> + case OVNACT_DHCPV4_RELAY_REQ: > >> + /* TODO. */ > >> + break; > >> + > >> + case OVNACT_DHCPV4_RELAY_RESP_FWD: > >> + /* TODO. */ > >> + break; > >> + > >> case OVNACT_PUT_DHCPV4_OPTS: > >> execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a), > >> "put_dhcp_opts", uflow, super); > >> -- > >> 2.36.6 > >> > >> _______________________________________________ > >> dev mailing list > >> d...@openvswitch.org > >> https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.org_mailman_listinfo_ovs-2Ddev&d=DwIFaQ&c=s883GpUCOChKOHiocYtGcg&r=2PQjSDR7A28z1kXE1ptSm6X36oL_nCq1XxeEt7FkLmA&m=5HAP0DT1kSP0Mh0H3I_grVNxhQBb42Y9IcgoumjojsaDHxLsgS8YUb6JZ8rBXJgA&s=iswZEbE_2lK-oi4pFB8Q6vFZkiQbcGU2F_5U2pfATSA&e= > > _______________________________________________ > dev mailing list > d...@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev