[ovs-dev] [PATCH v10 2/2] ovn-northd: Add logical flows to support native DHCP

Numan Siddique nusiddiq at redhat.com
Tue Jun 7 05:49:45 UTC 2016


OVN implements a native DHCP support which caters to the common
use case of providing an IP address to a booting instance by
providing stateless replies to DHCP requests based on statically
configured address mappings. To do this it allows a short list of
DHCP options to be configured and applied at each compute host
running ovn-controller.

A new table 'Subnet' is added in OVN NB DB to store the DHCP options.

For each logical port following flows are added if the CMS has defined
DHCP options in the 'Subnet' column

 - A logical flow which copies the DHCP options to the DHCP
   request packets using the 'put_dhcp_opts' action and advances the
   packet to the next stage.

 - A logical flow which implements the DHCP reponder by sending
   the DHCP reply back to the inport once the 'put_dhcp_opts' action
   is applied.

Signed-Off-by: Numan Siddique <nusiddiq at redhat.com>
---
 ovn/northd/ovn-northd.8.xml   |  89 +++++++++++-
 ovn/northd/ovn-northd.c       | 265 ++++++++++++++++++++++++++++++++++-
 ovn/ovn-nb.ovsschema          |  19 ++-
 ovn/ovn-nb.xml                | 314 +++++++++++++++++++++++++++++++++++++++++-
 ovn/utilities/ovn-nbctl.8.xml |  29 ++++
 ovn/utilities/ovn-nbctl.c     | 196 ++++++++++++++++++++++++++
 tests/ovn.at                  | 250 +++++++++++++++++++++++++++++++++
 tests/test-ovn-dhcp.c         | 135 ++++++++++++++++++
 8 files changed, 1287 insertions(+), 10 deletions(-)

diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 1983812..9785509 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -343,7 +343,88 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 6: Destination Lookup</h3>
+    <h3>Ingress Table 6: DHCP pause</h3>
+
+    <p>
+      This table adds the DHCP options to a DHCP packet from the
+      logical ports configured with IPv4 address(es) and DHCP options.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority-100 logical flow is added for these logical ports
+          which matches the packet with <code>udp.src</code> = 68 and
+          <code>udp.dst</code> = 67 and applies the action
+          <code>put_dhcp_opts</code> and advances the packet to the table 7.
+        </p>
+
+        <pre>
+put_dhcp_opts(<var>R</var>, offer_ip = <var>O</var>, DHCP_OPTIONS);
+next;
+        </pre>
+
+        <p>
+          The action <code>put_dhcp_opts</code> adds the DHCP options to
+          the packet and sets the <code>yiaddr</code> to the offer ip
+          defined in <var>O</var> only if the packet is a
+          DHCPDISCOVER/DHCPREQUEST and resumes the pipeline by setting
+          <code>0x1</code> to the OVS register <var>R</var>.
+          If the packet is non-DHCP packet or invalid DHCP packet
+          it resumes the pipeline without any modifications.
+        </p>
+
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advances to table 7.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 7: DHCP resume</h3>
+
+    <p>
+      This table implements DHCP responder for the resumed DHCP packets
+      from the previous table.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority 100 logical flow is added for the logical ports configured
+          with DHCP options which matches the packet with
+          <code>udp.src</code> = 68, <code>udp.dst</code> = 67 and
+          <code>reg0</code> = 0x1 and responds back to the
+          <code>inport</code> after applying these actions.
+          If <code>reg0</code> is set to 1, it means that the action
+          <code>put_dhcp_opts</code> was successful.
+        </p>
+
+        <pre>
+eth.dst = eth.src;
+eth.src = <var>E</var>;
+ip4.dst = <var>O</var>;
+ip4.src = <var>S</var>;
+udp.src = 67;
+udp.dst = 68;
+outport = <var>P</var>;
+inport = ""; /* Allow sending out inport. */
+output;
+        </pre>
+
+        <p>
+          Where <var>E</var> is the server mac address and <var>S</var> is the
+          server IPv4 address defined in the DHCP options and <var>O</var> is
+          the IPv4 address defined in the logical port's addresses column.
+        </p>
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advances to table 8.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 8: Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
@@ -387,6 +468,12 @@ output;
       This is similar to ingress table 4 except for <code>to-lport</code> ACLs.
     </p>
 
+    <p>
+      Also a priority 34000 logical flow is added for each subnet of the logical
+      switch which has DHCP options defined to allow the DHCP reply packet
+      from the <code>Ingress Table 7: DHCP resume</code>.
+    </p>
+
     <h3>Egress Table 2: Egress Port Security - IP</h3>
 
     <p>
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index cac0148..325fbc0 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -26,6 +26,7 @@
 #include "hash.h"
 #include "hmap.h"
 #include "json.h"
+#include "ovn/lib/ovn-dhcp.h"
 #include "ovn/lib/lex.h"
 #include "ovn/lib/ovn-nb-idl.h"
 #include "ovn/lib/ovn-sb-idl.h"
@@ -33,6 +34,7 @@
 #include "packets.h"
 #include "poll-loop.h"
 #include "smap.h"
+#include "sset.h"
 #include "stream.h"
 #include "stream-ssl.h"
 #include "unixctl.h"
@@ -94,7 +96,9 @@ enum ovn_stage {
     PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        3, "ls_in_pre_acl")      \
     PIPELINE_STAGE(SWITCH, IN,  ACL,            4, "ls_in_acl")          \
     PIPELINE_STAGE(SWITCH, IN,  ARP_RSP,        5, "ls_in_arp_rsp")      \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,        6, "ls_in_l2_lkup")      \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_PAUSE,     6, "ls_in_dhcp_pause")      \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESUME,    7, "ls_in_dhcp_resume")      \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,        8, "ls_in_l2_lkup")      \
                                                                       \
     /* Logical switch egress stages. */                               \
     PIPELINE_STAGE(SWITCH, OUT, PRE_ACL,     0, "ls_out_pre_acl")     \
@@ -1325,6 +1329,82 @@ has_stateful_acl(struct ovn_datapath *od)
     return false;
 }
 
