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.yerramn...@nutanix.com>
Co-authored-by: Huzaifa Calcuttawala <huzaif...@nutanix.com>
Signed-off-by: Huzaifa Calcuttawala <huzaif...@nutanix.com>
CC: Mary Manohar <mary.mano...@nutanix.com>
---
 northd/northd.c         | 271 +++++++++++++++++++++++++++++++++++++++-
 northd/northd.h         |  41 +++---
 northd/ovn-northd.8.xml | 211 +++++++++++++++++++++++++++----
 ovn-nb.ovsschema        |  21 +++-
 ovn-nb.xml              |  39 ++++++
 tests/atlocal.in        |   3 +
 tests/ovn-northd.at     |  38 ++++++
 tests/ovn.at            | 224 ++++++++++++++++++++++++++++++++-
 tests/system-ovn.at     | 148 ++++++++++++++++++++++
 9 files changed, 948 insertions(+), 48 deletions(-)

diff --git a/northd/northd.c b/northd/northd.c
index 331d9c267..045c3576b 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  |   |                 |
  * |     |                           |   |                 |
@@ -8543,6 +8547,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,
@@ -9144,6 +9232,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)) {
@@ -13623,6 +13718,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,
@@ -14893,6 +15148,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
@@ -15652,6 +15914,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);
@@ -15681,6 +15944,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 18cad5234..940926945 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -441,24 +441,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/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index b14a30285..1e7fb3888 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1934,6 +1934,12 @@ output;
         logical switch.
       </li>
 
+      <li>
+        A priority-100 flow that forwards all DHCP broadcast packets coming
+        from VIFs to the logical router port's MAC when DHCP relay is enabled
+        on the logical switch.
+      </li>
+
       <li>
         Priority-90 flows for transit switches that forward registered
         IP multicast traffic to their corresponding multicast group , which
