diff mbox series

[ovs-dev,RFC] DHCP Relay Agent support for overlay subnets

Message ID 20231103164832.232600-1-naveen.yerramneni@nutanix.com
State RFC
Headers show
Series [ovs-dev,RFC] DHCP Relay Agent support for overlay subnets | expand

Checks

Context Check Description
ovsrobot/apply-robot fail apply and check: fail

Commit Message

Naveen Yerramneni Nov. 3, 2023, 4:48 p.m. UTC
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.yerramneni@nutanix.com>
Co-authored-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
Signed-off-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
CC: Mary Manohar <mary.manohar@nutanix.com>
CC: Abhiram Sangana <sangana.abhiram@nutanix.com>
---
 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

Comments

0-day Robot Nov. 6, 2023, 9:23 p.m. UTC | #1
References:  <20231103164832.232600-1-naveen.yerramneni@nutanix.com>
 

Bleep bloop.  Greetings naveen.yerramneni, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


git-am:
.git/rebase-apply/patch:41: trailing whitespace.
[OVN_DHCP_MSG_RELEASE] = "RELEASE", 
.git/rebase-apply/patch:128: trailing whitespace.
    
.git/rebase-apply/patch:186: trailing whitespace.
 
.git/rebase-apply/patch:231: trailing whitespace.
                dhcp_msg_str_get(*in_dhcp_msg_type), 
.git/rebase-apply/patch:315: trailing whitespace.
    
warning: squelched 9 whitespace errors
warning: 14 lines add whitespace errors.
error: failed to create path 'ovs': perhaps a D/F conflict?
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 DHCP Relay Agent support for overlay subnets
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Numan Siddique Nov. 10, 2023, 5:22 p.m. UTC | #2
On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
<naveen.yerramneni@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.yerramneni@nutanix.com>
> Co-authored-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
> Signed-off-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
> CC: Mary Manohar <mary.manohar@nutanix.com>
> CC: Abhiram Sangana <sangana.abhiram@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 ?

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 ?


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
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Naveen Yerramneni Nov. 10, 2023, 7:06 p.m. UTC | #3
> On 10-Nov-2023, at 10:52 PM, Numan Siddique <numans@ovn.org> wrote:
> 
> On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
> <naveen.yerramneni@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.yerramneni@nutanix.com>
>> Co-authored-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
>> Signed-off-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
>> CC: Mary Manohar <mary.manohar@nutanix.com>
>> CC: Abhiram Sangana <sangana.abhiram@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
>> dev@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=
Numan Siddique Nov. 14, 2023, 9:31 p.m. UTC | #4
On Fri, Nov 10, 2023 at 2:46 PM Naveen Yerramneni
<naveen.yerramneni@nutanix.com> wrote:
>
>
>
> > On 10-Nov-2023, at 10:52 PM, Numan Siddique <numans@ovn.org> wrote:
> >
> > On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
> > <naveen.yerramneni@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.yerramneni@nutanix.com>
> >> Co-authored-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
> >> Signed-off-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
> >> CC: Mary Manohar <mary.manohar@nutanix.com>
> >> CC: Abhiram Sangana <sangana.abhiram@nutanix.com>
> >
> >
> > Hi Naveen,
> >
> > I had a couple of questions in your first RFC patch.  Can you please
> > answer those ?  Please see below
> >
> >
> > 2.  Can you please provide a few examples on how a logical port is
> > created ?  What address would be set for the logical port ?
> >     And once a VM gets IP using dhcp proxy,  is this IP address
> > stored in OVN Northbound db Logical_Switch_Port ?
> >     How does OVN learn about this mac-ip binding for a VM and forward
> > the packet later for any E-W or N-S traffic ?
>
> We had updated the implementation to DHCP Relay Agent in this patch.
> I have shared the example in this patch on how to enable the feature
> and also prerequisites,  limitations (copy-pasting them below)
>
>  Example:
>  ------------
>   ovn-nbctl ls-add sw1
>   ovn-nbctl lsp-add sw1 sw1-port1
>   ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to be specified when logical ports are created.
>   ovn-nbctl lr-add lr1
>   ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP is set in GIADDR field when relaying the DHCP requests to server.
>   ovn-nbctl lsp-add sw1 lr1-attachment
>   ovn-nbctl lsp-set-type lr1-attachment router
>   ovn-nbctl lsp-set-addresses lr1-attachment <MAC>
>   ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1
>   ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP>
>   ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid>
>   ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>
>
>  PREREQUISITES
>  --------------
>    - Logical Router Port (LRP) IP should be assigned (statically) from the same overlay subnet which is managed by DHCP server.
>    - LRP IP is used for GIADRR field when relaying the DHCP packets and also same IP needs to be configured as default gateway for the overlay subnet.
>    - Overlay subnets managed by external DHCP server are expected to be directly reachable from the underlay network.
>
>  Limitations:
>  --------------
>     - All OVN features that needs IP address to be configured on logical port (like proxy arp, etc) will not be supported for overlay subnets on which DHCP relay is enabled.
>
> IP address is NOT updated in the OVN northbound DB.
> OVN would be learning the VM MAC by sending ARP requests if entry is not found in Mac binding table for DHCP relay subnets.
>
> > 3.  Is it possible to handle all this DHCP proxy in the logical switch
> > pipeline itself ?  In a typical deployment where DHCP proxy is used,
> >    Who does the DHCP proxy ? Is it the router ?
>)
> We had updated the implementation to DHCP Relay Agent in this patch.
> Generally, DHCP Relay agent functionality is handled on Routers.
> I thought about possibility of implementing DHCP relay agent functionality in the logical switch pipeline but couldn’t find a way. One of the main reasons is:
>     - DHCP server response messages (OFFER, ACK) are destined to LRP IP (GIADDR field  is set to LRP IP in the DHCP discovery/request packets) and these packets first lands on RC node.
>       Response needs to be parsed first on the RC node (logical router pipeline) in order find out which destination (using client MAC in DHCP response payload) response has to be forwarded.
>
>

