diff mbox series

[ovs-dev,v5,3/4] northd: DHCP Relay Agent support for overlay IPv4 subnets.

Message ID 20240320143958.39052-4-naveen.yerramneni@nutanix.com
State Changes Requested
Delegated to: Numan Siddique
Headers show
Series DHCP Relay Agent support for overlay subnets. | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Naveen Yerramneni March 20, 2024, 2:39 p.m. UTC
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"}}},
                "options": {"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": "strong"},
                            "min": 0,
                            "max": 1}},

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_req
       - This stage process the DHCP request packets coming from DHCP clients.
       - DHCP request packets for which dhcp_relay_req_chk action
         (which gets applied in ip input stage) is successful are forwarded to DHCP server.
       - DHCP request packets for which dhcp_relay_req_chk action is unsuccessful gets dropped.
  2. lr_in_dhcp_relay_resp_chk
       - This stage applied the dhcp_relay_resp_chk action for  DHCP response packets coming
         from the DHCP server.
  3. lr_in_dhcp_relay_resp
       - DHCP response packets for which dhcp_relay_resp_chk is sucessful are forwarded
         to the DHCP clients.
       - DHCP response packets for which dhcp_relay_resp_chk is unsucessful gets dropped.

REGISTRY USAGE
---------------
  - reg9[7] : To store the result of dhcp_relay_req_chk action.
  - reg9[8] : To store the result of dhcp_relay_resp_chk action.
  - reg2 : To store the original dest ip for DHCP response packets.
           This is required to properly match the packets in
           lr_in_dhcp_relay_resp stage since dhcp_relay_resp_chk action
           changes the dest ip.

FLOWS
-----

Following are the flows added when DHCP Relay is configured on one overlay subnet,
one additonal flow is added in ls_in_l2_lkup table for each VM part of the 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>;next;/* DHCP_RELAY_REQ */)
  2. table=3 (lr_in_ip_input     ), priority=110  , match=(inport == <lrp> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && ip.frag == 0 && udp.src == 68 && udp.dst == 67),
     action=(reg9[7] = dhcp_relay_req_chk(<lrp_ip>, <dhcp_server_ip>);next; /* DHCP_RELAY_REQ */)
  3. table=3 (lr_in_ip_input     ), priority=110  , match=(ip4.src == <dhcp_server> && ip4.dst == <lrp> && udp.src == 67 && udp.dst == 67), action=(next;/* DHCP_RELAY_RESP */)
  4. table=4 (lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && reg9[7]),
     action=(ip4.src=<lrp>;ip4.dst=<dhcp_server>;udp.src=67;next; /* DHCP_RELAY_REQ */)
  5. table=4 (lr_in_dhcp_relay_req), priority=1    , match=(inport == <lrp> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && reg9[7] == 0),
     action=(drop; /* DHCP_RELAY_REQ */)
  6. table=18(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == <dhcp_server> && ip4.dst == <lrp> && ip.frag == 0 && udp.src == 67 && udp.dst == 67),
     action=(reg2 = ip4.dst;reg9[8] = dhcp_relay_resp_chk(<lrp_ip>, <dhcp_server_ip>);next;/* DHCP_RELAY_RESP */)
  7. table=19(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == <dhcp_server> && reg2 == <lrp_ip> && udp.src == 67 && udp.dst == 67 && reg9[8]),
     action=(ip4.src=<lrp>;udp.dst=68;outport=<lrp>;output; /* DHCP_RELAY_RESP */)
  8. table=19(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == <dhcp_server> && reg2 == <lrp_ip> && udp.src == 67 && udp.dst == 67 && reg9[8] == 0), action=(drop; /* DHCP_RELAY_RESP */)

Commands to enable the feature
------------------------------
  ovn-nbctl create DHCP_Relay name=<name> servers=<dhcp_server_ip>
  ovn-nbctl set Logical_Router_port <lrp> dhcp_relay=<relay_uuid>
  ovn-nbctl set Logical_Switch <ls> other_config:dhcp_relay_port=<router_patch_port>

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.

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>
---
 northd/northd.c  | 271 ++++++++++++++++++++++++++++++++++++++++++++++-
 northd/northd.h  |  41 +++----
 ovn-nb.ovsschema |  19 +++-
 ovn-nb.xml       |  39 +++++++
 tests/ovn.at     |   2 +-
 5 files changed, 349 insertions(+), 23 deletions(-)

Comments

0-day Robot March 20, 2024, 3:02 p.m. UTC | #1
References:  <20240320143958.39052-4-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.


