Blob Blame History Raw
From da4e67e3ebaf75701f7018f893d2b3285a5da394 Mon Sep 17 00:00:00 2001
From: Dumitru Ceara <dceara@redhat.com>
Date: Thu, 25 Feb 2021 15:27:43 +0100
Subject: [PATCH 10/16] northd: MAC learning: Add logical flows for fdb.

This patch adds the logical flows to do mac learning
for the logical ports whose port security is disabled
with 'unknown' addresses set.

Until now, OVN delivers the unknown destination packets to all
the logical ports with 'unknown' addresses set.  With this
mac learning feature, such flooding of packets is avoided once
OVN has learnt the port-to-mac bindings.

Note that we don't learn macs seen on the localnet logical ports.

Acked-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
(cherry picked from upstream commit dd94f1266ca4f3c750bc59c474ea342ef3ff9983)

Change-Id: Iff8c7789400a681bfe5c913855a4467b1805d2d9
---
 controller/lflow.c      |   5 +
 controller/lflow.h      |   9 +-
 lib/ovn-util.h          |   3 +
 northd/ovn-northd.8.xml | 162 ++++++++++++---
 northd/ovn-northd.c     |  96 ++++++---
 ovn-sb.ovsschema        |   6 +-
 tests/ovn-northd.at     |  32 +--
 tests/ovn.at            | 517 ++++++++++++++++++++++++++++++++++++++++++++----
 utilities/ovn-trace.c   |   7 +-
 9 files changed, 722 insertions(+), 115 deletions(-)

diff --git a/controller/lflow.c b/controller/lflow.c
index fb8f130..8f25877 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -88,6 +88,11 @@ static void lflow_resource_destroy_lflow(struct lflow_resource_ref *,
 static bool
 lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp)
 {
+    if (!strcmp(port_name, "none")) {
+        *portp = 0;
+        return true;
+    }
+
     const struct lookup_port_aux *aux = aux_;
 
     const struct sbrec_port_binding *pb
diff --git a/controller/lflow.h b/controller/lflow.h
index 9f8bed4..2eb2cb1 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -60,9 +60,9 @@ struct uuid;
  * you make any changes. */
 #define OFTABLE_PHY_TO_LOG            0
 #define OFTABLE_LOG_INGRESS_PIPELINE  8 /* First of LOG_PIPELINE_LEN tables. */
-#define OFTABLE_REMOTE_OUTPUT        32
-#define OFTABLE_LOCAL_OUTPUT         33
-#define OFTABLE_CHECK_LOOPBACK       34
+#define OFTABLE_REMOTE_OUTPUT        37
+#define OFTABLE_LOCAL_OUTPUT         38
+#define OFTABLE_CHECK_LOOPBACK       39
 #define OFTABLE_LOG_EGRESS_PIPELINE  40 /* First of LOG_PIPELINE_LEN tables. */
 #define OFTABLE_SAVE_INPORT          64
 #define OFTABLE_LOG_TO_PHY           65
@@ -74,9 +74,6 @@ struct uuid;
 #define OFTABLE_GET_FDB              71
 #define OFTABLE_LOOKUP_FDB           72
 
-/* The number of tables for the ingress and egress pipelines. */
-#define LOG_PIPELINE_LEN 24
-
 enum ref_type {
     REF_TYPE_ADDRSET,
     REF_TYPE_PORTGROUP,
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index 0823461..df4b0bc 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -261,4 +261,7 @@ BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk));
 /* See RFC 4960 Sections 3.3.7 and 8.5.1 for information on this flag. */
 #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
 
+/* The number of tables for the ingress and egress pipelines. */
+#define LOG_PIPELINE_LEN 29
+
 #endif
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 9488d8a..a964db9 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -307,7 +307,73 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 3: <code>from-lport</code> Pre-ACLs</h3>
+    <h3>Ingress Table 3: Lookup MAC address learning table</h3>
+
+    <p>
+      This table looks up the MAC learning table of the logical switch
+      datapath to check if the <code>port-mac</code> pair is present
+      or not. MAC is learnt only for logical switch VIF ports whose
+      port security is disabled and 'unknown' address set.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          For each such logical port <var>p</var> whose port security
+          is disabled and 'unknown' address set following flow
+          is added.
+        </p>
+
+        <ul>
+          <li>
+            Priority 100 flow with the match
+            <code>inport == <var>p</var></code> and action
+            <code>reg0[11] = lookup_fdb(inport, eth.src); next;</code>
+          </li>
+        </ul>
+      </li>
+
+      <li>
+        One priority-0 fallback flow that matches all packets and advances to
+        the next table.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 4: Learn MAC of 'unknown' ports.</h3>
+
+    <p>
+      This table learns the MAC addresses seen on the logical ports
+      whose port security is disabled and 'unknown' address set
+      if the <code>lookup_fdb</code> action returned false in the
+      previous table.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          For each such logical port <var>p</var> whose port security
+          is disabled and 'unknown' address set following flow
+          is added.
+        </p>
+
+        <ul>
+          <li>
+            Priority 100 flow with the match
+            <code>inport == <var>p</var> &amp;&amp; reg0[11] == 0</code> and
+            action <code>put_fdb(inport, eth.src); next;</code> which stores
+            the <code>port-mac</code> in the mac learning table of the
+            logical switch datapath and advances the packet to the next table.
+          </li>
+        </ul>
+      </li>
+
+      <li>
+        One priority-0 fallback flow that matches all packets and advances to
+        the next table.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 5: <code>from-lport</code> Pre-ACLs</h3>
 
     <p>
       This table prepares flows for possible stateful ACL processing in