Thanks.

No objections from me for this patch.  Perhaps you can resubmit with
the RFC tag removed.  This would encourage more reviews.

I'd strongly suggest adding system tests in system-ovn.at or system
tests based on ovn-fake-multinode (multinode.at)
system tests that can run a real dhcp relay server and test out the
functionality.

Thanks
Numan


> >
> > Thanks
> > Numan
> >
> >
> >> ---
> >> controller/pinctrl.c  | 436 ++++++++++++++++++++++++++++++++++++++++++
> >> include/ovn/actions.h |  26 +++
> >> lib/actions.c         | 114 +++++++++++
> >> lib/ovn-l7.h          |   1 +
> >> northd/northd.c       | 174 ++++++++++++++++-
> >> ovn-nb.ovsschema      |  23 ++-
> >> ovn-nb.xml            |  27 +++
> >> ovs                   |   2 +-
> >> tests/ovn-northd.at   |   6 +-
> >> tests/ovn.at          |  12 +-
> >> utilities/ovn-trace.c |   8 +
> >> 11 files changed, 812 insertions(+), 17 deletions(-)
> >> mode change 160000 => 120000 ovs
> >>
> >> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> >> index 3c1cecfde..ee68d0088 100644
> >> --- a/controller/pinctrl.c
> >> +++ b/controller/pinctrl.c
> >> @@ -383,6 +383,7 @@ static void pinctrl_handle_put_fdb(const struct flow *md,
> >>                                    const struct flow *headers)
> >>                                    OVS_REQUIRES(pinctrl_mutex);
> >>
> >> +
> >> COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
> >> COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
> >> COVERAGE_DEFINE(pinctrl_drop_controller_event);
> >> @@ -1888,6 +1889,431 @@ is_dhcp_flags_broadcast(ovs_be16 flags)
> >>     return flags & htons(DHCP_BROADCAST_FLAG);
> >> }
> >>
> >> +
> >> +static const char *dhcp_msg_str[] = {
> >> +[0] = "INVALID",
> >> +[DHCP_MSG_DISCOVER] = "DISCOVER",
> >> +[DHCP_MSG_OFFER] = "OFFER",
> >> +[DHCP_MSG_REQUEST] = "REQUEST",
> >> +[OVN_DHCP_MSG_DECLINE] = "DECLINE",
> >> +[DHCP_MSG_ACK] = "ACK",
> >> +[DHCP_MSG_NAK] = "NAK",
> >> +[OVN_DHCP_MSG_RELEASE] = "RELEASE",
> >> +[OVN_DHCP_MSG_INFORM] = "INFORM"
> >> +};
> >> +
> >> +static bool
> >> +dhcp_relay_is_msg_type_supported(uint8_t msg_type)
> >> +{
> >> +    return (msg_type >= DHCP_MSG_DISCOVER && msg_type <= OVN_DHCP_MSG_RELEASE);
> >> +}
> >> +
> >> +static const char *dhcp_msg_str_get(uint8_t msg_type)
> >> +{
> >> +    if (!dhcp_relay_is_msg_type_supported(msg_type)) {
> >> +        return "INVALID";
> >> +    }
> >> +    return dhcp_msg_str[msg_type];
> >> +}
> >> +
> >> +/* Called with in the pinctrl_handler thread context. */
> >> +static void
> >> +pinctrl_handle_dhcp_relay_req(
> >> +    struct rconn *swconn,
> >> +    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
> >> +    struct ofpbuf *userdata,
> >> +    struct ofpbuf *continuation)
> >> +{
> >> +    enum ofp_version version = rconn_get_version(swconn);
> >> +    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
> >> +    struct dp_packet *pkt_out_ptr = NULL;
> >> +
> >> +    /* Parse relay IP and server IP. */
> >> +    ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip);
> >> +    ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
> >> +    if (!relay_ip || !server_ip) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: relay ip or server ip not present in the userdata");
> >> +        return;
> >> +    }
> >> +
> >> +    /* Validate the DHCP request packet.
> >> +     * Format of the DHCP packet is
> >> +     * ------------------------------------------------------------------------
> >> +     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)|
> >> +     * ------------------------------------------------------------------------
> >> +     */
> >> +
> >> +    size_t in_l4_size = dp_packet_l4_size(pkt_in);
> >> +    const char *end = (char *)dp_packet_l4(pkt_in) + in_l4_size;
> >> +    const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in);
> >> +    if (!in_dhcp_ptr) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid or incomplete DHCP packet received");
> >> +        return;
> >> +    }
> >> +
> >> +    const struct dhcp_header *in_dhcp_data
> >> +        = (const struct dhcp_header *) in_dhcp_ptr;
> >> +    in_dhcp_ptr += sizeof *in_dhcp_data;
> >> +    if (in_dhcp_ptr > end) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid or incomplete DHCP packet received, "
> >> +                     "bad data length");
> >> +        return;
> >> +    }
> >> +    if (in_dhcp_data->op != DHCP_OP_REQUEST) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid opcode in the DHCP packet: %d",
> >> +                     in_dhcp_data->op);
> >> +        return;
> >> +    }
> >> +
> >> +    /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP
> >> +     * options is the DHCP magic cookie followed by the actual DHCP options.
> >> +     */
> >> +    ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
> >> +    if (in_dhcp_ptr + sizeof magic_cookie > end ||
> >> +        get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: magic cookie not present in the packet");
> >> +        return;
> >> +    }
> >> +
> >> +    if (in_dhcp_data->giaddr) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: giaddr is already set");
> >> +        return;
> >> +    }
> >> +
> >> +    if (in_dhcp_data->htype != 0x1) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: packet is recieved with unsupported hardware type");
> >> +        return;
> >> +    }
> >> +
> >> +    ovs_be32 *server_id_ptr = NULL;
> >> +    const uint8_t *in_dhcp_msg_type = NULL;
> >> +
> >> +    in_dhcp_ptr += sizeof magic_cookie;
> >> +    ovs_be32 request_ip = in_dhcp_data->ciaddr;
> >> +    while (in_dhcp_ptr < end) {
> >> +        const struct dhcp_opt_header *in_dhcp_opt =
> >> +            (const struct dhcp_opt_header *)in_dhcp_ptr;
> >> +        if (in_dhcp_opt->code == DHCP_OPT_END) {
> >> +            break;
> >> +        }
> >> +        if (in_dhcp_opt->code == DHCP_OPT_PAD) {
> >> +            in_dhcp_ptr += 1;
> >> +            continue;
> >> +        }
> >> +        in_dhcp_ptr += sizeof *in_dhcp_opt;
> >> +        if (in_dhcp_ptr > end) {
> >> +            break;
> >> +        }
> >> +        in_dhcp_ptr += in_dhcp_opt->len;
> >> +        if (in_dhcp_ptr > end) {
> >> +            break;
> >> +        }
> >> +
> >> +        switch (in_dhcp_opt->code) {
> >> +        case DHCP_OPT_MSG_TYPE:
> >> +            if (in_dhcp_opt->len == 1) {
> >> +                in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> >> +            }
> >> +            break;
> >> +        case DHCP_OPT_REQ_IP:
> >> +            if (in_dhcp_opt->len == 4) {
> >> +                request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
> >> +            }
> >> +            break;
> >> +        case OVN_DHCP_OPT_CODE_SERVER_ID: //Server Identifier
> >> +            if (in_dhcp_opt->len == 4) {
> >> +                server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> >> +            }
> >> +            break;
> >> +        default:
> >> +            break;
> >> +        }
> >> +    }
> >> +
> >> +    /* Check whether the DHCP Message Type (opt 53) is present or not */
> >> +    if (!in_dhcp_msg_type) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: missing message type");
> >> +        return;
> >> +    }
> >> +
> >> +    /* Relay the DHCP request packet */
> >> +    uint16_t new_l4_size = in_l4_size;
> >> +    size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
> >> +
> >> +    struct dp_packet pkt_out;
> >> +    dp_packet_init(&pkt_out, new_packet_size);
> >> +    dp_packet_clear(&pkt_out);
> >> +    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
> >> +    pkt_out_ptr = &pkt_out;
> >> +
> >> +    /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
> >> +    dp_packet_put(
> >> +        &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
> >> +
> >> +    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
> >> +    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
> >> +    pkt_out.l3_ofs = pkt_in->l3_ofs;
> >> +    pkt_out.l4_ofs = pkt_in->l4_ofs;
> >> +
> >> +    struct ip_header *out_ip = dp_packet_l3(&pkt_out);
> >> +
> >> +    struct udp_header *udp = dp_packet_put(
> >> +        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
> >> +
> >> +    struct dhcp_header *dhcp_data = dp_packet_put(
> >> +        &pkt_out, dp_packet_pull(pkt_in, new_l4_size-UDP_HEADER_LEN), new_l4_size-UDP_HEADER_LEN);
> >> +    dhcp_data->giaddr = *relay_ip;
> >> +    //TODO: incremental checkcum
> >> +    if (udp->udp_csum) {
> >> +        udp->udp_csum = 0;
> >> +        uint32_t p_csum = packet_csum_pseudoheader(out_ip);
> >> +        udp->udp_csum = csum_finish(csum_continue(p_csum, udp, new_l4_size));
> >> +    }
> >> +    pin->packet = dp_packet_data(&pkt_out);
> >> +    pin->packet_len = dp_packet_size(&pkt_out);
> >> +
> >> +    /* Log the DHCP message. */
> >> +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
> >> +    const struct eth_header *l2 = dp_packet_eth(&pkt_out);
> >> +    VLOG_INFO_RL(&rl, "DHCP_RELAY_REQ:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT
> >> +                " XID:%u"
> >> +                " REQ_IP:"IP_FMT
> >> +                " GIADDR:"IP_FMT
> >> +                " SERVER_ADDR:"IP_FMT,
> >> +                dhcp_msg_str_get(*in_dhcp_msg_type),
> >> +                ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), ntohl(dhcp_data->xid),
> >> +                IP_ARGS(request_ip), IP_ARGS(dhcp_data->giaddr),
> >> +                IP_ARGS(*server_ip));
> >> +    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
> >> +    if (pkt_out_ptr) {
> >> +        dp_packet_uninit(pkt_out_ptr);
> >> +    }
> >> +}
> >> +
> >> +/* Called with in the pinctrl_handler thread context. */
> >> +static void
> >> +pinctrl_handle_dhcp_relay_resp_fwd(
> >> +    struct rconn *swconn,
> >> +    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
> >> +    struct ofpbuf *userdata,
> >> +    struct ofpbuf *continuation)
> >> +{
> >> +    enum ofp_version version = rconn_get_version(swconn);
> >> +    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
> >> +    struct dp_packet *pkt_out_ptr = NULL;
> >> +
> >> +    /* Parse relay IP and server IP. */
> >> +    ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip);
> >> +    ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
> >> +    if (!relay_ip || !server_ip) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: relay ip or server ip not present in the userdata");
> >> +        return;
> >> +    }
> >> +
> >> +    /* Validate the DHCP request packet.
> >> +     * Format of the DHCP packet is
> >> +     * ------------------------------------------------------------------------
> >> +     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)|
> >> +     * ------------------------------------------------------------------------
> >> +     */
> >> +
> >> +    size_t in_l4_size = dp_packet_l4_size(pkt_in);
> >> +    const char *end = (char *)dp_packet_l4(pkt_in) + in_l4_size;
> >> +    const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in);
> >> +    if (!in_dhcp_ptr) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid or incomplete packet received");
> >> +        return;
> >> +    }
> >> +
> >> +    const struct dhcp_header *in_dhcp_data
> >> +        = (const struct dhcp_header *) in_dhcp_ptr;
> >> +    in_dhcp_ptr += sizeof *in_dhcp_data;
> >> +    if (in_dhcp_ptr > end) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid or incomplete packet received, "
> >> +                     "bad data length");
> >> +        return;
> >> +    }
> >> +    if (in_dhcp_data->op != DHCP_OP_REPLY) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid opcode in the packet: %d",
> >> +                     in_dhcp_data->op);
> >> +        return;
> >> +    }
> >> +
> >> +    /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP
> >> +     * options is the DHCP magic cookie followed by the actual DHCP options.
> >> +     */
> >> +    ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
> >> +    if (in_dhcp_ptr + sizeof magic_cookie > end ||
> >> +        get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: magic cookie not present in the packet");
> >> +        return;
> >> +    }
> >> +
> >> +    if (!in_dhcp_data->giaddr) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: giaddr is not set in request");
> >> +        return;
> >> +    }
> >> +    ovs_be32 giaddr = in_dhcp_data->giaddr;
> >> +
> >> +    ovs_be32 *server_id_ptr = NULL;
> >> +    ovs_be32 lease_time = 0;
> >> +    const uint8_t *in_dhcp_msg_type = NULL;
> >> +
> >> +    in_dhcp_ptr += sizeof magic_cookie;
> >> +    while (in_dhcp_ptr < end) {
> >> +        const struct dhcp_opt_header *in_dhcp_opt =
> >> +            (const struct dhcp_opt_header *)in_dhcp_ptr;
> >> +        if (in_dhcp_opt->code == DHCP_OPT_END) {
> >> +            break;
> >> +        }
> >> +        if (in_dhcp_opt->code == DHCP_OPT_PAD) {
> >> +            in_dhcp_ptr += 1;
> >> +            continue;
> >> +        }
> >> +        in_dhcp_ptr += sizeof *in_dhcp_opt;
> >> +        if (in_dhcp_ptr > end) {
> >> +            break;
> >> +        }
> >> +        in_dhcp_ptr += in_dhcp_opt->len;
> >> +        if (in_dhcp_ptr > end) {
> >> +            break;
> >> +        }
> >> +
> >> +        switch (in_dhcp_opt->code) {
> >> +        case DHCP_OPT_MSG_TYPE:
> >> +            if (in_dhcp_opt->len == 1) {
> >> +                in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> >> +            }
> >> +            break;
> >> +        case OVN_DHCP_OPT_CODE_SERVER_ID: //Server Identifier
> >> +            if (in_dhcp_opt->len == 4) {
> >> +                server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> >> +            }
> >> +            break;
> >> +        case OVN_DHCP_OPT_CODE_LEASE_TIME:
> >> +            if (in_dhcp_opt->len == 4) {
> >> +                lease_time = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
> >> +            }
> >> +            break;
> >> +        default:
> >> +            break;
> >> +        }
> >> +    }
> >> +
> >> +    /* Check whether the DHCP Message Type (opt 53) is present or not */
> >> +    if (!in_dhcp_msg_type) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing message type");
> >> +        return;
> >> +    }
> >> +
> >> +    if (!server_id_ptr) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing server identifier");
> >> +        return;
> >> +    }
> >> +
> >> +    if (*server_id_ptr != *server_ip) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: server identifier mismatch");
> >> +        return;
> >> +    }
> >> +
> >> +    if (giaddr != *relay_ip) {
> >> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> >> +        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: giaddr mismatch");
> >> +        return;
> >> +    }
> >> +
> >> +
> >> +    /* Update destination MAC & IP so that the packet is forward to the
> >> +     * right destination node.
> >> +     */
> >> +    uint16_t new_l4_size = in_l4_size;
> >> +    size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
> >> +
> >> +    struct dp_packet pkt_out;
> >> +    dp_packet_init(&pkt_out, new_packet_size);
> >> +    dp_packet_clear(&pkt_out);
> >> +    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
> >> +    pkt_out_ptr = &pkt_out;
> >> +
> >> +    /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
> >> +    struct eth_header *eth = dp_packet_put(
> >> +        &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
> >> +
> >> +    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
> >> +    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
> >> +    pkt_out.l3_ofs = pkt_in->l3_ofs;
> >> +    pkt_out.l4_ofs = pkt_in->l4_ofs;
> >> +
> >> +    struct udp_header *udp = dp_packet_put(
> >> +        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
> >> +
> >> +    struct dhcp_header *dhcp_data = dp_packet_put(
> >> +        &pkt_out, dp_packet_pull(pkt_in, new_l4_size-UDP_HEADER_LEN), new_l4_size-UDP_HEADER_LEN);
> >> +    memcpy(&eth->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
> >> dev@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
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Naveen Yerramneni Nov. 15, 2023, 12:52 a.m. UTC | #5
> On 15-Nov-2023, at 3:01 AM, Numan Siddique <numans@ovn.org> wrote:
> 
> On Fri, Nov 10, 2023 at 2:46 PM Naveen Yerramneni
> <naveen.yerramneni@nutanix.com> wrote:
>> 
>> 
>> 
>>> On 10-Nov-2023, at 10:52 PM, Numan Siddique <numans@ovn.org> wrote:
>>> 
>>> On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
>>> <naveen.yerramneni@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.yerramneni@nutanix.com>
>>>> Co-authored-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
>>>> Signed-off-by: Huzaifa Calcuttawala <huzaifa.c@nutanix.com>
>>>> CC: Mary Manohar <mary.manohar@nutanix.com>
>>>> CC: Abhiram Sangana <sangana.abhiram@nutanix.com>
>>> 
>>> 
>>> Hi Naveen,
>>> 
>>> I had a couple of questions in your first RFC patch.  Can you please
>>> answer those ?  Please see below
>>> 
>>> 
>>> 2.  Can you please provide a few examples on how a logical port is
>>> created ?  What address would be set for the logical port ?
>>>    And once a VM gets IP using dhcp proxy,  is this IP address
>>> stored in OVN Northbound db Logical_Switch_Port ?
>>>    How does OVN learn about this mac-ip binding for a VM and forward
>>> the packet later for any E-W or N-S traffic ?
>> 
>> We had updated the implementation to DHCP Relay Agent in this patch.
>> I have shared the example in this patch on how to enable the feature
>> and also prerequisites,  limitations (copy-pasting them below)
>> 
>> Example:
>> ------------
>>  ovn-nbctl ls-add sw1
>>  ovn-nbctl lsp-add sw1 sw1-port1
>>  ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to be specified when logical ports are created.
>>  ovn-nbctl lr-add lr1
>>  ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP is set in GIADDR field when relaying the DHCP requests to server.
>>  ovn-nbctl lsp-add sw1 lr1-attachment
>>  ovn-nbctl lsp-set-type lr1-attachment router
>>  ovn-nbctl lsp-set-addresses lr1-attachment <MAC>
>>  ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1
>>  ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP>
>>  ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid>
>>  ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>
>> 
>> PREREQUISITES
>> --------------
>>   - Logical Router Port (LRP) IP should be assigned (statically) from the same overlay subnet which is managed by DHCP server.
>>   - LRP IP is used for GIADRR field when relaying the DHCP packets and also same IP needs to be configured as default gateway for the overlay subnet.
>>   - Overlay subnets managed by external DHCP server are expected to be directly reachable from the underlay network.
>> 
>> Limitations:
>> --------------
>>    - All OVN features that needs IP address to be configured on logical port (like proxy arp, etc) will not be supported for overlay subnets on which DHCP relay is enabled.
>> 
>> IP address is NOT updated in the OVN northbound DB.
>> OVN would be learning the VM MAC by sending ARP requests if entry is not found in Mac binding table for DHCP relay subnets.
>> 
>>> 3.  Is it possible to handle all this DHCP proxy in the logical switch
>>> pipeline itself ?  In a typical deployment where DHCP proxy is used,
>>>   Who does the DHCP proxy ? Is it the router ?
>> )
>> We had updated the implementation to DHCP Relay Agent in this patch.
>> Generally, DHCP Relay agent functionality is handled on Routers.
>> I thought about possibility of implementing DHCP relay agent functionality in the logical switch pipeline but couldn’t find a way. One of the main reasons is:
>>    - DHCP server response messages (OFFER, ACK) are destined to LRP IP (GIADDR field  is set to LRP IP in the DHCP discovery/request packets) and these packets first lands on RC node.
>>      Response needs to be parsed first on the RC node (logical router pipeline) in order find out which destination (using client MAC in DHCP response payload) response has to be forwarded.
>> 
>> 
> 
> Thanks.
> 
> No objections from me for this patch.  Perhaps you can resubmit with
> the RFC tag removed.  This would encourage more reviews.
> 
> I'd strongly suggest adding system tests in system-ovn.at or system
> tests based on ovn-fake-multinode (multinode.at)
> system tests that can run a real dhcp relay server and test out the
> functionality.
> 
> Thanks
> Numan
> 


Thanks Numan !
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
>>>> dev@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
>> dev@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=-uUmPcN2FEoHZ07KXy8UXYIckCM-JjLapE4MIkybgFO_RBBFxJUHw7HgJxfCX5G3&s=7rEhUt53lH9syc3B-fDQmo-Up8KB1euT4R91dVlBPMk&e=
diff mbox series

Patch

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);