Add extra action called "get_remote_fdb()", which is almost the same
as "get_fdb()" but goes into the new OFTABLE_GET_REMOTE_FDB table.
Use the new action in the logical flows to determine if the traffic
should go towards the remote FDB. The current implementation will
give a priority to remove FDB in case there is duplicate MAC between
the SB FDB table and the remote FDB table.

In order to make the logical action that assigns to two registers
work, add default flows for the FDB and remote FDB side tables.
The packet would be dropped otherwise if it would fall through
the table without finding any FDB. This doesn't change
the behavior for non-EVPN LS, because the packet is either dropped
or will go towards the "unknown" multicast group as previously.

There is also an option that will allow CMS to set FDB preference
(local/remote), called "dynamic-routing-fdb-prefer-local" in LS
other config. It defaults to "false", which means remote has the
priority.

Reported-at: https://issues.redhat.com/browse/FDP-1387
Signed-off-by: Ales Musil <amu...@redhat.com>
---
v3: Rebase on top of latest main.
    Add the option to set FDB preference.

v2: Rebase on top of latest main.
---
 NEWS                  |  3 ++
 TODO.rst              |  3 --
 controller/lflow.c    |  1 +
 controller/physical.c | 14 +++++++++
 include/ovn/actions.h |  3 ++
 lib/actions.c         | 53 ++++++++++++++++++++++++++------
 lib/logical-fields.c  |  5 +++
 lib/ovn-util.c        |  4 +--
 northd/northd.c       | 71 +++++++++++++++++++++++++++++++++++--------
 northd/northd.h       |  3 ++
 ovn-nb.xml            | 21 +++++++++++++
 tests/ovn-northd.at   | 59 +++++++++++++++++++++++++++++++++++
 tests/ovn.at          | 19 ++++++++++--
 tests/system-ovn.at   | 19 ++++++++++++
 tests/test-ovn.c      |  1 +
 utilities/ovn-trace.c |  6 ++++
 16 files changed, 257 insertions(+), 28 deletions(-)

diff --git a/NEWS b/NEWS
index 9a7e2c8a8..eac073cac 100644
--- a/NEWS
+++ b/NEWS
@@ -54,6 +54,9 @@ Post v25.03.0
        for the EVPN traffic egressing through the tunnel.
      * Add the "other_config:dynamic-routing-vni" to Logical Switches. If set
        to valid integer the LS is considered to be connected to EVPN L2 domain.
+     * Add the "other_config:dynamic-routing-fdb-prefer-local" to Logical
+       Switches. If set to "true" the LS will prefer SB FDB table for
+       FDB lookup. Default is false.
 
 OVN v25.03.0 - 07 Mar 2025
 --------------------------
diff --git a/TODO.rst b/TODO.rst
index 0d3c29506..9a9df78f2 100644
--- a/TODO.rst
+++ b/TODO.rst
@@ -157,6 +157,3 @@ OVN To-do List
   * Allow ovn-evpn-local-ip to accept list of
     $VNI1:$LOCAL_IP1,$VNI2:$LOCAL_IP2 combinations which will be properly
     reflected in physical flows for given LS with VNI.
-
-  * Allow CMS to set FDB priority for EVPN, currently the remote FDB has
-   higher priority.
diff --git a/controller/lflow.c b/controller/lflow.c
index 04fb3ceed..b75ae5c0d 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -885,6 +885,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow 
*lflow,
         .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY,
         .ct_snat_vip_ptable = OFTABLE_CT_SNAT_HAIRPIN,
         .fdb_ptable = OFTABLE_GET_FDB,
+        .remote_fdb_ptable = OFTABLE_GET_REMOTE_FDB,
         .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
         .in_port_sec_ptable = OFTABLE_CHK_IN_PORT_SEC,
         .out_port_sec_ptable = OFTABLE_CHK_OUT_PORT_SEC,