@@ -332,7 +398,7 @@
       db="OVN_Northbound"/> table.
     </p>
 
-    <h3>Ingress Table 4: Pre-LB</h3>
+    <h3>Ingress Table 6: Pre-LB</h3>
 
     <p>
       This table prepares flows for possible stateful load balancing processing
@@ -399,7 +465,7 @@
       logical router datapath to logical switch datapath.
     </p>
 
-    <h3>Ingress Table 5: Pre-stateful</h3>
+    <h3>Ingress Table 7: Pre-stateful</h3>
 
     <p>
       This table prepares flows for all possible stateful processing
@@ -410,7 +476,7 @@
       <code>ct_next;</code> action.
     </p>
 
-    <h3>Ingress Table 6: <code>from-lport</code> ACL hints</h3>
+    <h3>Ingress Table 8: <code>from-lport</code> ACL hints</h3>
 
     <p>
       This table consists of logical flows that set hints
@@ -490,7 +556,7 @@
       </li>
     </ul>
 
-    <h3>Ingress table 7: <code>from-lport</code> ACLs</h3>
+    <h3>Ingress table 9: <code>from-lport</code> ACLs</h3>
 
     <p>
       Logical flows in this table closely reproduce those in the
@@ -599,7 +665,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 8: <code>from-lport</code> QoS Marking</h3>
+    <h3>Ingress Table 10: <code>from-lport</code> QoS Marking</h3>
 
     <p>
       Logical flows in this table closely reproduce those in the
@@ -621,7 +687,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 9: <code>from-lport</code> QoS Meter</h3>
+    <h3>Ingress Table 11: <code>from-lport</code> QoS Meter</h3>
 
     <p>
       Logical flows in this table closely reproduce those in the
@@ -643,7 +709,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 10: LB</h3>
+    <h3>Ingress Table 12: LB</h3>
 
     <p>
       It contains a priority-0 flow that simply moves traffic to the next
@@ -669,7 +735,7 @@
       connection.)
     </p>
 
-    <h3>Ingress Table 11: Stateful</h3>
+    <h3>Ingress Table 13: Stateful</h3>
 
     <ul>
       <li>
@@ -727,7 +793,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 12: Pre-Hairpin</h3>
+    <h3>Ingress Table 14: Pre-Hairpin</h3>
     <ul>
       <li>
         If the logical switch has load balancer(s) configured, then a
@@ -752,7 +818,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 13: Nat-Hairpin</h3>
+    <h3>Ingress Table 15: Nat-Hairpin</h3>
     <ul>
       <li>
          If the logical switch has load balancer(s) configured, then a
@@ -778,7 +844,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 14: Hairpin</h3>
+    <h3>Ingress Table 16: Hairpin</h3>
     <ul>
       <li>
         A priority-1 flow that hairpins traffic matched by non-default
@@ -791,7 +857,7 @@
       </li>
     </ul>
 
-    <h3>Ingress Table 15: ARP/ND responder</h3>
+    <h3>Ingress Table 17: ARP/ND responder</h3>
 
     <p>
       This table implements ARP/ND responder in a logical switch for known
@@ -1081,7 +1147,7 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 16: DHCP option processing</h3>
+    <h3>Ingress Table 18: DHCP option processing</h3>
 
     <p>
       This table adds the DHCPv4 options to a DHCPv4 packet from the
@@ -1142,7 +1208,7 @@ next;
       </li>
     </ul>
 
-    <h3>Ingress Table 17: DHCP responses</h3>
+    <h3>Ingress Table 19: DHCP responses</h3>
 
     <p>
       This table implements DHCP responder for the DHCP replies generated by
@@ -1223,7 +1289,7 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 18 DNS Lookup</h3>
+    <h3>Ingress Table 20 DNS Lookup</h3>
 
     <p>
       This table looks up and resolves the DNS names to the corresponding
@@ -1252,7 +1318,7 @@ reg0[4] = dns_lookup(); next;
       </li>
     </ul>
 
-    <h3>Ingress Table 19 DNS Responses</h3>
+    <h3>Ingress Table 21 DNS Responses</h3>
 
     <p>
       This table implements DNS responder for the DNS replies generated by
@@ -1287,7 +1353,7 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress table 20 External ports</h3>
+    <h3>Ingress table 22 External ports</h3>
 
     <p>
       Traffic from the <code>external</code> logical ports enter the ingress
@@ -1330,7 +1396,7 @@ output;
       </li>
     </ul>
 
-    <h3>Ingress Table 21 Destination Lookup</h3>
+    <h3>Ingress Table 23 Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
@@ -1493,12 +1559,58 @@ output;
       </li>
 
       <li>
