diff --git a/controller/lflow.c b/controller/lflow.c
index 2b632d2bf..71d396289 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -2065,11 +2065,16 @@ lflow_handle_changed_static_mac_bindings(
 static void
 consider_fdb_flows(const struct sbrec_fdb *fdb,
                    const struct hmap *local_datapaths,
-                   struct ovn_desired_flow_table *flow_table)
+                   struct ovn_desired_flow_table *flow_table,
+                   struct ovsdb_idl_index *sbrec_port_binding_by_key)
 {
-    if (!get_local_datapath(local_datapaths, fdb->dp_key)) {
+    struct local_datapath *ld = get_local_datapath(local_datapaths,
+                                                   fdb->dp_key);
+    if (!ld) {
         return;
     }
+    const struct sbrec_port_binding *pb = lport_lookup_by_key_with_dp(
+        sbrec_port_binding_by_key, ld->datapath, fdb->port_key);
 
     struct eth_addr mac;
     if (!eth_addr_from_string(fdb->mac, &mac)) {
@@ -2091,6 +2096,7 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
     ofpbuf_clear(&ofpacts);
 
     uint8_t value = 1;
+    uint8_t is_vif =  pb ? !strcmp(pb->type, "") : 0;
     put_load(&value, sizeof value, MFF_LOG_FLAGS,
              MLF_LOOKUP_FDB_BIT, 1, &ofpacts);
 
@@ -2101,6 +2107,18 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
     ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
                     fdb->header_.uuid.parts[0], &lookup_match, &ofpacts,
                     &fdb->header_.uuid);
+
+    if (is_vif) {
+        struct match lookup_match_vif = MATCH_CATCHALL_INITIALIZER;
+        match_set_metadata(&lookup_match_vif, htonll(fdb->dp_key));
+        match_set_dl_src(&lookup_match_vif, mac);
+        match_set_reg_masked(&lookup_match_vif, MFF_LOG_FLAGS - MFF_REG0,
+                             MLF_LOCALNET, MLF_LOCALNET);
+
+        ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
+                        fdb->header_.uuid.parts[0], &lookup_match_vif,
+                        &ofpacts, &fdb->header_.uuid);
+    }
     ofpbuf_uninit(&ofpacts);
 }
 
@@ -2109,11 +2127,13 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
 static void
 add_fdb_flows(const struct sbrec_fdb_table *fdb_table,
               const struct hmap *local_datapaths,
-              struct ovn_desired_flow_table *flow_table)
+              struct ovn_desired_flow_table *flow_table,
+              struct ovsdb_idl_index *sbrec_port_binding_by_key)
 {
     const struct sbrec_fdb *fdb;
     SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) {
-        consider_fdb_flows(fdb, local_datapaths, flow_table);
+        consider_fdb_flows(fdb, local_datapaths, flow_table,
+                           sbrec_port_binding_by_key);
     }
 }
 
@@ -2136,7 +2156,8 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
                          l_ctx_in->lb_hairpin_use_ct_mark,
                          l_ctx_out->flow_table);
     add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths,
-                  l_ctx_out->flow_table);
+                  l_ctx_out->flow_table,
+                  l_ctx_in->sbrec_port_binding_by_key);
     add_port_sec_flows(l_ctx_in->binding_lports, l_ctx_in->chassis,
                        l_ctx_out->flow_table);
 }
@@ -2226,7 +2247,8 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp,
     SBREC_FDB_FOR_EACH_EQUAL (fdb_row, fdb_index_row,
                               l_ctx_in->sbrec_fdb_by_dp_key) {
         consider_fdb_flows(fdb_row, l_ctx_in->local_datapaths,
-                           l_ctx_out->flow_table);
+                           l_ctx_out->flow_table,
+                           l_ctx_in->sbrec_port_binding_by_key);
     }
     sbrec_fdb_index_destroy_row(fdb_index_row);
 
@@ -2419,7 +2441,8 @@ lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in,
         VLOG_DBG("Add fdb flows for fdb "UUID_FMT,
                  UUID_ARGS(&fdb->header_.uuid));
         consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
-                           l_ctx_out->flow_table);
+                           l_ctx_out->flow_table,
+                           l_ctx_in->sbrec_port_binding_by_key);
     }
 
     return true;