diff --git a/controller/physical.c b/controller/physical.c
index a2b8c4071..60077ff71 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -3385,5 +3385,19 @@ physical_run(struct physical_ctx *p_ctx,
     physical_eval_remote_chassis_flows(p_ctx, &ofpacts, flow_table);
     physical_eval_evpn_flows(p_ctx, &ofpacts, flow_table);
 
+    /* Default flow for OFTABLE_GET_FDB table. */
+    match_init_catchall(&match);
+    ofpbuf_clear(&ofpacts);
+    put_load(0, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_GET_FDB, 0, 0,
+                    &match, &ofpacts, hc_uuid);
+
+    /* Default flow for OFTABLE_GET_REMOTE_FDB table. */
+    match_init_catchall(&match);
+    ofpbuf_clear(&ofpacts);
+    put_load(0, MFF_LOG_REMOTE_OUTPORT, 0, 32, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_GET_REMOTE_FDB, 0, 0,
+                    &match, &ofpacts, hc_uuid);
+
     ofpbuf_uninit(&ofpacts);
 }
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 30e5e4229..0eaef9112 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -121,6 +121,7 @@ struct collector_set_ids;
     OVNACT(SCTP_ABORT,        ovnact_nest)            \
     OVNACT(PUT_FDB,           ovnact_put_fdb)         \
     OVNACT(GET_FDB,           ovnact_get_fdb)         \
+    OVNACT(GET_REMOTE_FDB,    ovnact_get_fdb)         \
     OVNACT(LOOKUP_FDB,        ovnact_lookup_fdb)      \
     OVNACT(CHECK_IN_PORT_SEC,  ovnact_result)         \
     OVNACT(CHECK_OUT_PORT_SEC, ovnact_result)         \