-        One priority-0 fallback flow that matches all packets and outputs them
-        to the <code>MC_UNKNOWN</code> multicast group, which
-        <code>ovn-northd</code> populates with all enabled logical ports that
-        accept unknown destination packets.  As a small optimization, if no
-        logical ports accept unknown destination packets,
-        <code>ovn-northd</code> omits this multicast group and logical flow.
+        One priority-0 fallback flow that matches all packets with the
+        action <code>outport = get_fdb(eth.dst); next;</code>. The action
+        <code>get_fdb</code> gets the port for the <code>eth.dst</code>
+        in the MAC learning table of the logical switch datapath. If there
+        is no entry for <code>eth.dst</code> in the MAC learning table,
+        then it stores <code>none</code> in the <code>outport</code>.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 23 Destination unknown</h3>
+
+    <p>
+      This table handles the packets whose destination was not found or
+      and looked up in the MAC learning table of the logical switch
+      datapath. It contains the following flows.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          If the logical switch has logical ports with 'unknown' addresses set,
+          then the below logical flow is added
+        </p>
+
+        <ul>
+          <li>
+            Priority 50 flow with the match <code>outport == none</code> then
+            outputs them to the <code>MC_UNKNOWN</code> multicast group, which
+            <code>ovn-northd</code> populates with all enabled logical ports
+            that accept unknown destination packets.  As a small optimization,
+            if no logical ports accept unknown destination packets,
+            <code>ovn-northd</code> omits this multicast group and logical
+            flow.
+          </li>
+        </ul>
+
+        <p>
+          If the logical switch has no logical ports with 'unknown' address
+          set, then the below logical flow is added
+        </p>
+
+        <ul>
+          <li>
+            Priority 50 flow with the match <code>outport == none</code>
+            and drops the packets.
+          </li>
+        </ul>
+      </li>
+
+      <li>
+        One priority-0 fallback flow that outputs the packet to the egress
+        stage with the outport learnt from <code>get_fdb</code> action.
       </li>
     </ul>
 
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index a9bc061..d3c8f93 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -142,25 +142,28 @@ enum ovn_stage {
     PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_L2,    0, "ls_in_port_sec_l2")   \
     PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_IP,    1, "ls_in_port_sec_ip")   \
     PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_ND,    2, "ls_in_port_sec_nd")   \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        3, "ls_in_pre_acl")       \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_LB,         4, "ls_in_pre_lb")        \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   5, "ls_in_pre_stateful")  \
-    PIPELINE_STAGE(SWITCH, IN,  ACL_HINT,       6, "ls_in_acl_hint")      \
-    PIPELINE_STAGE(SWITCH, IN,  ACL,            7, "ls_in_acl")           \
-    PIPELINE_STAGE(SWITCH, IN,  QOS_MARK,       8, "ls_in_qos_mark")      \
-    PIPELINE_STAGE(SWITCH, IN,  QOS_METER,      9, "ls_in_qos_meter")     \
-    PIPELINE_STAGE(SWITCH, IN,  LB,            10, "ls_in_lb")            \
-    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      11, "ls_in_stateful")      \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   12, "ls_in_pre_hairpin")   \
-    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   13, "ls_in_nat_hairpin")       \
-    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       14, "ls_in_hairpin")       \
-    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    15, "ls_in_arp_rsp")       \
-    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  16, "ls_in_dhcp_options")  \
-    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 17, "ls_in_dhcp_response") \
-    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    18, "ls_in_dns_lookup")    \
-    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  19, "ls_in_dns_response")  \
-    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 20, "ls_in_external_port") \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       21, "ls_in_l2_lkup")       \
+    PIPELINE_STAGE(SWITCH, IN,  LOOKUP_FDB ,    3, "ls_in_lookup_fdb")    \
+    PIPELINE_STAGE(SWITCH, IN,  PUT_FDB,        4, "ls_in_put_fdb")       \
+    PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        5, "ls_in_pre_acl")       \
+    PIPELINE_STAGE(SWITCH, IN,  PRE_LB,         6, "ls_in_pre_lb")        \
+    PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   7, "ls_in_pre_stateful")  \
+    PIPELINE_STAGE(SWITCH, IN,  ACL_HINT,       8, "ls_in_acl_hint")      \
+    PIPELINE_STAGE(SWITCH, IN,  ACL,            9, "ls_in_acl")           \
+    PIPELINE_STAGE(SWITCH, IN,  QOS_MARK,      10, "ls_in_qos_mark")      \
+    PIPELINE_STAGE(SWITCH, IN,  QOS_METER,     11, "ls_in_qos_meter")     \
+    PIPELINE_STAGE(SWITCH, IN,  LB,            12, "ls_in_lb")            \
+    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      13, "ls_in_stateful")      \
+    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   14, "ls_in_pre_hairpin")   \
+    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   15, "ls_in_nat_hairpin")   \
+    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       16, "ls_in_hairpin")       \
+    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    17, "ls_in_arp_rsp")       \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  18, "ls_in_dhcp_options")  \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 19, "ls_in_dhcp_response") \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    20, "ls_in_dns_lookup")    \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  21, "ls_in_dns_response")  \
+    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 22, "ls_in_external_port") \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       23, "ls_in_l2_lkup")       \
+    PIPELINE_STAGE(SWITCH, IN,  L2_UNKNOWN,    24, "ls_in_l2_unknown")    \
                                                                           \
     /* Logical switch egress stages. */                                   \
     PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