+static bool
+build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip,
+                  struct ds *pause_action, struct ds *resume_action)
+{
+    if(smap_get_bool(&op->nbs->options, "dhcp_disabled", false)) {
+        /* CMS has disabled native dhcp for this lport */
+        return false;
+    }
+
+    struct nbrec_subnet *subnet = NULL;
+    ovs_be32 host_ip, mask;
+    for (size_t i = 0; i < op->od->nbs->n_subnets; i++) {
+        char *error = ip_parse_masked(op->od->nbs->subnets[i]->cidr, &host_ip,
+                                      &mask);
+        if (!error && !((offer_ip ^ host_ip) & mask)) {
+           /* offerip belongs to this subnet */
+            subnet = op->od->nbs->subnets[i];
+            break;
+        }
+        free(error);
+    }
+
+    if (!(subnet && subnet->gateway_ip && subnet->enable_dhcp
+          && subnet->ip_version == 4)) {
+        return false;
+    }
+
+
+    const char *server_ip = smap_get(&subnet->dhcp_options, "server_id");
+    const char *server_mac = smap_get(&subnet->dhcp_options, "server_mac");
+    const char *lease_time = smap_get(&subnet->dhcp_options, "lease_time");
+
+    if (!(server_ip && server_mac && lease_time)) {
+        /* "server_id", "server_mac" and "lease_time" should be present
+         * in the dhcp_options. */
+        return false;
+    }
+
+    struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
+    smap_clone(&dhcp_options, &subnet->dhcp_options);
+
+    /* server_mac is not dhcp option, delete it from the smap */
+    smap_remove(&dhcp_options, "server_mac");
+    smap_add(&dhcp_options, "router", subnet->gateway_ip);
+    char *netmask = xasprintf(IP_FMT, IP_ARGS(mask));
+    smap_add(&dhcp_options, "netmask", netmask);
+    free(netmask);
+
+    struct smap_node *node;
+    /* override the dhcp options define in the lport options if any */
+    SMAP_FOR_EACH(node, &op->nbs->options) {
+        if(!strncmp(node->key, "dhcp_opt_", 9)) {
+            smap_replace(&dhcp_options, &node->key[9], node->value);
+        }
+    }
+
+    ds_put_format(pause_action, "put_dhcp_opts(reg0, offerip = "IP_FMT", ",
+                  IP_ARGS(offer_ip));
+    SMAP_FOR_EACH(node, &dhcp_options) {
+        ds_put_format(pause_action, "%s = %s, ", node->key, node->value);
+    }
+
+    ds_chomp(pause_action, ' ');
+    ds_chomp(pause_action, ',');
+    ds_put_cstr(pause_action, "); next;");
+
+    ds_put_format(resume_action, "eth.dst = eth.src; eth.src = %s; "
+                  "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67; "
+                  "udp.dst = 68; outport = inport; inport = \"\";"
+                  " /* Allow sending out inport. */ output;",
+                  server_mac, IP_ARGS(offer_ip), server_ip);
+
+    smap_destroy(&dhcp_options);
+    return true;
+}
+
 static void
 build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
 {
@@ -1475,6 +1555,36 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
                           acl->match, "drop;");
         }
     }
+
+    /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all
+     * logical ports of the datapath if the CMS has configured DHCP options*/
+    if (od->nbs && od->nbs->n_ports && od->nbs->n_subnets) {
+        for (size_t i = 0; i < od->nbs->n_subnets; i++) {
+             if (!(od->nbs->subnets[i]->gateway_ip &&
+                   od->nbs->subnets[i]->enable_dhcp &&
+                   od->nbs->subnets[i]->ip_version == 4)) {
+                 continue;
+             }
+
+             const char *server_id = smap_get(
+                 &od->nbs->subnets[i]->dhcp_options, "server_id");
+             const char *server_mac = smap_get(
+                 &od->nbs->subnets[i]->dhcp_options, "server_mac");
+             const char *lease_time = smap_get(
+                 &od->nbs->subnets[i]->dhcp_options, "lease_time");
+             if (server_id && server_mac && lease_time) {
+                 struct ds match = DS_EMPTY_INITIALIZER;
+                 const char *actions = has_stateful ? "ct_commit; next;" :
+                                       "next;";
+                 ds_put_format(&match, "eth.src == %s && ip4.src == %s &&"
+                               " udp && udp.src == 67 && udp.dst == 68",
+                               server_mac, server_id);
+                 ovn_lflow_add(
+                     lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match),
+                     actions);
+             }
+        }
+    }
 }
 
 static void
@@ -1557,7 +1667,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
     }
 
-    /* Ingress table 3: ARP responder, skip requests coming from localnet ports.
+    /* Ingress table 5: ARP responder, skip requests coming from localnet ports.
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
@@ -1632,7 +1742,73 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;");
     }
 
-    /* Ingress table 6: Destination lookup, broadcast and multicast handling
+    /* Logical switch ingress table 6 and 7: DHCP pause and resume
+     * priority 100 flows. */
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbs) {
+           continue;
+        }
+
+        if (!lport_is_enabled(op->nbs) || !strcmp(op->nbs->type, "router")) {
+            /* Don't add the DHCP flows if the port is not enabled or if the
+             * port is a router port */
+            continue;
+        }
+
+        for (size_t i = 0; i < op->nbs->n_addresses; i++) {
+            struct lport_addresses laddrs;
+            if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+                                        false)) {
+                continue;
+            }
+
+            if (!laddrs.n_ipv4_addrs) {
+                continue;
+            }
+
+            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+                struct ds pause_action = DS_EMPTY_INITIALIZER;
+                struct ds resume_action = DS_EMPTY_INITIALIZER;
+                if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr,
+                                      &pause_action, &resume_action)) {
+                    struct ds match = DS_EMPTY_INITIALIZER;
+                    ds_put_format(
+                        &match, "inport == %s && eth.src == "ETH_ADDR_FMT
+                        " && ip4.src == 0.0.0.0 && "
+                        "ip4.dst == 255.255.255.255 && udp.src == 68 && "
+                        "udp.dst == 67", op->json_key,
+                        ETH_ADDR_ARGS(laddrs.ea));
+
+                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_PAUSE, 100,
+                                  ds_cstr(&match), ds_cstr(&pause_action));
+                    /* If reg0 is set to 1, it means the put_dhcp_opts action
+                     * is successful */
+                    ds_put_cstr(&match, " && reg0 == 1");
+                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, 100,
+                                  ds_cstr(&match), ds_cstr(&resume_action));
+                    ds_destroy(&match);
+                    ds_destroy(&pause_action);
+                    ds_destroy(&resume_action);
+                    break;
+                }
+            }
+            free(laddrs.ipv4_addrs);
+        }
+    }
+
+    /* Ingress table 6 and 7: DHCP pause and resume, by default goto next.
+     * (priority 0)*/
+
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbs) {
+            continue;
+        }
+
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_PAUSE, 0, "1", "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESUME, 0, "1", "next;");
+    }
+
+    /* Ingress table 8: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
@@ -1652,7 +1828,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                       "outport = \""MC_FLOOD"\"; output;");
     }
 
-    /* Ingress table 6: Destination lookup, unicast handling (priority 50), */
+    /* Ingress table 8: Destination lookup, unicast handling (priority 50), */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
             continue;