diff --git a/controller/lflow.h b/controller/lflow.h
index 5da4385e4..6ff499a94 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -100,6 +100,7 @@ struct lflow_ctx_in {
     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath;
     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_dp_group;
     struct ovsdb_idl_index *sbrec_port_binding_by_name;
+    struct ovsdb_idl_index *sbrec_port_binding_by_key;
     struct ovsdb_idl_index *sbrec_fdb_by_dp_key;
     struct ovsdb_idl_index *sbrec_mac_binding_by_datapath;
     struct ovsdb_idl_index *sbrec_static_mac_binding_by_datapath;
diff --git a/controller/lport.c b/controller/lport.c
index add7e91aa..b3721024b 100644
--- a/controller/lport.c
+++ b/controller/lport.c
@@ -44,13 +44,10 @@ lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,
 }
 
 const struct sbrec_port_binding *
-lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-                    uint64_t dp_key, uint64_t port_key)
+lport_lookup_by_key_with_dp(struct ovsdb_idl_index *sbrec_port_binding_by_key,
+                            const struct sbrec_datapath_binding *db,
+                            uint64_t port_key)
 {
-    /* Lookup datapath corresponding to dp_key. */
-    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
-        sbrec_datapath_binding_by_key, dp_key);
     if (!db) {
         return NULL;
     }
@@ -69,6 +66,19 @@ lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
     return retval;
 }
 
+const struct sbrec_port_binding *
+lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
+                    uint64_t dp_key, uint64_t port_key)
+{
+    /* Lookup datapath corresponding to dp_key. */
+    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
+        sbrec_datapath_binding_by_key, dp_key);
+
+    return lport_lookup_by_key_with_dp(sbrec_port_binding_by_key, db,
+                                       port_key);
+}
+
 bool
 lport_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                           const struct sbrec_chassis *chassis,
diff --git a/controller/lport.h b/controller/lport.h
index 644c67255..2f72aef5e 100644
--- a/controller/lport.h
+++ b/controller/lport.h
@@ -43,6 +43,10 @@ const struct sbrec_port_binding *lport_lookup_by_key(
     struct ovsdb_idl_index *sbrec_port_binding_by_key,
     uint64_t dp_key, uint64_t port_key);
 
+const struct sbrec_port_binding *lport_lookup_by_key_with_dp(
+    struct ovsdb_idl_index *sbrec_port_binding_by_key,
+    const struct sbrec_datapath_binding *dp, uint64_t port_key);
+
 enum can_bind {
     CANNOT_BIND = 0,
     CAN_BIND_AS_MAIN,
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index a5848869a..1bd456560 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -3789,6 +3789,11 @@ init_lflow_ctx(struct engine_node *node,
                 engine_get_input("SB_port_binding", node),
                 "name");
 
+    struct ovsdb_idl_index *sbrec_port_binding_by_key =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_port_binding", node),
+                "key");
+
     struct ovsdb_idl_index *sbrec_logical_flow_by_dp =
         engine_ovsdb_node_get_index(
                 engine_get_input("SB_logical_flow", node),
@@ -3888,6 +3893,7 @@ init_lflow_ctx(struct engine_node *node,
     l_ctx_in->sbrec_logical_flow_by_logical_dp_group =
         sbrec_logical_flow_by_dp_group;
     l_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
+    l_ctx_in->sbrec_port_binding_by_key = sbrec_port_binding_by_key;
     l_ctx_in->sbrec_fdb_by_dp_key = sbrec_fdb_by_dp_key;
     l_ctx_in->sbrec_mac_binding_by_datapath = sbrec_mac_binding_by_datapath;
     l_ctx_in->sbrec_static_mac_binding_by_datapath =
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index d88e951a6..bf7f00296 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -377,9 +377,13 @@ static const struct sbrec_fdb *fdb_lookup(
     uint32_t dp_key, const char *mac);
 static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+            struct ovsdb_idl_index *sbrec_port_binding_by_key,
+            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                         const struct fdb_entry *fdb_e)
                         OVS_REQUIRES(pinctrl_mutex);
 static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+            struct ovsdb_idl_index *sbrec_port_binding_by_key,
+            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
                         OVS_REQUIRES(pinctrl_mutex);
 static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn);
@@ -3586,7 +3590,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                       chassis);
     bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
                     chassis, active_tunnels);
-    run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
+    run_put_fdbs(ovnsb_idl_txn, sbrec_port_binding_by_key,
+                 sbrec_datapath_binding_by_key, sbrec_fdb_by_dp_key_mac);
     run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
                         sbrec_port_binding_by_key, chassis);
     ovs_mutex_unlock(&pinctrl_mutex);
