[ovs-dev] [PATCH v5] ovn-northd: Add logical flows to support native DHCPv4

Numan Siddique nusiddiq at redhat.com
Thu Jul 14 17:07:51 UTC 2016


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

A new table 'DHCP_Options' is added in OVN NB DB to store the DHCP
options. Logical ports refer to this table to configure the DHCPv4
options.

For each logical port configured with DHCPv4 Options following flows
are added
 - A logical flow which copies the DHCPv4 options to the DHCPv4
   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 DHCPv4 reply back to the inport once the 'put_dhcp_opts' action
   is applied.

Signed-off-by: Numan Siddique <nusiddiq at redhat.com>
Co-authored-by: Ben Pfaff <blp at ovn.org>
Signed-off-by: Ben Pfaff <blp at ovn.org>
---
 ovn/northd/ovn-northd.8.xml   |  91 +++++++++++++-
 ovn/northd/ovn-northd.c       | 256 +++++++++++++++++++++++++++++++++++++-
 ovn/ovn-nb.ovsschema          |  20 ++-
 ovn/ovn-nb.xml                | 270 ++++++++++++++++++++++++++++++++++++++++
 ovn/utilities/ovn-nbctl.8.xml |  30 +++++
 ovn/utilities/ovn-nbctl.c     | 197 +++++++++++++++++++++++++++++
 tests/ovn.at                  | 281 ++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1135 insertions(+), 10 deletions(-)

diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 178a1a9..447e042 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -423,7 +423,90 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 10: Destination Lookup</h3>
+    <h3>Ingress Table 10: DHCP option processing</h3>
+
+    <p>
+      This table adds the DHCPv4 options to a DHCPv4 packet from the
+      logical ports configured with IPv4 address(es) and DHCPv4 options.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority-100 logical flow is added for these logical ports
+          which matches the IPv4 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 next table.
+        </p>
+
+        <pre>
+reg0[3] = put_dhcp_opts(offer_ip = <var>O</var>, <i>options</i>...);
+next;
+        </pre>
+
+        <p>
+          For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a
+          DHCP reply, adds the DHCP offer IP <var>O</var> and options to the
+          packet, and stores 1 into reg0[3].  For other kinds of packets, it
+          just stores 0 into reg0[3].  Either way, it continues to the next
+          table.
+        </p>
+
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advances to table 11.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 11: DHCP responses</h3>
+
+    <p>
+      This table implements DHCP responder for the DHCP replies generated by
+      the previous table.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority 100 logical flow is added for the logical ports configured
+          with DHCPv4 options which matches IPv4 packets with <code>udp.src == 68
+          &amp;&amp; udp.dst == 67 &amp;&amp; reg0[3] == 1</code> and
+          responds back to the <code>inport</code> after applying these
+          actions.  If <code>reg0[3]</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 DHCPv4 options and <var>O</var> is
+          the IPv4 address defined in the logical port's addresses column.
+        </p>
+
+        <p>
+          (This terminates ingress packet processing; the packet does not go
+           to the next ingress table.)
+        </p>
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advances to table 12.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 12: Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
@@ -497,6 +580,12 @@ output;
       there are no rules added for load balancing new connections.
     </p>
 
+    <p>
+      Also a priority 34000 logical flow is added for each logical port which
+      has DHCPv4 options defined to allow the DHCPv4 reply packet from the
+      <code>Ingress Table 11: DHCP responses</code>.
+    </p>
+
     <h3>Egress Table 6: Egress Port Security - IP</h3>
 
     <p>
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index b1c2c6c..b3b8e69 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"
@@ -99,7 +100,9 @@ enum ovn_stage {
     PIPELINE_STAGE(SWITCH, IN,  LB,             7, "ls_in_lb")           \
     PIPELINE_STAGE(SWITCH, IN,  STATEFUL,       8, "ls_in_stateful")     \
     PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,     9, "ls_in_arp_rsp")      \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       10, "ls_in_l2_lkup")      \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,   10, "ls_in_dhcp_options") \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE,  11, "ls_in_dhcp_response") \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,        12, "ls_in_l2_lkup")      \
                                                                       \
     /* Logical switch egress stages. */                               \
     PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")     \
@@ -140,6 +143,7 @@ enum ovn_stage {
 #define REGBIT_CONNTRACK_DEFRAG "reg0[0]"
 #define REGBIT_CONNTRACK_COMMIT "reg0[1]"
 #define REGBIT_CONNTRACK_NAT    "reg0[2]"
