This functionality can be enabled at the logical switch level:
  - "other_config:fdb_local" can be used to enable/disable this
    functionality, it is disabled by default.
  - "other_config:fdb_local_idle_timeout" sepcifies idle timeout
    for locally learned fdb flows, default timeout is 300 secs.

If enabled, below lflow is added for each port that has unknown addr set.
  - table=2 (ls_in_lookup_fdb), priority=100, match=(inport == <in_port>),
    action=(commit_fdb_local(timeout=<timeout>); next;

New OVN action: "commit_fdb_local". This sets following OVS action.
  - learn(table=71,idle_timeout=<timeout>,delete_learned,OXM_OF_METADATA[],
          NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_NX_REG14[]->NXM_NX_REG15[])

This is useful when OVN is managing VLAN network that has multiple ports
set with unknown addr and localnet_learn_fdb is enabled. With this config,
if there is east-west traffic flowing between VMs part of same VLAN
deployed on different hypervisors then, MAC addrs of the source and
destination VMs keeps flapping between VM port and localnet port in 
Southbound FDB table. Enabling fdb_local config makes fdb table local to
the chassis and avoids MAC flapping.

Signed-off-by: Naveen Yerramneni <naveen.yerramn...@nutanix.com>
---
 include/ovn/actions.h |   7 +++
 lib/actions.c         |  94 ++++++++++++++++++++++++++++++++++++
 northd/northd.c       |  26 ++++++++++
 ovn-nb.xml            |  14 ++++++
 tests/ovn.at          | 108 ++++++++++++++++++++++++++++++++++++++++++
 utilities/ovn-trace.c |   2 +
 6 files changed, 251 insertions(+)

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 49cfe0624..85ac92cd3 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -127,6 +127,7 @@ struct collector_set_ids;
     OVNACT(CHK_LB_AFF,        ovnact_result)          \
     OVNACT(SAMPLE,            ovnact_sample)          \
     OVNACT(MAC_CACHE_USE,     ovnact_null)            \
+    OVNACT(COMMIT_FDB_LOCAL,  ovnact_commit_fdb_local) \
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -514,6 +515,12 @@ struct ovnact_commit_lb_aff {
     uint16_t timeout;
 };
 
+/* OVNACT_COMMIT_FBD_LOCAL. */
+struct ovnact_commit_fdb_local{
+    struct ovnact ovnact;
+    uint16_t timeout;  /* fdb_local flow timeout */
+};
+
 /* Internal use by the helpers below. */
 void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
 void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
diff --git a/lib/actions.c b/lib/actions.c
index a73fe1a1e..f5aa78db1 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -5236,6 +5236,98 @@ format_MAC_CACHE_USE(const struct ovnact_null *null 
OVS_UNUSED, struct ds *s)
     ds_put_cstr(s, "mac_cache_use;");
 }
 