@@ -227,6 +230,7 @@ enum ovn_stage {
 #define REGBIT_ACL_HINT_ALLOW     "reg0[8]"
 #define REGBIT_ACL_HINT_DROP      "reg0[9]"
 #define REGBIT_ACL_HINT_BLOCK     "reg0[10]"
+#define REGBIT_LKUP_FDB           "reg0[11]"
 
 /* Register definitions for switches and routers. */
 
@@ -4967,6 +4971,42 @@ build_lswitch_input_port_sec_od(
     }
 }
 
+static void
+build_lswitch_learn_fdb_op(
+        struct ovn_port *op, struct hmap *lflows,
+        struct ds *actions, struct ds *match)
+{
+    if (op->nbsp && !op->n_ps_addrs && !strcmp(op->nbsp->type, "") &&
+        op->has_unknown) {
+        ds_clear(match);
+        ds_clear(actions);
+        ds_put_format(match, "inport == %s", op->json_key);
+        ds_put_format(actions, REGBIT_LKUP_FDB
+                      " = lookup_fdb(inport, eth.src); next;");
+        ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 100,
+                                ds_cstr(match), ds_cstr(actions),
+                                &op->nbsp->header_);
+
+        ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
+        ds_clear(actions);
+        ds_put_cstr(actions, "put_fdb(inport, eth.src); next;");
+        ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_PUT_FDB, 100,
+                                ds_cstr(match), ds_cstr(actions),
+                                &op->nbsp->header_);
+    }
+}
+
+static void
+build_lswitch_learn_fdb_od(
+        struct ovn_datapath *od, struct hmap *lflows)
+{
+
+    if (od->nbs) {
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_LOOKUP_FDB, 0, "1", "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_PUT_FDB, 0, "1", "next;");
+    }
+}
+
 /* Egress table 8: Egress port security - IP (priorities 90 and 80)
  * if port security enabled.
  *
@@ -6824,16 +6864,25 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows)
     struct ds actions = DS_EMPTY_INITIALIZER;
     struct ovn_datapath *od;
 
-    /* Ingress table 19: Destination lookup for unknown MACs (priority 0). */
+    /* Ingress table 24: Destination lookup for unknown MACs (priority 0). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
         }
 
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
+                      "outport = get_fdb(eth.dst); next;");
+
         if (od->has_unknown) {
-            ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
-                                 "outport = \""MC_UNKNOWN"\"; output;");
+            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
+                          "outport == \"none\"",
+                          "outport = \""MC_UNKNOWN"\"; output;");
+        } else {
+            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
+                          "outport == \"none\"", "drop;");
         }
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1",
+                      "output;");
     }
 
     ds_destroy(&match);
@@ -11734,6 +11783,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
     build_fwd_group_lflows(od, lsi->lflows);
     build_lswitch_lflows_admission_control(od, lsi->lflows);
     build_lswitch_input_port_sec_od(od, lsi->lflows);
+    build_lswitch_learn_fdb_od(od, lsi->lflows);
     build_lswitch_arp_nd_responder_default(od, lsi->lflows);
     build_lswitch_dns_lookup_and_response(od, lsi->lflows);
     build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows);
@@ -11773,6 +11823,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
     /* Build Logical Switch Flows. */
     build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions,
                                     &lsi->match);