+#define REGBIT_DHCP_OPTS_RESULT "reg0[3]"
 
 /* Returns an "enum ovn_stage" built from the arguments. */
 static enum ovn_stage
@@ -1325,6 +1329,77 @@ lsp_is_up(const struct nbrec_logical_switch_port *lsp)
 }
 
 static bool
+build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip,
+                    struct ds *options_action, struct ds *response_action)
+{
+    if (!op->nbs->dhcpv4_options) {
+        /* CMS has disabled native DHCPv4 for this lport. */
+        return false;
+    }
+
+    ovs_be32 host_ip, mask;
+    char *error = ip_parse_masked(op->nbs->dhcpv4_options->cidr, &host_ip,
+                                  &mask);
+    if (error || ((offer_ip ^ host_ip) & mask)) {
+       /* Either
+        *  - cidr defined is invalid or
+        *  - the offer ip of the logical port doesn't belong to the cidr
+        *    defined in the DHCPv4 options.
+        *  */
+        free(error);
+        return false;
+    }
+
+    const char *server_ip = smap_get(
+        &op->nbs->dhcpv4_options->options, "server_id");
+    const char *server_mac = smap_get(
+        &op->nbs->dhcpv4_options->options, "server_mac");
+    const char *lease_time = smap_get(
+        &op->nbs->dhcpv4_options->options, "lease_time");
+    const char *router = smap_get(
+            &op->nbs->dhcpv4_options->options, "router");
+
+    if (!(server_ip && server_mac && lease_time && router)) {
+        /* "server_id", "server_mac", "lease_time" and "router" should be
+         * present in the dhcp_options. */
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Required DHCPv4 options not defined for lport - %s",
+                     op->json_key);
+        return false;
+    }
+
+    struct smap dhcpv4_options = SMAP_INITIALIZER(&dhcpv4_options);
+    smap_clone(&dhcpv4_options, &op->nbs->dhcpv4_options->options);
+
+    /* server_mac is not DHCPv4 option, delete it from the smap. */
+    smap_remove(&dhcpv4_options, "server_mac");
+    char *netmask = xasprintf(IP_FMT, IP_ARGS(mask));
+    smap_add(&dhcpv4_options, "netmask", netmask);
+    free(netmask);
+
+    ds_put_format(options_action,
+                  REGBIT_DHCP_OPTS_RESULT" = put_dhcp_opts(offerip = "
+                  IP_FMT", ", IP_ARGS(offer_ip));
+    struct smap_node *node;
+    SMAP_FOR_EACH(node, &dhcpv4_options) {
+        ds_put_format(options_action, "%s = %s, ", node->key, node->value);
+    }
+
+    ds_chomp(options_action, ' ');
+    ds_chomp(options_action, ',');
+    ds_put_cstr(options_action, "); next;");
+
+    ds_put_format(response_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(&dhcpv4_options);
+    return true;
+}
+
+static bool
 has_stateful_acl(struct ovn_datapath *od)
 {
     for (size_t i = 0; i < od->nbs->n_acls; i++) {
@@ -1632,6 +1707,35 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
                           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 DHCPv4 options*/
+    if (od->nbs && od->nbs->n_ports) {
+        for (size_t i = 0; i < od->nbs->n_ports; i++) {
+            if (od->nbs->ports[i]->dhcpv4_options) {
+                const char *server_id = smap_get(
+                    &od->nbs->ports[i]->dhcpv4_options->options, "server_id");
+                const char *server_mac = smap_get(
+                    &od->nbs->ports[i]->dhcpv4_options->options, "server_mac");
+                const char *lease_time = smap_get(
+                    &od->nbs->ports[i]->dhcpv4_options->options, "lease_time");
+                const char *router = smap_get(
+                    &od->nbs->ports[i]->dhcpv4_options->options, "router");
+                if (server_id && server_mac && lease_time && router) {
+                    struct ds match = DS_EMPTY_INITIALIZER;
+                    const char *actions =
+                        has_stateful ? "ct_commit; next;" : "next;";
+                    ds_put_format(&match, "outport == \"%s\" && eth.src == %s "
+                                  "&& ip4.src == %s && udp && udp.src == 67 "
+                                  "&& udp.dst == 68", od->nbs->ports[i]->name,
+                                  server_mac, server_id);
+                    ovn_lflow_add(
+                        lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match),
+                        actions);
+                }
+            }
+        }
+    }
 }
 
 static void
