> On 24-Jan-2024, at 6:30 PM, Naveen Yerramneni <naveen.yerramn...@nutanix.com> > wrote: > > > >> On 24-Jan-2024, at 8:59 AM, Numan Siddique <num...@ovn.org> wrote: >> >> On Tue, Jan 23, 2024 at 8:02 PM Naveen Yerramneni >> <naveen.yerramn...@nutanix.com> wrote: >>> >>> >>> >>>> On 16-Jan-2024, at 2:30 AM, Numan Siddique <num...@ovn.org> wrote: >>>> >>>> On Tue, Dec 12, 2023 at 1:05 PM Naveen Yerramneni >>>> <naveen.yerramn...@nutanix.com> wrote: >>>>> >>>>> This patch contains changes to enable DHCP Relay Agent support for >>>>> overlay subnets. >>>>> >>>>> 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(<lrp_ip>,<dhcp_server_ip>);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> >>>> >>>> Hi Naveen, >>>> >>>> Thanks for the patch. Sorry for the delayed response. >>>> >>>> I've a few comments. >>>> >>>> 1. Regarding the newly added Table - DHCP_Relay in NB DB and the >>>> newly added columns in Logical_Switch and >>>> Logical_Router table. >>>> >>>> I don't think there is a need to add the new table DHCP_Relay >>>> since it only stores the dhcp relay agent server ip. >>>> Also it could complicate the northd incremental processing. >>>> >>>> If for example we have below logical switches and router >>>> >>>> ovn-nbctl lr-add R1 >>>> ovn-nbctl ls-add sw0 >>>> ovn-nbctl ls-add sw1 >>>> ovn-nbctl ls-add sw-ext >>>> ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 >>>> ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 >>>> ovn-nbctl lrp-add R1 rp-ext 00:00:02:01:02:03 172.16.1.254/24 >>>> >>>> ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ >>>> type=router options:router-port=rp-sw0 \ >>>> -- lsp-set-addresses sw0-rp router >>>> >>>> ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \ >>>> type=router options:router-port=rp-sw1 \ >>>> -- lsp-set-addresses sw1-rp router >>>> >>>> I'd suggest doing something like below to enable this feature. >>>> >>>> ovn-nbctl set Logical_Switch_Port sw0-rp options:dhcp_relay=true >>>> ovn-nbctl set Logical_Switch_Port sw1-rp options:dhcp_relay=true >>>> >>>> (Make sure that only one logical switch port of type router can >>>> have this flag - dhcp_relay set >>>> for a given logical switch and document this limitation.) >>> >>> Ack. This suggestion looks good. >>> >>>> ovn-nbctl set Logical_Router_port rp-sw0 options:dhcp_relay_ip=172.16.1.1 >>>> ovn-nbctl set Logical_Router_port rp-sw1 options:dhcp_relay_ip=172.16.1.1 >>>> >>>> Let me know if there are any limitations with this. >>> >>> The reason why I added new table is , it would be useful in future if we add >>> additional options (like setting hop count in DHCP header, etc) to DHCP >>> relay >>> functionality. What do you recommend if we have to add more options >>> In future ? >> >> I see. If there is a possibility of adding more options, then having >> a separate table makes sense. >> I'd suggest to add the options column to the DHCP_Relay table even if >> this patch presently is not using >> any. This would help in upgrades. >> >> But I don't think there is a need to add a new column in the logical >> switch port table to enable dhcp realy. >> >> Thanks >> Numan > > Sure, I will add options column to DHCP_Relay column. > I will use options:dhcp_relay for LSP instead of new column as you suggested. > > Thanks, > Naveen >
Hi Numan, I started working on your comments. Regd options:dhcp_relay for LSP: Since DHCP relay is applicable at logical switch level (for entire subnet). I am thinking what if we add options:dhcp_relay with value as string (lsp name of port type router) to Logical Switch table ? Please let me know your thoughts on this. > >> >>> >>> >>> >>>> 2. Regarding the newly added actions - dhcp_relay_req() and >>>> dhcp_relay_resp_fwd(). >>>> Both of these actions are encoded as OVS controller action with >>>> pause enabled. >>>> Which means ovs-vswitchd has to freeze the flow translation and >>>> resume the flow translation >>>> once the ovn-controller resumes it. But the functions >>>> pinctrl_handle_dhcp_relay_req() >>>> and pinctrl_handle_dhcp_relay_resp_fwd() do not resume the packet >>>> if the packet >>>> has some errors. This is wrong. Otherwise vswitchd will never thaw the >>>> frozen translation. >>>> >>>> You can see the existing OVN actions - put_dhcp_opts() and few others >>>> which >>>> use controller action with pause. In such actions, the result of >>>> these actions >>>> are stored in a register bit (i.e if put_dhcp_opts() was successful or >>>> not) >>>> and in the next stage we take a decision based on the result. >>>> >>>> For the action dhcp_relay_req(relay_ip, server_ip), I don't >>>> think you should use the pause flag. >>>> Also in this action the argument server_ip is never used in the >>>> function pinctrl_handle_dhcp_relay_req() >>>> other than to just log. >>>> >>>> I'd suggest you do something like this: >>>> >>>> table=3 (lr_in_ip_input ), priority=110 , match=(inport == >>>> "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src >>>> == 68 && udp.dst == 67), >>>> action=(dhcp_relay_req { ip4.src = 192.168.1.1; ip4.dst = >>>> 172.16.1.1; udp.src = 67; dhcp_header.giaddr = <relay_ip>; >>>> next(pipeline=ingress,table=S_ROUTER_IN_UNSNAT); /* DHCP_RELAY_REQ */ >>>> } >>>> >>>> dhcp_relay_req action would get translated into a controller >>>> action with pause=false and all the inner actions of this are encoded >>>> as >>>> normal actions and stored in the userdata of controller action. >>>> Please see icmp4_error {} as an example. >>>> Add a new OVN field 'dhcp_header.giaddr' which gets translated as >>>> controller action with pause flag set. >>>> Please see the existing OVN field - icmp4.frag_mtu as an example >>>> and see this commit for reference [1] >>>> When encoding this new OVN field, store the relay_ip in the >>>> userdata buffer and in pinctrl.c >>>> get the relay_ip value and store it in the dhcp header field. >>>> >>>> >>>> For the action dhcp_relay_resp_fwd, I'd suggest something like below: >>>> >>>> table=17 (lr_in_dhcp_relay_resp_chk), priority=110 , >>>> match=(ip4.src == 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == >>>> 67 && udp.dst == 67), >>>> action=(reg0[0] = dhcp_relay_resp_chk(dhcp_header.giaddr == >>>> <relay_ip>); next;) >>>> table=17 (lr_in_dhcp_relay_resp), priority=110 , match=(ip4.src >>>> == 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == >>>> 67 && reg0[0] == 1), >>>> action=(ip4.src = 192.168.1.1; udp.dst = 68; outport = "lrp1"; >>>> output; /* DHCP_RELAY_RESP */) >>>> >>>> I used reg0[0] as an example. You may need to check the free >>>> register bit and use it. >>>> >>>> You need to encode dhcp_relay_resp_chk as controller action with >>>> pause=true, and store the relay_ip in the userdata buffer. >>>> And in pinctrl.c check that 'dhcp_header.giaddr == relay_ip' >>>> or not. If so, set the result register bit to 1, else to 0. >>>> >>>> Let me know if you've any questions. >>>> >>> >>> Ack. Thanks for the suggestions and detailed explanation. >>> Before implementation I had referred to icmp4_error and native dhcp_server >>> flows >>> but I had slight misunderstanding about pause flag. >>> Regd dhcp_relay_req: I think it might be better to implement two stage processing for dhcp_relay_req similar to dhcp_relay_resp (something like below). This will avoid multiple OVN actions/fields if we update additional fields (like hop count in DHCP header) in future. table=3 (lr_in_ip_input ), priority=110 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(regx[y] = dhcp_relay_req_chk(192.168.1.1,172.16.1.1);next; /* DHCP_RELAY_REQ */) table=4 (lr_in_ip_dhcp_req ), priority=110 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && regx[y]), action=(ip4.src=192.168.1.1;ip4.dst=172.16.1.1; udp.src=67;next; /* DHCP_RELAY_REQ */) table=4 (lr_in_ip_dhcp_req ), priority=1 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && regx[y] == 0), action=drop; Please let me know if you are fine with this approach. >>> >>>> 3. The newly added functions in pinctrl.c have a lot of repetitive >>>> code and it is very much similar to existing >>>> pinctrl_handle_put_dhcp_opts() >>>> Please see if the duplicate code can be avoided. >>> >>> Ack. >>> >>> >>> >>>> [1] - >>>> https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_ovn-2Dorg_ovn_commit_3d9fec3fd5992e1201b4d4fdf43f1f397e8d5ea1&d=DwIFaQ&c=s883GpUCOChKOHiocYtGcg&r=2PQjSDR7A28z1kXE1ptSm6X36oL_nCq1XxeEt7FkLmA&m=jUP6tr4FN6iSRj6v8rdyetsvEpT13QUHVMbw__3u6Sm7qAhyuu9tBdezdVmkqt0p&s=xAleLPNTzueIGuScqWZRp7ppL2D7bbjqLZc6q4xk3Rg&e= >>>> >>>> Thanks >>>> Numan >>>> >>>>> --- >>>>> controller/pinctrl.c | 441 ++++++++++++++++++++++++++++++++++++++++++ >>>>> include/ovn/actions.h | 26 +++ >>>>> lib/actions.c | 117 +++++++++++ >>>>> lib/ovn-l7.h | 1 + >>>>> northd/northd.c | 177 ++++++++++++++++- >>>>> ovn-nb.ovsschema | 25 ++- >>>>> ovn-nb.xml | 28 +++ >>>>> tests/atlocal.in | 3 + >>>>> tests/ovn-northd.at | 41 +++- >>>>> tests/ovn.at | 12 +- >>>>> tests/system-ovn.at | 150 ++++++++++++++ >>>>> utilities/ovn-trace.c | 28 +++ >>>>> 12 files changed, 1032 insertions(+), 17 deletions(-) >>>>> >>>>> diff --git a/controller/pinctrl.c b/controller/pinctrl.c >>>>> index 5a35d56f6..45240f01d 100644 >>>>> --- a/controller/pinctrl.c >>>>> +++ b/controller/pinctrl.c >>>>> @@ -1897,6 +1897,437 @@ 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; >>>>> + /* Server Identifier */ >>>>> + case OVN_DHCP_OPT_CODE_SERVER_ID: >>>>> + 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; >>>>> + if (udp->udp_csum) { >>>>> + udp->udp_csum = recalc_csum32(udp->udp_csum, >>>>> + 0, dhcp_data->giaddr); >>>>> + } >>>>> + 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; >>>>> + /* Server Identifier */ >>>>> + case OVN_DHCP_OPT_CODE_SERVER_ID: >>>>> + 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( >>>>> @@ -3203,6 +3634,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 49cfe0624..47d41b90f 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 >>>>> @@ -750,6 +760,22 @@ enum action_opcode { >>>>> >>>>> /* multicast group split buffer action. */ >>>>> ACTION_OPCODE_MG_SPLIT_BUF, >>>>> + >>>>> + /* "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 a73fe1a1e..69df428c6 100644 >>>>> --- a/lib/actions.c >>>>> +++ b/lib/actions.c >>>>> @@ -2629,6 +2629,118 @@ 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) >>>>> +{ >>>>> + /* 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) >>>>> +{ >>>>> + /* 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 +5563,11 @@ 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 07dffb15a..7ac831fae 100644 >>>>> --- a/northd/northd.c >>>>> +++ b/northd/northd.c >>>>> @@ -181,11 +181,13 @@ 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, >>>>> \ >>>>> @@ -9610,6 +9612,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, >>>>> @@ -10181,6 +10257,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)) { >>>>> @@ -14458,6 +14541,86 @@ 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; >>>>> + /* currently not supporting custom port */ >>>>> + 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, >>>>> @@ -15673,6 +15836,8 @@ 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" >>>>> @@ -16154,6 +16319,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); >>>>> @@ -16183,6 +16349,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 b2e0993e0..6863d52cd 100644 >>>>> --- a/ovn-nb.ovsschema >>>>> +++ b/ovn-nb.ovsschema >>>>> @@ -1,7 +1,7 @@ >>>>> { >>>>> "name": "OVN_Northbound", >>>>> - "version": "7.2.0", >>>>> - "cksum": "1069338687 34162", >>>>> + "version": "7.3.0", >>>>> + "cksum": "2325497400 35185", >>>>> "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 fcb1c6ecc..dc20892e1 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,11 @@ 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 +4296,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/tests/atlocal.in b/tests/atlocal.in >>>>> index 63d891b89..32d1c374e 100644 >>>>> --- a/tests/atlocal.in >>>>> +++ b/tests/atlocal.in >>>>> @@ -187,6 +187,9 @@ fi >>>>> # Set HAVE_DHCPD >>>>> find_command dhcpd >>>>> >>>>> +# Set HAVE_DHCLIENT >>>>> +find_command dhclient >>>>> + >>>>> # Set HAVE_BFDD_BEACON >>>>> find_command bfdd-beacon >>>>> >>>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at >>>>> index 19e4f1263..4d8c9ff26 100644 >>>>> --- a/tests/ovn-northd.at >>>>> +++ b/tests/ovn-northd.at >>>>> @@ -8786,9 +8786,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 >>>>> @@ -10966,3 +10966,38 @@ Status: active >>>>> >>>>> AT_CLEANUP >>>>> ]) >>>>> + >>>>> +OVN_FOR_EACH_NORTHD_NO_HV([ >>>>> +AT_SETUP([check DHCP RELAY AGENT]) >>>>> +ovn_start NORTHD_TYPE >>>>> + >>>>> +check ovn-nbctl ls-add ls0 >>>>> +check ovn-nbctl lsp-add ls0 ls0-port1 >>>>> +check ovn-nbctl lsp-set-addresses ls0-port1 02:00:00:00:00:10 >>>>> +check ovn-nbctl lr-add lr0 >>>>> +check ovn-nbctl lrp-add lr0 lrp1 02:00:00:00:00:01 192.168.1.1/24 >>>>> +check ovn-nbctl lsp-add ls0 lrp1-attachment >>>>> +check ovn-nbctl lsp-set-type lrp1-attachment router >>>>> +check ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02 >>>>> +check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1 >>>>> +check ovn-nbctl lrp-add lr0 lrp-ext 02:00:00:00:00:02 192.168.2.1/24 >>>>> + >>>>> +dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1) >>>>> +check ovn-nbctl set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay >>>>> +rp_uuid=$(ovn-nbctl --bare --colum=_uuid list logical_router_port lrp1) >>>>> +check ovn-nbctl set Logical_Switch ls0 dhcp_relay_port=$rp_uuid >>>>> + >>>>> +check ovn-nbctl --wait=sb sync >>>>> + >>>>> +ovn-sbctl lflow-list > lflows >>>>> +AT_CAPTURE_FILE([lflows]) >>>>> + >>>>> +AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], >>>>> [0], [dnl >>>>> + table=??(lr_in_ip_input ), priority=110 , match=(inport == "lrp1" >>>>> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && >>>>> udp.dst == 67), >>>>> action=(dhcp_relay_req(192.168.1.1,172.16.1.1);ip4.src=192.168.1.1;ip4.dst=172.16.1.1;udp.src=67;next; >>>>> /* DHCP_RELAY_REQ */) >>>>> + table=??(lr_in_ip_input ), priority=110 , match=(ip4.src == >>>>> 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), >>>>> action=(next;/* DHCP_RELAY_RESP */) >>>>> + table=??(lr_in_dhcp_relay_resp_fwd), priority=110 , match=(ip4.src == >>>>> 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), >>>>> action=(dhcp_relay_resp_fwd(192.168.1.1,172.16.1.1);ip4.src=192.168.1.1;udp.dst=68;outport="lrp1";output; >>>>> /* DHCP_RELAY_RESP */) >>>>> + table=??(ls_in_l2_lkup ), priority=100 , match=(inport == >>>>> "ls0-port1" && eth.src == 02:00:00:00:00:10 && ip4.src == 0.0.0.0 && >>>>> ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), >>>>> action=(eth.dst=02:00:00:00:00:01;outport="lrp1-attachment";next;/* >>>>> DHCP_RELAY_REQ */) >>>>> +]) >>>>> + >>>>> +AT_CLEANUP >>>>> +]) >>>>> diff --git a/tests/ovn.at b/tests/ovn.at >>>>> index e8c79512b..839c07ce2 100644 >>>>> --- a/tests/ovn.at >>>>> +++ b/tests/ovn.at >>>>> @@ -21905,7 +21905,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 >>>>> ]) >>>>> >>>>> @@ -28964,7 +28964,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 >>>>> @@ -29089,7 +29089,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 >>>>> @@ -29586,7 +29586,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 >>>>> ]) >>>>> >>>>> @@ -29605,13 +29605,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/tests/system-ovn.at b/tests/system-ovn.at >>>>> index 7b9daba0d..591933a95 100644 >>>>> --- a/tests/system-ovn.at >>>>> +++ b/tests/system-ovn.at >>>>> @@ -12032,3 +12032,153 @@ as >>>>> OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d >>>>> /connection dropped.*/d"]) >>>>> AT_CLEANUP >>>>> + >>>>> +OVN_FOR_EACH_NORTHD([ >>>>> +AT_SETUP([DHCP RELAY AGENT]) >>>>> +AT_SKIP_IF([test $HAVE_DHCPD = no]) >>>>> +AT_SKIP_IF([test $HAVE_DHCLIENT = no]) >>>>> +AT_SKIP_IF([test $HAVE_TCPDUMP = no]) >>>>> +ovn_start >>>>> +OVS_TRAFFIC_VSWITCHD_START() >>>>> + >>>>> +ADD_BR([br-int]) >>>>> +ADD_BR([br-ext]) >>>>> + >>>>> +ovs-ofctl add-flow br-ext action=normal >>>>> +# Set external-ids in br-int needed for ovn-controller >>>>> +ovs-vsctl \ >>>>> + -- set Open_vSwitch . external-ids:system-id=hv1 \ >>>>> + -- set Open_vSwitch . >>>>> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >>>>> + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ >>>>> + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >>>>> + -- set bridge br-int fail-mode=secure >>>>> other-config:disable-in-band=true >>>>> + >>>>> +# Start ovn-controller >>>>> +start_daemon ovn-controller >>>>> + >>>>> +ADD_NAMESPACES(sw01) >>>>> +ADD_VETH(sw01, sw01, br-int, "0", "f0:00:00:01:02:03") >>>>> +ADD_NAMESPACES(sw11) >>>>> +ADD_VETH(sw11, sw11, br-int, "0", "f0:00:00:02:02:03") >>>>> +ADD_NAMESPACES(server) >>>>> +ADD_VETH(s1, server, br-ext, "172.16.1.1/24", "f0:00:00:01:02:05", \ >>>>> + "172.16.1.254") >>>>> + >>>>> +check ovn-nbctl lr-add R1 >>>>> + >>>>> +check ovn-nbctl ls-add sw0 >>>>> +check ovn-nbctl ls-add sw1 >>>>> +check ovn-nbctl ls-add sw-ext >>>>> + >>>>> +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 >>>>> +check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 >>>>> +check ovn-nbctl lrp-add R1 rp-ext 00:00:02:01:02:03 172.16.1.254/24 >>>>> + >>>>> +dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1) >>>>> +check ovn-nbctl set Logical_Router_port rp-sw0 dhcp_relay=$dhcp_relay >>>>> +check ovn-nbctl set Logical_Router_port rp-sw1 dhcp_relay=$dhcp_relay >>>>> +check ovn-nbctl lrp-set-gateway-chassis rp-ext hv1 >>>>> + >>>>> +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ >>>>> + type=router options:router-port=rp-sw0 \ >>>>> + -- lsp-set-addresses sw0-rp router >>>>> +check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \ >>>>> + type=router options:router-port=rp-sw1 \ >>>>> + -- lsp-set-addresses sw1-rp router >>>>> + >>>>> +rp_uuid=$(ovn-nbctl --bare --colum=_uuid list logical_router_port rp-sw0) >>>>> +check ovn-nbctl set Logical_Switch sw0 dhcp_relay_port=$rp_uuid >>>>> +rp_uuid=$(ovn-nbctl --bare --colum=_uuid list logical_router_port rp-sw1) >>>>> +check ovn-nbctl set Logical_Switch sw1 dhcp_relay_port=$rp_uuid >>>>> + >>>>> +check ovn-nbctl lsp-add sw-ext ext-rp -- set Logical_Switch_Port ext-rp \ >>>>> + type=router options:router-port=rp-ext \ >>>>> + -- lsp-set-addresses ext-rp router >>>>> +check ovn-nbctl lsp-add sw-ext lnet \ >>>>> + -- lsp-set-addresses lnet unknown \ >>>>> + -- lsp-set-type lnet localnet \ >>>>> + -- lsp-set-options lnet network_name=phynet >>>>> + >>>>> +check ovn-nbctl lsp-add sw0 sw01 \ >>>>> + -- lsp-set-addresses sw01 "f0:00:00:01:02:03" >>>>> + >>>>> +check ovn-nbctl lsp-add sw1 sw11 \ >>>>> + -- lsp-set-addresses sw11 "f0:00:00:02:02:03" >>>>> + >>>>> +AT_CHECK([ovs-vsctl set Open_vSwitch . >>>>> external-ids:ovn-bridge-mappings=phynet:br-ext]) >>>>> + >>>>> +OVN_POPULATE_ARP >>>>> + >>>>> +check ovn-nbctl --wait=hv sync >>>>> + >>>>> +DHCP_TEST_DIR="/tmp/dhcp-test" >>>>> +rm -rf $DHCP_TEST_DIR >>>>> +mkdir $DHCP_TEST_DIR >>>>> +cat > $DHCP_TEST_DIR/dhcpd.conf <<EOF >>>>> +subnet 172.16.1.0 netmask 255.255.255.0 { >>>>> +} >>>>> +subnet 192.168.1.0 netmask 255.255.255.0 { >>>>> + range 192.168.1.10 192.168.1.10; >>>>> + option routers 192.168.1.1; >>>>> + option broadcast-address 192.168.1.255; >>>>> + default-lease-time 60; >>>>> + max-lease-time 120; >>>>> +} >>>>> +subnet 192.168.2.0 netmask 255.255.255.0 { >>>>> + range 192.168.2.10 192.168.2.10; >>>>> + option routers 192.168.2.1; >>>>> + option broadcast-address 192.168.2.255; >>>>> + default-lease-time 60; >>>>> + max-lease-time 120; >>>>> +} >>>>> +EOF >>>>> +cat > $DHCP_TEST_DIR/dhclien.conf <<EOF >>>>> +timeout 2 >>>>> +EOF >>>>> + >>>>> +touch $DHCP_TEST_DIR/dhcpd.leases >>>>> +chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases >>>>> +chmod 775 $DHCP_TEST_DIR >>>>> +chmod 664 $DHCP_TEST_DIR/dhcpd.leases >>>>> + >>>>> + >>>>> +NETNS_DAEMONIZE([server], [dhcpd -4 -f -cf $DHCP_TEST_DIR/dhcpd.conf s1 >>>>> > dhcpd.log 2>&1], [dhcpd.pid]) >>>>> + >>>>> +NS_CHECK_EXEC([server], [tcpdump -l -nvv -i s1 udp > pkt.pcap >>>>> 2>tcpdump_err &]) >>>>> +OVS_WAIT_UNTIL([grep "listening" tcpdump_err]) >>>>> +on_exit 'kill $(pidof tcpdump)' >>>>> + >>>>> +NS_CHECK_EXEC([sw01], [dhclient -1 -q -lf >>>>> $DHCP_TEST_DIR/dhclient-sw01.lease -pf $DHCP_TEST_DIR/dhclient-sw01.pid >>>>> -cf $DHCP_TEST_DIR/dhclien.conf sw01]) >>>>> +NS_CHECK_EXEC([sw11], [dhclient -1 -q -lf >>>>> $DHCP_TEST_DIR/dhclient-sw11.lease -pf $DHCP_TEST_DIR/dhclient-sw11.pid >>>>> -cf $DHCP_TEST_DIR/dhclien.conf sw11]) >>>>> + >>>>> +OVS_WAIT_UNTIL([ >>>>> + total_pkts=$(cat pkt.pcap | wc -l) >>>>> + test ${total_pkts} -ge 8 >>>>> +]) >>>>> + >>>>> +on_exit 'kill `cat $DHCP_TEST_DIR/dhclient-sw01.pid` && >>>>> +kill `cat $DHCP_TEST_DIR/dhclient-sw11.pid` && rm -rf $DHCP_TEST_DIR' >>>>> + >>>>> +NS_CHECK_EXEC([sw01], [ip addr show sw01 | grep -oP >>>>> '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl >>>>> +192.168.1.10 >>>>> +]) >>>>> +NS_CHECK_EXEC([sw11], [ip addr show sw11 | grep -oP >>>>> '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl >>>>> +192.168.2.10 >>>>> +]) >>>>> +OVS_APP_EXIT_AND_WAIT([ovn-controller]) >>>>> + >>>>> +as ovn-sb >>>>> +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) >>>>> + >>>>> +as ovn-nb >>>>> +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) >>>>> + >>>>> +as northd >>>>> +OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) >>>>> + >>>>> +as >>>>> +OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d >>>>> +/failed to query port patch-.*/d >>>>> +/.*terminating with signal 15.*/d"]) >>>>> +AT_CLEANUP >>>>> +]) >>>>> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c >>>>> index 0b86eae7b..ae9dd77de 100644 >>>>> --- a/utilities/ovn-trace.c >>>>> +++ b/utilities/ovn-trace.c >>>>> @@ -2328,6 +2328,25 @@ execute_put_dhcp_opts(const struct ovnact_put_opts >>>>> *pdo, >>>>> execute_put_opts(pdo, name, uflow, super); >>>>> } >>>>> >>>>> +static void >>>>> +execute_dhcpv4_relay_resp_fwd(const struct ovnact_dhcp_relay *dr, >>>>> + const char *name, struct flow *uflow, >>>>> + struct ovs_list *super) >>>>> +{ >>>>> + ovntrace_node_append( >>>>> + super, OVNTRACE_NODE_ERROR, >>>>> + "/* We assume that this packet is DHCPOFFER or DHCPACK and " >>>>> + "DHCP broadcast flag is set. Dest IP is set to broadcast. " >>>>> + "Dest MAC is set to broadcast but in real network this is >>>>> unicast " >>>>> + "which is extracted from DHCP header. */"); >>>>> + >>>>> + /* Assume DHCP broadcast flag is set */ >>>>> + uflow->nw_dst = 0xFFFFFFFF; >>>>> + /* Dest MAC is set to broadcast but in real network this is unicast >>>>> */ >>>>> + struct eth_addr bcast_mac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; >>>>> + uflow->dl_dst = bcast_mac; >>>>> +} >>>>> + >>>>> static void >>>>> execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo, >>>>> const char *name, struct flow *uflow, >>>>> @@ -3215,6 +3234,15 @@ trace_actions(const struct ovnact *ovnacts, size_t >>>>> ovnacts_len, >>>>> "put_dhcpv6_opts", uflow, super); >>>>> break; >>>>> >>>>> + case OVNACT_DHCPV4_RELAY_REQ: >>>>> + /* Nothing to do for tracing. */ >>>>> + break; >>>>> + >>>>> + case OVNACT_DHCPV4_RELAY_RESP_FWD: >>>>> + >>>>> execute_dhcpv4_relay_resp_fwd(ovnact_get_DHCPV4_RELAY_RESP_FWD(a), >>>>> + "dhcp_relay_resp_fwd", uflow, super); >>>>> + break; >>>>> + >>>>> case OVNACT_PUT_ND_RA_OPTS: >>>>> execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a), >>>>> "put_nd_ra_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=jUP6tr4FN6iSRj6v8rdyetsvEpT13QUHVMbw__3u6Sm7qAhyuu9tBdezdVmkqt0p&s=jJ3kFCf5o6dc-gW8diGvfaIQVC0Gwhe2y5aJYZJo0Rk&e= >>> >>> _______________________________________________ >>> 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=Y0VulqBkhPSTIRvGcgPUyjhLDQiFRE2rJOS17q4U2rvbGvuTRX_KWc30pZQRDbFM&s=j60qqTajjKcQRA2_J_ZfXj1eIu5RmLsENUpdcQKh3PI&e= > > _______________________________________________ > dev mailing list > d...@openvswitch.org > https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.org_mailman_listinfo_ovs-2Ddev&d=DwIGaQ&c=s883GpUCOChKOHiocYtGcg&r=2PQjSDR7A28z1kXE1ptSm6X36oL_nCq1XxeEt7FkLmA&m=6ylfQ0EScd-bjMbFXhqTwjQMA0Mo8RWEiJGFuHPdQpQUVGX7EJROAcJYXsARR4gM&s=_b1yAjDOLFWiWXZph7kH-IZ2akA0WkpvOZxXboASDA8&e= _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev