diff --git a/NEWS b/NEWS
index 74adb2562..a2e4dd426 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,7 @@ Post-v2.11.0
    - OVN:
      * Select IPAM mac_prefix in a random manner if not provided by the user
      * Added the HA chassis group support and deprecated Gateway chassis.
+     * Added 'external' logical port support.
 
 v2.11.0 - 19 Feb 2019
 ---------------------
diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c
index 2929eccd7..404f0e757 100644
--- a/ovn/controller/binding.c
+++ b/ovn/controller/binding.c
@@ -502,6 +502,18 @@ consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn,
          * for them. */
         sset_add(local_lports, binding_rec->logical_port);
         our_chassis = false;
+    } else if (!strcmp(binding_rec->type, "external")) {
+        if (ha_chassis_group_contains(binding_rec->ha_chassis_group,
+                                      chassis_rec)) {
+            our_chassis = ha_chassis_group_is_active(
+                binding_rec->ha_chassis_group,
+                active_tunnels, chassis_rec);
+
+            add_local_datapath(sbrec_datapath_binding_by_key,
+                               sbrec_port_binding_by_datapath,
+                               sbrec_port_binding_by_name,
+                               binding_rec->datapath, false, local_datapaths);
+        }
     }
 
     if (our_chassis
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 986a28c8a..ad626f617 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -145,6 +145,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
      * ports that have a Gateway_Chassis that point's to our own
      * chassis */
     sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect");
+    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external");
     if (chassis) {
         /* This should be mostly redundant with the other clauses for port
          * bindings, but it allows us to catch any ports that are assigned to
diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c
index aa03919bb..a9d4b8736 100644
--- a/ovn/lib/ovn-util.c
+++ b/ovn/lib/ovn-util.c
@@ -319,6 +319,7 @@ static const char *OVN_NB_LSP_TYPES[] = {
     "localport",
     "router",
     "vtep",
+    "external",
 };
 
 bool
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 392a5efc9..c8883d60d 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -626,7 +626,8 @@ nd_na_router {
     <p>
       This table adds the DHCPv4 options to a DHCPv4 packet from the
       logical ports configured with IPv4 address(es) and DHCPv4 options,
-      and similarly for DHCPv6 options.
+      and similarly for DHCPv6 options. This table also adds flows for the
+      logical ports of type <code>external</code>.
     </p>
 
     <ul>
@@ -827,7 +828,39 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 16 Destination Lookup</h3>
+    <h3>Ingress table 16 External ports</h3>
+
+    <p>
+      Traffic from the <code>external</code> logical ports enter the ingress
+      datapath pipeline via the <code>localnet</code> port. This table adds the
+      below logical flows to handle the traffic from these ports.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority-100 flow is added for each <code>external</code> logical
+          port which doesn't reside on a chassis to drop the ARP/IPv6 NS
+          request to the router IP(s) (of the logical switch) which matches
+          on the <code>inport</code> of the <code>external</code> logical port
+          and the valid <code>eth.src</code> address(es) of the
+          <code>external</code> logical port.
+        </p>
+
+        <p>
+          This flow guarantees that the ARP/NS request to the router IP
+          address from the external ports is responded by only the chassis
+          which has claimed these external ports. All the other chassis,
+          drops these packets.
+        </p>
+      </li>
+
+      <li>
+        A priority-0 flow that matches all packets to advances to table 17.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 17 Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index be8dc36a6..a82eb18ee 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -119,7 +119,8 @@ enum ovn_stage {
     PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 13, "ls_in_dhcp_response") \
     PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    14, "ls_in_dns_lookup")    \
     PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  15, "ls_in_dns_response")  \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       16, "ls_in_l2_lkup")       \
+    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 16, "ls_in_external_port") \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       17, "ls_in_l2_lkup")       \
                                                                           \
     /* Logical switch egress stages. */                                   \
     PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
@@ -2332,6 +2333,18 @@ ovn_port_update_sbrec(struct northd_context *ctx,
             }
 
             sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
+
+            if (!strcmp(op->nbsp->type, "external")) {
+                if (op->nbsp->ha_chassis_group) {
+                    sync_ha_chassis_group_for_sbpb(
+                        ctx, op->nbsp->ha_chassis_group,
+                        sbrec_chassis_by_name, op->sb);
+                    sset_add(active_ha_chassis_grps,
+                             op->nbsp->ha_chassis_group->name);
+                } else {
+                    sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
+                }
+            }
         } else {
             const char *chassis = NULL;
             if (op->peer && op->peer->od && op->peer->od->nbr) {
@@ -3029,6 +3042,12 @@ lsp_is_up(const struct nbrec_logical_switch_port *lsp)
     return !lsp->up || *lsp->up;
 }
 
+static bool
+lsp_is_external(const struct nbrec_logical_switch_port *nbsp)
+{
+    return !strcmp(nbsp->type, "external");
+}
+
 static bool
 build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip,
                     struct ds *options_action, struct ds *response_action,
@@ -3926,6 +3945,10 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
      * logical ports of the datapath if the CMS has configured DHCPv4 options.
      * */
     for (size_t i = 0; i < od->nbs->n_ports; i++) {
+        if (lsp_is_external(od->nbs->ports[i])) {
+            continue;
+        }
+
         if (od->nbs->ports[i]->dhcpv4_options) {
             const char *server_id = smap_get(
                 &od->nbs->ports[i]->dhcpv4_options->options, "server_id");
@@ -4312,6 +4335,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
+        if (lsp_is_external(op->nbsp)) {
+            continue;
+        }
+
         ds_clear(&match);
         ds_clear(&actions);
         ds_put_format(&match, "inport == %s", op->json_key);
@@ -4378,6 +4405,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
+        if (lsp_is_external(op->nbsp)) {
+            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++) {
                 ds_clear(&match);
@@ -4486,6 +4517,14 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
+        bool is_external = lsp_is_external(op->nbsp);
+        if (is_external && (!op->od->localnet_port ||
+                            !op->nbsp->ha_chassis_group)) {
+            /* If it's an external port and there is no localnet port
+             * and if it doesn't belong to an HA chassis group ignore it. */
+            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;
@@ -4498,9 +4537,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                     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,
+                        "udp.src == 68 && udp.dst == 67",
+                        is_external ? op->od->localnet_port->json_key :
+                            op->json_key,
                         op->lsp_addrs[i].ea_s);
 
+                    if (is_external) {
+                        ds_put_format(&match, " && is_chassis_resident(%s)",
+                                      op->json_key);
+                    }
+
                     ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
                                   100, ds_cstr(&match),
                                   ds_cstr(&options_action));
@@ -4515,9 +4561,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                      */
                     ds_put_format(
                         &match, "inport == %s && eth.src == %s && "
-                        "%s && udp.src == 68 && udp.dst == 67", op->json_key,
+                        "%s && udp.src == 68 && udp.dst == 67",
+                        is_external ? op->od->localnet_port->json_key :
+                            op->json_key,
                         op->lsp_addrs[i].ea_s, ds_cstr(&ipv4_addr_match));
 
+                    if (is_external) {
+                        ds_put_format(&match, " && is_chassis_resident(%s)",
+                                      op->json_key);
+                    }
+
                     ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
                                   100, ds_cstr(&match),
                                   ds_cstr(&options_action));
@@ -4528,8 +4581,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                     ds_put_format(
                         &match, "inport == %s && eth.src == %s && "
                         "ip4 && udp.src == 68 && udp.dst == 67"
-                        " && "REGBIT_DHCP_OPTS_RESULT, op->json_key,
+                        " && "REGBIT_DHCP_OPTS_RESULT,
+                        is_external ? op->od->localnet_port->json_key :
+                            op->json_key,
                         op->lsp_addrs[i].ea_s);
+
+                    if (is_external) {
+                        ds_put_format(&match, " && is_chassis_resident(%s)",
+                                      op->json_key);
+                    }
+
                     ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE,
                                   100, ds_cstr(&match),
                                   ds_cstr(&response_action));
@@ -4550,9 +4611,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                     ds_put_format(
                         &match, "inport == %s && eth.src == %s"
                         " && ip6.dst == ff02::1:2 && udp.src == 546 &&"
-                        " udp.dst == 547", op->json_key,
+                        " udp.dst == 547",
+                        is_external ? op->od->localnet_port->json_key :
+                            op->json_key,
                         op->lsp_addrs[i].ea_s);
 
+                    if (is_external) {
+                        ds_put_format(&match, " && is_chassis_resident(%s)",
+                                      op->json_key);
+                    }
+
                     ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100,
                                   ds_cstr(&match), ds_cstr(&options_action));
 
@@ -4604,7 +4672,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
     /* Ingress table 12 and 13: DHCP options and response, by default goto
      * next. (priority 0).
      * Ingress table 14 and 15: DNS lookup and response, by default goto next.
-     * (priority 0).*/
+     * (priority 0).
+     * Ingress table 16 - External port handling, by default goto next.
+     * (priority 0). */
 
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
@@ -4615,9 +4685,60 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;");
     }
 
