> On 15-Nov-2023, at 3:01 AM, Numan Siddique <[email protected]> wrote:
> 
> On Fri, Nov 10, 2023 at 2:46 PM Naveen Yerramneni
> <[email protected]> wrote:
>> 
>> 
>> 
>>> On 10-Nov-2023, at 10:52 PM, Numan Siddique <[email protected]> wrote:
>>> 
>>> On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
>>> <[email protected]> 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 <[email protected]>
>>>> Co-authored-by: Huzaifa Calcuttawala <[email protected]>
>>>> Signed-off-by: Huzaifa Calcuttawala <[email protected]>
>>>> CC: Mary Manohar <[email protected]>
>>>> CC: Abhiram Sangana <[email protected]>
>>> 
>>> 
>>> 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 !
I will resubmit the patch after adding the tests.

Thanks,
Naveen


> 
>>> 
>>> 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->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
>>>> [email protected]
>>>> 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
>> [email protected]
>> https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.org_mailman_listinfo_ovs-2Ddev&d=DwIFaQ&c=s883GpUCOChKOHiocYtGcg&r=2PQjSDR7A28z1kXE1ptSm6X36oL_nCq1XxeEt7FkLmA&m=-uUmPcN2FEoHZ07KXy8UXYIckCM-JjLapE4MIkybgFO_RBBFxJUHw7HgJxfCX5G3&s=7rEhUt53lH9syc3B-fDQmo-Up8KB1euT4R91dVlBPMk&e=

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to