@@ -8256,6 +8261,8 @@ fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key,
 static void
 run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+            struct ovsdb_idl_index *sbrec_port_binding_by_key,
+            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
             const struct fdb_entry *fdb_e)
 {
     /* Convert ethernet argument to string form for database. */
@@ -8264,14 +8271,28 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
              ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac));
 
     /* Update or add an FDB entry. */
+    const struct sbrec_port_binding *sb_entry_pb = NULL;
+    const struct sbrec_port_binding *new_entry_pb = NULL;
     const struct sbrec_fdb *sb_fdb =
         fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string);
     if (!sb_fdb) {
         sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn);
         sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key);
         sbrec_fdb_set_mac(sb_fdb, mac_string);
+    } else {
+        /* check whether sb_fdb->port_key is vif or localnet type */
+        sb_entry_pb = lport_lookup_by_key(
+            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+            sb_fdb->dp_key, sb_fdb->port_key);
+        new_entry_pb = lport_lookup_by_key(
+            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+            fdb_e->dp_key, fdb_e->port_key);
+    }
+    /* Do not have localnet overwrite a previous vif entry */
+    if (!sb_entry_pb || !new_entry_pb || strcmp(sb_entry_pb->type, "") ||
+        strcmp(new_entry_pb->type, "localnet")) {
+        sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
     }
-    sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
 
     /* For backward compatibility check if timestamp column is available
      * in SB DB. */
@@ -8282,6 +8303,8 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
 
 static void
 run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+             struct ovsdb_idl_index *sbrec_port_binding_by_key,
+             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
              struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
              OVS_REQUIRES(pinctrl_mutex)
 {
@@ -8291,7 +8314,9 @@ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
 
     const struct fdb_entry *fdb_e;
     HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) {
-        run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e);
+        run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac,
+                    sbrec_port_binding_by_key,
+                    sbrec_datapath_binding_by_key, fdb_e);
     }
     ovn_fdbs_flush(&put_fdbs);
 }
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index a7b64ef67..272277ec4 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -77,6 +77,7 @@ enum mff_log_flags_bits {
     MLF_CHECK_PORT_SEC_BIT = 12,
     MLF_LOOKUP_COMMIT_ECMP_NH_BIT = 13,
     MLF_USE_LB_AFF_SESSION_BIT = 14,
+    MLF_LOCALNET_BIT = 15,
 };
 
 /* MFF_LOG_FLAGS_REG flag assignments */
@@ -124,6 +125,10 @@ enum mff_log_flags {
     MLF_LOOKUP_COMMIT_ECMP_NH = (1 << MLF_LOOKUP_COMMIT_ECMP_NH_BIT),
 
     MLF_USE_LB_AFF_SESSION = (1 << MLF_USE_LB_AFF_SESSION_BIT),
+
+    /* Indicate that the port is localnet. */
+    MLF_LOCALNET = (1 << MLF_LOCALNET_BIT),
+
 };
 
 /* OVN logical fields
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index fd509d9ee..7a1e66d0a 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -129,6 +129,10 @@ ovn_init_symtab(struct shash *symtab)
              MLF_USE_SNAT_ZONE);
     expr_symtab_add_subfield(symtab, "flags.use_snat_zone", NULL,
                              flags_str);
+    snprintf(flags_str, sizeof flags_str, "flags[%d]",
+             MLF_LOCALNET_BIT);
+    expr_symtab_add_subfield(symtab, "flags.localnet", NULL,
+                             flags_str);
 
     /* Connection tracking state. */
     expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