@@ -956,6 +957,8 @@ struct ovnact_encode_params {
                                   * 'ct_snat_to_vip' to resubmit. */
     uint8_t fdb_ptable; /* OpenFlow table for
                          * 'get_fdb' to resubmit. */
+    uint8_t remote_fdb_ptable; /* OpenFlow table for
+                                * 'get_remote_fdb' to resubmit. */
     uint8_t fdb_lookup_ptable; /* OpenFlow table for
                                 * 'lookup_fdb' to resubmit. */
     uint8_t in_port_sec_ptable; /* OpenFlow table for
diff --git a/lib/actions.c b/lib/actions.c
index 00e439285..98ab368fc 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -4385,19 +4385,22 @@ ovnact_put_fdb_free(struct ovnact_put_fdb *put_fdb 
OVS_UNUSED)
 }
 
 static void
-format_GET_FDB(const struct ovnact_get_fdb *get_fdb, struct ds *s)
+format_get_fdb(const struct ovnact_get_fdb *get_fdb, const char *type,
+               struct ds *s)
 {
     expr_field_format(&get_fdb->dst, s);
-    ds_put_cstr(s, " = get_fdb(");
+    ds_put_format(s, " = %s(", type);
     expr_field_format(&get_fdb->mac, s);
     ds_put_cstr(s, ");");
 }
 
 static void
-encode_GET_FDB(const struct ovnact_get_fdb *get_fdb,
+encode_get_fdb(const struct ovnact_get_fdb *get_fdb,
                const struct ovnact_encode_params *ep,
-               struct ofpbuf *ofpacts)
+               bool remote, struct ofpbuf *ofpacts)
 {
+    const enum mf_field_id outport_id =
+        remote ? MFF_LOG_REMOTE_OUTPORT : MFF_LOG_OUTPORT;
     struct mf_subfield dst = expr_resolve_field(&get_fdb->dst);
     ovs_assert(dst.field);
 
@@ -4405,25 +4408,53 @@ encode_GET_FDB(const struct ovnact_get_fdb *get_fdb,
         { expr_resolve_field(&get_fdb->mac), MFF_ETH_DST },
     };
     encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-    put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts);
-    emit_resubmit(ofpacts, ep->fdb_ptable);
+    put_load(0, outport_id, 0, 32, ofpacts);
+    emit_resubmit(ofpacts, remote ? ep->remote_fdb_ptable : ep->fdb_ptable);
     encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
 
-    if (dst.field->id != MFF_LOG_OUTPORT) {
+    if (dst.field->id != outport_id) {
         struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
         orm->dst = dst;
-        orm->src.field = mf_from_id(MFF_LOG_OUTPORT);
+        orm->src.field = mf_from_id(outport_id);
         orm->src.ofs = 0;
         orm->src.n_bits = 32;
     }
 }
 
+static void
+format_GET_FDB(const struct ovnact_get_fdb *get_fdb, struct ds *s)
+{
+    format_get_fdb(get_fdb, "get_fdb", s);
+}
+
+static void
+encode_GET_FDB(const struct ovnact_get_fdb *get_fdb,
+               const struct ovnact_encode_params *ep,
+               struct ofpbuf *ofpacts)
+{
+    encode_get_fdb(get_fdb, ep, false, ofpacts);
+}
+
+static void
+format_GET_REMOTE_FDB(const struct ovnact_get_fdb *get_fdb, struct ds *s)
+{
+    format_get_fdb(get_fdb, "get_remote_fdb", s);
+}
+
+static void
+encode_GET_REMOTE_FDB(const struct ovnact_get_fdb *get_fdb,
+                      const struct ovnact_encode_params *ep,
+                      struct ofpbuf *ofpacts)
+{
+    encode_get_fdb(get_fdb, ep, true, ofpacts);
+}
+
 static void
 parse_get_fdb(struct action_context *ctx,
               struct expr_field *dst,
               struct ovnact_get_fdb *get_fdb)
 {
-    lexer_get(ctx->lexer); /* Skip get_bfd. */
+    lexer_get(ctx->lexer); /* Skip get_bfd/get_remote_fdb. */
     lexer_get(ctx->lexer); /* Skip '('. */
 
     /* Validate that the destination is a 32-bit, modifiable field if it
@@ -5765,6 +5796,10 @@ parse_set_action(struct action_context *ctx)
                    && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
             parse_get_fdb(
                 ctx, &lhs, ovnact_put_GET_FDB(ctx->ovnacts));
+        } else if (!strcmp(ctx->lexer->token.s, "get_remote_fdb")
+                   && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+            parse_get_fdb(
+                ctx, &lhs, ovnact_put_GET_REMOTE_FDB(ctx->ovnacts));
         } else if (!strcmp(ctx->lexer->token.s, "lookup_fdb")
                    && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
             parse_lookup_fdb(
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index f19eb579b..fcafeeac2 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -72,6 +72,11 @@ ovn_init_symtab(struct shash *symtab)
     expr_symtab_add_string(symtab, "inport", MFF_LOG_INPORT, NULL);
     expr_symtab_add_string(symtab, "outport", MFF_LOG_OUTPORT, NULL);
 
+    /* The port isn't reserved along the pipeline it's just defined as symbol
+     * to support matching on string and moving between string registers. */
+    expr_symtab_add_string(symtab, "remote_outport",
+                           MFF_LOG_REMOTE_OUTPORT, NULL);
+
     /* Logical registers:
      *     128-bit xxregs
      *     64-bit xregs
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index 70a1c9404..fbba82758 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -915,8 +915,8 @@ ip_address_and_port_from_lb_key(const char *key, char 
**ip_address,
  *
  * NOTE: If OVN_NORTHD_PIPELINE_CSUM is updated make sure to double check
  * whether an update of OVN_INTERNAL_MINOR_VER is required. */
-#define OVN_NORTHD_PIPELINE_CSUM "1158333617 10744"
-#define OVN_INTERNAL_MINOR_VER 9
+#define OVN_NORTHD_PIPELINE_CSUM "2405300854 10800"
+#define OVN_INTERNAL_MINOR_VER 10
 
 /* Returns the OVN version. The caller must free the returned value. */
 char *
diff --git a/northd/northd.c b/northd/northd.c
index f02801f48..98d223177 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -254,12 +254,14 @@ static const char *reg_ct_state[] = {
  * | R0 |     REGBIT_{CONNTRACK/DHCP/DNS}              |   |                   
                |
  * |    |     REGBIT_{HAIRPIN/HAIRPIN_REPLY}           |   |                   
                |
  * |    | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} |   |                   
                |
- * |    |     REGBIT_ACL_{LABEL/STATELESS}             | X |                   
                |
- * +----+----------------------------------------------+ X |                   
                |
- * | R1 |       REG_CT_TP_DST (0..15)                  | R |                   
                |
- * |    |       REG_CT_PROTO (16..23)                  | E |                   
                |
- * |    |   (>= IN_CT_EXTRACT && <= IN_LB_AFF_LEARN)   | G |                   
                |
- * +----+----------------------------------------------+ 0 |                   
                |
+ * |    |     REGBIT_ACL_{LABEL/STATELESS}             |   |                   
                |
+ * +----+----------------------------------------------+   |                   
                |
+ * | R1 |       REG_CT_TP_DST (0..15)                  |   |                   
                |
+ * |    |       REG_CT_PROTO (16..23)                  |   |                   
                |
+ * |    |   (>= IN_CT_EXTRACT && <= IN_LB_AFF_LEARN)   | X |                   
                |
+ * |    |       remote_outport                         | X |                   
                |
+ * |    |   (>= L2_LKUP && <= L2_UNKNOWN)              | R |                   
                |
+ * +----+----------------------------------------------+ E |                   
                |
  * | R2 |                 REG_LB_PORT                  | G |                   
                |
  * |    |  (>= IN_PRE_STATEFUL && <= IN_LB_AFF_LEARN)  | 0 |                   
                |
  * |    |                 REG_ACL_ID                   |   |                   
                |
@@ -812,10 +814,10 @@ ovn_datapath_update_external_ids(struct ovn_datapath *od)
                             "%u", age_threshold);
         }
 