@@ -1689,7 +1865,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */
+    /* Ingress table 8: Destination lookup for unknown MACs (priority 0). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
@@ -2400,6 +2576,77 @@ ovnsb_db_run(struct northd_context *ctx)
 }
 
 
+static struct dhcp_opts_map supported_dhcp_opts[] = {
+    OFFERIP,
+    DHCP_OPT_NETMASK,
+    DHCP_OPT_ROUTER,
+    DHCP_OPT_DNS_SERVER,
+    DHCP_OPT_LOG_SERVER,
+    DHCP_OPT_LPR_SERVER,
+    DHCP_OPT_SWAP_SERVER,
+    DHCP_OPT_POLICY_FILTER,
+    DHCP_OPT_ROUTER_SOLICITATION,
+    DHCP_OPT_NIS_SERVER,
+    DHCP_OPT_NTP_SERVER,
+    DHCP_OPT_SERVER_ID,
+    DHCP_OPT_TFTP_SERVER,
+    DHCP_OPT_CLASSLESS_STATIC_ROUTE,
+    DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE,
+    DHCP_OPT_IP_FORWARD_ENABLE,
+    DHCP_OPT_ROUTER_DISCOVERY,
+    DHCP_OPT_ETHERNET_ENCAP,
+    DHCP_OPT_DEFAULT_TTL,
+    DHCP_OPT_TCP_TTL,
+    DHCP_OPT_MTU,
+    DHCP_OPT_LEASE_TIME,
+    DHCP_OPT_T1,
+    DHCP_OPT_T2
+};
+
+static void
+check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
+{
+    static bool nothing_to_add = false;
+
+    if (nothing_to_add) {
+        return;
+    }
+
+    struct hmap dhcp_opts_to_add = HMAP_INITIALIZER(&dhcp_opts_to_add);
+    for (size_t i = 0; (i < sizeof(supported_dhcp_opts) /
+                            sizeof(supported_dhcp_opts[0])); i++) {
+        hmap_insert(&dhcp_opts_to_add, &supported_dhcp_opts[i].hmap_node,
+                    dhcp_opt_hash(supported_dhcp_opts[i].name));
+    }
+
+    const struct sbrec_dhcp_options *opt_row, *opt_row_next;
+    SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
+        struct dhcp_opts_map *dhcp_opt =
+            dhcp_opts_find(&dhcp_opts_to_add, opt_row->name);
+        if (dhcp_opt) {
+            hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node);
+        }
+        else {
+            sbrec_dhcp_options_delete(opt_row);
+        }
+    }
+
+    if (!dhcp_opts_to_add.n) {
+        nothing_to_add = true;
+    }
+
+    struct dhcp_opts_map *opt;
+    HMAP_FOR_EACH_POP(opt, hmap_node, &dhcp_opts_to_add) {
+        struct sbrec_dhcp_options *sbrec_dhcp_option =
+            sbrec_dhcp_options_insert(ctx->ovnsb_txn);
+        sbrec_dhcp_options_set_name(sbrec_dhcp_option, opt->name);
+        sbrec_dhcp_options_set_code(sbrec_dhcp_option, opt->code);
+        sbrec_dhcp_options_set_type(sbrec_dhcp_option, opt->type);
+    }
+
+    hmap_destroy(&dhcp_opts_to_add);
+}
+
 static char *default_nb_db_;
 
 static const char *
@@ -2568,6 +2815,10 @@ main(int argc, char *argv[])
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_options);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_chassis);
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcp_options);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name);
 
     /* Main loop. */
     exiting = false;
@@ -2581,7 +2832,9 @@ main(int argc, char *argv[])
 
         ovnnb_db_run(&ctx);
         ovnsb_db_run(&ctx);