checkpatch:
WARNING: Line is 101 characters long (recommended limit is 79)
#132 FILE: northd/northd.c:237:
 * | R2     REG_DHCP_RELAY_DIP_IPV4  | X |                 | 0 |                                    |

Lines checked: 626, Warnings: 1, Errors: 0


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

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/northd/northd.c b/northd/northd.c
index 1839b7d8b..3c5372691 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -91,7 +91,6 @@  static bool use_ct_inv_match = true;
 static bool default_acl_drop;
 
 #define MAX_OVN_TAGS 4096
-
 
 /* Due to various hard-coded priorities need to implement ACLs, the
  * northbound database supports a smaller range of ACL priorities than
@@ -153,6 +152,8 @@  static bool default_acl_drop;
 #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
 #define REGBIT_DST_NAT_IP_LOCAL "reg9[4]"
 #define REGBIT_KNOWN_LB_SESSION "reg9[6]"
+#define REGBIT_DHCP_RELAY_REQ_CHK "reg9[7]"
+#define REGBIT_DHCP_RELAY_RESP_CHK "reg9[8]"
 
 /* Register to store the eth address associated to a router port for packets
  * received in S_ROUTER_IN_ADMISSION.
@@ -168,6 +169,7 @@  static bool default_acl_drop;
 #define REG_NEXT_HOP_IPV6 "xxreg0"
 #define REG_SRC_IPV4 "reg1"
 #define REG_SRC_IPV6 "xxreg1"
+#define REG_DHCP_RELAY_DIP_IPV4 "reg2"
 #define REG_ROUTE_TABLE_ID "reg7"
 
 /* Register used to store backend ipv6 address
@@ -232,7 +234,7 @@  static bool default_acl_drop;
  * | R1  |   SRC_IPV4 for ARP-REQ    | 0 |                 | R |                                    |
  * |     |      (>= IP_INPUT)        |   |                 | E |     NEXT_HOP_IPV6 (>= DEFRAG )     |
  * +-----+---------------------------+---+-----------------+ G |                                    |
- * | R2  |        UNUSED             | X |                 | 0 |                                    |
+ * | R2     REG_DHCP_RELAY_DIP_IPV4  | X |                 | 0 |                                    |
  * |     |                           | R |                 |   |                                    |
  * +-----+---------------------------+ E |     UNUSED      |   |                                    |
  * | R3  |        UNUSED             | G |                 |   |                                    |
@@ -259,7 +261,9 @@  static bool default_acl_drop;
  * |     |   EGRESS_LOOPBACK/        | G |     UNUSED      |
  * | R9  |   PKT_LARGER/             | 4 |                 |
  * |     |   LOOKUP_NEIGHBOR_RESULT/ |   |                 |
- * |     |   SKIP_LOOKUP_NEIGHBOR}   |   |                 |
+ * |     |   SKIP_LOOKUP_NEIGHBOR/   |   |                 |
+ * |     |REGBIT_DHCP_RELAY_REQ_CHK/ |   |                 |
+ * |     |REGBIT_DHCP_RELAY_RESP_CHK}|   |                 |
  * |     |                           |   |                 |
  * |     | REG_ORIG_TP_DPORT_ROUTER  |   |                 |
  * |     |                           |   |                 |
@@ -8555,6 +8559,90 @@  build_dhcpv6_options_flows(struct ovn_port *op,
     ds_destroy(&match);
 }
 
+static const char *
+ls_dhcp_relay_port(const struct ovn_datapath *od)
+{
+    return smap_get(&od->nbs->other_config, "dhcp_relay_port");
+}
+
+static void
+build_lswitch_dhcp_relay_flows(struct ovn_port *op,
+                           const struct hmap *ls_ports,
+                           struct lflow_table *lflows,
+                           struct ds *match,
+                           struct ds *actions)
+{
+    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) {
+        return;
+    }
+
+    /* configure dhcp relay flows only when peer router  has
+     * relay config enabled */
+    const char *dhcp_relay_port = ls_dhcp_relay_port(op->od);
+    if (!dhcp_relay_port) {
+        return;
+    }
+
+    struct ovn_port *sp = ovn_port_find(ls_ports, dhcp_relay_port);
+
+    if (!sp || !sp->nbsp || !sp->peer) {
+        return;
+    }
+
+    struct ovn_port *rp = sp->peer;
+    if (!rp || !rp->nbrp || !rp->nbrp->dhcp_relay || rp->peer != sp) {
+        return;
+    }
+
+    char *server_ip_str = NULL;
+    uint16_t port;
+    int addr_family;
+    struct in6_addr server_ip;
+    struct nbrec_dhcp_relay *dhcp_relay = rp->nbrp->dhcp_relay;
+
+    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_clear(match);
+    ds_clear(actions);
+
+    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(actions,
+                  "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(actions),
+                              op->key,
+                              NULL,
+                              &op->nbsp->header_,
+                              op->lflow_ref);
+    ds_clear(match);
+    ds_clear(actions);
+    free(server_ip_str);
+}
+
 static void
 build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
                                                  const struct ovn_port *port,
