> On 10-Nov-2023, at 10:52 PM, Numan Siddique <num...@ovn.org> wrote:
> 
> On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
> <naveen.yerramn...@nutanix.com> wrote:
>> 
>>    This patch contains changes to enable DHCP Relay Agent support for 
>> overlay subnets.
>> 
>>    NOTE:
>>    -----
>>      - This patch has required changes to enable basic DHCP Relay 
>> functionality for overlay subnets. Sending this for review to get the 
>> initial feedback about the approach taken.
>> 
>>    POST RFC REVIEW
>>    ----------------
>>      1. Address review comments/suggestions
>>      2. Address TODOs
>>      3. Add unit tests
>>      4. Complete testing
>> 
>>    USE CASE:
>>    ----------
>>      - Enable IP address assignment for overlay subnets from the centralized 
>> DHCP server present in the underlay network.
>> 
>>    PREREQUISITES
>>    --------------
>>      - Logical Router Port IP should be assigned (statically) from the same 
>> overlay subnet which is managed by DHCP server.
>>      - LRP IP is used for GIADRR field when relaying the DHCP packets and 
>> also same IP needs to be configured as default gateway for the overlay 
>> subnet.
>>      - Overlay subnets managed by external DHCP server are expected to be 
>> directly reachable from the underlay network.
>> 
>>    EXPECTED PACKET FLOW:
>>    ----------------------
>>    Following is the expected packet flow inorder to support DHCP rleay 
>> functionality in OVN.
>>      1. DHCP client originates DHCP discovery (broadcast).
>>      2. DHCP relay (running on the OVN) receives the broadcast and forwards 
>> the packet to the DHCP server by converting it to unicast. While forwarding 
>> the packet, it updates the GIADDR in DHCP header to its
>>         interface IP on which DHCP packet is received.
>>      3. DHCP server uses GIADDR field to decide the IP address pool from 
>> which IP has to be assigned and DHCP offer is sent to the same IP (GIADDR).
>>      4. DHCP relay agent forwards the offer to the client, it resets the 
>> GIADDR field when forwarding the offer to the client.
>>      5. DHCP client sends DHCP request (broadcast) packet.
>>      6. DHCP relay (running on the OVN) receives the broadcast and forwards 
>> the packet to the DHCP server by converting it to unicast. While forwarding 
>> the packet, it updates the GIADDR in DHCP header to its
>>         interface IP on which DHCP packet is received.
>>      7. DHCP Server sends the ACK packet.
>>      8. DHCP relay agent forwards the ACK packet to the client, it resets 
>> the GIADDR field when forwarding the ACK to the client.
>>      9. All the future renew/release packets are directly exchanged between 
>> DHCP client and DHCP server.
>> 
>>    OVN DHCP RELAY PACKET FLOW:
>>    ----------------------------
>>    To add DHCP Relay support on OVN, we need to replicate all the behavior 
>> described above using distributed logical switch and logical router.
>>    At, highlevel packet flow is distributed among Logical Switch and Logical 
>> Router on source node (where VM is deployed) and redirect chassis(RC) node.
>>      1. Request packet gets processed on the source node where VM is 
>> deployed and relays the packet to DHCP server.
>>      2. Response packet is first processed on RC node (which first recieves 
>> the packet from underlay network). RC node forwards the packet to the right 
>> node by filling in the dest MAC and IP.
>> 
>>    OVN Packet flow with DHCP relay is explained below.
>>      1. DHCP client (VM) sends the DHCP discover packet (broadcast).
>>      2. Logical switch converts the packet to L2 unicast by setting the 
>> destination MAC to LRP's MAC
>>      3. Logical Router receives the packet and redirects it to the OVN 
>> controller.
>>      4. OVN controller updates the required information(GIADDR) in the DHCP 
>> payload after doing the required checks. If any check fails, packet is 
>> dropped.
>>      5. Logical Router converts the packet to L3 unicast and forwards it to 
>> the server. This packets gets routed like any other packet (via RC node).
>>      6. Server replies with DHCP offer.
>>      7. RC node processes the DHCP offer and forwards it to the OVN 
>> controller.
>>      8. OVN controller does sanity checks and  updates the destination MAC 
>> (available in DHCP header), destination IP (available in DHCP header), 
>> resets GIADDR  and reinjects the packet to datapath.
>>         If any check fails, packet is dropped.
>>      9. Logical router updates the source IP and port and forwards the 
>> packet to logical switch.
>>      10. Logical switch delivers the packet to the DHCP client.
>>      11. Similar steps are performed for Request and Ack packets.
>>      12. All the future renew/release packets are directly exchanged between 
>> DHCP client and DHCP server
>> 
>>    NEW OVN ACTIONS
>>    ---------------
>> 
>>      1. dhcp_relay_req(<relay-ip>, <server-ip>)
>>          - This action executes on the source node on which the DHCP request 
>> originated.
>>          - This action relays the DHCP request coming from client to the 
>> server. Relay-ip is used to update GIADDR in the DHCP header.
>>      2. dhcp_relay_resp_fwd(<relay-ip>, <server-ip>)
>>          - This action executes on the first node (RC node) which processes 
>> the DHCP response from the server.
>>          - This action updates  the destination MAC and destination IP so 
>> that the response can be forwarded to the appropriate node from which 
>> request was originated.
>>          - Relay-ip, server-ip are used to validate GIADDR and SERVER ID in 
>> the DHCP payload.
>> 
>>    FLOWS
>>    -----
>>    Following are the flows required for one overlay subnet.
>> 
>>      1. table=27(ls_in_l2_lkup      ), priority=100  , match=(inport == 
>> <vm_port> && eth.src == <vm_mac> && ip4.src == 0.0.0.0 && ip4.dst == 
>> 255.255.255.255 && udp.src == 68 && udp.dst == 67), 
>> action=(eth.dst=<lrp_mac>;outport=<lrp-port>;next;/* DHCP_RELAY_REQ */)
>>      2. table=3 (lr_in_ip_input     ), priority=110  , match=(inport == 
>> <lrp_port> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 
>> 68 && udp.dst == 67), 
>> action=(dhcp_relay_req(<lrp_ip>,<dhcp_server_ip>);ip4.src=<lrp_ip>;ip4.dst=<dhcp_server_ip>;udp.src=67;next;
>>  /* DHCP_RELAY_REQ */)
>>      3. table=3 (lr_in_ip_input     ), priority=110  , match=(ip4.src == 
>> <dhcp_server_ip> && ip4.dst ==<lrp_ip> && udp.src == 67 && udp.dst == 67), 
>> action=(next;/* DHCP_RELAY_RESP */)
>>      4. table=17(lr_in_dhcp_relay_resp_fwd), priority=110  , match=(ip4.src 
>> == <dhcp_server_ip> && ip4.dst == <lrp_ip> && udp.src == 67 && udp.dst == 
>> 67), 
>> action=(dhcp_relay_resp_fwd();ip4.src=<lrp_ip>;udp.dst=68;outport=<lrp_port>;output;
>>  /* DHCP_RELAY_RESP */)
>> 
>>    NEW PIPELINE STAGES
>>    -------------------
>>    Following stage is added for DHCP relay feature. Some of the flows are 
>> fitted into the existing pipeline tages.
>>      1. lr_in_dhcp_relay_resp_fwd
>>          - Forward teh DHCP response to the appropriate node
>> 
>>    NB SCHEMA CHANGES
>>    ----------------
>>      1. New DHCP_Relay table
>>          "DHCP_Relay": {
>>                "columns": {
>>            "name": {"type": "string"},
>>                    "servers": {"type": {"key": "string",
>>                                           "min": 0,
>>                                           "max": 1}},
>>                    "external_ids": {
>>                        "type": {"key": "string", "value": "string",
>>                                "min": 0, "max": "unlimited"}}},
>>                "isRoot": true},
>>      2. New column to Logical_Router_Port table
>>          "dhcp_relay": {"type": {"key": {"type": "uuid",
>>                                "refTable": "DHCP_Relay",
>>                                "refType": "weak"},
>>                                "min": 0,
>>                                "max": 1}},
>>      3. New column to Logical_Switch_table
>>          "dhcp_relay_port": {"type": {"key": {"type": "uuid",
>>                                        "refTable": "Logical_Router_Port",
>>                                        "refType": "weak"},
>>                                         "min": 0,
>>                                         "max": 1}}},
>>    Commands to enable the feature:
>>    ------------------------------
>>      - ovn-nbctl create DHCP_Relay servers=<ip>
>>      - ovn-nbctl set Logical_Router_port <lrp_uuid> 
>> dhcp_relay=<dhcp_relay_uuid>
>>      - ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>
>> 
>>    Example:
>>    -------
>>     ovn-nbctl ls-add sw1
>>     ovn-nbctl lsp-add sw1 sw1-port1
>>     ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to be 
>> specified when logical ports are created.
>>     ovn-nbctl lr-add lr1
>>     ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP is 
>> set in GIADDR field when relaying the DHCP requests to server.
>>     ovn-nbctl lsp-add sw1 lr1-attachment
>>     ovn-nbctl lsp-set-type lr1-attachment router
>>     ovn-nbctl lsp-set-addresses lr1-attachment <MAC>
>>     ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1
>>     ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP>
>>     ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid>
>>     ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>
>> 
>>    Limitations:
>>    ------------
>>      - All OVN features that needs IP address to be configured on logical 
>> port (like proxy arp, etc) will not be supported for overlay subnets on 
>> which DHCP relay is enabled.
>> 
>>    References:
>>    ----------
>>      - rfc1541, rfc1542, rfc2131
>> 
>> Signed-off-by: Naveen Yerramneni <naveen.yerramn...@nutanix.com>
>> Co-authored-by: Huzaifa Calcuttawala <huzaif...@nutanix.com>
>> Signed-off-by: Huzaifa Calcuttawala <huzaif...@nutanix.com>
>> CC: Mary Manohar <mary.mano...@nutanix.com>
>> CC: Abhiram Sangana <sangana.abhi...@nutanix.com>
> 
> 
> Hi Naveen,
> 
> I had a couple of questions in your first RFC patch.  Can you please
> answer those ?  Please see below
> 
> 
> 2.  Can you please provide a few examples on how a logical port is
> created ?  What address would be set for the logical port ?
>     And once a VM gets IP using dhcp proxy,  is this IP address
> stored in OVN Northbound db Logical_Switch_Port ?
>     How does OVN learn about this mac-ip binding for a VM and forward
> the packet later for any E-W or N-S traffic ?