-
+        if (ctx.ovnsb_txn) {
+            check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
+        }
         unixctl_server_run(unixctl);
         unixctl_server_wait(unixctl);
         if (exiting) {
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index fa21b30..d7f72a7 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "2.1.2",
-    "cksum": "429668869 5325",
+    "version": "2.2.0",
+    "cksum": "2504037701 6138",
     "tables": {
         "Logical_Switch": {
             "columns": {
@@ -16,6 +16,11 @@
                                           "refType": "strong"},
                                   "min": 0,
                                   "max": "unlimited"}},
+                "subnets": {"type": {"key": {"type": "uuid",
+                                             "refTable": "Subnet",
+                                             "refType": "strong"},
+                                  "min": 0,
+                                  "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
@@ -63,6 +68,16 @@
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "isRoot": false},
+        "Subnet": {
+            "columns": {
+                "cidr": {"type": "string"},
+                "ip_version": {"type": {"key": {"type": "integer",
+                                                 "enum": ["set", [4, 6]]}}},
+                "gateway_ip": {"type": "string"},
+                "enable_dhcp": {"type": "boolean"},
+                "dhcp_options": {"type": {"key": "string", "value": "string",
+                                          "min": 0, "max": "unlimited"}}},
+            "isRoot": false},
         "Logical_Router": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 41092f1..c00160f 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -73,11 +73,18 @@
       Access control rules that apply to packets within the logical switch.
     </column>
 
+    <column name="subnets">
+      <p>
+        Subnets configured to the logical switch.
+      </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="Logical_Port" title="L2 logical switch port">
@@ -205,6 +212,28 @@
           interface, in kb.
         </column>
       </group>
+
+      <group title="DHCP Options">
+        <p>
+          These options apply to logical ports with <ref column="type"/> having
+          (empty string)
+        </p>
+
+        <column name="options" key="dhcp_opt_OPTION_NAME">
+          Each logical port can override the DHCP options defined in the
+          <ref column="dhcp_options"/> of <ref table="Subnet"/>
+          by defining them in this column with the prefix "dhcp_opt_".
+          Please see the <ref column="dhcp_options"/> of
+          <ref table="Subnet"/> for supported DHCP options.
+
+          Example: key="dhcp_opt_mtu", value="1300"
+        </column>
+
+        <column name="options" key="dhcp_disabled">
+          If this is defined, <code>ovn-northd</code> will disable the native
+          DHCP responder for the logical port.
+        </column>
+      </group>
     </group>
 
     <group title="Containers">
@@ -598,6 +627,289 @@
     </group>
   </table>
 
+  <table name="Subnet" title="L2 logical switch subnet">
+    <p>
+      A subnet within an L2 logical switch. This is an optional table. CMS
+      can add the rows to this table to define the subnets belonging to logical
+      switch.
+    </p>
+
+    <column name="cidr">
+      <p>
+        cidr of the subnet.
+      </p>
+    </column>
+
+    <column name="ip_version">
+      <p>
+        IP version of the subnet -4 or 6.
+      </p>
+    </column>
+
+    <column name="gateway_ip">
+      <p>
+        Gateway ip of the subnet.
+      </p>
+    </column>
+
+    <column name="enable_dhcp">
+      <p>
+        If set to true, native DHCP support will be enabled for all the
+        logical ports having the IPv4 address from the subnet cidr.
+      </p>
+    </column>
+
+    <group title="DHCP options">
+      <column name="dhcp_options">
+        <p>
+          OVN implements a native DHCP support which caters to the common
+          use case of providing an IP address to a booting instance by
+          providing stateless replies to DHCP requests based on statically
+          configured address mappings. To do this it allows a short list of
+          DHCP options to be configured and applied at each compute host
+          running ovn-controller.
+        </p>
+
+        <p>
+          CMS should define the set of DHCP options as key/value pairs.
+          The defined DHCP options will be include in the DHCP response to the
+          DHCP DISCOVER/REQUEST packet from the logical ports having the IPv4
+          addresses in the <ref column="cidr"/>.
+        </p>
+      </column>
+
+      <group title="Supported DHCP options">
+        <p>
+          Below are the supported DHCP options whose values are IPv4 address
+          or addresses. If the value has more than one IPv4 address, then it
+          should be enclosed within '{}' braces. Please refer to the
+          RFC 2132 <code>"https://tools.ietf.org/html/rfc2132"</code> for
+          more details on the DHCP options and their codes.
+        </p>
+
+        <column name="dhcp_options" key="netmask">
+          <p>
+            The DHCP option code for this option is 1.
+          </p>
+
+          <p>
+            Example. key="netmask", value="255.255.255.0"
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="router">
+          <p>
+            The DHCP option code for this option is 3.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="dns_server">
+          <p>
+            The DHCP option code for this option is 6.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="log_server">
+          <p>
+            The DHCP option code for this option is 7.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="lpr_server">
+          <p>
+            The DHCP option code for this option is 9.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="swap_server">
+          <p>
+            The DHCP option code for this option is 16.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="policy_filter">
+          <p>
+            The DHCP option code for this option is 21.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="router_solicitation">
+          <p>
+            The DHCP option code for this option is 32.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="nis_server">
+          <p>
+            The DHCP option code for this option is 41.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="ntp_server">
+          <p>
+            The DHCP option code for this option is 42.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="server_id">
+          <p>
+            The DHCP option code for this option is 54.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="tftp_server">
+          <p>
+            The DHCP option code for this option is 66.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="classless_static_route">
+          <p>
+            The DHCP option code for this option is 121.
+          </p>
+
+          <p>
+             This option can contain one or more static routes, each of which
+             consists of a destination descriptor and the IP address of the
+             router that should be used to reach that destination. Please see
+             RFC 3442 for more details.
+          </p>
+
+          <p>
+            Example.
+            key="classless_static_route"
+            value="{30.0.0.0/24,10.0.0.10, 0.0.0.0/0,10.0.0.1}"
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="ms_classless_static_route">
+          <p>
+            The DHCP option code for this option is 249. This option is
+            similar to <code>classless_static_route</code> supported by
+            Microsoft Windows DHCP clients.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="server_mac">
+          <p>
+            <code>eth.src</code> will be set to this value in the DHCP
+            response packet.
+          </p>
+        </column>
+      </group>
+
+      <group title="Other supported DHCP options">
+        <column name="dhcp_options" key="ip_forward_enable">
+          <p>
+            The DHCP option code for this option is 19.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>bool</code>.
+
+            Example. key="ip_forward_enable", value="1"
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="router_discovery">
+          <p>
+            The DHCP option code for this option is 31.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>bool</code>.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="ethernet_encap">
+          <p>
+            The DHCP option code for this option is 36.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>bool</code>.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="default_ttl">
+          <p>
+            The DHCP option code for this option is 23.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>uint8</code>.
+
+            Example. key="default_ttl", value="128".
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="tcp_ttl">
+          <p>
+            The DHCP option code for this option is 37.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>uint8</code>.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="mtu">
+          <p>
+            The DHCP option code for this option is 26.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>uint16</code>.
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="lease_time">
+          <p>
+            The DHCP option code for this option is 51.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>uint32</code>.
+
+            Example. key="lease_time", value="42000"
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="T1">
+          <p>
+            The DHCP option code for this option is 58.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>uint32</code>.
+
+            Example. key="T1", value="30000"
+          </p>
+        </column>
+
+        <column name="dhcp_options" key="T2">
+          <p>
+            The DHCP option code for this option is 59.
+          </p>
+
+          <p>
+            The value of this DHCP option is of type <code>uint32</code>.
+
+            Example. key="T2", value="40000"
+          </p>
+        </column>
+      </group>
+
+      <group title="Mandatory DHCP options">
+        <p>
+          DHCP options <code>"server_id"</code>, <code>"server_mac"</code>
+          and <code>"lease_time"</code> are mandatory options which CMS should
+          define for <code>OVN</code> to support native DHCP.
+        </p>
+      </group>
+    </group>
+  </table>
+
   <table name="Logical_Router" title="L3 logical router">
     <p>
       Each row represents one L3 logical router.
@@ -645,7 +957,7 @@
         <code>Open_vSwitch</code> table of Open_vSwitch database.
       </column>
     </group>
-    
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index f592036..9ad550b 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -100,6 +100,35 @@
       </dd>
     </dl>
 
+    <h1>Subnet Commands</h1>
+
+    <dl>
+      <dt><code>subnet-add</code> <var>lswitch</var> <var>cidr</var> <var>gateway_ip</var> [<var>enable_dhcp</var>]</dt>
+      <dd>
+        Adds a Subnet to the <var>lswitch</var>
+      </dd>
+
+      <dt><code>subnet-del</code> <var>subnet</var></dt>
+      <dd>
+        Deletes the subnet referred by <var>subnet</var> UUID.
+      </dd>
+
+      <dt><code>subnet-list</code> <var>lswitch</var></dt>
+      <dd>
+        Lists all the subnets belonging to the <var>lswitch</var>
+      </dd>
+
+      <dt><code>subnet-set-dhcp-options</code> <var>subnet</var> [<var>key=value</var>]...</dt>
+      <dd>
+        Sets the DHCP options for the <var>subnet</var>
+      </dd>
+
+      <dt><code>subnet-get-dhcp-options</code> <var>subnet</var></dt>
+      <dd>
+        Lists the DHCP options of the <var>subnet</var>
+      </dd>
+    </dl>
+
     <h1>ACL Commands</h1>
     <dl>
       <dt>[<code>--log</code>] <code>acl-add</code> <var>lswitch</var> <var>direction</var> <var>priority</var> <var>match</var> <var>action</var></dt>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 321040e..afb2879 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -352,6 +352,18 @@ Logical port commands:\n\
                             Set options related to the type of LPORT\n\
   lport-get-options LPORT   Get the type specific options for LPORT\n\
 \n\
+Subnet commands:\n\
+  subnet-add LSWITCH CIDR GATEWAYIP [ENABLE_DHCP]\n\
+                            add a subnet to LSWITCH\n\
+  subnet-del SUBNET_UUID\n\
+                            remove subnet from its attached LSWITCH\n\
+  subnet-list LSWITCH\n\
+                            list subnets attached to LSWITCH\n\
+  subnet-set-dhcp-options SUBNET_UUID KEY=VALUE [KEY=VALUE]...\n\
+                            Set DHCP options for the SUBNET\n\
+  subnet-get-dhcp-options SUBNET_UUID\n\
+                            Get the DHCP options for the SUBNET\n\
+\n\
 %s\
 \n\
 Options:\n\
@@ -664,6 +676,7 @@ nbctl_lswitch_list(struct ctl_context *ctx)
     smap_destroy(&lswitches);
     free(nodes);
 }
+
 
 /* Find the lrport given its id. */
 static const struct nbrec_logical_router_port *
@@ -1237,6 +1250,175 @@ nbctl_lport_get_options(struct ctl_context *ctx)
     }
 }
 