-    /* Ingress table 16: Destination lookup, broadcast and multicast handling
+    HMAP_FOR_EACH (op, key_node, ports) {
+        if (!op->nbsp || !lsp_is_external(op->nbsp) ||
+            !op->od->localnet_port) {
+           continue;
+        }
+
+        /* Table 16: External port. Drop ARP request for router ips from
+         * external ports  on chassis not binding those ports.
+         * This makes the router pipeline to be run only on the chassis
+         * binding the external ports. */
+
+        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+            for (size_t j = 0; j < op->od->n_router_ports; j++) {
+                struct ovn_port *rp = op->od->router_ports[j];
+                for (size_t k = 0; k < rp->n_lsp_addrs; k++) {
+                    for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv4_addrs;
+                         l++) {
+                        ds_clear(&match);
+                        ds_put_format(
+                            &match, "inport == %s && eth.src == %s"
+                            " && !is_chassis_resident(%s)"
+                            " && arp.tpa == %s && arp.op == 1",
+                            op->od->localnet_port->json_key,
+                            op->lsp_addrs[i].ea_s, op->json_key,
+                            rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
+                        ovn_lflow_add(lflows, op->od,
+                                      S_SWITCH_IN_EXTERNAL_PORT, 100,
+                                      ds_cstr(&match), "drop;");
+                    }
+                    for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs;
+                         l++) {
+                        ds_clear(&match);
+                        ds_put_format(
+                            &match, "inport == %s && eth.src == %s"
+                            " && !is_chassis_resident(%s)"
+                            " && nd_ns && ip6.dst == {%s, %s} && "
+                            "nd.target == %s",
+                            op->od->localnet_port->json_key,
+                            op->lsp_addrs[i].ea_s, op->json_key,
+                            rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
+                            rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
+                            rp->lsp_addrs[k].ipv6_addrs[l].addr_s);
+                        ovn_lflow_add(lflows, op->od,
+                                      S_SWITCH_IN_EXTERNAL_PORT, 100,
+                                      ds_cstr(&match), "drop;");
+                    }
+                }
+            }
+        }
+    }
+    /* Ingress table 17: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbsp) {
@@ -4637,9 +4758,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                       "outport = \""MC_FLOOD"\"; output;");
     }
 
-    /* Ingress table 16: Destination lookup, unicast handling (priority 50), */
+    /* Ingress table 17: Destination lookup, unicast handling (priority 50), */
     HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