+    build_lswitch_learn_fdb_op(op, lsi->lflows, &lsi->actions,
+                               &lsi->match);
     build_lswitch_arp_nd_responder_skip_local(op, lsi->lflows,
                                               &lsi->match);
     build_lswitch_arp_nd_responder_known_ips(op, lsi->lflows,
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 246e390..b5d3338 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "20.16.0",
-    "cksum": "1219580357 26536",
+    "version": "20.16.1",
+    "cksum": "4243908307 26536",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -103,7 +103,7 @@
                                                        "egress"]]}}},
                 "table_id": {"type": {"key": {"type": "integer",
                                               "minInteger": 0,
-                                              "maxInteger": 23}}},
+                                              "maxInteger": 32}}},
                 "priority": {"type": {"key": {"type": "integer",
                                               "minInteger": 0,
                                               "maxInteger": 65535}}},
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index e4831ae..ce8b654 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1753,10 +1753,10 @@ AT_CAPTURE_FILE([sw1flows])
 
 AT_CHECK(
   [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl
-sw0flows:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw0flows:  table=7 (ls_in_acl          ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
-sw1flows:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw1flows:  table=7 (ls_in_acl          ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
+sw0flows:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw0flows:  table=9 (ls_in_acl          ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
+sw1flows:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw1flows:  table=9 (ls_in_acl          ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
 ])
 
 AS_BOX([2])
@@ -1769,10 +1769,10 @@ ovn-sbctl dump-flows sw1 > sw1flows2
 AT_CAPTURE_FILE([sw1flows2])
 
 AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl
-sw0flows2:  table=5 (ls_out_acl         ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw0flows2:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw1flows2:  table=5 (ls_out_acl         ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw1flows2:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+sw0flows2:  table=5 (ls_out_acl         ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw0flows2:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw1flows2:  table=5 (ls_out_acl         ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw1flows2:  table=5 (ls_out_acl         ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
 ])
 
 AS_BOX([3])
@@ -1787,16 +1787,16 @@ AT_CAPTURE_FILE([sw1flows3])
 AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl
 sw0flows3:  table=5 (ls_out_acl         ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
 sw0flows3:  table=5 (ls_out_acl         ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-sw0flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw0flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw0flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw0flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+sw0flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw0flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw0flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw0flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
 sw1flows3:  table=5 (ls_out_acl         ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
 sw1flows3:  table=5 (ls_out_acl         ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-sw1flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw1flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw1flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-sw1flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+sw1flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw1flows3:  table=5 (ls_out_acl         ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw1flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
+sw1flows3:  table=5 (ls_out_acl         ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
 ])
 
 AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
index c13f5d6..ea155d2 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -8241,18 +8241,18 @@ as hv1
 AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1])
 
 # On hv1, check that there are no flows outputting bcast to tunnel
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0])
 
 # On hv2, check that no flow outputs bcast to tunnel to hv1.
 as hv2
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0])
 
 # Now bind vif2 on hv2.
 AT_CHECK([ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2])
 
 # At this point, the broadcast flow on vif2 should be deleted.
-# because, there is now a localnet vif bound (table=32 programming logic)
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
+# because, there is now a localnet vif bound (table=37 programming logic)
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0])
 
 # Verify that the local net patch port exists on hv2.
 OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1])
@@ -10383,12 +10383,12 @@ AT_CAPTURE_FILE([hv2flows])
 
 AT_CHECK(
   [# Check that redirect mapping is programmed only on hv2
-   grep table=33 hv1flows | grep =0x3,metadata=0x1 | wc -l
-   grep table=33 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
+   grep table=38 hv1flows | grep =0x3,metadata=0x1 | wc -l
+   grep table=38 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
 
    # Check that hv1 sends chassisredirect port traffic to hv2
-   grep table=32 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
-   grep table=32 hv2flows | grep =0x3,metadata=0x1 | wc -l
+   grep table=37 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
+   grep table=37 hv2flows | grep =0x3,metadata=0x1 | wc -l
 
    # Check that arp reply on distributed gateway port is only programmed on hv2
    grep arp hv1flows | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l
@@ -10938,8 +10938,8 @@ as hv3 reset_pcap_file hv3-vif1 hv3/vif1
 as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
 sleep 2
 
-# On hv1, table 32 check that no packet goes via the tunnel port
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 \
+# On hv1, table 37 check that no packet goes via the tunnel port
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=37 \
 | grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0
 ]])
 
@@ -11629,20 +11629,20 @@ echo $hv2_gw1_ofport
 echo $hv2_gw2_ofport
 
 echo "--- hv1 ---"
-as hv1 ovs-ofctl dump-flows br-int table=32
+as hv1 ovs-ofctl dump-flows br-int table=37
 
 echo "--- hv2 ---"
-as hv2 ovs-ofctl dump-flows br-int table=32
+as hv2 ovs-ofctl dump-flows br-int table=37
 
 gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
 gw2_chassis=$(fetch_column Chassis _uuid name=gw2)
 
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
 | wc -l], [0], [1
 ])
 
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
 | wc -l], [0], [1
 ])
@@ -11683,12 +11683,12 @@ wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
 # we make sure that the hypervisors noticed, and inverted the slave ports
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
 | wc -l], [0], [1
 ])
 
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
 | wc -l], [0], [1
 ])
@@ -11841,12 +11841,12 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
 wait_row_count HA_Chassis_Group 1
 wait_row_count HA_Chassis 2
 
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
 | wc -l], [0], [1
 ])
 
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
 | wc -l], [0], [1
 ])
@@ -11898,12 +11898,12 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
 # Increase the priority of gw2
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
 
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
 | wc -l], [0], [1
 ])
 
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
 | wc -l], [0], [1
 ])
@@ -15370,7 +15370,7 @@ wait_for_ports_up ls1-lp_ext1
 # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined
 # to router mac.
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \
-table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
+table=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
 grep -c "actions=drop"], [0], [1
 ])
 
@@ -15968,6 +15968,7 @@ ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
 
 ovn-nbctl lsp-add sw0 sw0-p0 \
     -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2"
+
 ovn-nbctl lsp-add sw0 sw0-p1 \
     -- lsp-set-addresses sw0-p1 "f0:00:00:11:02:03 192.168.1.3 2001::3"
 
@@ -16033,7 +16034,7 @@ policy=$(fetch_column nb:Logical_Router_Policy _uuid priority=2000)
 check ovn-nbctl set logical_router_policy $policy options:pkt_mark=100
 as hv2
 # add a flow in egress pipeline to check pkt marking