@@ -1811,8 +1915,8 @@ 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 9: ARP responder, skip requests coming from localnet ports.
-     * (priority 100). */
+    /* Ingress table 9: ARP/ND responder, skip requests coming from localnet
+     * ports. (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
             continue;
@@ -1908,7 +2012,69 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;");
     }
 
-    /* Ingress table 10: Destination lookup, broadcast and multicast handling
+    /* Logical switch ingress table 10 and 11: DHCP options and response
+         * priority 100 flows. */
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbs) {
+           continue;
+        }
+
+        if (!lsp_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;
+        }
+
+        if (!op->nbs->dhcpv4_options) {
+            /* CMS has disabled native DHCPv4 for this lport. */
+            continue;
+        }
+
+        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
+                struct ds options_action = DS_EMPTY_INITIALIZER;
+                struct ds response_action = DS_EMPTY_INITIALIZER;
+                if (build_dhcpv4_action(
+                        op, op->lsp_addrs[i].ipv4_addrs[j].addr,
+                        &options_action, &response_action)) {
+                    struct ds match = DS_EMPTY_INITIALIZER;
+                    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[i].ea_s);
+
+                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
+                                  100, ds_cstr(&match),
+                                  ds_cstr(&options_action));
+                    /* If REGBIT_DHCP_OPTS_RESULT is set, it means the
+                     * put_dhcp_opts action  is successful */
+                    ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT);
+                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE,
+                                  100, ds_cstr(&match),
+                                  ds_cstr(&response_action));
+                    ds_destroy(&match);
+                    ds_destroy(&options_action);
+                    ds_destroy(&response_action);
+                    break;
+                }
+            }
+        }
+    }
+
+    /* Ingress table 10 and 11: DHCP options and response, 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_OPTIONS, 0, "1", "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
+    }
+
+    /* Ingress table 12: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
@@ -1928,7 +2094,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                       "outport = \""MC_FLOOD"\"; output;");
     }
 
-    /* Ingress table 10: Destination lookup, unicast handling (priority 50), */
+    /* Ingress table 12: Destination lookup, unicast handling (priority 50), */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
             continue;