+static void
+parse_commit_fdb_local(struct action_context *ctx,
+                     struct ovnact_commit_fdb_local *fdb_local)
+{
+    uint16_t timeout = 0;
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN); /* Skip '('. */
+    if (!lexer_match_id(ctx->lexer, "timeout")) {
+        lexer_syntax_error(ctx->lexer, "invalid parameter");
+        return;
+    }
+    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+        lexer_syntax_error(ctx->lexer, "invalid parameter");
+        return;
+    }
+    if (!action_parse_uint16(ctx, &timeout, "fdb_local flow timeout")) {
+        return;
+    }
+    fdb_local->timeout = timeout;
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN); /* Skip ')'. */
+}
+
+static void
+format_COMMIT_FDB_LOCAL(const struct ovnact_commit_fdb_local *fdb_local,
+                      struct ds *s)
+{
+    ds_put_format(s, "commit_fdb_local(timeout=%u);", fdb_local->timeout);
+}
+
+static void
+ovnact_commit_fdb_local_free(struct ovnact_commit_fdb_local *fdb_local 
OVS_UNUSED)
+{
+}
+
+static void
+commit_fdb_local_learn_action(struct ovnact_commit_fdb_local *fdb_local,
+                        struct ofpbuf *ofpacts, uint32_t cookie)
+{
+    struct ofpact_learn *ol = ofpact_put_LEARN(ofpacts);
+    struct match match = MATCH_CATCHALL_INITIALIZER;
+    struct ofpact_learn_spec *ol_spec;
+    unsigned int imm_bytes;
+    uint8_t *src_imm;
+
+    ol->flags = NX_LEARN_F_DELETE_LEARNED;
+    ol->idle_timeout = fdb_local->timeout;
+    ol->hard_timeout = OFP_FLOW_PERMANENT;
+    ol->priority = OFP_DEFAULT_PRIORITY;
+    ol->table_id = OFTABLE_GET_FDB;
+    ol->cookie = htonll(cookie);
+
+    /* Match on metadata of the packet that created the new table. */
+    ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+    ol_spec->dst.field = mf_from_id(MFF_METADATA);
+    ol_spec->dst.ofs = 0;
+    ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+    ol_spec->n_bits = ol_spec->dst.n_bits;
+    ol_spec->dst_type = NX_LEARN_DST_MATCH;
+    ol_spec->src_type = NX_LEARN_SRC_FIELD;
+    ol_spec->src.field = mf_from_id(MFF_METADATA);
+
+    /* Match on metadata of the packet. */
+    ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+    ol_spec->dst.field = mf_from_id(MFF_ETH_DST);
+    ol_spec->dst.ofs = 0;
+    ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+    ol_spec->n_bits = ol_spec->dst.n_bits;
+    ol_spec->dst_type = NX_LEARN_DST_MATCH;
+    ol_spec->src_type = NX_LEARN_SRC_FIELD;
+    ol_spec->src.field = mf_from_id(MFF_ETH_SRC);
+
+
+    /* Load MFF_LOG_OUTPORT from MFF_IN_PORT. */
+    ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+    ol_spec->dst.field = mf_from_id(MFF_LOG_OUTPORT);
+    ol_spec->dst.ofs = 0;
+    ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+    ol_spec->n_bits = ol_spec->dst.n_bits;
+    ol_spec->dst_type = NX_LEARN_DST_LOAD;
+    ol_spec->src_type = NX_LEARN_SRC_FIELD;
+    ol_spec->src.field = mf_from_id(MFF_LOG_INPORT);
+
+    ofpact_finish_LEARN(ofpacts, &ol);
+}
+
+static void
+encode_COMMIT_FDB_LOCAL(const struct ovnact_commit_fdb_local *fdb_local,
+                      const struct ovnact_encode_params *ep,
+                      struct ofpbuf *ofpacts)
+{
+     commit_fdb_local_learn_action(fdb_local, ofpacts, 
ep->lflow_uuid.parts[0]);
+}
+
 static void
 encode_MAC_CACHE_USE(const struct ovnact_null *null OVS_UNUSED,
                      const struct ovnact_encode_params *ep,
@@ -5451,6 +5543,8 @@ parse_action(struct action_context *ctx)
         parse_sample(ctx);
     } else if (lexer_match_id(ctx->lexer, "mac_cache_use")) {
         ovnact_put_MAC_CACHE_USE(ctx->ovnacts);
+    } else if (lexer_match_id(ctx->lexer, "commit_fdb_local")) {
+        parse_commit_fdb_local(ctx, ovnact_put_COMMIT_FDB_LOCAL(ctx->ovnacts));
     } else {
         lexer_syntax_error(ctx->lexer, "expecting action");
     }
diff --git a/northd/northd.c b/northd/northd.c
index d1465ddf7..de18694a0 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -1834,6 +1834,12 @@ localnet_can_learn_mac(const struct 
nbrec_logical_switch_port *nbsp)
     return smap_get_bool(&nbsp->options, "localnet_learn_fdb", false);
 }
 