+static void
+nbctl_subnet_add(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *lswitch;
+    lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+    /* Validate the cidr */
+    int64_t ip_version = 4;
+    ovs_be32 ip;
+    unsigned int plen;
+    char *error = ip_parse_cidr(ctx->argv[2], &ip, &plen);
+    if (error){
+        /* check if its IPv6 cidr */
+        free(error);
+        struct in6_addr ipv6;
+        error = ipv6_parse_cidr(ctx->argv[2], &ipv6, &plen);
+        if (error) {
+            free(error);
+            VLOG_WARN("Invalid cidr format '%s'", ctx->argv[2]);
+            return;
+        }
+        ip_version = 6;
+    }
+
+    /* Validate the gateway ip format */
+    bool valid_gatewap_ip = false;
+    if (ip_version == 4) {
+        valid_gatewap_ip = ip_parse(ctx->argv[3], &ip);
+    }
+    else {
+        struct in6_addr ipv6;
+        valid_gatewap_ip = ipv6_parse(ctx->argv[3], &ipv6);
+    }
+
+    if (!valid_gatewap_ip) {
+        VLOG_WARN("Invalid Gateway ip format '%s'", ctx->argv[3]);
+        return;
+    }
+
+    bool enable_dhcp = true;
+    if (ctx->argc == 5) {
+         if (!strcmp(ctx->argv[4], "false")) {
+             enable_dhcp = false;
+         }
+    }
+
+    struct nbrec_subnet *subnet = nbrec_subnet_insert(ctx->txn);
+    nbrec_subnet_set_cidr(subnet, ctx->argv[2]);
+    nbrec_subnet_set_ip_version(subnet, ip_version);
+    nbrec_subnet_set_gateway_ip(subnet, ctx->argv[3]);
+    nbrec_subnet_set_enable_dhcp(subnet, enable_dhcp);
+
+    /* Insert the subnet into the logical switch */
+    nbrec_logical_switch_verify_subnets(lswitch);
+    struct nbrec_subnet **new_subnets = xmalloc(sizeof *new_subnets *
+                                                (lswitch->n_subnets + 1));
+    memcpy(new_subnets, lswitch->subnets,
+           sizeof *new_subnets * lswitch->n_subnets);
+    new_subnets[lswitch->n_subnets] = subnet;
+    nbrec_logical_switch_set_subnets(lswitch, new_subnets,
+                                     lswitch->n_subnets + 1);
+    free(new_subnets);
+}
+
+static void
+remove_subnet(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+    const struct nbrec_subnet *subnet = lswitch->subnets[idx];
+
+    struct nbrec_subnet **new_subnets
+        = xmemdup(lswitch->subnets, sizeof *new_subnets * lswitch->n_subnets);
+    new_subnets[idx] = new_subnets[lswitch->n_subnets - 1];
+    nbrec_logical_switch_verify_subnets(lswitch);
+    nbrec_logical_switch_set_subnets(lswitch, new_subnets, lswitch->n_subnets - 1);
+    free(new_subnets);
+
+    nbrec_subnet_delete(subnet);
+}
+
+static const struct nbrec_subnet *
+subnet_get(struct ctl_context *ctx, char *id)
+{
+    struct uuid subnet_uuid;
+    if (uuid_from_string(&subnet_uuid, id)) {
+        return nbrec_subnet_get_for_uuid(ctx->idl, &subnet_uuid);
+    }
+
+    return NULL;
+}
+
+static void
+nbctl_subnet_del(struct ctl_context *ctx)
+{
+    const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
+    if (!subnet) {
+        VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
+        return;
+    }
+
+    /* Find the switch that contains 'subnet', then delete it. */
+    const struct nbrec_logical_switch *lswitch;
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
+        for (size_t i = 0; i < lswitch->n_subnets; i++) {
+            if (lswitch->subnets[i] == subnet) {
+                remove_subnet(lswitch, i);
+                return;
+            }
+        }
+    }
+
+    VLOG_WARN("subnet %s is not part of any logical switch",
+              ctx->argv[1]);
+}
+
+static void
+nbctl_subnet_list(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *lswitch;
+    lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false);
+    if (!lswitch) {
+        return;
+    }
+
+    for (size_t i = 0; i < lswitch->n_subnets; i++) {
+        const struct nbrec_subnet *subnet = lswitch->subnets[i];
+        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
+                      UUID_ARGS(&subnet->header_.uuid), subnet->cidr);
+    }
+}
+
+static void
+nbctl_subnet_set_dhcp_options(struct ctl_context *ctx)
+{
+    const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
+    if (!subnet) {
+        VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
+        return;
+    }
+
+    struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
+    for (size_t i = 2; i < ctx->argc; i++) {
+        char *key, *value;
+        value = xstrdup(ctx->argv[i]);
+        key = strsep(&value, "=");
+        if (value) {
+            smap_add(&dhcp_options, key, value);
+        }
+        free(key);
+    }
+
+    nbrec_subnet_set_dhcp_options(subnet, &dhcp_options);
+    smap_destroy(&dhcp_options);
+}
+
+static void
+nbctl_subnet_get_dhcp_options(struct ctl_context *ctx)
+{
+    const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
+    if (!subnet) {
+        VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
+        return;
+    }
+
+    struct smap_node *node;
+    SMAP_FOR_EACH(node, &subnet->dhcp_options) {
+        ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
+    }
+}
+
 enum {
     DIR_FROM_LPORT,
     DIR_TO_LPORT
@@ -1435,6 +1617,10 @@ static const struct ctl_table_class tables[] = {
      {{NULL, NULL, NULL},
       {NULL, NULL, NULL}}},
 
+    {&nbrec_table_subnet,
+     {{&nbrec_table_subnet, NULL, NULL},
+     {NULL, NULL, NULL}}},
+
     {&nbrec_table_logical_router,
      {{&nbrec_table_logical_router, &nbrec_logical_router_col_name, NULL},
       {NULL, NULL, NULL}}},
@@ -1822,6 +2008,16 @@ static const struct ctl_command_syntax nbctl_commands[] = {
     { "lport-get-options", 1, 1, "LPORT", NULL, nbctl_lport_get_options, NULL,
       "", RO },
 
+    /* subnet commands */
+    {"subnet-add", 3, 4, "LSWITCH CIDR GATEWAY_IP ENABLE_DHCP", NULL,
+      nbctl_subnet_add, NULL, "", RW },
+    {"subnet-del", 1, 1, "SUBNET", NULL, nbctl_subnet_del, NULL, "", RW},
+    {"subnet-list", 1, 1, "LSWITCH", NULL, nbctl_subnet_list, NULL, "", RO},
+    {"subnet-set-dhcp-options", 1, INT_MAX, "SUBNET KEY=VALUE [KEY=VALUE]...",
+      NULL, nbctl_subnet_set_dhcp_options, NULL, "", RW },
+    {"subnet-get-dhcp-options", 1, 1, "SUBNET", NULL,
+      nbctl_subnet_get_dhcp_options, NULL, "", RO },
+
     {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
 };
 
diff --git a/tests/ovn.at b/tests/ovn.at
index b0c365b..71e8c48 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2923,6 +2923,256 @@ AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg0 $dhcp_options $expected_dhc
 
 AT_CLEANUP
 
+AT_SETUP([ovn -- dhcp : 1 HV, 2 LS, 3 lports])
+AT_KEYWORDS([dhcp])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl lswitch-add ls1
+ovn-nbctl lswitch-add ls2
+
+ovn-nbctl -- --id=@s1 create Subnet cidr=10.0.0.0/24 gateway_ip=10.0.0.1 \
+ip_version=4 enable_dhcp=true dhcp_options="\"server_id\"=\"10.0.0.1\" \
+\"server_mac\"=\"00:00:00:10:00:01\" \"lease_time\"=\"36\"" \
+-- add Logical_Switch ls1 subnets @s1
+
+ovn-nbctl lport-add ls1 ls1-lp1 \
+-- lport-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
+
+ovn-nbctl lport-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
+
+ovn-nbctl lport-add ls2 ls2-lp1 \
+-- lport-set-addresses ls2-lp1 "f0:00:00:00:00:02 20.0.0.3"
+
+ovn-nbctl lport-set-port-security ls2-lp1 "f0:00:00:00:00:02 20.0.0.3"
+
+ovn-nbctl lport-add ls1 ls1-lp2 \
+-- lport-set-addresses ls1-lp2 "f0:00:00:00:00:03 10.0.0.5"
+
+ovn-nbctl lport-set-port-security ls1-lp2 "f0:00:00:00:00:03 10.0.0.5"
+#disable dhcp on lport - ls1-lp2
+ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=true"
+
+net_add n1
+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 hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=ls2-lp1 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+    set interface hv1-vif3 external-ids:iface-id=ls1-lp2 \
+    options:tx_pcap=hv1/vif3-tx.pcap \
+    options:rxq_pcap=hv1/vif3-rx.pcap \
+    ofport-request=3
+
+ovn_populate_arp
+
+sleep 2
+
+# This shell function sends a DHCP request packet
+# test_dhcp INPORT SRC_MAC DHCP_TYPE OUTPORT...
+# The OUTPORTs (zero or more) list the VIFs on which the original DHCP
+# packet should be received twice (one from ovn-controller and the other
+# from the "ovs-ofctl monitor br-int resume"
+test_dhcp() {
+    local inport=$1 src_mac=$2 dhcp_type=$3
+    local request=ffffffffffff${src_mac}080045100110000000008011000000000000ffffffff
+    # udp header and dhcp header
+    request+=0044004300fc0000
+    request+=010106006359aa760000000000000000000000000000000000000000${src_mac}
+    # client hardware padding
+    request+=00000000000000000000
+    # server hostname
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    # boot file name
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    # dhcp magic cookie
+    request+=63825363
+    # dhcp message type
+    request+=3501${dhcp_type}ff
+    shift; shift; shift;
+    for outport; do
+        # the packet will be received twice, one from ovn-controller
+        # and the other from ovs-ofctl monitor br-int resume
+        echo $request | trim_zeros >> $outport.expected
+        echo $request | trim_zeros >> $outport.expected
+    done
+    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
+}
+
+reset_pcap_file() {
+    local iface=$1
+    local pcap_file=$2
+    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+    rm -f ${pcap_file}*.pcap
+    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
+AT_CAPTURE_FILE([ofctl_monitor0.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------------------"
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+echo "---------------------"
+
+echo "---------------------"
+ovn-sbctl dump-flows
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+#send DHCP DISCOVER
+test_dhcp 1 f00000000001 01
+
+# Wait for the expected number of NXT_RESUMEs to be logged.
+echo "waiting for 1 NXT_RESUMEs..."
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+# Extract the dhcp options in userdata. It will be in the format
+# ' userdata=00.00.00.02.00.00.00.00.00.00.00.00.0a.00.00.04.01.04.ff......'
+# Skip 59 columns - userdata=<DHCP_ACTION_CODE (8 B)><REG-IDX (4B)><OFFER-IP (4 B)>
+dhcp_opts_in_user_data=`cat ofctl_monitor0.log  | grep userdata | cut -c 59-`
+
+AT_CHECK([ovstest test-ovn-dhcp hv1/vif1-tx.pcap 10.0.0.4 10.0.0.1 \
+          00:00:00:10:00:01 1 $dhcp_opts_in_user_data], [0], [ignore])
+
+# ovs-ofctl also resumes the packets and this causes other ports to receive
+# the DHCP request packet. So reset the pcap files so that its easier
+# for "test-ovn-dhcp" to validate the DHCP reply packet
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+# run dhcp test on ls2-lp1. There are no dhcp options defined. So the dhcp
+# packet should not be handled by ovn-controller
+test_dhcp 2 f00000000002 01
+
+# NXT_RESUMEs should be 1.
+echo "waiting for 1 NXT_RESUMEs..."
+cat ofctl_monitor*.log
+echo "###########################################"
+
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+ovn-nbctl subnet-add ls2 20.0.0.0/24 20.0.0.1 true
+subnet_id=`ovn-nbctl subnet-list ls2 | cut -c -37`
+
+ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \
+"lease_time=4500"
+
+sleep 1
+
+# there is no "server_mac" field in the dhcp options. So there should be no
+# dhcp flow for this port
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
+
+ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \
+"server_mac=00:00:00:10:00:02" "lease_time=4500"
+
+sleep 2
+OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
+
+test_dhcp 2 f00000000002 03
+
+# NXT_RESUMEs should be 2.
+echo "waiting for 2 NXT_RESUMEs..."
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+dhcp_opts_in_user_data=`cat ofctl_monitor0.log  | grep userdata | \
+cut -c 59- | sed '1d'`
+
+AT_CHECK([ovstest test-ovn-dhcp hv1/vif2-tx.pcap 20.0.0.3 20.0.0.1 \
+          00:00:00:10:00:02 3 $dhcp_opts_in_user_data], [0], [ignore])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+reset_pcap_file hv1-vif3 hv1/vif3
+
+#enable dhcp on lport - ls1-lp2
+ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=false"
+
+sleep 2
+
+OVS_WAIT_UNTIL([test 3 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
+
+#send invalid dhcp packet on hv1/vif3 (ie ls1-lp3).
+# This packet should be received by hv1/vif1 (ls1-lp1)
+test_dhcp 3 f00000000003 04 1
+
+# NXT_RESUMEs should be 3.
+echo "waiting for 3 NXT_RESUMEs..."
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+# vif3-tx.pcap should not have received the DHCP reply packet
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets
+AT_CHECK([cat 3.packets], [0], [])
+
+# vif1-tx.pcap should have received the DHCP (invalid) request packet
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets
+cat 1.expected > expout
+AT_CHECK([cat 1.packets], [0], [expout])
+
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+ovn-sbctl list port_binding
+echo "---------------------"
+ovn-sbctl dump-flows
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+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 main
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+AT_CLEANUP
+
 AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router])
 AT_KEYWORDS([ovngatewayrouter])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c
index 79cee08..b879a03 100644
--- a/tests/test-ovn-dhcp.c
+++ b/tests/test-ovn-dhcp.c
@@ -16,6 +16,10 @@
 #include <assert.h>
 #include <config.h>
 #include "command-line.h"
+#include "dp-packet.h"
+#include <errno.h>
+#include "flow.h"
+#include "lib/dhcp.h"
 #include "openvswitch/ofp-actions.h"
 #include "ovstest.h"
 #include "ovn/lib/actions.h"
@@ -23,6 +27,7 @@
 #include "ovn/lib/expr.h"
 #include "ovn/lib/logical-fields.h"
 #include "shash.h"
+#include "pcap-file.h"
 
 static void
 add_logical_register(struct shash *symtab, enum mf_field_id id)
@@ -146,4 +151,134 @@ test_put_dhcp_opts_action(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
     exit(0);
 }
 
+static void
+test_ovn_dhcp_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    if (argc != 7) {
+        printf("Usage: %s pcap-file offer-ip server-ip"
+                " server-mac dhcp-type userdata\n", argv[0]);
+        exit(1);
+    }
+
+    int retval = 1;
+    FILE *pcap;
+
+    set_program_name(argv[0]);
+
+    pcap = fopen(argv[1], "rb");
+    if (!pcap) {
+        ovs_fatal(errno, "failed to open %s", argv[1]);
+    }
+
+    retval = ovs_pcap_read_header(pcap);
+    if (retval) {
+        ovs_fatal(retval > 0 ? retval : 0, "reading pcap header failed");
+    }
+
+    /* verify if the offer-ip is in proper format */
+    ovs_be32 expected_offer_ip;
+    if (!ovs_scan(argv[2], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_offer_ip))) {
+        ovs_fatal(1, "invalid expected offer ip");
+    }
+
+    /* verify if the server-ip is in proper format */
+    ovs_be32 server_ip;
+    if (!ovs_scan(argv[3], IP_SCAN_FMT, IP_SCAN_ARGS(&server_ip))) {
+        ovs_fatal(1, "invalid expected server ip");
+    }
+
+    struct eth_addr server_mac;
+    if (!eth_addr_from_string(argv[4], &server_mac)) {
+        ovs_fatal(1, "invalid expected server mac");
+    }
+
+    struct dp_packet *packet = NULL;
+    retval = ovs_pcap_read(pcap, &packet, NULL);
+    if (retval == EOF) {
+        ovs_fatal(0, "unexpected end of file reading pcap file : [%s]\n",
+                  argv[1]);
+    } else if (retval) {
+        ovs_fatal(retval, "error reading pcap file");
+    }
+
+    int exit_code = 1;
+    struct flow flow;
+    flow_extract(packet, &flow);
+
+    struct dhcp_header const *dhcp_data = dp_packet_get_udp_payload(packet);
+    if (dhcp_data->op != (uint8_t)2) {
+        printf("Invalid dhcp op reply code : %d\n", dhcp_data->op);
+        goto exit;
+    }
+
+    if (flow.tp_src != htons(DHCP_SERVER_PORT) &&
+        flow.tp_dst != htons(DHCP_CLIENT_PORT)) {
+        printf("Error. Not a dhcp response packet \n");
+        goto exit;
+    }
+
+    if (flow.nw_dst != expected_offer_ip) {
+        printf("Error. Offered ip : "IP_FMT " : Expected ip : %s\n",
+        IP_ARGS(flow.nw_dst), argv[2]);
+        goto exit;
+    }
+
+    if(dhcp_data->yiaddr != expected_offer_ip) {
+        printf("Error. Offered yiaddr : "IP_FMT " : Expected ip : %s\n",
+        IP_ARGS(dhcp_data->yiaddr), argv[2]);
+        goto exit;
+    }
+
+    /* Verify the dhcp option cookie */
+    uint8_t const *footer = (uint8_t *)dhcp_data + sizeof(*dhcp_data);
+    ovs_be32 dhcp_cookie = htonl(DHCP_MAGIC_COOKIE);
+    if (memcmp(footer, &dhcp_cookie, sizeof(ovs_be32))) {
+        printf("Error. Invalid dhcp magic cookie\n");
+        goto exit;
+    }
+
+    /* Validate userdata. It should be ASCII hex */
+    uint64_t dhcp_opts_stub[1024 / 8];
+    struct ofpbuf dhcp_opts = OFPBUF_STUB_INITIALIZER(dhcp_opts_stub);
+    if (atoi(argv[5]) == 1) {
+        /* DHCP reply type should be OFFER (02) */
+        ofpbuf_put_hex(&dhcp_opts, "350102", NULL);
+    } else {
+        /* DHCP reply type should be ACK (05) */
+        ofpbuf_put_hex(&dhcp_opts, "350105", NULL);
+    }
+
+    if (ofpbuf_put_hex(&dhcp_opts, argv[6], NULL)[0] != '\0') {
+        printf("Error. Invalid userdata\n");
+        goto exit;
+    }
+
+    /* 4 bytes padding, 1 byte FF and 4 bytes padding */
+    ofpbuf_put_hex(&dhcp_opts, "00000000FF00000000", NULL);
+
+    footer += sizeof(uint32_t);
+
+    size_t dhcp_opts_size = (const char *)dp_packet_tail(packet) - (
+        const char *)footer;
+    if (dhcp_opts_size != dhcp_opts.size) {
+        printf("Error. dhcp options size mismatch\n");
+        goto exit;
+    }
+
+    if (memcmp(footer, dhcp_opts.data, dhcp_opts.size)) {
+        printf("Error. Invalid dhcp options present\n");
+        goto exit;
+    }
+
+    exit_code = 0;
+
+exit:
+    fclose(pcap);
+    if (packet) {
+        dp_packet_delete(packet);
+    }
+    exit(exit_code);
+}
+
 OVSTEST_REGISTER("test-ovn-put-dhcp-opts-action", test_put_dhcp_opts_action);
+OVSTEST_REGISTER("test-ovn-dhcp", test_ovn_dhcp_main);
-- 
2.5.5




More information about the dev mailing list