+        if (!op->nbsp || lsp_is_external(op->nbsp)) {
             continue;
         }
 
@@ -4756,7 +4877,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
-    /* Ingress table 16: Destination lookup for unknown MACs (priority 0). */
+    /* Ingress table 17: Destination lookup for unknown MACs (priority 0). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
@@ -4791,7 +4912,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
      * Priority 150 rules drop packets to disabled logical ports, so that they
      * don't even receive multicast or broadcast packets. */
     HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
+        if (!op->nbsp || lsp_is_external(op->nbsp)) {
             continue;
         }
 
diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
index 3936e6016..8c9e10635 100644
--- a/ovn/ovn-architecture.7.xml
+++ b/ovn/ovn-architecture.7.xml
@@ -1678,6 +1678,77 @@
     </li>
   </ol>
 
+  <h2>Native OVN services for external logical ports</h2>
+
+  <p>
+    To support OVN native services (like DHCP/IPv6 RA/DNS lookup) to the
+    cloud resources which are external, OVN supports <code>external</code>
+    logical ports.
+  </p>
+
+  <p>
+    Below are some of the use cases where <code>external</code> ports can be
+    used.
+  </p>
+
+  <ul>
+    <li>
+      VMs connected to SR-IOV nics - Traffic from these VMs by passes the
+      kernel stack and local <code>ovn-controller</code> do not bind these
+      ports and cannot serve the native services.
+    </li>
+    <li>
+      When CMS supports provisioning baremetal servers.
+    </li>
+  </ul>
+
+  <p>
+    OVN will provide the native services if CMS has done the below
+    configuration in the <dfn>OVN Northbound Database</dfn>.
+  </p>
+
+  <ul>
+    <li>
+      A row is created in <code>Logical_Switch_Port</code>, configuring the
+      <ref column="addresses" table="Logical_Switch_Port" db="OVN_NB"/> column
+      and setting the <ref column="type" table="Logical_Switch_Port"
+      db="OVN_NB"/> to <code>external</code>.
+    </li>
+
+    <li>
+      <ref column="ha_chassis_group" table="Logical_Switch_Port"
+      db="OVN_NB"/> column is configured.
+    </li>
+
+    <li>
+      The HA chassis which belongs to the HA chassis group has the
+      <code>ovn-bridge-mappings</code> configured and has proper L2
+      connectivity so that it can receive the DHCP and other related request
+      packets from these external resources.
+    </li>
+
+    <li>
+      The Logical_Switch of this port has a <code>localnet</code> port.
+    </li>
+
+    <li>
+      Native OVN services are enabled by configuring the DHCP and other
+      options like the way it is done for the normal logical ports.
+    </li>
+  </ul>
+
+  <p>
+    It is recommended to use the same HA chassis group for all the external
+    ports of a logical switch. Otherwise, the physical switch might see MAC
+    flap issue when different chassis provide the native services. For
+    example when supporting native DHCPv4 service, DHCPv4 server mac
+    (configured in <ref column="options:server_mac" table="DHCP_Options"
+    db="OVN_NB"/> column in table <ref table="DHCP_Options"/>) originating
+    from different ports can cause MAC flap issue.
+    The MAC of the logical router IP(s) can also flap if the same HA chassis
+    group is not set for all the external ports of a logical switch.
+  </p>
+
   <h1>Security</h1>
 
   <h2>Role-Based Access Controls for the Soutbound DB</h2>
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 48d27b960..884130b7a 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.15.0",
-    "cksum": "1078795414 21917",
+    "version": "5.15.1",
+    "cksum": "2715809578 22214",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -102,6 +102,12 @@
                                             "refType": "weak"},
                                  "min": 0,
                                  "max": 1}},