+static bool
+ls_is_fdb_local(const struct nbrec_logical_switch *nbs)
+{
+    return smap_get_bool(&nbs->other_config, "fdb_local", false);
+}
+
 static bool
 lsp_is_type_changed(const struct sbrec_port_binding *sb,
                 const struct nbrec_logical_switch_port *nbsp,
@@ -7033,6 +7039,8 @@ build_lswitch_port_sec_op(struct ovn_port *op, struct 
hmap *lflows,
     }
 }
 
+#define FDB_LOCAL_DEF_IDLE_TIMEOUT_S 300
+
 static void
 build_lswitch_learn_fdb_op(
         struct ovn_port *op, struct hmap *lflows,
@@ -7042,6 +7050,24 @@ build_lswitch_learn_fdb_op(
 
     if (!op->n_ps_addrs && op->has_unknown && (!strcmp(op->nbsp->type, "") ||
         (lsp_is_localnet(op->nbsp) && localnet_can_learn_mac(op->nbsp)))) {
+
+        if (ls_is_fdb_local(op->od->nbs))
+        {
+            uint32_t idle_timeout =smap_get_uint(
+                        &op->od->nbs->other_config, "fdb_local_idle_timeout",
+                        FDB_LOCAL_DEF_IDLE_TIMEOUT_S);
+            ds_clear(match);
+            ds_clear(actions);
+            ds_put_format(match, "inport == %s", op->json_key);
+            ds_put_format(actions, "commit_fdb_local(timeout=%u); next;",
+                                    idle_timeout);
+            ovn_lflow_add_with_lport_and_hint(lflows, op->od,
+                                              S_SWITCH_IN_LOOKUP_FDB, 100,
+                                              ds_cstr(match), ds_cstr(actions),
+                                              op->key, &op->nbsp->header_);
+            return;
+        }
+
         ds_clear(match);
         ds_clear(actions);
         ds_put_format(match, "inport == %s", op->json_key);
diff --git a/ovn-nb.xml b/ovn-nb.xml
index fcb1c6ecc..3547ec4a6 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -803,6 +803,20 @@
       </column>
     </group>
 
+    <group title="Local FDB options">
+      <column name="other_config" key="fdb_local"
+              type='{"type": "boolean"}'>
+        If set to <code>true</code>, FDB flows are commited only to the
+        local chassis instead of southbound DB. Default is false.
+      </column>
+      <column name="other_config" key="fdb_local_idle_timeout"
+              type='{"type": "integer", "minInteger": 0, "maxInteger": 65535}'>
+        Local FDB flows <code>idle_timeout</code> value in seconds. FDB local
+        flows exceeding this timeout will be automatically removed. The value
+        defaults to 300, 0 means disabled.
+      </column>
+    </group>
+
     <column name="copp">
       <p>
         The control plane protection policy from table <ref table="Copp"/>
diff --git a/tests/ovn.at b/tests/ovn.at
index 92cf27581..dff50364a 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -34504,6 +34504,114 @@ OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Local FDB MAC learning])
+ovn_start
+net_add n1
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+
+AT_CHECK([ovn-nbctl lsp-add ls0 vif0])
+AT_CHECK([ovn-nbctl lsp-set-addresses vif0 "50:54:00:00:00:03 10.0.0.3" 
"unknown"])
+AT_CHECK([ovn-nbctl set logical_switch_port vif0 options:requested-tnl-key=2])
+
+AT_CHECK([ovn-nbctl lsp-add ls0 vif1])
+AT_CHECK([ovn-nbctl lsp-set-addresses vif1 "50:54:00:00:00:04 10.0.0.4"])
+AT_CHECK([ovn-nbctl set logical_switch_port vif1 options:requested-tnl-key=3])
+
+AT_CHECK([ovn-nbctl lsp-add ls0 ln_port])
+AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
+AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
+AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
+AT_CHECK([ovn-nbctl set logical_switch_port ln_port 
options:localnet_learn_fdb=true])
+AT_CHECK([ovn-nbctl set logical_switch_port ln_port 
options:requested-tnl-key=1])
+
+AT_CHECK([ovn-nbctl set logical_switch ls0 other_config:fdb_local=true])
+
+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 vif0 -- \
+    set interface vif0 external-ids:iface-id=vif0 \
+    options:tx_pcap=hv1/vif0-tx.pcap \
+    options:rxq_pcap=hv1/vif0-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=vif1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=2
+ovs-vsctl -- add-port br-phys ext0 -- \
+    set interface ext0 \
+    options:tx_pcap=hv1/ext0-tx.pcap \
+    options:rxq_pcap=hv1/ext0-rx.pcap \
+    ofport-request=3
+ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+
+wait_for_ports_up
+AT_CHECK([ovn-nbctl --wait=hv sync])
+
+send_packet() {
+    src_mac=$1
+    src_ip=$2
+    dst_mac=$3
+    dst_ip=$4
+    iface=$5
+    
packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+    ovs-appctl netdev-dummy/receive $iface $packet
+}
+
+
+# Check that there is commit_fdb_local_fdb() flow added by ovn-northd for vif0 
and localnet
+ovn-sbctl dump-flows ls0 > sw0flows
+AT_CAPTURE_FILE([sw0flows])
+
+AT_CHECK([grep "ls_in_lookup_fdb" sw0flows | sort], [0], [dnl
+  table=2 (ls_in_lookup_fdb   ), priority=0    , dnl
+match=(1), action=(next;)
+  table=2 (ls_in_lookup_fdb   ), priority=100  , dnl
+match=(inport == "ln_port"), action=(commit_fdb_local(timeout=300); next;)
+  table=2 (ls_in_lookup_fdb   ), priority=100  , dnl
+match=(inport == "vif0"), action=(commit_fdb_local(timeout=300); next;)
+])
+
+AT_CHECK([grep "ls_in_put_fdb" sw0flows | sort], [0], [dnl
+  table=3 (ls_in_put_fdb      ), priority=0    , dnl
+match=(1), action=(next;)
+])
+
+
+src_mac="505400000003"
+src_ip=`ip_to_hex 10.0.0.3`
+dst_mac="505400000004"
+dst_ip=`ip_to_hex 10.0.0.4`
+
+# send packet from vif0(which has unknown addr set) to vif1
+send_packet $src_mac $src_ip $dst_mac $dst_ip vif0
+
+# send packet from vif1 to vif0(which has unknown addr set)
+send_packet $dst_mac $dst_ip $src_mac $src_ip vif1
+
+# send packet from underlay to vif1
+src_mac="505400000064"
+src_ip=`ip_to_hex 10.0.0.100`
+send_packet $src_mac $src_ip $dst_mac $dst_ip ext0
+AT_CHECK([ovn-nbctl --wait=hv sync])
+
+# Make sure that OVS table 71 is populated on hv1.
+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
+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f7- | 
sort], [0], [dnl
+idle_timeout=300, idle_age=0, metadata=0x1,dl_dst=50:54:00:00:00:03 
actions=load:0x2->NXM_NX_REG15[[]]
+idle_timeout=300, idle_age=0, metadata=0x1,dl_dst=50:54:00:00:00:64 
actions=load:0x1->NXM_NX_REG15[[]]
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([MAC binding aging])
 AT_SKIP_IF([test $HAVE_SCAPY = no])
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 0b86eae7b..354f84a4b 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -3355,6 +3355,8 @@ trace_actions(const struct ovnact *ovnacts, size_t 
ovnacts_len,
             break;
         case OVNACT_MAC_CACHE_USE:
             break;
+        case OVNACT_COMMIT_FDB_LOCAL:
+            break;
         }
     }
     ofpbuf_uninit(&stack);
-- 
2.36.6

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

Reply via email to