@@ -9156,6 +9244,13 @@  build_lswitch_dhcp_options_and_response(struct ovn_port *op,
         return;
     }
 
+    if (op->od && op->od->nbs
+        && ls_dhcp_relay_port(op->od)) {
+        /* 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)) {
@@ -13611,6 +13706,166 @@  build_dhcpv6_reply_flows_for_lrouter_port(
     }
 }
 
+static void
+build_dhcp_relay_flows_for_lrouter_port(
+        struct ovn_port *op, struct lflow_table *lflows,
+        struct ds *match, struct ds *actions,
+        struct lflow_ref *lflow_ref)
+{
+    if (!op->nbrp || !op->nbrp->dhcp_relay) {
+        return;
+
+    }
+
+    /* configure dhcp relay flows only when peer switch has
+     * relay config enabled */
+    struct ovn_port *sp = op->peer;
+    if (!sp || !sp->nbsp || sp->peer != op ||
+        !sp->od || !ls_dhcp_relay_port(sp->od)) {
+        return;
+    }
+
+    struct nbrec_dhcp_relay *dhcp_relay = op->nbrp->dhcp_relay;
+    if (!dhcp_relay->servers) {
+        return;
+    }
+
+    int addr_family;
+    /* currently not supporting custom port,
+     * dhcp server port is always set to 67 when installing flows */
+    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;
+    }
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    ds_put_format(
+        match, "inport == %s && "
+        "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+        "ip.frag == 0 && udp.src == 68 && udp.dst == 67",
+        op->json_key);
+    ds_put_format(actions,
+                REGBIT_DHCP_RELAY_REQ_CHK
+                " = dhcp_relay_req_chk(%s, %s);"
+                "next; /* DHCP_RELAY_REQ */",
+                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(actions),
+                            &op->nbrp->header_, lflow_ref);
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    ds_put_format(
+        match, "inport == %s && "
+        "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+        "udp.src == 68 && udp.dst == 67 && "
+        REGBIT_DHCP_RELAY_REQ_CHK,
+        op->json_key);
+    ds_put_format(actions,
+                "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_RELAY_REQ */",
+                op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+
+    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_REQ, 100,
+                            ds_cstr(match), ds_cstr(actions),
+                            &op->nbrp->header_, lflow_ref);
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    ds_put_format(
+        match, "inport == %s && "
+        "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+        "udp.src == 68 && udp.dst == 67 && "
+        REGBIT_DHCP_RELAY_REQ_CHK" == 0",
+        op->json_key);
+    ds_put_format(actions,
+                "drop; /* DHCP_RELAY_REQ */");
+
+    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_REQ, 1,
+                            ds_cstr(match), ds_cstr(actions),
+                            &op->nbrp->header_, lflow_ref);
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    ds_put_format(
+        match, "ip4.src == %s && ip4.dst == %s && "
+        "ip.frag == 0 && udp.src == 67 && udp.dst == 67",
+        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+    ds_put_format(actions, "next;/* DHCP_RELAY_RESP */");
+    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
+                            ds_cstr(match), ds_cstr(actions),
+                            &op->nbrp->header_, lflow_ref);
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    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(actions,
+          REG_DHCP_RELAY_DIP_IPV4" = ip4.dst;"
+          REGBIT_DHCP_RELAY_RESP_CHK
+          " = dhcp_relay_resp_chk(%s, %s);next;/* DHCP_RELAY_RESP */",
+          op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+
+    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP_CHK,
+                            100,
+                            ds_cstr(match), ds_cstr(actions),
+                            &op->nbrp->header_, lflow_ref);
+
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    ds_put_format(
+        match, "ip4.src == %s && "
+        REG_DHCP_RELAY_DIP_IPV4" == %s && "
+        "udp.src == 67 && udp.dst == 67 && "
+        REGBIT_DHCP_RELAY_RESP_CHK,
+        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+    ds_put_format(actions,
+          "ip4.src=%s;udp.dst=68;"
+          "outport=%s;output; /* DHCP_RELAY_RESP */",
+          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,
+                            100,
+                            ds_cstr(match), ds_cstr(actions),
+                            &op->nbrp->header_, lflow_ref);
+
+    ds_clear(match);
+    ds_clear(actions);
+
+    ds_put_format(
+        match, "ip4.src == %s && "
+        REG_DHCP_RELAY_DIP_IPV4" == %s && "
+        "udp.src == 67 && udp.dst == 67 && "
+        REGBIT_DHCP_RELAY_RESP_CHK" == 0",
+        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+    ds_put_format(actions,
+          "drop; /* DHCP_RELAY_RESP */");
+    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP,
+                            1,
+                            ds_cstr(match), ds_cstr(actions),
+                            &op->nbrp->header_, lflow_ref);
+    ds_clear(match);
+    ds_clear(actions);
+    free(server_ip_str);
+}
+
 static void
 build_ipv6_input_flows_for_lrouter_port(
         struct ovn_port *op, struct lflow_table *lflows,
@@ -14881,6 +15136,13 @@  static void build_lr_nat_defrag_and_lb_default_flows(
                   lflow_ref);
     ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;",
                   lflow_ref);