-ovs-ofctl --protocols=OpenFlow13 add-flow br-int "table=32,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64 actions=resubmit(,33)"
+ovs-ofctl --protocols=OpenFlow13 add-flow br-int "table=37,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64 actions=resubmit(,38)"
 
 dst_ip=$(ip_to_hex 172 16 2 10)
 fip_ip=$(ip_to_hex 172 16 1 2)
@@ -16045,7 +16046,7 @@ echo $(get_arp_req f00000010204 $fip_ip $gw_router_ip) >> expected
 send_arp_reply 2 1 $gw_router_mac f00000010204 $gw_router_ip $fip_ip
 echo "${gw_router_mac}f0000001020408004500001c00004000fe0121b4${fip_ip}${dst_ip}${data}" >> expected
 
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep pkt_mark=0x64 | grep -q n_packets=1],[0])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=37 | grep pkt_mark=0x64 | grep -q n_packets=1],[0])
 
 OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
 
@@ -19034,7 +19035,7 @@ m4_define([DVR_N_S_PING],
    OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
 
    # Confirm that packets did not go out via tunnel port.
-   AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=33 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
+   AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=38 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
 ]])
 
    # Confirm that packet went out via localnet port
@@ -19164,7 +19165,8 @@ list mac_binding], [0], [lr0-sw0
 50:54:00:00:00:03
 ])
 
-AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
 grep controller | grep -v n_packets=0 | wc -l`])
 
@@ -19181,7 +19183,8 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_p
 
 # The packet should not be sent to ovn-controller. The packet
 # count should be 1 only.
-AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
 grep controller | grep -v n_packets=0 | wc -l`])
 
@@ -19194,7 +19197,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
 
 # The garp packet should be sent to ovn-controller and the mac_binding entry
 # should be updated.
-OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 
 check_row_count MAC_Binding 1
 
@@ -19219,7 +19223,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
 
 # The garp packet should be sent to ovn-controller and the mac_binding entry
 # should be updated.
-OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 
 OVS_WAIT_UNTIL(
     [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:33 \
@@ -19240,7 +19245,8 @@ OVS_WAIT_UNTIL(
 | grep n_packets=1 | wc -l`]
 )
 
-AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 
 # Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023
 eth_src=505400000023
@@ -19257,7 +19263,8 @@ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
 
 # The garp packet should be sent to ovn-controller and the mac_binding entry
 # should be updated.
-OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 
 # Wait for an entry in table=67 for the learnt mac_binding entry.
 
@@ -19273,7 +19280,8 @@ OVS_WAIT_UNTIL(
 | grep n_packets=1 | wc -l`]
 )
 
-AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 
 send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
 OVS_WAIT_UNTIL(
@@ -19281,7 +19289,8 @@ OVS_WAIT_UNTIL(
 | grep n_packets=2 | wc -l`]
 )
 
-AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
+grep table_id=10 | wc -l`])
 
 OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
@@ -21988,22 +21997,22 @@ AT_CHECK([test ! -z $p1_zoneid])
 p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g')
 AT_CHECK([test ! -z $p2_zoneid])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
 reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 1])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
 reg15=0x${p2_dpkey} | grep "load:0x${p2_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
 
 ovs-vsctl set interface hv1-vif1 external_ids:iface-id=foo
 OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xdown])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
 
 p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
@@ -22015,16 +22024,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
 p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
 AT_CHECK([test ! -z $p1_zoneid])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
 
 ovs-vsctl del-port hv1-vif2
 OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
 
-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 0])
 
 p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g')
@@ -22032,7 +22041,7 @@ AT_CHECK([test -z $p2_zoneid])
 
 ovn-nbctl lsp-del sw0-p1
 
-OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
 reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
 
 p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
@@ -22616,7 +22625,8 @@ ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router
 
 # Create logical port p1 in s1
 ovn-nbctl lsp-add s1 p1 \
--- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
+-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" \
+-- lsp-set-port-security p1 "f0:00:00:00:01:02 10.0.1.2"
 
 # Create two hypervisor and create OVS ports corresponding to logical ports.
 net_add n1
@@ -24221,3 +24231,426 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
 
 OVN_CLEANUP([hv1])
 AT_CLEANUP