diff --git a/northd/northd.c b/northd/northd.c
index f8b046d83..4138e41bb 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -7045,6 +7045,9 @@ build_lswitch_learn_fdb_op(
         ds_clear(match);
         ds_clear(actions);
         ds_put_format(match, "inport == %s", op->json_key);
+        if (lsp_is_localnet(op->nbsp)) {
+            ds_put_cstr(actions, "flags.localnet = 1; ");
+        }
         ds_put_format(actions, REGBIT_LKUP_FDB
                       " = lookup_fdb(inport, eth.src); next;");
         ovn_lflow_add_with_lport_and_hint(lflows, op->od,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index df8ed6750..97718821f 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -399,14 +399,16 @@
     <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.
+      or not. MAC is learnt for logical switch VIF ports whose
+      port security is disabled and 'unknown' address setn as well as
+      for localnet ports with option localnet_learn_fdb. A localnet
+      port entry does not overwrite a VIF port entry.
     </p>
 
     <ul>
       <li>
         <p>
-          For each such logical port <var>p</var> whose port security
+          For each such VIF logical port <var>p</var> whose port security
           is disabled and 'unknown' address set following flow
           is added.
         </p>
@@ -420,6 +422,22 @@
         </ul>
       </li>
 
+      <li>
+        <p>
+          For each such localnet logical port <var>p</var> following flow
+          is added.
+        </p>
+
+        <ul>
+          <li>
+            Priority 100 flow with the match
+            <code>inport == <var>p</var></code> and action
+            <code>flags.localnet = 1;</code>
+            <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.
@@ -429,17 +447,20 @@
     <h3>Ingress Table 3: 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
+      This table learns the MAC addresses seen on the VIF logical ports
+      whose port security is disabled and 'unknown' address set as well
+      as on localnet ports with localnet_learn_fdb option set
       if the <code>lookup_fdb</code> action returned false in the
-      previous table.
+      previous table. For localnet ports (with flags.localnet = 1),
+      lookup_fdb returns true if (port, mac) is found or if a mac
+      is found for a port of type vif.
     </p>
 
     <ul>
       <li>
         <p>
-          For each such logical port <var>p</var> whose port security
-          is disabled and 'unknown' address set following flow
+          For each such VIF logical port <var>p</var> whose port security is
+          disabled and 'unknown' address set and localnet port following flow
           is added.
         </p>
 
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 8d0bed94d..519b9da8f 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -1721,9 +1721,12 @@
 
           <p>
             Looks up <var>A</var> in fdb table. If an entry is found
-            and the the logical port key is <var>P</var>, <code>P</code>,
+            and the logical port key is <var>P</var>, <code>P</code>,
             stores <code>1</code> in the 1-bit subfield
-            <var>R</var>, else 0.
+            <var>R</var>, else 0. If <code>flags.localnet</code> is set
+            then <code>1</code> is stored if an entry is found and the
+            logical port key is <var>P</var> or if an entry is found and
+            the entry port type is VIF.
           </p>
 
           <p>
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 196fe01fb..a1242258f 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -8635,7 +8635,7 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort
 AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=true])
 AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort | sed 's/table=./table=?/'], [0], [dnl
   table=? (ls_in_lookup_fdb   ), priority=0    , match=(1), action=(next;)
-  table=? (ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln_port"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+  table=? (ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln_port"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
   table=? (ls_in_put_fdb      ), priority=0    , match=(1), action=(next;)
   table=? (ls_in_put_fdb      ), priority=100  , match=(inport == "ln_port" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
 ])
diff --git a/tests/ovn.at b/tests/ovn.at
index b8c61f87f..8ac82383d 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -31486,10 +31486,12 @@ 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_UNQUOTED([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 ])
 
 AT_CHECK_UNQUOTED([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 ])
 
@@ -31514,6 +31516,7 @@ priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 actions=load:0x$port_ke
 ])
 
 AT_CHECK_UNQUOTED([cat hv3_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 ])
 
@@ -31564,16 +31567,22 @@ AT_CAPTURE_FILE([hv1_offlows_table72.txt])
 AT_CAPTURE_FILE([hv2_offlows_table72.txt])
 AT_CAPTURE_FILE([hv3_offlows_table72.txt])
 AT_CHECK_UNQUOTED([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
 ])
 
 AT_CHECK_UNQUOTED([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
 ])
 
 AT_CHECK_UNQUOTED([cat hv3_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
+priority=100,reg10=0x8000/0x8000,metadata=0x$dp_key,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
 priority=100,reg14=0x$port_key,metadata=0x$dp_key,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
 ])