+    ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_REQ, 0, "1",
+                  "next;", lflow_ref);
+    ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_RESP_CHK, 0, "1",
+                  "next;", lflow_ref);
+    ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_RESP, 0, "1",
+                  "next;", lflow_ref);
+
 
     /* Send the IPv6 NS packets to next table. When ovn-controller
      * generates IPv6 NS (for the action - nd_ns{}), the injected
@@ -15640,6 +15902,7 @@  build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
     build_lswitch_icmp_packet_toobig_admin_flows(op, lflows, match, actions);
     build_lswitch_ip_unicast_lookup(op, lflows, actions,
                                     match);
+    build_lswitch_dhcp_relay_flows(op, ls_ports, lflows, match, actions);
 
     /* Build Logical Router Flows. */
     build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, match, actions);
@@ -15669,6 +15932,8 @@  build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
                                                  op->lflow_ref);
     build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
                                               op->lflow_ref);
+    build_dhcp_relay_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
+                                            &lsi->actions, op->lflow_ref);
     build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
                                             &lsi->match, &lsi->actions,
                                             lsi->meter_groups,
diff --git a/northd/northd.h b/northd/northd.h
index 3f1cd8341..0a349c8f7 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -437,24 +437,29 @@  enum ovn_stage {
     PIPELINE_STAGE(ROUTER, IN,  LOOKUP_NEIGHBOR, 1, "lr_in_lookup_neighbor") \
     PIPELINE_STAGE(ROUTER, IN,  LEARN_NEIGHBOR,  2, "lr_in_learn_neighbor") \
     PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,        3, "lr_in_ip_input")     \