We had updated the implementation to DHCP Relay Agent in this patch.
I have shared the example in this patch on how to enable the feature 
and also prerequisites,  limitations (copy-pasting them below) 

 Example:
 ------------
  ovn-nbctl ls-add sw1
  ovn-nbctl lsp-add sw1 sw1-port1
  ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to be 
specified when logical ports are created.
  ovn-nbctl lr-add lr1
  ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP is set 
in GIADDR field when relaying the DHCP requests to server.
  ovn-nbctl lsp-add sw1 lr1-attachment
  ovn-nbctl lsp-set-type lr1-attachment router
  ovn-nbctl lsp-set-addresses lr1-attachment <MAC>
  ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1
  ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP>
  ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid>
  ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>

 PREREQUISITES
 --------------
   - Logical Router Port (LRP) IP should be assigned (statically) from the same 
overlay subnet which is managed by DHCP server.
   - LRP IP is used for GIADRR field when relaying the DHCP packets and also 
same IP needs to be configured as default gateway for the overlay subnet.
   - Overlay subnets managed by external DHCP server are expected to be 
directly reachable from the underlay network.
    
 Limitations:
 --------------
    - All OVN features that needs IP address to be configured on logical port 
(like proxy arp, etc) will not be supported for overlay subnets on which DHCP 
relay is enabled.