@@ -1961,7 +2127,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 10: Destination lookup for unknown MACs (priority 0). */
+    /* Ingress table 12: Destination lookup for unknown MACs (priority 0). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
@@ -2976,6 +3142,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 *
@@ -3144,6 +3381,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);
 
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
@@ -3161,6 +3402,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);
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 460d5bd..3cf07c1 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.0.0",
-    "cksum": "849073644 7576",
+    "version": "5.1.0",
+    "cksum": "2201958537 8295",
     "tables": {
         "Logical_Switch": {
             "columns": {
@@ -48,6 +48,11 @@
                                            "max": "unlimited"}},
                 "up": {"type": {"key": "boolean", "min": 0, "max": 1}},
                 "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
+                "dhcpv4_options": {"type": {"key": {"type": "uuid",
+                                            "refTable": "DHCP_Options",
+                                            "refType": "weak"},
+                                 "min": 0,
+                                 "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
@@ -149,6 +154,15 @@
                                                              "snat",
                                                              "dnat_and_snat"
                                                                ]]}}}},
-            "isRoot": false}
+            "isRoot": false},
+        "DHCP_Options": {
+            "columns": {
+                "cidr": {"type": "string"},
+                "options": {"type": {"key": "string", "value": "string",
+                                     "min": 0, "max": "unlimited"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "isRoot": true}
     }
 }
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index e571eeb..86dbfa7 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -509,6 +509,12 @@
     </group>
 
     <group title="Common Columns">
+      <column name="dhcpv4_options">
+        This column defines the DHCPv4 Options to be included by the
+        <code>ovn-controller</code> when it replies to the DHCPv4 requests.
+        Please see the <ref table="DHCP_Options"/> table.
+      </column>
+
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
       </column>
@@ -926,4 +932,268 @@
     </column>
   </table>
 
+  <table name="DHCP_Options" title="DHCP options.">
+    <p>
+      OVN implements a native DHCPv4 support which caters to the common
+      use case of providing an IPv4 address to a booting instance by
+      providing stateless replies to DHCPv4 requests based on statically
+      configured address mappings. To do this it allows a short list of
+      DHCPv4 options to be configured and applied at each compute host
+      running ovn-controller.
+    </p>
+
+    <column name="cidr">
+      <p>
+        The DHCPv4 options will be included if the logical port has the IPv4
+        address in this <ref column="cidr"/>.
+      </p>
+    </column>
+
+    <group title="DHCPv4 options">
+      <p>
+        CMS should define the set of DHCPv4 options as key/value pairs in the
+        <ref column="options"/> column of this table. In order for the
+        <code>ovn-controller</code> to include these DHCPv4 options, the
+        <ref column="dhcpv4_options"/> of <ref table="Logical_Switch_Port"/>
+        should refer to an entry in this table.
+      </p>
+
+      <group title="Supported v4 options">
+        <p>
+          Below are the supported DHCPv4 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 DHCPv4 options and their codes.
+        </p>
+
+        <column name="options" key="netmask">
+          <p>
+            The DHCPv4 option code for this option is 1.
+          </p>
+
+          <p>
+            Example. key="netmask", value="255.255.255.0"
+          </p>
+        </column>
+
+        <column name="options" key="router">
+          <p>
+            The DHCPv4 option code for this option is 3.
+          </p>
+        </column>
+
+        <column name="options" key="dns_server">
+          <p>
+            The DHCPv4 option code for this option is 6.
+          </p>
+        </column>
+
+        <column name="options" key="log_server">
+          <p>
+            The DHCPv4 option code for this option is 7.
+          </p>
+        </column>
+
+        <column name="options" key="lpr_server">
+          <p>
+            The DHCPv4 option code for this option is 9.
+          </p>
+        </column>
+
+        <column name="options" key="swap_server">
+          <p>
+            The DHCPv4 option code for this option is 16.
+          </p>
+        </column>
+
+        <column name="options" key="policy_filter">
+          <p>
+            The DHCPv4 option code for this option is 21.
+          </p>
+        </column>
+
+        <column name="options" key="router_solicitation">
+          <p>
+            The DHCPv4 option code for this option is 32.
+          </p>
+        </column>
+
+        <column name="options" key="nis_server">
+          <p>
+            The DHCPv4 option code for this option is 41.
+          </p>
+        </column>
+
+        <column name="options" key="ntp_server">
+          <p>
+            The DHCPv4 option code for this option is 42.
+          </p>
+        </column>
+
+        <column name="options" key="server_id">
+          <p>
+            The DHCPv4 option code for this option is 54.
+          </p>
+        </column>
+
+        <column name="options" key="tftp_server">
+          <p>
+            The DHCPv4 option code for this option is 66.
+          </p>
+        </column>
+
+        <column name="options" key="classless_static_route">
+          <p>
+            The DHCPv4 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="options" key="ms_classless_static_route">
+          <p>
+            The DHCPv4 option code for this option is 249. This option is
+            similar to <code>classless_static_route</code> supported by
+            Microsoft Windows DHCPv4 clients.
+          </p>
+        </column>
+
+        <column name="options" key="server_mac">
+          <p>
+            <code>eth.src</code> will be set to this value in the DHCPv4
+            response packet.
+          </p>
+        </column>
+      </group>
+
+      <group title="Other supported DHCPv4 options">
+        <column name="options" key="ip_forward_enable">
+          <p>
+            The DHCPv4 option code for this option is 19.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>bool</code>.
+
+            Example. key="ip_forward_enable", value="1"
+          </p>
+        </column>
+
+        <column name="options" key="router_discovery">
+          <p>
+            The DHCPv4 option code for this option is 31.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>bool</code>.
+          </p>
+        </column>
+
+        <column name="options" key="ethernet_encap">
+          <p>
+            The DHCPv4 option code for this option is 36.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>bool</code>.
+          </p>
+        </column>
+
+        <column name="options" key="default_ttl">
+          <p>
+            The DHCPv4 option code for this option is 23.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>uint8</code>.
+
+            Example. key="default_ttl", value="128".
+          </p>
+        </column>
+
+        <column name="options" key="tcp_ttl">
+          <p>
+            The DHCPv4 option code for this option is 37.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>uint8</code>.
+          </p>
+        </column>
+
+        <column name="options" key="mtu">
+          <p>
+            The DHCPv4 option code for this option is 26.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>uint16</code>.
+          </p>
+        </column>
+
+        <column name="options" key="lease_time">
+          <p>
+            The DHCPv4 option code for this option is 51.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>uint32</code>.
+
+            Example. key="lease_time", value="42000"
+          </p>
+        </column>
+
+        <column name="options" key="T1">
+          <p>
+            The DHCPv4 option code for this option is 58.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>uint32</code>.
+
+            Example. key="T1", value="30000"
+          </p>
+        </column>
+
+        <column name="options" key="T2">
+          <p>
+            The DHCPv4 option code for this option is 59.
+          </p>
+
+          <p>
+            The value of this DHCPv4 option is of type <code>uint32</code>.
+
+            Example. key="T2", value="40000"
+          </p>
+        </column>
+      </group>
+
+      <group title="Mandatory DHCPv4 options">
+        <p>
+          DHCPv4 options <code>"server_id"</code>, <code>"server_mac"</code>,
+          <code>"router"</code> and <code>"lease_time"</code> are mandatory
+          options which CMS should define for <code>OVN</code> to support
+          native DHCPv4.
+        </p>
+      </group>
+    </group>
+
+    <group title="Common Columns">
+      <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+  </table>
 </database>
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index 6330aa1..9d58bc5 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -391,6 +391,36 @@
       </dd>
     </dl>
 
+    <h1>DHCP Options commands</h1>
+
+    <dl>
+      <dt><code>dhcp-options-create</code> <var>cidr</var> [<var>key=value</var>]</dt>
+      <dd>
+        Creates a new DHCP Options entry in the <code>DHCP_Options</code> table
+        with the specified <code>cidr</code> and optional <code>external-ids</code>.
+      </dd>
+
+      <dt><code>dhcp-options-list</code></dt>
+      <dd>
+        Lists the DHCP Options entries.
+      </dd>
+
+      <dt><code>dhcp-options-del</code> <var>dhcp-option</var></dt>
+      <dd>
+        Deletes the DHCP Options entry referred by <var>dhcp-option</var> UUID.
+      </dd>
+
+      <dt><code>dhcp-options-set-options</code> <var>dhcp-option</var> [<var>key=value</var>]...</dt>
+      <dd>
+        Set the DHCP Options for the <var>dhcp-option</var> UUID.
+      </dd>
+
+      <dt><code>dhcp-options-get-options</code> <var>dhcp-option</var></dt>
+      <dd>
+        Lists the DHCP Options for the <var>dhcp-option</var> UUID.
+      </dd>
+    </dl>
+
     <h1>Database Commands</h1>
     <p>These commands query and modify the contents of <code>ovsdb</code> tables.
     They are a slight abstraction of the <code>ovsdb</code> interface and
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 25916da..7a16d88 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -70,6 +70,8 @@ static void run_prerequisites(struct ctl_command[], size_t n_commands,
                               struct ovsdb_idl *);
 static bool do_nbctl(const char *args, struct ctl_command *, size_t n,
                      struct ovsdb_idl *);
+static const struct nbrec_dhcp_options *dhcp_options_get(
+    struct ctl_context *ctx, const char *id, bool must_exist);
 
 int
 main(int argc, char *argv[])
@@ -334,6 +336,9 @@ Logical switch port commands:\n\
   lsp-set-options PORT KEY=VALUE [KEY=VALUE]...\n\
                             set options related to the type of PORT\n\
   lsp-get-options PORT      get the type specific options for PORT\n\
+  lsp-set-dhcpv4-options PORT [DHCP_OPTIONS_UUID]\n\
+                            set dhcpv4 options for PORT\n\
+  lsp-get-dhcpv4-options PORT  get the dhcpv4 options for PORT\n\
 \n\
 Logical router commands:\n\
   lr-add [ROUTER]           create a logical router named ROUTER\n\
@@ -358,6 +363,19 @@ Route commands:\n\
                             remove routes from ROUTER\n\
   lr-route-list ROUTER      print routes for ROUTER\n\
 \n\
+\n\
+DHCP Options commands:\n\
+  dhcp-options-create CIDR [EXTERNAL_IDS]\n\
+                           create a DHCP options row with CIDR\n\
+  dhcp-options-del DHCP_OPTIONS_UUID\n\
+                           delete DHCP_OPTIONS_UUID\n\
+  dhcp-options-list        \n\
+                           lists the DHCP_Options rows\n\
+  dhcp-options-set-options DHCP_OPTIONS_UUID  KEY=VALUE [KEY=VALUE]...\n\
+                           set DHCP options to the DHCP_OPTIONS_UUID\n\
+  dhcp-options-get-options DHCO_OPTIONS_UUID \n\
+                           displays the DHCP options of th DHCP_OPTIONS_UUID\n\
+\n\
 %s\
 \n\
 Options:\n\
@@ -1027,6 +1045,45 @@ nbctl_lsp_get_options(struct ctl_context *ctx)
     }
 }
 
+static void
+nbctl_lsp_set_dhcpv4_options(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch_port *lsp;
+
+    lsp = lsp_by_name_or_uuid(ctx, id, true);
+    const struct nbrec_dhcp_options *dhcp_opt = NULL;
+    if (ctx->argc == 3 ) {
+        dhcp_opt = dhcp_options_get(ctx, ctx->argv[2], true);
+    }
+
+    if (dhcp_opt) {
+        ovs_be32 ip;
+        unsigned int plen;
+        char *error = ip_parse_cidr(dhcp_opt->cidr, &ip, &plen);
+        if (error){
+            free(error);
+            ctl_fatal("DHCP options cidr '%s' is not IPv4", dhcp_opt->cidr);
+            return;
+        }
+    }
+    nbrec_logical_switch_port_set_dhcpv4_options(lsp, dhcp_opt);
+}
+
+static void
+nbctl_lsp_get_dhcpv4_options(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_switch_port *lsp;
+
+    lsp = lsp_by_name_or_uuid(ctx, id, true);
+    if (lsp->dhcpv4_options) {
+        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
+                      UUID_ARGS(&lsp->dhcpv4_options->header_.uuid),
+                      lsp->dhcpv4_options->cidr);
+    }
+}
+
 enum {
     DIR_FROM_LPORT,
     DIR_TO_LPORT
@@ -1283,6 +1340,126 @@ nbctl_lr_list(struct ctl_context *ctx)
     free(nodes);
 }
 
+static const struct nbrec_dhcp_options *
+dhcp_options_get(struct ctl_context *ctx, const char *id, bool must_exist)
+{
+    struct uuid dhcp_opts_uuid;
+    const struct nbrec_dhcp_options *dhcp_opts = NULL;
+    if (uuid_from_string(&dhcp_opts_uuid, id)) {
+        dhcp_opts = nbrec_dhcp_options_get_for_uuid(ctx->idl, &dhcp_opts_uuid);
+    }
+
+    if (!dhcp_opts && must_exist) {
+        ctl_fatal("%s: dhcp options UUID not found", id);
+    }
+    return dhcp_opts;
+}
+
+static void
+nbctl_dhcp_options_create(struct ctl_context *ctx)
+{
+    /* Validate the cidr */
+    ovs_be32 ip;
+    unsigned int plen;
+    char *error = ip_parse_cidr(ctx->argv[1], &ip, &plen);
+    if (error){
+        /* check if its IPv6 cidr */
+        free(error);
+        struct in6_addr ipv6;
+        error = ipv6_parse_cidr(ctx->argv[1], &ipv6, &plen);
+        if (error) {
+            free(error);
+            ctl_fatal("Invalid cidr format '%s'", ctx->argv[1]);
+            return;
+        }
+    }
+
+    struct nbrec_dhcp_options *dhcp_opts = nbrec_dhcp_options_insert(ctx->txn);
+    nbrec_dhcp_options_set_cidr(dhcp_opts, ctx->argv[1]);
+
+    struct smap ext_ids = SMAP_INITIALIZER(&ext_ids);
+    for (size_t i = 2; i < ctx->argc; i++) {
+        char *key, *value;
+        value = xstrdup(ctx->argv[i]);
+        key = strsep(&value, "=");
+        if (value) {
+            smap_add(&ext_ids, key, value);
+        }
+        free(key);
+    }
+
+    nbrec_dhcp_options_set_external_ids(dhcp_opts, &ext_ids);
+    smap_destroy(&ext_ids);
+}
+
+static void
+nbctl_dhcp_options_set_options(struct ctl_context *ctx)
+{
+    const struct nbrec_dhcp_options *dhcp_opts = dhcp_options_get(
+        ctx, ctx->argv[1], true);
+
+    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_dhcp_options_set_options(dhcp_opts, &dhcp_options);
+    smap_destroy(&dhcp_options);
+}
+
+static void
+nbctl_dhcp_options_get_options(struct ctl_context *ctx)
+{
+    const struct nbrec_dhcp_options *dhcp_opts = dhcp_options_get(
+        ctx, ctx->argv[1], true);
+
+    struct smap_node *node;
+    SMAP_FOR_EACH(node, &dhcp_opts->options) {
+        ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
+    }
+}
+
+static void
+nbctl_dhcp_options_del(struct ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *id = ctx->argv[1];
+    const struct nbrec_dhcp_options *dhcp_opts;
+
+    dhcp_opts = dhcp_options_get(ctx, id, must_exist);
+    if (!dhcp_opts) {
+        return;
+    }
+
+    nbrec_dhcp_options_delete(dhcp_opts);
+}
+
+static void
+nbctl_dhcp_options_list(struct ctl_context *ctx)
+{
+    const struct nbrec_dhcp_options *dhcp_opts;
+    struct smap dhcp_options;
+
+    smap_init(&dhcp_options);
+    NBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opts, ctx->idl) {
+        smap_add_format(&dhcp_options, dhcp_opts->cidr, UUID_FMT,
+                        UUID_ARGS(&dhcp_opts->header_.uuid));
+    }
+    const struct smap_node **nodes = smap_sort(&dhcp_options);
+    for (size_t i = 0; i < smap_count(&dhcp_options); i++) {
+        const struct smap_node *node = nodes[i];
+        ds_put_format(&ctx->output, "%s\n", node->value);
+    }
+    smap_destroy(&dhcp_options);
+    free(nodes);
+}
+
 /* The caller must free the returned string. */
 static char *
 normalize_ipv4_prefix(ovs_be32 ipv4, unsigned int plen)