-        int64_t vni = ovn_smap_get_llong(&od->nbs->other_config,
-                                         "dynamic-routing-vni", -1);
-        if (ovn_is_valid_vni(vni)) {
-            smap_add_format(&ids, "dynamic-routing-vni", "%"PRIi64, vni);
+        if (od->has_evpn_vni) {
+            const char *vni = smap_get(&od->nbs->other_config,
+                                       "dynamic-routing-vni");
+            smap_add(&ids, "dynamic-routing-vni", vni);
         }
     }
 
@@ -976,6 +978,12 @@ join_datapaths(const struct nbrec_logical_switch_table 
*nbrec_ls_table,
                           false)) {
             od->lb_with_stateless_mode = true;
         }
+
+        int64_t vni = ovn_smap_get_llong(&od->nbs->other_config,
+                                         "dynamic-routing-vni", -1);
+        if (ovn_is_valid_vni(vni)) {
+            od->has_evpn_vni = true;
+        }
     }
 
     const struct nbrec_logical_router *nbr;
@@ -5816,12 +5824,17 @@ build_lswitch_learn_fdb_od(
     struct lflow_ref *lflow_ref)
 {
     ovs_assert(od->nbs);
+    const char *lkp_action = od->has_evpn_vni
+        ? "outport = get_fdb(eth.dst); "
+          "remote_outport = get_remote_fdb(eth.dst); next;"
+        : "outport = get_fdb(eth.dst); next;";
+
     ovn_lflow_add(lflows, od, S_SWITCH_IN_LOOKUP_FDB, 0, "1", "next;",
                   lflow_ref);
     ovn_lflow_add(lflows, od, S_SWITCH_IN_PUT_FDB, 0, "1", "next;",
                   lflow_ref);
     ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
-                  "outport = get_fdb(eth.dst); next;", lflow_ref);
+                  lkp_action, lflow_ref);
 
     ovn_lflow_add(lflows, od, S_SWITCH_OUT_LOOKUP_FDB, 0, "1", "next;",
                   lflow_ref);
@@ -9351,6 +9364,36 @@ is_vlan_transparent(const struct ovn_datapath *od)
     return smap_get_bool(&od->nbs->other_config, "vlan-passthru", false);
 }
 