+                "ha_chassis_group": {
+                    "type": {"key": {"type": "uuid",
+                                     "refTable": "HA_Chassis_Group",
+                                     "refType": "weak"},
+                             "min": 0,
+                             "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 75d27a0eb..68aea46ab 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -353,6 +353,53 @@
           <dd>
             A port to a logical switch on a VTEP gateway.
           </dd>
+
+          <dt><code>external</code></dt>
+          <dd>
+            <p>
+              Represents a logical port which is external and not having
+              an OVS port in the integration bridge.
+              <code>OVN</code> will never receive any traffic from this port or
+              send any traffic to this port. <code>OVN</code> can support
+              native services like DHCPv4/DHCPv6/DNS for this port.
+              If <ref column="ha_chassis_group"/> is defined,
+              <code>ovn-controller</code> running in the master chassis of
+              the HA chassis group will bind this port to provide these native
+              services. It is expected that this port belong to a bridged
+              logical switch (with a <code>localnet</code> port).
+            </p>
+
+            <p>
+              It is recommended to use the same HA chassis group for all the
+              external ports of a logical switch. Otherwise, the physical
+              switch might see MAC flap issue when different chassis provide
+              the native services. For example when supporting native DHCPv4
+              service, DHCPv4 server mac (configured in
+              <ref column="options:server_mac" table="DHCP_Options"
+              db="OVN_NB"/> column in table <ref table="DHCP_Options"/>)
+              originating from different ports can cause MAC flap issue.
+              The MAC of the logical router IP(s) can also flap if the
+              same HA chassis group is not set for all the external ports
+              of a logical switch.
+            </p>
+
+            <p>
+              Below are some of the use cases where <code>external</code>
+              ports can be used.
+            </p>
+
+            <ul>
+              <li>
+                VMs connected to SR-IOV nics - Traffic from these VMs by passes
+                the kernel stack and local <code>ovn-controller</code> do not
+                bind these ports and cannot serve the native services.
+              </li>
+
+              <li>
+                When CMS supports provisioning baremetal servers.
+              </li>
+            </ul>
+          </dd>
         </dl>
       </column>
     </group>
@@ -901,6 +948,15 @@
       </column>
     </group>
 
+    <column name="ha_chassis_group">
+      References a row in the OVN Northbound database's
+      <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
+      It indicates the HA chassis group to use if the
+      <ref column="type"/> is set to <code>external</code>.
+      If <ref column="type"/> is not to <code>external</code>, this
+      column is ignored.
+    </column>
+
     <group title="Naming">
       <column name="external_ids" key="neutron:port_name">
         <p>
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index efaec250f..993c5afea 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -397,7 +397,7 @@ ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
 
 # ovn-northd should not create HA chassis group and HA chassis rows
 # unless the HA chassis group in OVN NB DB is associated to
-# a logical router port.
+# a logical router port or logical port of type external.
 AT_CHECK([ovn-sbctl --bare --columns name find ha_chassis_group name="hagrp1" \
 | wc -l], [0], [0
 ])
@@ -731,4 +731,104 @@ ovn-nbctl clear logical_router_port lr0-public options
 OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
 AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
 
+# Delete old sw0.
+ovn-nbctl ls-del sw0
+
+# Create external logical ports and associate ha_chassis_group
+ovn-nbctl ls-add sw0
+ovn-nbctl lsp-add sw0 sw0-pext1
+ovn-nbctl lsp-add sw0 sw0-pext2
+ovn-nbctl lsp-add sw0 sw0-p1
+
+ovn-nbctl lsp-set-addresses sw0-pext1 "00:00:00:00:00:03 10.0.0.3"
+ovn-nbctl lsp-set-addresses sw0-pext2 "00:00:00:00:00:03 10.0.0.4"
+ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:03 10.0.0.5"
+
+ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
+
+ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30
+ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20
+ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10
+
+# ovn-northd should not create HA chassis group and HA chassis rows
+# unless the HA chassis group in OVN NB DB is associated to
+# a logical router port or logical port of type external.
+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
+AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+
+hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group \
+name=hagrp1`
+
+# The type of the lsp - sw0-pext1 is still not set to external.
+# So ha_chassis_group should be ignored.
+ovn-nbctl set logical_switch_port sw0-pext1 ha_chassis_group=$hagrp1_uuid
+
+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+
+# Set the type of sw0-pext1 to external
+ovn-nbctl lsp-set-type sw0-pext1 external
+
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+
+sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
+name=hagrp1`
+
+AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
+ha_chassis_group find port_binding logical_port=sw0-pext1`])
+
+# Set the type of sw0-pext2 to external and associate ha_chassis_group
+ovn-nbctl lsp-set-type sw0-pext2 external
+ovn-nbctl set logical_switch_port sw0-pext2 ha_chassis_group=$hagrp1_uuid
+
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
+ha_chassis_group find port_binding logical_port=sw0-pext1`])
+
+OVS_WAIT_UNTIL([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
+ha_chassis_group find port_binding logical_port=sw0-pext2`])
+
+# sw0-p1 is a normal port. So ha_chassis_group should not be set
+# in port_binding.
+ovn-nbctl --wait=sb set logical_switch_port sw0-p1 \
+ha_chassis_group=$hagrp1_uuid
+
+OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=sw0-p1) = x], [0], [])
+
+# Clear ha_chassis_group for sw0-pext1
+ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group
+
+OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=sw0-pext1) = x], [0], [])
+
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+
+# Clear ha_chassis_group for sw0-pext2
+ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group
+
+OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=sw0-pext2) = x], [0], [])
+
+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
+AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+
+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])
+
 AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
index d6a068e0a..89fde43f8 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -9857,11 +9857,10 @@ grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
 ])
 
 # make sure that flows for handling the outside router port reside on gw1
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
 grep 00:00:02:01:02:04 | wc -l], [0], [[1
 ]])
-
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
 grep 00:00:02:01:02:04 | wc -l], [0], [[0
 ]])
 
@@ -9972,10 +9971,10 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
 ]])
 
 # make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
 grep 00:00:02:01:02:04 | wc -l], [0], [[1
 ]])
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
 grep 00:00:02:01:02:04 | wc -l], [0], [[0
 ]])
 
@@ -9987,10 +9986,10 @@ as main ovs-vsctl del-port n1 $port
 bfd_dump
 
 # make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
 grep 00:00:02:01:02:04 | wc -l], [0], [[1
 ]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
 grep 00:00:02:01:02:04 | wc -l], [0], [[0
 ]])
 
@@ -12111,6 +12110,723 @@ as hv2 start_daemon ovn-controller
 OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP
 
+AT_SETUP([ovn -- external logical port])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+net_add n1
+sim_add hv1
+sim_add hv2
+sim_add hv3
+
+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 ae70::4"
+
+# Add a couple of external logical port
+ovn-nbctl lsp-add ls1 ls1-lp_ext1 \
+-- lsp-set-addresses ls1-lp_ext1 "f0:00:00:00:00:03 10.0.0.6 ae70::6"
+ovn-nbctl lsp-set-port-security ls1-lp_ext1 \
+"f0:00:00:00:00:03 10.0.0.6 ae70::6"
+ovn-nbctl lsp-set-type ls1-lp_ext1 external
+
+ovn-nbctl lsp-add ls1 ls1-lp_ext2 \
+-- lsp-set-addresses ls1-lp_ext2 "f0:00:00:00:00:04 10.0.0.7 ae70::7"
+ovn-nbctl lsp-set-port-security ls1-lp_ext2 \
+"f0:00:00:00:00:04 10.0.0.7 ae70::8"
+ovn-nbctl lsp-set-type ls1-lp_ext2 external
+
+d1="$(ovn-nbctl 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\"")"
+
+d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \
+options="\"server_id\"=\"00:00:00:10:00:01\"")"
+
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1}
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext1 ${d1}
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext2 ${d1}
+
+ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d2}
+ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext1 ${d2}
+ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext2 ${d2}
+
+# Create a logical router and connect it to ls1
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-ls1 a0:10:00:00:00:01 10.0.0.1/24
+ovn-nbctl lsp-add ls1 ls1-lr0
+ovn-nbctl set Logical_Switch_Port ls1-lr0 type=router \
+    options:router-port=lr0-ls1 addresses=router
+
+# Create HA chassis group
+ovn-nbctl ha-chassis-group-add hagrp1
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30
+
+hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name="hagrp1"`
+
+# There should be 1 HA_Chassis rows with chassis sets
+OVS_WAIT_UNTIL([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
+| grep '-' | wc -l ], [0], [1
+])
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-phys hv1-ext1 -- \
+    set interface hv1-ext1 options:tx_pcap=hv1/ext1-tx.pcap \
+    options:rxq_pcap=hv1/ext1-rx.pcap \
+    ofport-request=2
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-phys hv2-ext2 -- \
+    set interface hv2-ext2 options:tx_pcap=hv2/ext2-tx.pcap \
+    options:rxq_pcap=hv2/ext2-rx.pcap \
+    ofport-request=2
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+as hv3
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.3
+ovs-vsctl -- add-port br-phys hv3-ext3 -- \
+    set interface hv3-ext3 options:tx_pcap=hv3/ext3-tx.pcap \
+    options:rxq_pcap=hv3/ext3-rx.pcap \
+    ofport-request=2
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and
+# hv2 as ha-chassis-group is not set and no localnet port added to ls1.
+AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
+wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+
+hv1_uuid=$(ovn-sbctl list chassis hv1 | grep uuid | awk '{print $3}')
+hv2_uuid=$(ovn-sbctl list chassis hv2 | grep uuid | awk '{print $3}')
+hv3_uuid=$(ovn-sbctl list chassis hv3 | grep uuid | awk '{print $3}')
+
+# The port_binding row for ls1-lp_ext1 should have empty chassis
+chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+
+AT_CHECK([test x$chassis == x], [0], [])
+
+# Associate hagrp1 ha-chassis-group to ls1-lp_ext1
+ovn-nbctl --wait=hv set Logical_Switch_Port ls1-lp_ext1 \
+ha-chassis-group=$hagrp1_uuid
+
+# Get the hagrp1 uuid in SB DB.
+sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
+name="hagrp1"`
+
+# Wait till ls1-lp_ext1 port_binding has ha_chassis_group set
+OVS_WAIT_UNTIL(
+    [sb_pb_hagrp=`ovn-sbctl --bare --columns ha_chassis_group find \
+port_binding logical_port=ls1-lp_ext1`
+     test "$sb_pb_hagrp" = "$sb_hagrp1_uuid"])
+
+# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2
+# as no localnet port added to ls1 yet.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+
+# Add the localnet port to the logical switch ls1
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl --wait=hv lsp-set-options ln-public network_name=phys
+
+ln_public_key=$(ovn-sbctl list port_binding ln-public | grep  tunnel_key | \
+awk '{print $3}')
+
+# The ls1-lp_ext1 should be bound to hv1 as only hv1 is part of the
+# ha chassis group.
+OVS_WAIT_UNTIL(
+    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+    test "$chassis" = "$hv1_uuid"])
+
+# There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
+wc -l], [0], [3
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+grep reg14=0x$ln_public_key | wc -l], [0], [1
+])
+
+# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+
+# No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and
+# hv2 as requested-chassis option is not set.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.07" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.07" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
+])
+
+as hv1
+ovs-vsctl show
+
+# 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 use_ip=$5
+    shift; shift; shift; shift; shift;
+    if test $use_ip != 0; then
+        src_ip=$1
+        dst_ip=$2
+        shift; shift;
+    else
+        src_ip=`ip_to_hex 0 0 0 0`
+        dst_ip=`ip_to_hex 255 255 255 255`
+    fi
+    local request=ffffffffffff${src_mac}0800451001100000000080110000${src_ip}${dst_ip}
+    # udp header and dhcp header
+    request=${request}0044004300fc0000
+    request=${request}010106006359aa760000000000000000000000000000000000000000${src_mac}
+    # client hardware padding
+    request=${request}00000000000000000000
+    # server hostname
+    request=${request}0000000000000000000000000000000000000000000000000000000000000000
+    request=${request}0000000000000000000000000000000000000000000000000000000000000000
+    # boot file name
+    request=${request}0000000000000000000000000000000000000000000000000000000000000000
+    request=${request}0000000000000000000000000000000000000000000000000000000000000000
+    request=${request}0000000000000000000000000000000000000000000000000000000000000000
+    request=${request}0000000000000000000000000000000000000000000000000000000000000000
+    # dhcp magic cookie
+    request=${request}63825363
+    # dhcp message type
+    request=${request}3501${dhcp_type}ff
+
+    local srv_mac=$1 srv_ip=$2 expected_dhcp_opts=$3
+    # 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`
+    ip_len=$(printf "%x" $ip_len)
+    udp_len=$(printf "%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=${reply}004300440${udp_len}0000020106006359aa760000000000000000
+    # your ip address
+    reply=${reply}${offer_ip}
+    # next server ip address, relay agent ip address, client mac address
+    reply=${reply}0000000000000000${src_mac}
+    # client hardware padding
+    reply=${reply}00000000000000000000
+    # server hostname
+    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+    # boot file name
+    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+    # dhcp magic cookie
+    reply=${reply}63825363
+    # dhcp message type
+    local dhcp_reply_type=02
+    if test $dhcp_type = 03; then
+        dhcp_reply_type=05
+    fi
+    reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000
+    echo $reply >> ext1_v4.expected
+
+    as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
+}
+
+
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
+# This shell function sends a DHCPv6 request packet
+# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
+# The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
+# packet should be received twice (one from ovn-controller and the other
+# from the "ovs-ofctl monitor br-int resume"
+test_dhcpv6() {
+    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
+    local req_pkt_in_expected=$6
+    local request=ffffffffffff${src_mac}86dd00000000002a1101${src_lla}
+    # dst ip ff02::1:2
+    request=${request}ff020000000000000000000000010002
+    # udp header and dhcpv6 header
+    request=${request}02220223002affff${msg_code}010203
+    # Client identifier
+    request=${request}0001000a00030001${src_mac}
+    # IA-NA (Identity Association for Non Temporary Address)
+    request=${request}0003000c0102030400000e1000001518
+    shift; shift; shift; shift; shift;
+
+    local server_mac=000000100001
+    local server_lla=fe80000000000000020000fffe100001
+    local reply_code=07
+    if test $msg_code = 01; then
+        reply_code=02
+    fi
+    local msg_len=54
+    if test $offer_ip = 1; then
+        msg_len=28
+    fi
+    local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101
+    reply=${reply}${server_lla}${src_lla}
+
+    # udp header and dhcpv6 header
+    reply=${reply}0223022200${msg_len}ffff${reply_code}010203
+    # Client identifier
+    reply=${reply}0001000a00030001${src_mac}
+    # IA-NA
+    if test $offer_ip != 1; then
+        reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}
+        reply=${reply}ffffffffffffffff
+    fi
+    # Server identifier
+    reply=${reply}0002000a00030001${server_mac}
+
+    echo $reply | trim_zeros >> ext${inport}_v6.expected
+    # The inport also receives the request packet since it is connected
+    # to the br-phys.
+    #echo $request >> ext${inport}_v6.expected
+
+    as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${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_hv1.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv1.log
+
+AT_CAPTURE_FILE([ofctl_monitor0_hv2.log])
+as hv2 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv2.log
+
+AT_CAPTURE_FILE([ofctl_monitor0_hv3.log])
+as hv3 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv3.log
+
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+# Send DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 1 in hv1.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 0 in hv2.
+OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.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.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+rm -f ext1_v4.packets
+
+# Send DHCPv6 request
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs should be 2 in hv1.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 0 in hv2.
+OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+rm -f ext1_v6.expected
+rm -f ext1_v6.packets
+
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+# Delete the ha-chassis hv1.
+ovn-nbctl ha-chassis-group-remove-chassis hagrp1 hv1
+OVS_WAIT_UNTIL(
+    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+    test "$chassis" = ""])
+
+# Add hv2 to the ha chassis group
+ovn-nbctl --wait=hv ha-chassis-group-add-chassis hagrp1 hv2 20
+
+ovn-sbctl list ha_chassis_group
+ovn-sbctl list ha_chassis
+
+ovn-sbctl find port_binding logical_port=ls1-lp_ext1
+
+# The ls1-lp_ext1 should be bound to hv2
+OVS_WAIT_UNTIL(
+    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+    test "$chassis" = "$hv2_uuid"])
+
+# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
+wc -l], [0], [3
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+grep reg14=0x$ln_public_key | wc -l], [0], [1
+])
+
+# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+grep reg14=0x$ln_public_key | wc -l], [0], [0
+])
+
+# Send DHCPDISCOVER again for hv1/ext1. The DHCP response should come from
+# hv2 ovn-controller.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 2 in hv1.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 1 in hv2.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.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.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+
+# Send DHCPv6 request again
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 1
+
+# NXT_RESUMEs should be 2 in hv1.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+rm -f ext1_v6.expected
+rm -f ext1_v6.packets
+
+as hv1
+ovs-vsctl show
+reset_pcap_file hv1-ext1 hv1/ext1
+reset_pcap_file br-phys_n1 hv1/br-phys_n1
+reset_pcap_file br-phys hv1/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv2-ext2 hv2/ext2
+reset_pcap_file br-phys_n1 hv2/br-phys_n1
+reset_pcap_file br-phys hv2/br-phys
+
+# From  ls1-lp_ext1, send ARP request for the router ip. The ARP
+# response should come from the router pipeline of hv2.
+ext1_mac=f00000000003
+router_mac=a01000000001
+ext1_ip=`ip_to_hex 10 0 0 6`
+router_ip=`ip_to_hex 10 0 0 1`
+arp_request=ffffffffffff${ext1_mac}08060001080006040001${ext1_mac}${ext1_ip}000000000000${router_ip}
+
+as hv1 ovs-appctl netdev-dummy/receive hv1-ext1 $arp_request
+expected_response=${src_mac}${router_mac}08060001080006040002${router_mac}${router_ip}${ext1_mac}${ext1_ip}
+echo $expected_response > expout
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_arp_resp
+AT_CHECK([cat ext1_arp_resp], [0], [expout])
+
+# Verify that the response came from hv2
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap > ext1_arp_resp
+AT_CHECK([cat ext1_arp_resp], [0], [expout])
+
+# Now add 3 ha chassis to the ha chassis group
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv2 20
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 10
+
+# hv1 should be master and claim ls1-lp_ext1
+OVS_WAIT_UNTIL(
+    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+    test "$chassis" = "$hv1_uuid"])
+
+as hv1
+ovs-vsctl show
+reset_pcap_file hv1-ext1 hv1/ext1
+reset_pcap_file br-phys_n1 hv1/br-phys_n1
+reset_pcap_file br-phys hv1/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv2-ext2 hv2/ext2
+reset_pcap_file br-phys_n1 hv2/br-phys_n1
+reset_pcap_file br-phys hv2/br-phys
+
+as hv3
+ovs-vsctl show
+reset_pcap_file hv3-ext3 hv3/ext3
+reset_pcap_file br-phys_n1 hv3/br-phys_n1
+reset_pcap_file br-phys hv3/br-phys
+
+# Send DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 3 in hv1.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.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.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+rm -f ext1_v4.packets
+
+# Send DHCPv6 request
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs should be 4 in hv1.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+rm -f ext1_v6.expected
+rm -f ext1_v6.packets
+as hv1 reset_pcap_file hv1-ext1 hv1/ext1
+
+# Now increase the priority of hv3 so it becomes master.
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 50
+
+# hv3 should be master and claim ls1-lp_ext1
+OVS_WAIT_UNTIL(
+    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+    test "$chassis" = "$hv3_uuid"])
+
+as hv1
+ovs-vsctl show
+reset_pcap_file hv1-ext1 hv1/ext1
+reset_pcap_file br-phys_n1 hv1/br-phys_n1
+reset_pcap_file br-phys hv1/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv2-ext2 hv2/ext2
+reset_pcap_file br-phys_n1 hv2/br-phys_n1
+reset_pcap_file br-phys hv2/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv3-ext3 hv3/ext3
+reset_pcap_file br-phys_n1 hv3/br-phys_n1
+reset_pcap_file br-phys hv3/br-phys
+
+# Send DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 4 in hv1.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 1 in hv3.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.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.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+rm -f ext1_v4.packets
+
+# Send DHCPv6 request
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs should be 4 in hv1.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv3.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+# disconnect hv3 from the network, hv1 should take over
+as hv3
+port=${sandbox}_br-phys
+as main ovs-vsctl del-port n1 $port
+
+# hv1 should be master and claim ls1-lp_ext1
+OVS_WAIT_UNTIL(
+    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+    test "$chassis" = "$hv1_uuid"])
+
+OVN_CLEANUP([hv1],[hv2],[hv3])
+AT_CLEANUP
+
 AT_SETUP([ovn -- ovn-controller restart])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
 ovn_start