+
+AT_SETUP([ovn -- OVN FDB (MAC learning) - 2 HVs, 2 LS, 1 LR ])
+ovn_start
+
+# Create the first logical switch with one port
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" unknown
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+# Port security is set for sw0-p2
+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+
+# sw0-p1 and sw0-p3 have unknown address and no port security.
+# FDB should be enabled for these lports.
+check ovn-nbctl lsp-add sw0 sw0-p3
+check ovn-nbctl lsp-set-addresses sw0-p3 unknown
+
+# Create the second logical switch with one port
+check ovn-nbctl ls-add sw1
+check ovn-nbctl lsp-add sw1 sw1-p1
+check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 11.0.0.3" unknown
+
+check ovn-nbctl lsp-add sw1 sw1-p2
+check ovn-nbctl lsp-set-addresses sw1-p2 "40:54:00:00:00:04 11.0.0.4"
+check ovn-nbctl lsp-set-port-security sw1-p2 "40:54:00:00:00:04 11.0.0.4"
+
+# Create a logical router and attach both logical switches
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 router
+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 11.0.0.1/24
+check ovn-nbctl lsp-add sw1 sw1-lr0
+check ovn-nbctl lsp-set-type sw1-lr0 router
+check ovn-nbctl lsp-set-addresses sw1-lr0 router
+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+ovn-nbctl --wait=hv sync
+
+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=sw0-p1 \
+    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=sw1-p2 \
+    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=sw0-p3 \
+    options:tx_pcap=hv1/vif3-tx.pcap \
+    options:rxq_pcap=hv1/vif3-rx.pcap \
+    ofport-request=3
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
+    set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+    options:tx_pcap=hv2/vif1-tx.pcap \
+    options:rxq_pcap=hv2/vif1-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-int hv2-vif2 -- \
+    set interface hv2-vif2 external-ids:iface-id=sw1-p1 \
+    options:tx_pcap=hv2/vif2-tx.pcap \
+    options:rxq_pcap=hv2/vif2-rx.pcap \
+    ofport-request=2
+
+OVN_POPULATE_ARP
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+
+send_icmp_packet() {
+    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8
+    shift 8
+
+    local ip_ttl=ff
+    local ip_len=001c
+    local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data}
+    echo $packet > expected
+    as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
+}
+
+reset_pcap_file() {
+    local iface=$1
+    local pcap_file=$2
+    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+    rm -f ${pcap_file}*.pcap
+    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
+AS_BOX([Wait for all ports to be up])
+wait_for_ports_up
+
+# Check that there is put_fdb() flow added by ovn-northd for sw0-p1
+ovn-sbctl dump-flows sw0 > sw0flows
+AT_CAPTURE_FILE([sw0flows])
+
+AT_CHECK([grep "ls_in_lookup_fdb" sw0flows | sort], [0], [dnl
+  table=3 (ls_in_lookup_fdb   ), priority=0    , dnl
+match=(1), action=(next;)
+  table=3 (ls_in_lookup_fdb   ), priority=100  , dnl
+match=(inport == "sw0-p1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+  table=3 (ls_in_lookup_fdb   ), priority=100  , dnl
+match=(inport == "sw0-p3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+])
+
+AT_CHECK([grep "ls_in_put_fdb" sw0flows | sort], [0], [dnl
+  table=4 (ls_in_put_fdb      ), priority=0    , dnl
+match=(1), action=(next;)
+  table=4 (ls_in_put_fdb      ), priority=100  , dnl
+match=(inport == "sw0-p1" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
+  table=4 (ls_in_put_fdb      ), priority=100  , dnl
+match=(inport == "sw0-p3" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
+])
+
+# Send a packet from sw0-p1 with a different mac not present
+# in it's addresses.
+AS_BOX([Send a pkt from sw0-p1 with a different mac address])
+
+# Use the src mac 50:54:00:00:00:13 instead of 50:54:00:00:00:03
+src_mac=505400000013
+src_ip=$(ip_to_hex 10 0 0 13)
+
+# send the packet to sw0-p2
+dst_mac=505400000004
+dst_ip=$(ip_to_hex 10 0 0 4)
+
+data=0800bee4391a0001
+send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+# There should be one row in fdb
+AS_BOX([Check that the FDB entry is created])
+wait_row_count FDB 1
+
+sw0_dpkey=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0)
+sw0p1_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p1)
+sw0p3_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p3)
+
+check_column '50:54:00:00:00:13' fdb mac
+check_column $sw0_dpkey fdb dp_key
+check_column $sw0p1_dpkey fdb port_key
+
+# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2.
+AS_BOX([Check that ovn-controller programs the flows for FDB])
+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
+
+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
+AT_CAPTURE_FILE([hv2_offlows_table71.txt])
+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
+])
+
+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
+])
+
+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
+
+AT_CAPTURE_FILE([hv1_offlows_table72.txt])
+AT_CAPTURE_FILE([hv2_offlows_table72.txt])
+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+])
+
+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+])
+
+# Use the src mac 50:54:00:00:00:14 instead of 50:54:00:00:00:03
+src_mac=505400000014
+src_ip=$(ip_to_hex 10 0 0 14)
+
+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+
+send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+# There should be two rows in fdb
+wait_row_count FDB 2
+
+check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac
+check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key
+check_column "$sw0p1_dpkey $sw0p1_dpkey" fdb port_key
+
+# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2.
+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
+
+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
+AT_CAPTURE_FILE([hv2_offlows_table71.txt])
+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG15[[]]
+])
+
+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG15[[]]
+])
+
+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
+
+AT_CAPTURE_FILE([hv1_offlows_table72.txt])
+AT_CAPTURE_FILE([hv2_offlows_table72.txt])
+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
+])
+
+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
+])
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+
+# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:13
+src_mac=505400000004
+src_ip=$(ip_to_hex 10 0 0 4)
+
+dst_mac=505400000013
+dst_ip=$(ip_to_hex 10 0 0 13)
+
+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+dst_mac=505400000014
+dst_ip=$(ip_to_hex 10 0 0 14)
+
+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
+
+# Send a packet from sw0-p2 to an unknown mac. Should be received
+# by both sw0-p1 and sw0-p3 (as unknown is set).
+AS_BOX([Send pkt from sw0-p2 to an unknown mac])
+
+src_mac=505400000004
+src_ip=$(ip_to_hex 10 0 0 4)
+
+dst_mac=505400000023
+dst_ip=$(ip_to_hex 10 0 0 23)
+
+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
+
+AS_BOX([Flip the mac - 50:54:00:00:00:13 from sw0-p1 to sw0-p3])
+
+# Use the src mac 50:54:00:00:00:13
+src_mac=505400000013
+src_ip=$(ip_to_hex 10 0 0 23)
+
+# send the packet to sw0-p2
+dst_mac=505400000004
+dst_ip=$(ip_to_hex 10 0 0 4)
+
+data=0800bee4391a0001
+
+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
+
+# Send the pkt from sw0-p3 to sw0-p2.
+send_icmp_packet 3 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+# fdb row count should be still 2. But the mac 50:54:00:00:00:13
+# should be learnt on sw0-p3.
+
+wait_row_count FDB 2
+
+check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac
+check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key
+check_column "$sw0p1_dpkey $sw0p3_dpkey" fdb port_key
+
+check_column "$sw0p3_dpkey" fdb port_key mac="50\:54\:00\:00\:00\:13"
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
+
+# Send the packet from sw0-p2 to sw0-p3 with the dst mac 50:54:00:00:00:13
+src_mac=505400000004
+src_ip=$(ip_to_hex 10 0 0 4)
+
+dst_mac=505400000013
+dst_ip=$(ip_to_hex 10 0 0 13)
+
+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
+
+# sw0-p1 should not receive the packet.
+: > expected
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+AS_BOX([Test routing])
+
+# Test the routing.
+# Send the packet from sw1-p2 (hv1) to sw0-p1 (hv1) with dst ip 10.0.0.14
+# The packet should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14
+# Before sending add mac_binding entry for 10.0.0.14
+
+lr0_dp_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
+
+ovn-sbctl create mac_binding ip=10.0.0.14 logical_port=lr0-sw0 \
+mac="50\:54\:00\:00\:00\:14" datapath=$lr0_dp_uuid
+
+# Wait till the mac_binding flows appear in hv1
+OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=66 \
+| grep -c  reg0=0xa00000e)])
+
+src_mac=405400000004
+src_ip=$(ip_to_hex 11 0 0 4)
+
+dst_mac=00000000ff02 # lr0-sw1 mac
+dst_ip=$(ip_to_hex 10 0 0 14)
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
+
+send_icmp_packet 2 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+
+exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data}
+echo $exp_packet > expected
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+# sw0-p3 should not receive the packet.
+: > expected
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
+
+# Now the send the packet from sw1-p1 (hv2) to sw0-p1 (hv1) with dst ip 10.0.0.14
+# The acket should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14
+
+src_mac=405400000003
+src_ip=$(ip_to_hex 11 0 0 3)
+
+dst_mac=00000000ff02 # lr0-sw1 mac
+dst_ip=$(ip_to_hex 10 0 0 14)
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+send_icmp_packet 2 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+
+exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data}
+echo $exp_packet > expected
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+AS_BOX([Clear the FDB rows])
+
+# Clear the fdb rows.
+check ovn-sbctl --all destroy fdb
+ovn-sbctl list fdb
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
+
+# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:14
+# It should be delivered to both sw0-p1 and sw0-p3 since we have cleared the
+# FDB table.
+src_mac=505400000004
+src_ip=$(ip_to_hex 10 0 0 4)
+
+dst_mac=505400000014
+dst_ip=$(ip_to_hex 10 0 0 13)
+
+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
+
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
+
+# Make sure that OVS tables 71 and 72 are empty.
+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
+
+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
+AT_CAPTURE_FILE([hv2_offlows_table71.txt])
+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST], [1], [dnl
+])
+
+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST], [1], [dnl
+])
+
+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
+
+AT_CAPTURE_FILE([hv1_offlows_table72.txt])
+AT_CAPTURE_FILE([hv2_offlows_table72.txt])
+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST], [1], [dnl
+])
+
+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl
+])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 8376309..fb88bc0 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -945,7 +945,7 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf,
             .pipeline = (!strcmp(sblf->pipeline, "ingress")
                          ? OVNACT_P_INGRESS
                          : OVNACT_P_EGRESS),
-            .n_tables = 24,
+            .n_tables = LOG_PIPELINE_LEN,
             .cur_ltable = sblf->table_id,
         };
         uint64_t stub[1024 / 8];
@@ -1184,6 +1184,11 @@ ovntrace_lookup_port(const void *dp_, const char *port_name,
         return true;
     }
 
+    if (!strcmp(port_name, "none")) {
+        *portp = 0;
+        return true;
+    }
+
     const struct ovntrace_port *port = ovntrace_port_lookup_by_name(port_name);
     if (port) {
         if (port->dp == dp) {
-- 
1.8.3.1