@@ -2864,6 +2870,44 @@ icmp6_error {
         </p>
       </li>
 
+      <li>
+        <p>
+            For each logical router port configured with DHCP relay the
+            following priority-110 flows are added to manage the DHCP relay
+            traffic:
+
+            <ul>
+              <li>
+                <p>
+                  if <code>inport</code> is lrp and <code>ip4.src == 0.0.0.0
+                  </code> and <code>ip4.dst == 255.255.255.255</code> and
+                  <code>ip4.frag == 0 </code> and <code>udp.src == 68</code>
+                  and <code>udp.dst == 67</code>, the <code>dhcp_relay_req_chk
+                  </code> action is executed.
+                </p>
+
+              <pre>
+                reg9[7] = dhcp_relay_req_chk(<var>lrp_ip</var>,
+                                            <var>dhcp_server_ip</var>);next
+              </pre>
+
+              <p>
+                if action is successful then, GIADDR in the dhcp header is
+                updated with lrp ip and stores 1 into reg9[7] else stores 0
+                into reg9[7].
+              </p>
+              </li>
+
+              <li>
+               if <code>ip4.src</code> is DHCP server ip and <code>ip4.dst
+               </code> is lrp IP and <code>udp.src == 67</code> and
+               <code>udp.dst == 67</code>, the packet is advanced to the next
+               pipeline stage.
+              </li>
+            </ul>
+        </p>
+      </li>
+
       <li>
         <p>
           L3 admission control: Priority-120 flows allows IGMP and MLD packets
@@ -3328,8 +3372,50 @@ icmp6 {
     </ul>
 
 
+    <h3>Ingress Table 4: DHCP Relay Request</h3>
+    <p>
+      This stage process the DHCP request packets on which
+      <code>dhcp_relay_req_chk</code> action is applied in the IP input stage.
+    </p>
+    <ul>
+      <li>
+        <p>
+          A priority-100 logical flow is added for each logical router port
+          configured with DHCP relay that matches <code>inport</code> is lrp
+          and <code>ip4.src == 0.0.0.0</code> and
+          <code>ip4.dst == 255.255.255.255</code> and <code>udp.src == 68
+          </code> and <code>udp.dst == 67</code> and <code>reg9[7] == 1</code>
+          and applies following actions. If <code>reg9[7]</code> is set to 1
+          then, <code>dhcp_relay_req_chk</code> action was successful.
+        </p>
+
+        <pre>
+ip4.src=<var>lrp ip</var>;
+ip4.dst=<var>dhcp server ip</var>;
+udp.src = 67;
+next;
+        </pre>
+      </li>
+
+      <li>
+        <p>
+          A priority-1 logical flow is added for each logical router port
+          configured with DHCP relay that matches <code>inport</code> is lrp
+          and <code>ip4.src == 0.0.0.0</code> and
+          <code>ip4.dst == 255.255.255.255</code> and <code>udp.src == 68
+          </code> and <code>udp.dst == 67</code> and <code>reg9[7] == 0</code>
+          and drops the packet. If <code>reg9[7]</code> is set to 0 then,
+          <code>dhcp_relay_req_chk</code> action was unsuccessful.
+        </p>
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advance to the next
+        table.
+      </li>
+    </ul>
 
-    <h3>Ingress Table 4: UNSNAT</h3>
+    <h3>Ingress Table 5: UNSNAT</h3>
 
     <p>
       This is for already established connections' reverse traffic.
@@ -3338,7 +3424,7 @@ icmp6 {
       unSNATted here.
     </p>
 
-    <p>Ingress Table 4: UNSNAT on Gateway and Distributed Routers</p>
+    <p>Ingress Table 5: UNSNAT on Gateway and Distributed Routers</p>
     <ul>
       <li>
         <p>
@@ -3365,7 +3451,7 @@ icmp6 {
       </li>
     </ul>
 
-    <p>Ingress Table 4: UNSNAT on Gateway Routers</p>
+    <p>Ingress Table 5: UNSNAT on Gateway Routers</p>
 
     <ul>
       <li>
@@ -3414,7 +3500,7 @@ icmp6 {
       </li>
     </ul>
 
-    <p>Ingress Table 4: UNSNAT on Distributed Routers</p>
+    <p>Ingress Table 5: UNSNAT on Distributed Routers</p>
 
     <ul>
       <li>
@@ -3461,7 +3547,7 @@ icmp6 {
       </li>
     </ul>
 
-    <h3>Ingress Table 5: DEFRAG</h3>
+    <h3>Ingress Table 6: DEFRAG</h3>
 
     <p>
       This is to send packets to connection tracker for tracking and
@@ -3504,7 +3590,7 @@ icmp6 {
       this allows potentially related ICMP traffic to pass through CT.
     </p>
 
-    <h3>Ingress Table 6: Load balancing affinity check</h3>
+    <h3>Ingress Table 7: Load balancing affinity check</h3>
 
     <p>
       Load balancing affinity check table contains the following
@@ -3531,7 +3617,7 @@ icmp6 {
       </li>
     </ul>
 
-    <h3>Ingress Table 7: DNAT</h3>
+    <h3>Ingress Table 8: DNAT</h3>
 
     <p>
       Packets enter the pipeline with destination IP address that needs to
@@ -3539,7 +3625,7 @@ icmp6 {
       in the reverse direction needs to be unDNATed.
     </p>
 
-    <p>Ingress Table 7: Load balancing DNAT rules</p>
+    <p>Ingress Table 8: Load balancing DNAT rules</p>
 
     <p>
       Following load balancing DNAT flows are added for Gateway router or
@@ -3660,7 +3746,7 @@ icmp6 {
       </li>
     </ul>
 
-    <p>Ingress Table 7: DNAT on Gateway Routers</p>
+    <p>Ingress Table 8: DNAT on Gateway Routers</p>
 
     <ul>
       <li>
@@ -3702,7 +3788,7 @@ icmp6 {
       </li>
     </ul>
 
-    <p>Ingress Table 7: DNAT on Distributed Routers</p>
+    <p>Ingress Table 8: DNAT on Distributed Routers</p>
 
     <p>
       On distributed routers, the DNAT table only handles packets
@@ -3757,7 +3843,7 @@ icmp6 {
       </li>
     </ul>
 
-    <h3>Ingress Table 8: Load balancing affinity learn</h3>
+    <h3>Ingress Table 9: Load balancing affinity learn</h3>
 
     <p>
       Load balancing affinity learn table contains the following
@@ -3785,7 +3871,7 @@ icmp6 {
       </li>
     </ul>
 
-    <h3>Ingress Table 9: ECMP symmetric reply processing</h3>
+    <h3>Ingress Table 10: ECMP symmetric reply processing</h3>
     <ul>
       <li>
         If ECMP routes with symmetric reply are configured in the
@@ -3804,7 +3890,7 @@ icmp6 {
       </li>
     </ul>
 
-    <h3>Ingress Table 10: IPv6 ND RA option processing</h3>
+    <h3>Ingress Table 11: IPv6 ND RA option processing</h3>
 
     <ul>
       <li>
@@ -3834,7 +3920,7 @@ reg0[5] = put_nd_ra_opts(<var>options</var>);next;
       </li>
     </ul>
 
-    <h3>Ingress Table 11: IPv6 ND RA responder</h3>
+    <h3>Ingress Table 12: IPv6 ND RA responder</h3>
 
     <p>
       This table implements IPv6 ND RA responder for the IPv6 ND RA replies
@@ -3879,7 +3965,7 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 12: IP Routing Pre</h3>
+    <h3>Ingress Table 13: IP Routing Pre</h3>
 
     <p>
       If a packet arrived at this table from Logical Router Port <var>P</var>
@@ -3909,7 +3995,7 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 13: IP Routing</h3>
+    <h3>Ingress Table 14: IP Routing</h3>
 
     <p>
       A packet that arrives at this table is an IP packet that should be
@@ -4115,7 +4201,7 @@ select(reg8[16..31], <var>MID1</var>, <var>MID2</var>, 
...);
       </li>
     </ul>
 
-    <h3>Ingress Table 14: IP_ROUTING_ECMP</h3>
+    <h3>Ingress Table 15: IP_ROUTING_ECMP</h3>
 
     <p>
       This table implements the second part of IP routing for ECMP routes
@@ -4172,7 +4258,7 @@ outport = <var>P</var>;
       </li>
     </ul>
 
-    <h3>Ingress Table 15: Router policies</h3>
+    <h3>Ingress Table 16: Router policies</h3>
     <p>
       This table adds flows for the logical router policies configured
       on the logical router. Please see the
@@ -4244,7 +4330,7 @@ next;
       </li>
     </ul>
 
-    <h3>Ingress Table 16: ECMP handling for router policies</h3>
+    <h3>Ingress Table 17: ECMP handling for router policies</h3>
     <p>
       This table handles the ECMP for the router policies configured
       with multiple nexthops.
@@ -4293,7 +4379,84 @@ outport = <var>P</var>
       </li>
     </ul>
 
-    <h3>Ingress Table 17: ARP/ND Resolution</h3>
+    <h3>Ingress Table 18: DHCP Relay Response Check</h3>
+    <p>
+      This stage process the DHCP response packets coming from the DHCP server.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+         A priority 100 logical flow is added for each logical router port
+         configured with DHCP relay that matches <code>ip4.src</code> is
+         DHCP server ip and <code>ip4.dst</code> is lrp IP and
+         <code>ip4.frag == 0</code> and <code>udp.src == 67</code> and
+         <code>udp.dst == 67</code> and applies <code>dhcp_relay_resp_chk
+         </code> action. Original destination ip is stored in reg2.
+        </p>
+
+      <pre>
+        reg9[8] = dhcp_relay_resp_chk(<var>lrp_ip</var>,
+                                      <var>dhcp_server_ip</var>);next
+      </pre>
+
+      <p>
+        if action is successful then, dest mac and dest IP addresses are
+        updated in the packet and stores 1 into reg9[8] else stores 0 into
+        reg9[8].
+      </p>
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advance to the next
+        table.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 19: DHCP Relay Response</h3>
+    <p>
+      This stage process the DHCP response packets on which
+      <code>dhcp_relay_resp_chk</code> action is applied in the previous stage.
+    </p>
+    <ul>
+      <li>
+        <p>
+          A priority 100 logical flow is added for each logical router port
+          configured with DHCP relay that matches <code>ip4.src</code> is
+          DHCP server ip and <code>reg2</code> is lrp IP and
+          <code>udp.src == 67</code> and <code>udp.dst == 67</code>
+          and <code>reg9[8] == 1</code> and applies following actions. If
+          <code>reg9[8]</code> is set to 1 then,
+          <code>dhcp_relay_resp_chk</code> was successful.
+        </p>
+
+        <pre>
+ip4.src = <var>lrp ip</var>;
+udp.dst = 68;
+outport = <var>lrp port</var>;
+output;
+        </pre>
+      </li>
+
+      <li>
+        <p>
+          A priority 1 logical flow is added for the logical router port
+          on which DHCP relay is enabled that matches <code>ip4.src</code>
+          is DHCP server ip and <code>reg2</code> is lrp IP and
+          <code>udp.src == 67</code> and <code>udp.dst == 67</code>
+          and <code>reg9[8] == 0</code> and drops the packet. If
+          <code>reg9[8]</code> is set to 0 then,
+          <code>dhcp_relay_resp_chk</code> was unsuccessful.
+        </p>
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advance to the next
+        table.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 20: ARP/ND Resolution</h3>
 
     <p>
       Any packet that reaches this table is an IP packet whose next-hop
@@ -4507,7 +4670,7 @@ outport = <var>P</var>
 
     </ul>
 
-    <h3>Ingress Table 18: Check packet length</h3>
+    <h3>Ingress Table 21: Check packet length</h3>
 
     <p>
       For distributed logical routers or gateway routers with gateway
@@ -4544,7 +4707,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next;
       and advances to the next table.
     </p>
 
-    <h3>Ingress Table 19: Handle larger packets</h3>
+    <h3>Ingress Table 22: Handle larger packets</h3>
 
     <p>
       For distributed logical routers or gateway routers with gateway port
@@ -4607,7 +4770,7 @@ icmp6 {
       and advances to the next table.
     </p>
 
-    <h3>Ingress Table 20: Gateway Redirect</h3>
+    <h3>Ingress Table 23: Gateway Redirect</h3>
 
     <p>
       For distributed logical routers where one or more of the logical router
@@ -4691,7 +4854,7 @@ icmp6 {
       </li>
     </ul>
 
-    <h3>Ingress Table 21: ARP Request</h3>
+    <h3>Ingress Table 24: ARP Request</h3>
 
     <p>
       In the common case where the Ethernet destination has been resolved, this
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index a9c5b7af5..10ce50b25 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "7.3.0",
-    "cksum": "3546526738 34483",
+    "version": "7.3.1",
+    "cksum": "3899022625 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/atlocal.in b/tests/atlocal.in
index 63d891b89..32d1c374e 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -187,6 +187,9 @@ fi
 # Set HAVE_DHCPD
 find_command dhcpd
 
+# Set HAVE_DHCLIENT
+find_command dhclient
+
 # Set HAVE_BFDD_BEACON
 find_command bfdd-beacon
 
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index dcc29ffa8..7267c7017 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -12191,6 +12191,44 @@ check_row_count nb:QoS 0
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([check DHCP RELAY])
+ovn_start NORTHD_TYPE
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lsp-add ls0 ls0-port1
+check ovn-nbctl lsp-set-addresses ls0-port1 02:00:00:00:00:10
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lrp1 02:00:00:00:00:01 192.168.1.1/24
+check ovn-nbctl lsp-add ls0 lrp1-attachment
+check ovn-nbctl lsp-set-type lrp1-attachment router
+check ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02
+check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1
+check ovn-nbctl lrp-add lr0 lrp-ext 02:00:00:00:00:02 192.168.2.1/24
+
+dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1)
+check ovn-nbctl set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay
+check ovn-nbctl set Logical_Switch ls0 
other_config:dhcp_relay_port=lrp1-attachment
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl
+  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && ip.frag == 0 && udp.src == 
68 && udp.dst == 67), action=(reg9[[7]] = dhcp_relay_req_chk(192.168.1.1, 
172.16.1.1);next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next;/* DHCP_RELAY_RESP */)
+  table=??(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=192.168.1.1;ip4.dst=172.16.1.1;udp.src=67;next; /* 
DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_req), priority=1    , match=(inport == "lrp1" && 
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 */)
+  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst;reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 
172.16.1.1);next;/* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]]), action=(ip4.src=192.168.1.1;udp.dst=68;outport="lrp1";output; /* 
DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]] == 0), action=(drop; /* DHCP_RELAY_RESP */)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(inport == "ls0-port1" 
&& eth.src == 02:00:00:00:00:10 && ip4.src == 0.0.0.0 && ip4.dst == 
255.255.255.255 && udp.src == 68 && udp.dst == 67), 
action=(eth.dst=02:00:00:00:00:01;outport="lrp1-attachment";next;/* 
DHCP_RELAY_REQ */)
+])
+
+AT_CLEANUP
+])
+
 AT_SETUP([NB_Global and SB_Global incremental processing])
 
 ovn_start
diff --git a/tests/ovn.at b/tests/ovn.at
index 1ad4159cf..395bcf142 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -34273,7 +34273,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]]"
@@ -37806,3 +37806,225 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows 
br-int table=0 |grep priority=1
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DHCP RELAY])
+ovn_start
+net_add n1
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+AT_CHECK([ovn-nbctl lsp-add ls0 vif0])
+AT_CHECK([ovn-nbctl lsp-set-addresses vif0 "50:54:00:00:00:10"])
+AT_CHECK([ovn-nbctl lsp-add ls0 lrp1-attachment])
+AT_CHECK([ovn-nbctl lsp-set-type lrp1-attachment router])
+AT_CHECK([ovn-nbctl lsp-set-addresses lrp1-attachment 50:54:00:00:00:01])
+AT_CHECK([ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1])
+
+AT_CHECK([ovn-nbctl lr-add lr0])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 50:54:00:00:00:01 192.168.1.1/24])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp2 50:54:00:00:00:02 172.16.1.254/24])
+
+AT_CHECK([ovn-nbctl ls-add ls-ext])
+AT_CHECK([ovn-nbctl lsp-add ls-ext lrp2-attachment])
+AT_CHECK([ovn-nbctl lsp-set-type lrp2-attachment router])
+AT_CHECK([ovn-nbctl lsp-set-addresses lrp2-attachment 50:54:00:00:00:02])
+AT_CHECK([ovn-nbctl lsp-set-options lrp2-attachment router-port=lrp2])
+AT_CHECK([ovn-nbctl lsp-add ls-ext ln_port])
+AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
+AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
+AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
+
+dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1)
+AT_CHECK([ovn-nbctl set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay])
+AT_CHECK([ovn-nbctl set Logical_Switch ls0 
other_config:dhcp_relay_port=lrp1-attachment])
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int vif0 -- \
+    set interface vif0 external-ids:iface-id=vif0 \
+    options:tx_pcap=hv1/vif0-tx.pcap \
+    options:rxq_pcap=hv1/vif0-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-phys ext0 -- \
+    set interface ext0 \
+    options:tx_pcap=hv1/ext0-tx.pcap \
+    options:rxq_pcap=hv1/ext0-rx.pcap \
+    ofport-request=2
+
+ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+
+wait_for_ports_up
+AT_CHECK([ovn-nbctl --wait=hv sync])
+
+send_dhcp_packet() {
+    src_mac=${1}
+    src_ip=${2}
+    dst_mac=${3}
+    dst_ip=${4}
+    op_code=${5}
+    msg_type=${6}
+    yiaddr=$7
+    giaddr=${8}
+    sid=${9}
+    iface=${10}
+    #echo "ARGS: ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10}"
+    echo "ARGS: $@"
+    if [[ $op_code == "01" ]]; then
+        ip_len=0111
+        udp_len=00fd
+        src_port=0044
+    else
+        ip_len=011d
+        udp_len=0109
+        src_port=0043
+    fi
+    flags=0000
+
+    local 
pkt=${dst_mac}${src_mac}08004510${ip_len}0000000080110000${src_ip}${dst_ip}
+    # udp header and dhcp header
+    pkt=${pkt}${src_port}0043${udp_len}0000
+    
pkt=${pkt}${op_code}0106006359aa760000${flags}00000000${yiaddr}00000000${giaddr}${src_mac}
+    # client hardware padding
+    pkt=${pkt}00000000000000000000
+    # server hostname
+    pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+    pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+    # boot file name
+    pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+    pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+    pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+    pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+    # dhcp magic cookie
+    pkt=${pkt}63825363
+    # dhcp message type
+    pkt=${pkt}3501${msg_type}
+    # dhcp server identifier and subnet mask options
+    if  [[ $op_code == "02" ]]; then
+        pkt=${pkt}3604${sid}
+        pkt=${pkt}0104ffffff00
+    fi
+    # dhcp pad option
+    pkt=${pkt}00
+    # dhcp end option
+    pkt=${pkt}ff
+
+    tcpdump_hex "-- sending DHCP pkt on hv1-$iface" $pkt
+
+    ovs-appctl netdev-dummy/receive $iface $pkt
+}
+
+ovn-sbctl dump-flows > lflows
+AT_CAPTURE_FILE([lflows])
+
+# Get the OF table numbers
+dhcp_relay_req_table=$(ovn-debug lflow-stage-to-oftable lr_in_dhcp_relay_req)
+dhcp_relay_resp_table=$(ovn-debug lflow-stage-to-oftable lr_in_dhcp_relay_resp)
+
+# ====================================================
+# Send DHCP valid discovery
+src_mac="505400000010"
+src_ip=`ip_to_hex 0.0.0.0`
+dst_mac="ffffffffffff"
+dst_ip=`ip_to_hex 255.255.255.255`
+yiaddr=`ip_to_hex 0.0.0.0`
+giaddr=`ip_to_hex 0.0.0.0`
+sid=$src_ip
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 01 01 $yiaddr $giaddr $sid 
vif0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_req_table > 
pflows1_dhcp_relay_req
+AT_CAPTURE_FILE([pflows1_dhcp_relay_req])
+
+AT_CHECK([cat pflows1_dhcp_relay_req | grep -v NXST | grep 255.255.255.255 | 
grep resubmit |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+
+# ====================================================
+# Send DHCP discovery with giaddr set
+giaddr=`ip_to_hex 192.168.1.1`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 01 01 $yiaddr $giaddr $sid 
vif0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_req_table > 
pflows2_dhcp_relay_req
+AT_CAPTURE_FILE([pflows2_dhcp_relay_req])
+
+AT_CHECK([cat pflows2_dhcp_relay_req | grep -v NXST | grep 255.255.255.255 | 
grep drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+
+# ====================================================
+# Send DHCP valid offer
+src_mac="50540000001f"
+src_ip=`ip_to_hex 172.16.1.1`
+dst_mac="505400000002"
+dst_ip=`ip_to_hex 192.168.1.1`
+yiaddr=`ip_to_hex 192.168.1.10`
+giaddr=`ip_to_hex 192.168.1.1`
+sid=$src_ip
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid 
ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > 
pflows1_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows1_dhcp_relay_resp])
+
+AT_CHECK([cat pflows1_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep 
resubmit |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+# ====================================================
+# Send DHCP offer with incorrect giaddr
+giaddr=`ip_to_hex 192.168.1.10`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid 
ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > 
pflows2_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows2_dhcp_relay_resp])
+
+AT_CHECK([cat pflows2_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep 
drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+
+giaddr=`ip_to_hex 192.168.1.1`
+
+# ====================================================
+# Send DHCP offer with yiaddr outside of the subnet
+yiaddr=`ip_to_hex 192.168.2.10`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid 
ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > 
pflows2_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows2_dhcp_relay_resp])
+
+AT_CHECK([cat pflows2_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep 
drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=2
+])
+
+yiaddr=`ip_to_hex 192.168.1.10`
+
+# ====================================================
+# Send DHCP offer with differnt server identifier
+sid=`ip_to_hex 172.16.1.100`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid 
ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > 
pflows2_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows2_dhcp_relay_resp])
+
+AT_CHECK([cat pflows2_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep 
drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=3
+])
+
+sid=`ip_to_hex 172.16.1.1`
+
+OVN_CLEANUP([hv1
+/WARN\|DHCP_RELAY/d
+])
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 5848f3901..b9f731396 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -12512,3 +12512,151 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
patch-.*/d
 /connection dropped.*/d"])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DHCP RELAY])