@@ -1927,6 +2104,11 @@ static const struct ctl_table_class tables[] = {
      {{&nbrec_table_address_set, &nbrec_address_set_col_name, NULL},
       {NULL, NULL, NULL}}},
 
+    {&nbrec_table_dhcp_options,
+     {{&nbrec_table_dhcp_options, NULL,
+       NULL},
+      {NULL, NULL, NULL}}},
+
     {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
 };
 
@@ -2173,6 +2355,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
       nbctl_lsp_set_options, NULL, "", RW },
     { "lsp-get-options", 1, 1, "PORT", NULL, nbctl_lsp_get_options, NULL,
       "", RO },
+    { "lsp-set-dhcpv4-options", 1, 2, "PORT [DHCP_OPT_UUID]", NULL,
+      nbctl_lsp_set_dhcpv4_options, NULL, "", RW },
+    { "lsp-get-dhcpv4-options", 1, 1, "PORT", NULL,
+      nbctl_lsp_get_dhcpv4_options, NULL, "", RO },
 
     /* logical router commands. */
     { "lr-add", 0, 1, "[ROUTER]", NULL, nbctl_lr_add, NULL,
@@ -2199,6 +2385,17 @@ static const struct ctl_command_syntax nbctl_commands[] = {
     { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL,
       "", RO },
 
+    /* DHCP_Options commands */
+    {"dhcp-options-create", 1, INT_MAX, "CIDR [EXTERNAL:IDS]", NULL,
+     nbctl_dhcp_options_create, NULL, "", RW },
+    {"dhcp-options-del", 1, 1, "DHCP_OPT_UUID", NULL,
+     nbctl_dhcp_options_del, NULL, "", RW},
+    {"dhcp-options-list", 0, 0, "", NULL, nbctl_dhcp_options_list, NULL, "", RO},
+    {"dhcp-options-set-options", 1, INT_MAX, "DHCP_OPT_UUID KEY=VALUE [KEY=VALUE]...",
+    NULL, nbctl_dhcp_options_set_options, NULL, "", RW },
+    {"dhcp-options-get-options", 1, 1, "DHCP_OPT_UUID", NULL,
+     nbctl_dhcp_options_get_options, NULL, "", RO },
+
     {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
 };
 
diff --git a/tests/ovn.at b/tests/ovn.at
index 12de125..04a86b6 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -3064,6 +3064,287 @@ OVN_CLEANUP([hv1],[hv2])
 
 AT_CLEANUP
 
+AT_SETUP([ovn -- dhcpv4 : 1 HV, 2 LS, 2 LSPs/LS])
+AT_KEYWORDS([dhcpv4])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl ls-add ls1
+
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
+
+ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
+
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
+
+ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
+
+ovn-nbctl ls-add ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4"
+ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4"
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 30.0.0.7"
+ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7"
+
+ovn-nbctl -- --id=@d1 create DHCP_Options cidr=10.0.0.0/24 \
+options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \
+\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"" \
+-- add Logical_Switch_Port ls1-lp1 dhcpv4_options @d1 \
+-- add Logical_Switch_Port ls1-lp2 dhcpv4_options @d1
+
+ovn-nbctl -- --id=@d2 create DHCP_Options cidr=30.0.0.0/24 \
+options="\"server_id\"=\"30.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:02\" \
+\"lease_time\"=\"3600\"" -- add Logical_Switch_Port ls2-lp2 dhcpv4_options @d2
+
+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=ls1-lp2 \
+    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=ls2-lp1 \
+    options:tx_pcap=hv1/vif3-tx.pcap \
+    options:rxq_pcap=hv1/vif3-rx.pcap \
+    ofport-request=3
+
+ovs-vsctl -- add-port br-int hv1-vif4 -- \
+    set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \
+    options:tx_pcap=hv1/vif4-tx.pcap \
+    options:rxq_pcap=hv1/vif4-rx.pcap \
+    ofport-request=4
+
+ovn_populate_arp
+
+sleep 2
+
+as hv1 ovs-vsctl show
+
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
+# This shell function sends a DHCP request packet
+# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ...
+test_dhcp() {
+    local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4
+    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
+
+    if test $offer_ip != 0; then
+        local srv_mac=$5 srv_ip=$6 expected_dhcp_opts=$7
+        # total IP length will be the IP length of the request packet
+        # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2)
+        ip_len=`expr 280 + ${#expected_dhcp_opts} / 2`
+        udp_len=`expr $ip_len - 20`
+        printf -v ip_len "%x" $ip_len
+        printf -v udp_len "%x" $udp_len
+        # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len
+        local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip}
+        # udp header and dhcp header.
+        # $udp_len var will be in 3 digits. So adding a '0' before $udp_len
+        reply+=004300440${udp_len}0000020106006359aa760000000000000000
+        # your ip address
+        reply+=${offer_ip}
+        # next server ip address, relay agent ip address, client mac address
+        reply+=0000000000000000${src_mac}
+        # client hardware padding
+        reply+=00000000000000000000
+        # server hostname
+        reply+=0000000000000000000000000000000000000000000000000000000000000000
+        reply+=0000000000000000000000000000000000000000000000000000000000000000
+        # boot file name
+        reply+=0000000000000000000000000000000000000000000000000000000000000000
+        reply+=0000000000000000000000000000000000000000000000000000000000000000
+        reply+=0000000000000000000000000000000000000000000000000000000000000000
+        reply+=0000000000000000000000000000000000000000000000000000000000000000
+        # dhcp magic cookie
+        reply+=63825363
+        # dhcp message type
+        local dhcp_reply_type=02
+        if test $dhcp_type = 03; then
+            dhcp_reply_type=05
+        fi
+        reply+=3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000
+        echo $reply >> $inport.expected
+    else
+        shift; shift; shift; shift;
+        for outport; do
+            echo $request | trim_zeros >> $outport.expected
+        done
+    fi
+    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
+}
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+
+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 DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 4`
+server_ip=`ip_to_hex 10 0 0 1`
+expected_dhcp_opts=0104ffffff0003040a00000136040a000001330400000e10
+test_dhcp 1 f00000000001 01 $offer_ip ff1000000001 $server_ip $expected_dhcp_opts
+
+# NXT_RESUMEs should be 1.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+cat 1.expected | cut -c -48 > expout
+AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat 1.expected | cut -c 53- > expout
+AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
+
+# 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 to test.
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+# Send DHCPREQUEST.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+expected_dhcp_opts=0104ffffff0003040a00000136040a000001330400000e10
+test_dhcp 2 f00000000002 03 $offer_ip ff1000000001 $server_ip $expected_dhcp_opts
+
+# NXT_RESUMEs should be 2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+cat 2.expected | cut -c -48 > expout
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat 2.expected | cut -c 53- > expout
+AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+# Send Invalid DHCPv4 packet on ls1-lp2. It should be received by ovn-controller
+# but should be resumed without the reply.
+# ls1-lp1 (vif1-tx.pcap) should receive the DHCPv4 request packet twice,
+# one from ovn-controller and the other from "ovs-ofctl resume."
+offer_ip=0
+test_dhcp 2 f00000000002 08 $offer_ip 1 1
+
+# NXT_RESUMEs should be 3.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+# vif1-tx.pcap should have received the DHCPv4 (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])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+# Send DHCPv4 packet on ls2-lp1. It doesn't have any DHCPv4 options defined.
+# ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once.
+
+test_dhcp 3 f00000000003 01 0 4
+
+# Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for
+# this lport.
+test_dhcp 4 f00000000004 01 0 3
+
+# NXT_RESUMEs should be 3.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets
+cat 3.expected > expout
+AT_CHECK([cat 3.packets], [0], [expout])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif4-tx.pcap | trim_zeros > 4.packets
+cat 4.expected > expout
+AT_CHECK([cat 4.packets], [0], [expout])
+
+as hv1
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+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])
-- 
2.7.4




More information about the dev mailing list