On Wed, Apr 24, 2024 at 5:57 AM Naveen Yerramneni <
naveen.yerramn...@nutanix.com> wrote:

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

Thanks.  I applied this patch to the main with the below changes.

I also added a NEWS entry for this feature and enhanced the ovn-northd.at
test case a bit.


----------------------------------------------------------------------------
diff --git a/northd/northd.c b/northd/northd.c
index 9fa3ec96cb..b541e8455c 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -91,6 +91,7 @@ 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
@@ -8574,8 +8575,7 @@ build_lswitch_dhcp_relay_flows(struct ovn_port *op,
         return;
     }

-    if (!op->od || !op->od->n_router_ports ||
-        !op->od->nbs) {
+    if (!op->od || !op->od->n_router_ports || !op->od->nbs) {
         return;
     }

@@ -8621,8 +8621,8 @@ build_lswitch_dhcp_relay_flows(struct ovn_port *op,
         "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);
+                  "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),
@@ -13724,10 +13724,10 @@ 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)
+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;
@@ -13772,10 +13772,10 @@ build_dhcp_relay_flows_for_lrouter_port(
         "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);
+                  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),
@@ -13791,8 +13791,9 @@ build_dhcp_relay_flows_for_lrouter_port(
         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);
+                  "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),
@@ -13807,8 +13808,7 @@ build_dhcp_relay_flows_for_lrouter_port(
         "udp.src == 68 && udp.dst == 67 && "
         REGBIT_DHCP_RELAY_REQ_CHK" == 0",
         op->json_key);
-    ds_put_format(actions,
-        "drop; /* DHCP_RELAY_REQ */");
+    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),
@@ -13821,7 +13821,7 @@ build_dhcp_relay_flows_for_lrouter_port(
         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 */");
+    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);
@@ -13834,10 +13834,10 @@ build_dhcp_relay_flows_for_lrouter_port(
         "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);
+          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,
@@ -13855,28 +13855,24 @@ build_dhcp_relay_flows_for_lrouter_port(
         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);
+                  "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),
+                            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 */");
+    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),
+                            1, ds_cstr(match), ds_cstr(actions),
                             &op->nbrp->header_, lflow_ref);
     ds_clear(match);
     ds_clear(actions);
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 297b6aceaf..27183c551c 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -12288,13 +12288,47 @@ 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_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_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 */)
+  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 */)
+])
+
+# Set invalid dhcp_relay_port in ls0
+check ovn-nbctl --wait=sb set Logical_Switch ls0
other_config:dhcp_relay_port=foo
+
+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 */)
+])
+
+# Clear the dhcp_relay from lrp1
+check ovn-nbctl --wait=sb clear Logical_Router_port lrp1 dhcp_relay
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0],
[dnl
+])
+
+# set the dhcp_relay_port in ls0 to proper one.
+check ovn-nbctl --wait=sb set Logical_Switch ls0
other_config:dhcp_relay_port=lrp1-attachment
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0],
[dnl
 ])

 AT_CLEANUP
----------------------------------------------------------------------------------------

> ---
>  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
>
>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to