diff mbox

[ovs-dev,v4] ovn-northd: Add logical flows to support native DHCPv4

Message ID 9e5f1174-ac57-9ccc-18de-0f64e208c537@redhat.com
State Superseded
Headers show

Commit Message

Numan Siddique July 4, 2016, 3:54 a.m. UTC
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@redhat.com>
Co-authored-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 ovn/northd/ovn-northd.8.xml   |  91 +++++++++++++-
 ovn/northd/ovn-northd.c       | 267 +++++++++++++++++++++++++++++++++++++--
 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, 1144 insertions(+), 12 deletions(-)

Comments

Zong Kai LI July 5, 2016, 6:34 a.m. UTC | #1
>
> +    /* 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->nbs->n_addresses; i++) {
> +            struct lport_addresses laddrs;
> +            if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs,
> +                                       false)) {
> +                continue;
> +            }
> +
> +            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
> +                struct ds options_action = DS_EMPTY_INITIALIZER;
> +                struct ds response_action = DS_EMPTY_INITIALIZER;
> +                if (build_dhcpv4_action(op, laddrs.ipv4_addrs[j].addr,
> +                                        &options_action,
> &response_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_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;
> +                }
> +            }
> +            free(laddrs.ipv4_addrs);
> +        }
> +    }
> +
> +    /* 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;");
> +    }
>

Great work, but only one thing makes me feel uncomfortable.
DHCP Options flows are generated per each port, but not per each switch
datapath.
Since the resumed packet copies L2-L4 fields from original packet, and DHCP
options fields are the same for each port in the same switch. It doesn't
make sense to build DHCP Options flows for each ports.
The inport and eth.src in DHCP Options flows, I think we need logical
switch metadata field indeed.

Thanks and have a nice day.
Zong Kai, LI
Numan Siddique July 6, 2016, 9:14 a.m. UTC | #2
On Tue, Jul 5, 2016 at 12:04 PM, Zong Kai LI <zealokii@gmail.com> wrote:

> +    /* 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->nbs->n_addresses; i++) {
>> +            struct lport_addresses laddrs;
>> +            if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs,
>> +                                       false)) {
>> +                continue;
>> +            }
>> +
>> +            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
>> +                struct ds options_action = DS_EMPTY_INITIALIZER;
>> +                struct ds response_action = DS_EMPTY_INITIALIZER;
>> +                if (build_dhcpv4_action(op, laddrs.ipv4_addrs[j].addr,
>> +                                        &options_action,
>> &response_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_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;
>> +                }
>> +            }
>> +            free(laddrs.ipv4_addrs);
>> +        }
>> +    }
>> +
>> +    /* 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;");
>> +    }
>>
>
> Great work, but only one thing makes me feel uncomfortable.
> DHCP Options flows are generated per each port, but not per each switch
> datapath.
> Since the resumed packet copies L2-L4 fields from original packet, and
> DHCP options fields are the same for each port in the same switch. It
> doesn't make sense to build DHCP Options flows for each ports.
> The inport and eth.src in DHCP Options flows, I think we need logical
> switch metadata field indeed.
>
>
​
Since DHCP options are associated with logical ports, I don't see any other
way. Also the IP address to be offered in the DHCP response comes from the
Logical_Switch_Port.addresses right ?

​


> Thanks and have a nice day.
> Zong Kai, LI
>
Zong Kai LI July 6, 2016, 12:15 p.m. UTC | #3
On Wed, Jul 6, 2016 at 5:14 PM, Numan Siddique <nusiddiq@redhat.com> wrote:

>
>
> On Tue, Jul 5, 2016 at 12:04 PM, Zong Kai LI <zealokii@gmail.com> wrote:
>
>> +    /* 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->nbs->n_addresses; i++) {
>>> +            struct lport_addresses laddrs;
>>> +            if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs,
>>> +                                       false)) {
>>> +                continue;
>>> +            }
>>> +
>>> +            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
>>> +                struct ds options_action = DS_EMPTY_INITIALIZER;
>>> +                struct ds response_action = DS_EMPTY_INITIALIZER;
>>> +                if (build_dhcpv4_action(op, laddrs.ipv4_addrs[j].addr,
>>> +                                        &options_action,
>>> &response_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_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;
>>> +                }
>>> +            }
>>> +            free(laddrs.ipv4_addrs);
>>> +        }
>>> +    }
>>> +
>>> +    /* 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;");
>>> +    }
>>>
>>
>> Great work, but only one thing makes me feel uncomfortable.
>> DHCP Options flows are generated per each port, but not per each switch
>> datapath.
>> Since the resumed packet copies L2-L4 fields from original packet, and
>> DHCP options fields are the same for each port in the same switch. It
>> doesn't make sense to build DHCP Options flows for each ports.
>> The inport and eth.src in DHCP Options flows, I think we need logical
>> switch metadata field indeed.
>>
>>
> ​
> Since DHCP options are associated with logical ports, I don't see
> any other way. Also the IP address to be offered in the DHCP response
> comes from the Logical_Switch_Port.addresses right ?
>
> ​
>
>
>> Thanks and have a nice day.
>> Zong Kai, LI
>>
>
> I think I missed your discussion in
http://patchwork.ozlabs.org/patch/635772/ .
In some side, this is the simplest way to implement DHCP options in DB, and
it's flexible to allow port from the same net to have different DHCP
options.

But even we follow this design, there are still something need to fix for
port has multiple IPv4 addresses case.

And yes, for response_actions, we do need to get IPv4 address from port, so
it makes sense to build response_action per each port. But I'm not sure it
comes the same to options_action. I'm thinking on it, not a good
explanation found yet.

While processing DHCP options for multiple IPv4 addresses case, I would
suggest a variant solution:
1, add column dhcpv4_options to table Logical_Switch, and it should be a
list of ref. This will be default options for all ports on a logical switch.
2, keep column dhcpv4_options in Logical_Switch_Port, if a port want to has
some customized DHCP options, it can set this. (higher priority than
default options)
3, add column enable_dhcp_options(bool) to table Logical_Switch_Port, if
admin wants to disable DHCP_Options for a port, just set this to false.
4, Logical_Switch.dhcpv4_options is empty while
Logical_Switch_Port.dhcpv4_options is not empty, this means logical switch
doesn't have default DHCP options, but a port can have its owner DHCP
options.

What's benefit?
1, more columns added, but less data in total for logical switch ports.
2, less logical flows for native DHCPv4, logical switch default options
flows will work for ports using default DHCP options.
3, it keeps the flexible ability to allow ports have different DHCP options
in the same switch.

BTW, in commit message:
 - A logical flow which implements the DHCP reponder by sending
reponder => responder.

Thanks and have a nice day. :)
Zong Kai, LI
Ramu Ramamurthy July 11, 2016, 5:49 p.m. UTC | #4
Tested-By: Ramu Ramamurthy <ramu.ramamurthy@us.ibm.com> against the
corresponding (rebased) openstack patch:
https://review.openstack.org/#/c/243174/
Hence, Acked-By: Ramu Ramamurthy <ramu.ramamurthy@us.ibm.com>


On Sun, Jul 3, 2016 at 8:54 PM, Numan Siddique <nusiddiq@redhat.com> wrote:
> 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@redhat.com>
> Co-authored-by: Ben Pfaff <blp@ovn.org>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
diff mbox

Patch

diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 6bc83ea..7072a7a 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 f4b4435..e605cb0 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
@@ -1329,6 +1333,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++) {
@@ -1636,6 +1711,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
@@ -1814,8 +1918,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 3: 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;
@@ -1829,7 +1933,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 5: ARP/ND responder, reply for known IPs.
+    /* Ingress table 9: ARP/ND responder, reply for known IPs.
      * (priority 50). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
@@ -1916,7 +2020,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 5: ARP/ND responder, by default goto next.
+    /* Ingress table 9: ARP/ND responder, by default goto next.
      * (priority 0)*/
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
@@ -1926,7 +2030,76 @@  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 6: 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->nbs->n_addresses; i++) {
+            struct lport_addresses laddrs;
+            if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs,
+                                       false)) {
+                continue;
+            }
+
+            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+                struct ds options_action = DS_EMPTY_INITIALIZER;
+                struct ds response_action = DS_EMPTY_INITIALIZER;
+                if (build_dhcpv4_action(op, laddrs.ipv4_addrs[j].addr,
+                                        &options_action, &response_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_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;
+                }
+            }
+            free(laddrs.ipv4_addrs);
+        }
+    }
+
+    /* 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) {
@@ -1946,7 +2119,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 12: Destination lookup, unicast handling (priority 50), */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbs) {
             continue;
@@ -1983,7 +2156,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 6: 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;
@@ -2933,6 +3106,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 *
@@ -3101,6 +3345,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);
@@ -3118,6 +3366,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 ee7c2c6..8bb1cd4 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "3.2.0",
-    "cksum": "1784604034 7539",
+    "version": "3.3.0",
+    "cksum": "1364201762 8258",
     "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"}}},
@@ -148,6 +153,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 ff2e695..646b726 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -502,6 +502,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>
@@ -921,4 +927,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 b4b8501..ed358d5 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -382,6 +382,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 ad70a05..b21e76c 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -69,6 +69,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[])
@@ -333,6 +335,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\
@@ -357,6 +362,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\
@@ -1018,6 +1036,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
@@ -1274,6 +1331,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)
@@ -1879,6 +2056,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}}}
 };
 
@@ -2125,6 +2307,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,
@@ -2150,6 +2336,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 feb68d3..b3c7363 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2936,6 +2936,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])