IP address is NOT updated in the OVN northbound DB.
OVN would be learning the VM MAC by sending ARP requests if entry is not found 
in Mac binding table for DHCP relay subnets.   

> 3.  Is it possible to handle all this DHCP proxy in the logical switch
> pipeline itself ?  In a typical deployment where DHCP proxy is used,
>    Who does the DHCP proxy ? Is it the router ?

We had updated the implementation to DHCP Relay Agent in this patch.
Generally, DHCP Relay agent functionality is handled on Routers.
I thought about possibility of implementing DHCP relay agent functionality in 
the logical switch pipeline but couldn’t find a way. One of the main reasons is:
    - DHCP server response messages (OFFER, ACK) are destined to LRP IP (GIADDR 
field  is set to LRP IP in the DHCP discovery/request packets) and these 
packets first lands on RC node.
      Response needs to be parsed first on the RC node (logical router 
pipeline) in order find out which destination (using client MAC in DHCP 
response payload) response has to be forwarded.


> 
> Thanks
> 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
>> d...@openvswitch.org
>> https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.org_mailman_listinfo_ovs-2Ddev&d=DwIFaQ&c=s883GpUCOChKOHiocYtGcg&r=2PQjSDR7A28z1kXE1ptSm6X36oL_nCq1XxeEt7FkLmA&m=5HAP0DT1kSP0Mh0H3I_grVNxhQBb42Y9IcgoumjojsaDHxLsgS8YUb6JZ8rBXJgA&s=iswZEbE_2lK-oi4pFB8Q6vFZkiQbcGU2F_5U2pfATSA&e=

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to