+static void
+build_lswitch_lflows_evpn_l2_unknown(struct ovn_datapath *od,
+                                     struct lflow_table *lflows,
+                                     struct lflow_ref *lflow_ref)
+{
+    if (od->has_unknown) {
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
+                      "outport == \"none\" && remote_outport == \"none\"",
+                      "outport = \""MC_UNKNOWN "\"; output;", lflow_ref);
+    } else {
+        ovn_lflow_add_drop_with_desc(
+            lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50, "outport == \"none\" && "
+            "remote_outport == \"none\"", "No L2 destination", lflow_ref);
+    }
+
+    if (smap_get_bool(&od->nbs->other_config,
+                  "dynamic-routing-fdb-prefer-local", false)) {
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 25,
+                      "outport == \"none\"",
+                      "outport = remote_outport; output;", lflow_ref);
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1",
+                      "output;", lflow_ref);
+    } else {
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 25,
+                      "remote_outport == \"none\"", "output;", lflow_ref);
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1",
+                      "outport = remote_outport; output;", lflow_ref);
+    }
+}
+
 static void
 build_lswitch_lflows_l2_unknown(struct ovn_datapath *od,
                                 struct lflow_table *lflows,
@@ -17604,7 +17647,11 @@ build_lswitch_and_lrouter_iterate_by_ls(struct 
ovn_datapath *od,
     ovn_lflow_add(lsi->lflows, od, S_SWITCH_IN_CT_EXTRACT, 0, "1", "next;",
                   NULL);
     build_lswitch_lb_affinity_default_flows(od, lsi->lflows, NULL);
-    build_lswitch_lflows_l2_unknown(od, lsi->lflows, NULL);
+    if (od->has_evpn_vni) {
+        build_lswitch_lflows_evpn_l2_unknown(od, lsi->lflows, NULL);
+    } else {
+        build_lswitch_lflows_l2_unknown(od, lsi->lflows, NULL);
+    }
     build_mcast_flood_lswitch(od, lsi->lflows, &lsi->actions, NULL);
 }
 
diff --git a/northd/northd.h b/northd/northd.h
index 1108793d7..1235f3912 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -404,6 +404,9 @@ struct ovn_datapath {
      * This is applicable only to routers with "remote" ports. */
     bool is_transit_router;
 
+    /* Indicates that the LS has valid vni associated with it. */
+    bool has_evpn_vni;
+
     /* OVN northd only needs to know about logical router gateway ports for
      * NAT/LB on a distributed router.  The "distributed gateway ports" are
      * populated only when there is a gateway chassis or ha chassis group
diff --git a/ovn-nb.xml b/ovn-nb.xml
index c7c1fd6c7..84c4e2b88 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -886,6 +886,27 @@
           removal/change in the future.
         </p>
       </column>
+
+      <column name="other_config" key="dynamic-routing-evpn-fdb-prefer-local"
+              type='{"type": "boolean"}'>
+        <p>
+          This option defines the preference of FDB lookup, if set to
+          true OVN will try to find the FDB entry in SB <code>FDB</code>
+          table first. Then it tries to resolve the FDB via
+          <code>ovn-controller</code> local EVPN FDB cache. The option
+          default to false.
+        </p>
+
+        <p>
+          Only relevant if <ref column="other_config" key="dynamic-routing-vni"
+                                table="Logical_switch"/> is set to valid VNI.
+        </p>
+
+        <p>
+          NOTE: this feature is experimental and may be subject to
+          removal/change in the future.
+        </p>
+      </column>
     </group>
 
     <group title="IP Multicast Snooping Options">
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 05acd6f4d..303619a8b 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -17641,3 +17641,62 @@ check as northd ovn-appctl -t ovn-northd 
inc-engine/clear-stats
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([LS dynamic-routing-fdb-prefer-local])
+ovn_start ovn-northd
+
+AS_BOX([Create logical switch.])
+check ovn-nbctl --wait=sb  ls-add ls-evpn
+ovn-sbctl dump-flows ls-evpn > lflows
+
+AT_CHECK([grep 'ls_in_l2_lkup' lflows | grep "get_fdb" | ovn_strip_lflows], 
[0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
get_fdb(eth.dst); next;)
+])
+
+AT_CHECK([grep 'ls_in_l2_unknown' lflows | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
+  table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
action=(drop;)
+])
+
+check ovn-nbctl --wait=hv set logical_switch ls-evpn 
other_config:dynamic-routing-vni=10
+ovn-sbctl dump-flows ls-evpn > lflows
+
+AT_CHECK([grep 'ls_in_l2_lkup' lflows | grep "get_fdb" | ovn_strip_lflows], 
[0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
get_fdb(eth.dst); remote_outport = get_remote_fdb(eth.dst); next;)
+])
+
+AT_CHECK([grep 'ls_in_l2_unknown' lflows | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(outport = 
remote_outport; output;)
+  table=??(ls_in_l2_unknown   ), priority=25   , match=(remote_outport == 
"none"), action=(output;)
+  table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none" && 
remote_outport == "none"), action=(drop;)
+])
+
+check ovn-nbctl --wait=hv set logical_switch ls-evpn 
other_config:dynamic-routing-fdb-prefer-local=true
+ovn-sbctl dump-flows ls-evpn > lflows
+
+AT_CHECK([grep 'ls_in_l2_lkup' lflows | grep "get_fdb" | ovn_strip_lflows], 
[0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
get_fdb(eth.dst); remote_outport = get_remote_fdb(eth.dst); next;)
+])
+
+AT_CHECK([grep 'ls_in_l2_unknown' lflows | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
+  table=??(ls_in_l2_unknown   ), priority=25   , match=(outport == "none"), 
action=(outport = remote_outport; output;)
+  table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none" && 
remote_outport == "none"), action=(drop;)
+])
+
+check ovn-nbctl --wait=hv remove logical_switch ls-evpn other_config 
dynamic-routing-fdb-prefer-local
+ovn-sbctl dump-flows ls-evpn > lflows
+
+AT_CHECK([grep 'ls_in_l2_lkup' lflows | grep "get_fdb" | ovn_strip_lflows], 
[0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
get_fdb(eth.dst); remote_outport = get_remote_fdb(eth.dst); next;)
+])
+
+AT_CHECK([grep 'ls_in_l2_unknown' lflows | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(outport = 
remote_outport; output;)
+  table=??(ls_in_l2_unknown   ), priority=25   , match=(remote_outport == 
"none"), action=(output;)
+  table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none" && 
remote_outport == "none"), action=(drop;)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index 6fbfc7fa9..dd8ef1e06 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2087,6 +2087,13 @@ reg15 = get_fdb(eth.dst);
 outport = get_fdb(ip4.dst);
     Cannot use 32-bit field ip4.dst[[0..31]] where 48-bit field is required.
 
+# get_remote_fdb
+reg1 = get_remote_fdb(eth.dst);
+    encodes as 
set_field:0->reg1,resubmit(,OFTABLE_GET_REMOTE_FDB),move:NXM_NX_REG1[[]]->NXM_NX_XXREG0[[64..95]]
+
+reg1 = get_remote_fdb(eth.src);
+    encodes as 
push:NXM_OF_ETH_DST[[]],push:NXM_OF_ETH_SRC[[]],pop:NXM_OF_ETH_DST[[]],set_field:0->reg1,resubmit(,OFTABLE_GET_REMOTE_FDB),pop:NXM_OF_ETH_DST[[]],move:NXM_NX_REG1[[]]->NXM_NX_XXREG0[[64..95]]
+
 # lookup_fdb
 reg0[[0]] = lookup_fdb(inport, eth.src);
     encodes as 
set_field:0/0x100->reg10,resubmit(,OFTABLE_LOOKUP_FDB),move:NXM_NX_REG10[[8]]->NXM_NX_XXREG0[[96]]
@@ -32987,10 +32994,12 @@ as hv2 ovs-ofctl dump-flows br-int 
table=OFTABLE_GET_FDB > hv2_offlows_table71.t
 AT_CAPTURE_FILE([hv1_offlows_table71.txt])
 AT_CAPTURE_FILE([hv2_offlows_table71.txt])
 AT_CHECK_UNQUOTED([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' 
-f8- | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 ])
 
 AT_CHECK_UNQUOTED([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' 
-f8- | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 ])
 
@@ -33024,6 +33033,7 @@ AT_CAPTURE_FILE([hv3_offlows_table71.txt])
 AT_CAPTURE_FILE([hv3_offlows_table72.txt])
 
 AT_CHECK_UNQUOTED([cat hv3_offlows_table71.txt | grep -v NXST | cut -d ' ' 
-f8- | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 ])
 
@@ -33056,16 +33066,19 @@ AT_CAPTURE_FILE([hv1_offlows_table71.txt])
 AT_CAPTURE_FILE([hv2_offlows_table71.txt])
 AT_CAPTURE_FILE([hv3_offlows_table71.txt])
 AT_CHECK_UNQUOTED([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' 
-f8- | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:14 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 ])
 
 AT_CHECK_UNQUOTED([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' 
-f8- | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:14 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 ])
 
 AT_CHECK_UNQUOTED([cat hv3_offlows_table71.txt | grep -v NXST | cut -d ' ' 
-f8- | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:13 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 priority=100,metadata=0x$dp_key,dl_dst=50:54:00:00:00:14 
actions=load:0x$port_key->NXM_NX_REG15[[]]
 ])
@@ -33258,10 +33271,12 @@ as hv2 ovs-ofctl dump-flows br-int 
table=OFTABLE_GET_FDB > hv2_offlows_table71.t
 
 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 hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | 
sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 ])
 
-AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST], [1], [dnl
+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | 
sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG15[[]]
 ])
 
 as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_LOOKUP_FDB > 
hv1_offlows_table72.txt
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 9c1e0e0dd..2366f513c 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -18417,18 +18417,37 @@ AT_CHECK([grep "resubmit" oftable_remote_vtep_output 
| grep -c "load:0xa900000a"
 AT_CHECK([grep "resubmit" oftable_remote_vtep_output | grep -c 
"load:0xa9000014"], [0], [3
 ])
 
+# Simulate remote workload.
+check bridge fdb add f0:00:0f:16:10:50 dev vxlan-$vni dst 169.0.0.10 static 
extern_learn
+check bridge fdb add f0:00:0f:16:10:60 dev vxlan-$vni dst 169.0.0.20 static 
extern_learn
+
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-fdb-list | cut -d',' -f2- | 
sort], [0], [dnl
+ MAC: f0:00:0f:16:10:50, binding_key: 0x80000001, dp_key: $dp_key
+ MAC: f0:00:0f:16:10:60, binding_key: 0x80000002, dp_key: $dp_key
+])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_GET_REMOTE_FDB | 
grep priority | \
+                   awk '{print $7, $8}' | sort], [0], [dnl
+priority=0 actions=load:0->NXM_NX_REG1[[]]
+priority=150,metadata=0x$dp_key,dl_dst=f0:00:0f:16:10:50 
actions=load:0x80000001->NXM_NX_REG1[[]]
+priority=150,metadata=0x$dp_key,dl_dst=f0:00:0f:16:10:60 
actions=load:0x80000002->NXM_NX_REG1[[]]
+])
+
 # Check that the recompute won't change the UUIDs and tunnel keys.
 ovn-appctl evpn/vtep-binding-list > bindings_before
 ovn-appctl evpn/vtep-multicast-group-list > mc_groups_before
+ovn-appctl evpn/vtep-fdb-list > fdb_before
 
 check ovn-appctl inc-engine/recompute
 check ovn-nbctl --wait=hv sync
 
 ovn-appctl evpn/vtep-binding-list > bindings_after
 ovn-appctl evpn/vtep-multicast-group-list > mc_groups_after
+ovn-appctl evpn/vtep-fdb-list > fdb_after
 
 check diff -q bindings_before bindings_after
 check diff -q mc_groups_before mc_groups_after
+check diff -q fdb_before fdb_after
 
 OVN_CLEANUP_CONTROLLER([hv1])
 
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index 1580f44c4..fae7e7bd5 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1380,6 +1380,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx 
OVS_UNUSED)
                 .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY,
                 .ct_snat_vip_ptable = OFTABLE_CT_SNAT_HAIRPIN,
                 .fdb_ptable = OFTABLE_GET_FDB,
+                .remote_fdb_ptable = OFTABLE_GET_REMOTE_FDB,
                 .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
                 .common_nat_ct_zone = MFF_LOG_DNAT_ZONE,
                 .in_port_sec_ptable = OFTABLE_CHK_IN_PORT_SEC,
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index a63a3be19..caeec9619 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -3536,6 +3536,12 @@ trace_actions(const struct ovnact *ovnacts, size_t 
ovnacts_len,
             execute_get_fdb(ovnact_get_GET_FDB(a), dp, uflow);
             break;
 
+        case OVNACT_GET_REMOTE_FDB:
+            ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT,
+                                 "/* The remote FDB table is different"
+                                 " per each chassis. */");
+            break;
+
         case OVNACT_LOOKUP_FDB:
             execute_lookup_fdb(ovnact_get_LOOKUP_FDB(a), dp, uflow, super);
             break;
-- 
2.50.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to