-    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,          4, "lr_in_unsnat")       \
-    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,          5, "lr_in_defrag")       \
-    PIPELINE_STAGE(ROUTER, IN,  LB_AFF_CHECK,    6, "lr_in_lb_aff_check") \
-    PIPELINE_STAGE(ROUTER, IN,  DNAT,            7, "lr_in_dnat")         \
-    PIPELINE_STAGE(ROUTER, IN,  LB_AFF_LEARN,    8, "lr_in_lb_aff_learn") \
-    PIPELINE_STAGE(ROUTER, IN,  ECMP_STATEFUL,   9, "lr_in_ecmp_stateful") \
-    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,   10, "lr_in_nd_ra_options") \
-    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE,  11, "lr_in_nd_ra_response") \
-    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING_PRE,  12, "lr_in_ip_routing_pre")  \
-    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,      13, "lr_in_ip_routing")      \
-    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_REQ,  4, "lr_in_dhcp_relay_req") \
+    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,          5, "lr_in_unsnat")       \
+    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,          6, "lr_in_defrag")       \
+    PIPELINE_STAGE(ROUTER, IN,  LB_AFF_CHECK,    7, "lr_in_lb_aff_check") \
+    PIPELINE_STAGE(ROUTER, IN,  DNAT,            8, "lr_in_dnat")         \
+    PIPELINE_STAGE(ROUTER, IN,  LB_AFF_LEARN,    9, "lr_in_lb_aff_learn") \
+    PIPELINE_STAGE(ROUTER, IN,  ECMP_STATEFUL,   10, "lr_in_ecmp_stateful") \
+    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,   11, "lr_in_nd_ra_options") \
+    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE,  12, "lr_in_nd_ra_response") \
+    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING_PRE,  13, "lr_in_ip_routing_pre")  \
+    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,      14, "lr_in_ip_routing")      \
+    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING_ECMP, 15, "lr_in_ip_routing_ecmp") \
+    PIPELINE_STAGE(ROUTER, IN,  POLICY,          16, "lr_in_policy")          \
+    PIPELINE_STAGE(ROUTER, IN,  POLICY_ECMP,     17, "lr_in_policy_ecmp")     \
+    PIPELINE_STAGE(ROUTER, IN,  DHCP_RELAY_RESP_CHK, 18,                      \
+                  "lr_in_dhcp_relay_resp_chk")                                \
+    PIPELINE_STAGE(ROUTER, IN,  DHCP_RELAY_RESP, 19,                          \
+                  "lr_in_dhcp_relay_resp")                                    \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,     20, "lr_in_arp_resolve")     \
+    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN,     21, "lr_in_chk_pkt_len")     \
+    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,     22, "lr_in_larger_pkts")     \
+    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,     23, "lr_in_gw_redirect")     \
+    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,     24, "lr_in_arp_request")     \
                                                                       \
     /* Logical router egress stages. */                               \
     PIPELINE_STAGE(ROUTER, OUT, CHECK_DNAT_LOCAL,   0,                       \
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index a9c5b7af5..a616dbd6b 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
     "version": "7.3.0",
-    "cksum": "3546526738 34483",
+    "cksum": "3858903371 35372",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -436,6 +436,11 @@ 
                 "ipv6_prefix": {"type": {"key": "string",
                                       "min": 0,
                                       "max": "unlimited"}},
+                "dhcp_relay": {"type": {"key": {"type": "uuid",
+                                            "refTable": "DHCP_Relay",
+                                            "refType": "strong"},
+                                            "min": 0,
+                                            "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
@@ -534,6 +539,18 @@ 
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "isRoot": true},
+        "DHCP_Relay": {
+            "columns": {
+                "name": {"type": "string"},
+                "servers": {"type": {"key": "string",
+                                       "min": 0,
+                                       "max": 1}},
+                "options": {"type": {"key": "string", "value": "string",
+                                     "min": 0, "max": "unlimited"}},
+                "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 b652046a7..5cb6ba640 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -703,6 +703,13 @@ 
         </ul>
       </column>
 
+      <column name="other_config" key="dhcp_relay_port">
+        If set to the name of logical switch port of type <code>router</code>
+        then, DHCP Relay is enabled for this logical switch provided the
+        corresponding <ref table="Logical_Router_Port"/> has DHCP Relay
+        configured.
+      </column>
+
       <column name="other_config" key="mac_only" type='{"type": "boolean"}'>
         Value used to request to assign L2 address only if neither subnet
         nor ipv6_prefix are specified
@@ -3066,6 +3073,11 @@  or
       port has all ingress and egress traffic dropped.
     </column>
 
+    <column name="dhcp_relay">
+      This column is used to enabled DHCP Relay. Please refer
+      to <ref table="DHCP_Relay"/> table.
+    </column>
+
     <group title="Distributed Gateway Ports">
       <p>
         Gateways, as documented under <code>Gateways</code> in the OVN
@@ -4379,6 +4391,33 @@  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="name">
+      <p>
+        A name for the DHCP Relay.
+      </p>
+    </column>
+    <column name="servers">
+      <p>
+        The DHCPv4 server IP address.
+      </p>
+    </column>
+    <column name="options">
+      <p>
+        Future purpose.
+      </p>
+    </column>
+    <group title="Common Columns">
+      <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+  </table>
+
   <table name="Connection" title="OVSDB client connections.">
     <p>
       Configuration for a database connection to an Open vSwitch database
diff --git a/tests/ovn.at b/tests/ovn.at
index 4d0c7ad53..32e3d8b13 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -34942,7 +34942,7 @@  check ovn-nbctl set nb_global . options:use_common_zone="true"
 check ovn-nbctl --wait=hv sync
 # Use constants so that if tables or registers change, this test can
 # be updated easily.
-DNAT_TABLE=15
+DNAT_TABLE=16
 SNAT_TABLE=45
 DNAT_ZONE_REG="NXM_NX_REG11[[0..15]]"
 SNAT_ZONE_REG="NXM_NX_REG12[[0..15]]"