+AT_SKIP_IF([test $HAVE_DHCPD = no])
+AT_SKIP_IF([test $HAVE_DHCLIENT = no])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+ADD_NAMESPACES(sw01)
+ADD_VETH(sw01, sw01, br-int, "0", "f0:00:00:01:02:03")
+ADD_NAMESPACES(sw11)
+ADD_VETH(sw11, sw11, br-int, "0", "f0:00:00:02:02:03")
+ADD_NAMESPACES(server)
+ADD_VETH(s1, server, br-ext, "172.16.1.1/24", "f0:00:00:01:02:05", \
+         "172.16.1.254")
+
+check ovn-nbctl lr-add R1
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl ls-add sw1
+check ovn-nbctl ls-add sw-ext
+
+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
+check ovn-nbctl lrp-add R1 rp-ext 00:00:02:01:02:03 172.16.1.254/24
+
+dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1)
+check ovn-nbctl set Logical_Router_port rp-sw0 dhcp_relay=$dhcp_relay
+check ovn-nbctl set Logical_Router_port rp-sw1 dhcp_relay=$dhcp_relay
+check ovn-nbctl lrp-set-gateway-chassis rp-ext hv1
+
+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
+    type=router options:router-port=rp-sw0 \
+    -- lsp-set-addresses sw0-rp router
+check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
+    type=router options:router-port=rp-sw1 \
+    -- lsp-set-addresses sw1-rp router
+
+check ovn-nbctl set Logical_Switch sw0 other_config:dhcp_relay_port=sw0-rp
+check ovn-nbctl set Logical_Switch sw1 other_config:dhcp_relay_port=sw1-rp
+
+check ovn-nbctl lsp-add sw-ext ext-rp -- set Logical_Switch_Port ext-rp \
+    type=router options:router-port=rp-ext \
+    -- lsp-set-addresses ext-rp router
+check ovn-nbctl lsp-add sw-ext lnet \
+        -- lsp-set-addresses lnet unknown \
+        -- lsp-set-type lnet localnet \
+        -- lsp-set-options lnet network_name=phynet
+
+check ovn-nbctl lsp-add sw0 sw01 \
+    -- lsp-set-addresses sw01 "f0:00:00:01:02:03"
+
+check ovn-nbctl lsp-add sw1 sw11 \
+    -- lsp-set-addresses sw11 "f0:00:00:02:02:03"
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . 
external-ids:ovn-bridge-mappings=phynet:br-ext])
+
+OVN_POPULATE_ARP
+
+check ovn-nbctl --wait=hv sync
+
+DHCP_TEST_DIR="/tmp/dhcp-test"
+rm -rf $DHCP_TEST_DIR
+mkdir $DHCP_TEST_DIR
+cat > $DHCP_TEST_DIR/dhcpd.conf <<EOF
+subnet 172.16.1.0 netmask 255.255.255.0 {
+}
+subnet 192.168.1.0 netmask 255.255.255.0 {
+  range 192.168.1.10 192.168.1.10;
+  option routers 192.168.1.1;
+  option broadcast-address 192.168.1.255;
+  default-lease-time 60;
+  max-lease-time 120;
+}
+subnet 192.168.2.0 netmask 255.255.255.0 {
+  range 192.168.2.10 192.168.2.10;
+  option routers 192.168.2.1;
+  option broadcast-address 192.168.2.255;
+  default-lease-time 60;
+  max-lease-time 120;
+}
+EOF
+cat > $DHCP_TEST_DIR/dhclien.conf <<EOF
+timeout 2
+EOF
+
+touch $DHCP_TEST_DIR/dhcpd.leases
+chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases
+chmod 775 $DHCP_TEST_DIR
+chmod 664 $DHCP_TEST_DIR/dhcpd.leases
+
+
+NETNS_DAEMONIZE([server], [dhcpd -4 -f -cf $DHCP_TEST_DIR/dhcpd.conf s1 > 
dhcpd.log 2>&1], [dhcpd.pid])
+
+NS_CHECK_EXEC([server], [tcpdump -l -nvv -i s1  udp > pkt.pcap 2>tcpdump_err 
&])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+on_exit 'kill $(pidof tcpdump)'
+
+NS_CHECK_EXEC([sw01], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw01.lease 
-pf $DHCP_TEST_DIR/dhclient-sw01.pid -cf $DHCP_TEST_DIR/dhclien.conf sw01])
+NS_CHECK_EXEC([sw11], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw11.lease 
-pf $DHCP_TEST_DIR/dhclient-sw11.pid -cf $DHCP_TEST_DIR/dhclien.conf sw11])
+
+OVS_WAIT_UNTIL([
+    total_pkts=$(cat pkt.pcap | wc -l)
+    test ${total_pkts} -ge 8
+])
+
+on_exit 'kill `cat $DHCP_TEST_DIR/dhclient-sw01.pid` &&
+kill `cat $DHCP_TEST_DIR/dhclient-sw11.pid` && rm -rf $DHCP_TEST_DIR'
+
+NS_CHECK_EXEC([sw01], [ip addr show sw01 | grep -oP 
'(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl
+192.168.1.10
+])
+NS_CHECK_EXEC([sw11], [ip addr show sw11 | grep -oP 
'(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl
+192.168.2.10
+])
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/failed to query port patch-.*/d
+/.*terminating with signal 15.*/d"])
+AT_CLEANUP
+])
-- 
2.36.6

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

Reply via email to