@@ -37014,3 +37023,303 @@ OVN_CHECK_PACKETS([hv1/vif150-tx.pcap], [expected])
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([pod to pod with localnet_learn_fdb])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+
+# 6 VIFs, 3 per HV: vif11, vif12, vif13 on hv1.
+# vif11 will exchange packets with vif21, vif12 w/ vif22 and so on.
+#
+ovn_start
+net_add n1
+
+check ovn-nbctl ls-add ls0
+
+check ovn-nbctl lsp-add ls0 ln_port -- \
+      lsp-set-addresses ln_port unknown -- \
+      lsp-set-type ln_port localnet -- \
+      lsp-set-options ln_port network_name=physnet1
+
+for i in $(seq 1 3); do
+    check ovn-nbctl lsp-add ls0 vif1$i -- \
+          lsp-set-addresses vif1$i unknown
+    check ovn-nbctl lsp-add ls0 vif2$i -- \
+          lsp-set-addresses vif2$i unknown
+done
+
+n_pkt=(0 0 0 0 0 0)
+
+for hv in 1 2; do
+    sim_add hv${hv}
+    as hv${hv}
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.0.${hv}
+
+    for i in $(seq 1 3); do
+        ovs-vsctl -- add-port br-int vif${hv}${i} -- \
+            set interface vif${hv}${i} external-ids:iface-id=vif${hv}${i} \
+            options:tx_pcap=hv${hv}/vif${hv}${i}-tx.pcap \
+            options:rxq_pcap=hv${hv}/vif${hv}${i}-rx.pcap \
+            ofport-request=$i
+    done
+    ovs-vsctl -- add-port br-phys ext0 -- \
+        set interface ext0 \
+        options:tx_pcap=hv${hv}/ext0-tx.pcap \
+        options:rxq_pcap=hv${hv}/ext0-rx.pcap \
+        ofport-request=4
+    ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+done
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+ln_port_key=$(fetch_column port_binding tunnel_key logical_port=ln_port)
+vif11_key=$(fetch_column port_binding tunnel_key logical_port=vif11)
+vif12_key=$(fetch_column port_binding tunnel_key logical_port=vif12)
+vif13_key=$(fetch_column port_binding tunnel_key logical_port=vif13)
+vif21_key=$(fetch_column port_binding tunnel_key logical_port=vif21)
+vif22_key=$(fetch_column port_binding tunnel_key logical_port=vif22)
+vif23_key=$(fetch_column port_binding tunnel_key logical_port=vif23)
+
+ensure_controller_run() {
+# We want to make sure controller could run at least one full loop.
+# We can't use wait=hv as sb might be sleeping.
+# Use 2 ovn-appctl to guarentee that ovn-controller run the full loop, and not just the unixctl handling
+  hv=$1
+  OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+  OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+}
+
+wait_for_packets() {
+    local hv=$1
+    local vif=$2
+    counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1))
+    n_pkt[[$counter]]=$((${n_pkt[[$counter]]} + 1))
+    echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter"
+    OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}])
+}
+
+check_no_packets() {
+    local hv=$1
+    local vif=$2
+    counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1))
+    echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter"
+    OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}])
+}
+
+send_packet() {
+    hv=$1
+    a_src=$2
+    a_dst=$3
+    dev=vif$2
+    AS_BOX([$(date +%H:%M:%S.%03N) sending packet from $dev in $hv $a_src to $a_dst])
+    packet=$(fmt_pkt "
+            Ether(dst='00:00:00:00:10:${a_dst}', src='00:00:00:00:10:${a_src}') /
+            IP(src='192.168.10.${a_src}', dst='192.168.10.${a_dst}') /
+            UDP(sport=1234, dport=1235)
+           ")
+    as $hv ovs-appctl netdev-dummy/receive $dev $packet
+}
+
+check_flow_count() {
+    hv=$1
+    count=$2
+    echo "Checking flow count for $hv is $count"
+    OVS_WAIT_UNTIL([test $count = $(as $hv ovs-ofctl dump-flows br-int table=72 | grep -v "NXST_FLOW reply" | wc -l)])
+}
+
+# Sending packet in both direction. Should create FDB entries for vifs
+# No localnet_learn_fdb yet
+AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21])
+send_packet hv1 11 21
+for i in 1 2 3; do
+    wait_for_packets hv2 vif2${i}
+done
+for i in 2 3; do
+    wait_for_packets hv1 vif1${i}
+done
+# vif11 should now own the mac
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+
+send_packet hv2 21 11
+wait_for_packets hv1 vif11
+for i in 2 3; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+
+check_flow_count hv1 4
+check_flow_count hv2 4
+
+# We now enable localnet_learn_fdb
+# We check how it behaves with existing vif entries in fdb
+check ovn-nbctl --wait=hv set logical_switch_port ln_port options:localnet_learn_fdb=true
+
+AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21 after learn_fdb])
+send_packet hv1 11 21
+
+wait_for_packets hv2 vif21
+for i in 2 3; do
+    check_no_packets hv2 vif2${i}
+done
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+
+send_packet hv2 21 11
+wait_for_packets hv1 vif11
+for i in 2 3; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+
+# In both controllers,
+# - 1st packet (in both dir) should have reached controller for the vif, not for localnet (as learn_fdb disabled for localnet)
+# - 2nd packet should not cause any upcall to controller as vif already owns the mac.
+AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+check_flow_count hv1 4
+check_flow_count hv2 4
+
+# Send a few more packets
+send_packet hv1 11 21
+send_packet hv2 21 11
+
+for hv in 1 2; do
+    wait_for_packets hv${hv} vif${hv}1
+    for i in 2 3; do
+        echo CHECK
+        check_no_packets hv${hv} vif${hv}${i}
+    done
+done
+
+# The last packets should have gone through the fast path
+AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+# Check that there are no bad surprises
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+
+# We will now create fdb entries AFTER enabing localnet_learn_fdb
+# We make ovn_controller (hv1 or hv2) to sleep to control who writes first to fdb
+# as otherwise no guarentee.
+AS_BOX([$(date +%H:%M:%S.%03N) vif12 <=> vif22])
+# We make sure that the fdb update by the vif is done after the localnet update
+sleep_controller hv1
+send_packet hv1 12 22
+for i in 1 3; do
+    wait_for_packets hv1 vif1${i}
+done
+for i in 1 2 3; do
+    wait_for_packets hv2 vif2${i}
+done
+
+# ln_port should own the mac as vif not written yet
+wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:12"'
+
+wake_up_controller hv1
+# vif1 should now own the mac
+wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"'
+
+sleep_controller hv2
+send_packet hv2 22 12
+wait_for_packets hv1 vif12
+for i in 1 3; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:22"'
+
+wake_up_controller hv2
+wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"'
+
+check_flow_count hv1 8
+check_flow_count hv2 8
+
+AS_BOX([$(date +%H:%M:%S.%03N) vif13 <=> vif23 ])
+# Now we do the other way around: we make sure that the localnet update is done after the vif update.
+# So, when packet is sent from vif1 to vif2, vif1 will be learnt (by hv1) and written in sb
+# Then, when we wake up ovn-controller on hv2, it will learn on localnet.
+# This used to cause localnet entry to overwrite vif entry in sb
+sleep_controller hv2
+send_packet hv1 13 23
+for i in 1 2; do
+    wait_for_packets hv1 vif1${i}
+done
+for i in 1 2 3; do
+    wait_for_packets hv2 vif2${i}
+done
+
+wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
+
+
+# At this point, FDB contains vif1 entry for mac 00:00:00:00:10:13.
+# However, as hv2 controller is sleeping, the flows in hv2 do not
+# contain the flows related to that fdb entry.
+# Hence, the packet which went through still failed the lookup.
+wake_up_controller hv2
+
+# FDB shoud not have changed. Just make sure controller has run and check fdb
+ensure_controller_run hv2
+wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
+
+sleep_controller hv1
+send_packet hv2 23 13
+wait_for_packets hv1 vif13
+for i in 1 2; do
+    check_no_packets hv2 vif2${i}
+done
+for i in 1 2; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
+
+wake_up_controller hv1
+ensure_controller_run hv1
+wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
+
+# In both controllers
+# - vif11 <=> vif21: 1 PACKET_IN
+# - vif12 <=> vif22: 2 PACKET_IN
+# - vif13 <=> vif23: 2 PACKET_IN
+# controller + .
+AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+# Send a few more packets
+send_packet hv1 13 23
+send_packet hv2 23 13
+wait_for_packets hv2 vif23
+wait_for_packets hv1 vif13
+for i in 1 2; do
+    check_no_packets hv1 vif1${i}
+    check_no_packets hv2 vif2${i}
+done
+send_packet hv1 13 23
+send_packet hv2 23 13
+wait_for_packets hv2 vif23
+wait_for_packets hv1 vif13
+for i in 1 2; do
+    check_no_packets hv1 vif1${i}
+    check_no_packets hv2 vif2${i}
+done
+
+# The last packets should have gone through the fast path
+AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+# Check that there are no bad surprises
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"'
+wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"'
+wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
+
+check_flow_count hv1 12
+check_flow_count hv2 12
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
