A network function (NF) in inline mode redirects matched traffic through a
service VM. When such traffic is an unknown-unicast flood (the destination
MAC is not yet in the FDB and not yet learned by the underlay), the copy
that is redirected to the NF can, after NF processing, be re-flooded and
sent back out of the port it originally arrived on. On a VLAN backed
logical switch this loop-back copy egresses the localnet port again and
makes the physical TOR switch flap the source MAC between ports.
Add two OVS-learn based logical actions that, together, allow northd to
detect and drop these loop-back copies:
- "nf_learn_orig_src_port(ipv6 = true|false)" installs a learned flow in
a new OFTABLE_NF_ORIG_SRC_PORT_LEARN table. The learned flow is keyed
on the logical datapath and the IP source and destination addresses
and matches future packets whose logical output port equals the
logical input port of the packet that created it, i.e. packets that
are about to be sent back out of the port they entered on. Matching
packets get MLF_NF_LOOKUP_HIT set. The learned flow has a 30s idle
timeout.
- "R = nf_lookup_orig_src_port()" resubmits to the learn table and stores
the result of the lookup (MLF_NF_LOOKUP_HIT) into the 1-bit field R.
Signed-off-by: Naveen Yerramneni <[email protected]>
Acked-by: Aditya Mehakare <[email protected]>
CC: Sragdhara Datta Chaudhuri <[email protected]>
---
controller/lflow.h | 1 +
include/ovn/actions.h | 15 +++
include/ovn/logical-fields.h | 7 ++
lib/actions.c | 190 +++++++++++++++++++++++++++++++++++
lib/ovn-util.c | 2 +-
ovn-sb.xml | 57 +++++++++++
tests/ovn-macros.at | 1 +
tests/ovn.at | 19 ++++
utilities/ovn-trace.c | 15 +++
9 files changed, 306 insertions(+), 1 deletion(-)
diff --git a/controller/lflow.h b/controller/lflow.h
index 4bae1dfab..ed6bf902a 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -104,6 +104,7 @@ struct uuid;
#define OFTABLE_CT_ORIG_PROTO_LOAD 86
#define OFTABLE_GET_REMOTE_FDB 87
#define OFTABLE_LEARN_REMOTE_FDB 88
+#define OFTABLE_NF_ORIG_SRC_PORT_LEARN 89
struct lflow_ctx_in {
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 2ca8dac8f..2d9f63765 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -140,6 +140,8 @@ struct collector_set_ids;
OVNACT(FLOOD_REMOTE, ovnact_null) \
OVNACT(CT_STATE_SAVE, ovnact_result) \
OVNACT(MIRROR, ovnact_mirror) \
+ OVNACT(NF_LEARN_ORIG_SRC_PORT, ovnact_nf_learn) \
+ OVNACT(NF_LOOKUP_ORIG_SRC_PORT, ovnact_nf_lookup) \
/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
enum OVS_PACKED_ENUM ovnact_type {
@@ -514,6 +516,19 @@ struct ovnact_lookup_fdb {
struct expr_field dst; /* 1-bit destination field. */
};
+/* OVNACT_NF_LEARN_ORIG_SRC_PORT. */
+struct ovnact_nf_learn {
+ struct ovnact ovnact;
+ bool ipv6; /* Learn IPv6 (true) or IPv4 (false) flow. */
+ uint16_t idle_timeout; /* Idle timeout of the learned flow, seconds. */
+};
+
+/* OVNACT_NF_LOOKUP_ORIG_SRC_PORT. */
+struct ovnact_nf_lookup {
+ struct ovnact ovnact;
+ struct expr_field dst; /* 1-bit destination field. */
+};
+
/* OVNACT_SAMPLE */
struct ovnact_sample {
struct ovnact ovnact;
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index 3c0fb22e7..54465bc2e 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -149,6 +149,7 @@ enum mff_log_flags_bits {
MLF_IGMP_IGMP_SNOOP_INJECT_BIT = 22,
MLF_PKT_SAMPLED_BIT = 23,
MLF_RECIRC_BIT = 24,
+ MLF_NF_LOOKUP_HIT_BIT = 25,
MLF_NETWORK_ID_START_BIT = 28,
MLF_NETWORK_ID_END_BIT = 31,
};
@@ -225,6 +226,12 @@ enum mff_log_flags {
/* Indicate the packet has been processed by LOCAL table once before. */
MLF_RECIRC = (1 << MLF_RECIRC_BIT),
+ /* Indicate that the network function post-redirect lookup matched, i.e.
+ * the packet is being sent back out of the port it originally entered the
+ * logical switch on. Used to drop such "loop-back" copies and avoid
+ * MAC flaps / L2 loops after network function redirection. */
+ MLF_NF_LOOKUP_HIT = (1 << MLF_NF_LOOKUP_HIT_BIT),
+
/* Assign network ID to packet to choose correct network for snat when
* lb_force_snat_ip=router_ip. */
MLF_NETWORK_ID = (OVN_MAX_NETWORK_ID << MLF_NETWORK_ID_START_BIT),
diff --git a/lib/actions.c b/lib/actions.c
index 3fbaed7af..e7355da0a 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -4636,6 +4636,189 @@ ovnact_lookup_fdb_free(struct ovnact_lookup_fdb
*get_fdb OVS_UNUSED)
{
}
+static void
+format_NF_LEARN_ORIG_SRC_PORT(const struct ovnact_nf_learn *nf_learn,
+ struct ds *s)
+{
+ ds_put_format(s, "nf_learn_orig_src_port(ipv6 = %s);",
+ nf_learn->ipv6 ? "true" : "false");
+}
+
+/* Adds a NXAST_LEARN spec matching the value of field 'id' (as it is in the
+ * packet that triggers the learn) on the same field of future packets. */
+static void
+nf_learn_put_match_field(struct ofpbuf *ofpacts, enum mf_field_id id)
+{
+ struct ofpact_learn_spec *ol_spec =
+ ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ ol_spec->dst.field = mf_from_id(id);
+ 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(id);
+}
+
+static void
+encode_NF_LEARN_ORIG_SRC_PORT(const struct ovnact_nf_learn *nf_learn,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+{
+ size_t ol_offset = ofpacts->size;
+ 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 = nf_learn->idle_timeout; /* seconds. */
+ ol->hard_timeout = OFP_FLOW_PERMANENT;
+ ol->priority = OFP_DEFAULT_PRIORITY;
+ ol->table_id = OFTABLE_NF_ORIG_SRC_PORT_LEARN;
+ ol->cookie = htonll(ep->lflow_uuid.parts[0]);
+
+ /* Match on the logical datapath. */
+ nf_learn_put_match_field(ofpacts, MFF_METADATA);
+
+ /* Match on the same ETH type as the packet that created the flow. */
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ ol_spec->dst.field = mf_from_id(MFF_ETH_TYPE);
+ 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_IMMEDIATE;
+ union mf_value imm_eth_type = {
+ .be16 = nf_learn->ipv6 ? htons(ETH_TYPE_IPV6) : htons(ETH_TYPE_IP)
+ };
+ mf_write_subfield_value(&ol_spec->dst, &imm_eth_type, &match);
+ /* Push value last, as this may reallocate 'ol_spec'. */
+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
+ memcpy(src_imm, &imm_eth_type, imm_bytes);
+
+ /* Match on the IP source and destination addresses. */
+ nf_learn_put_match_field(ofpacts,
+ nf_learn->ipv6 ? MFF_IPV6_SRC : MFF_IPV4_SRC);
+ nf_learn_put_match_field(ofpacts,
+ nf_learn->ipv6 ? MFF_IPV6_DST : MFF_IPV4_DST);
+
+ /* Match future packets whose logical output port equals the logical
+ * input port of the packet that created this flow. Such packets are
+ * being sent back out of the port they originally arrived on. */
+ 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_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
+ ol_spec->src.field = mf_from_id(MFF_LOG_INPORT);
+
+ /* Load the "lookup hit" flag into MFF_LOG_FLAGS of matching packets. */
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ ol_spec->dst.field = mf_from_id(MFF_LOG_FLAGS);
+ ol_spec->dst.ofs = MLF_NF_LOOKUP_HIT_BIT;
+ ol_spec->dst.n_bits = 1;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_LOAD;
+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
+ union mf_value imm_hit = { .u8 = 1 };
+ mf_write_subfield_value(&ol_spec->dst, &imm_hit, &match);
+ /* Push value last, as this may reallocate 'ol_spec'. */
+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
+ memcpy(src_imm, &imm_hit, imm_bytes);
+
+ ol = ofpbuf_at_assert(ofpacts, ol_offset, sizeof *ol);
+ ofpact_finish_LEARN(ofpacts, &ol);
+}
+
+static void
+parse_nf_learn_orig_src_port(struct action_context *ctx,
+ struct ovnact_nf_learn *nf_learn)
+{
+ nf_learn->idle_timeout = 30; /* seconds. */
+
+ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+ if (!lexer_match_id(ctx->lexer, "ipv6")) {
+ 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 (lexer_match_id(ctx->lexer, "true")) {
+ nf_learn->ipv6 = true;
+ } else if (lexer_match_id(ctx->lexer, "false")) {
+ nf_learn->ipv6 = false;
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting true or false");
+ return;
+ }
+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+ovnact_nf_learn_free(struct ovnact_nf_learn *nf_learn OVS_UNUSED)
+{
+}
+
+static void
+format_NF_LOOKUP_ORIG_SRC_PORT(const struct ovnact_nf_lookup *nf_lookup,
+ struct ds *s)
+{
+ expr_field_format(&nf_lookup->dst, s);
+ ds_put_cstr(s, " = nf_lookup_orig_src_port();");
+}
+
+static void
+encode_NF_LOOKUP_ORIG_SRC_PORT(
+ const struct ovnact_nf_lookup *nf_lookup,
+ const struct ovnact_encode_params *ep OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct mf_subfield dst = expr_resolve_field(&nf_lookup->dst);
+ ovs_assert(dst.field);
+
+ put_load(0, MFF_LOG_FLAGS, MLF_NF_LOOKUP_HIT_BIT, 1, ofpacts);
+ emit_resubmit(ofpacts, OFTABLE_NF_ORIG_SRC_PORT_LEARN);
+
+ struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
+ orm->dst = dst;
+ orm->src.field = mf_from_id(MFF_LOG_FLAGS);
+ orm->src.ofs = MLF_NF_LOOKUP_HIT_BIT;
+ orm->src.n_bits = 1;
+}
+
+static void
+parse_nf_lookup_orig_src_port(struct action_context *ctx,
+ struct expr_field *dst,
+ struct ovnact_nf_lookup *nf_lookup)
+{
+ lexer_get(ctx->lexer); /* Skip nf_lookup_orig_src_port. */
+ lexer_get(ctx->lexer); /* Skip '('. */
+
+ /* Validate that the destination is a 1-bit, modifiable field. */
+ char *error = expr_type_check(dst, 1, true, ctx->scope);
+ if (error) {
+ lexer_error(ctx->lexer, "%s", error);
+ free(error);
+ return;
+ }
+ nf_lookup->dst = *dst;
+
+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+ovnact_nf_lookup_free(struct ovnact_nf_lookup *nf_lookup OVS_UNUSED)
+{
+}
+
static void
parse_check_in_port_sec(struct action_context *ctx,
const struct expr_field *dst,
@@ -5897,6 +6080,10 @@ parse_set_action(struct action_context *ctx)
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
parse_lookup_fdb(
ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts));
+ } else if (!strcmp(ctx->lexer->token.s, "nf_lookup_orig_src_port")
+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+ parse_nf_lookup_orig_src_port(
+ ctx, &lhs, ovnact_put_NF_LOOKUP_ORIG_SRC_PORT(ctx->ovnacts));
} else if (!strcmp(ctx->lexer->token.s, "check_in_port_sec")
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
parse_check_in_port_sec(
@@ -6073,6 +6260,9 @@ parse_action(struct action_context *ctx)
ovnact_put_FLOOD_REMOTE(ctx->ovnacts);
} else if (lexer_match_id(ctx->lexer, "mirror")) {
parse_MIRROR_action(ctx);
+ } else if (lexer_match_id(ctx->lexer, "nf_learn_orig_src_port")) {
+ parse_nf_learn_orig_src_port(
+ ctx, ovnact_put_NF_LEARN_ORIG_SRC_PORT(ctx->ovnacts));
} else {
lexer_syntax_error(ctx->lexer, "expecting action");
}
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index e6143d7a9..5df75451c 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -1007,7 +1007,7 @@ 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 "3760014456 11249"
+#define OVN_NORTHD_PIPELINE_CSUM "3419371518 11361"
#define OVN_INTERNAL_MINOR_VER 14
/* Returns the OVN version. The caller must free the returned value. */
diff --git a/ovn-sb.xml b/ovn-sb.xml
index e45b63d73..526699740 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -1761,6 +1761,63 @@
</p>
</dd>
+ <dt>
+ <code>nf_learn_orig_src_port(ipv6 = <var>B</var>);</code>
+ </dt>
+
+ <dd>
+ <p>
+ Records the logical input port that the current IP packet
+ entered the logical switch on by learning a flow. The learned
+ flow matches a later packet of the same flow (same logical
+ datapath and IP source and destination addresses) when its
+ logical output port equals that input port, that is, when the
+ packet is about to be sent back out of the port it originally
+ arrived on.
+ </p>
+
+ <p>
+ <var>B</var> must be <code>true</code> to learn an IPv6 flow or
+ <code>false</code> to learn an IPv4 flow. The learned flow has a
+ 30 second idle timeout. This action is used together with
+ <code>nf_lookup_orig_src_port()</code> to drop the duplicate
+ copies that a network function (NF) redirection can produce when
+ the destination MAC address is still unknown, which would
+ otherwise cause MAC address flaps or L2 loops.
+ </p>
+
+ <p>
+ <b>Example:</b>
+ <code>nf_learn_orig_src_port(ipv6 = false);</code>
+ </p>
+ </dd>
+
+ <dt>
+ <code><var>R</var> = nf_lookup_orig_src_port();</code>
+ </dt>
+
+ <dd>
+ <p>
+ <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+ </p>
+
+ <p>
+ Looks up the flow learned by
+ <code>nf_learn_orig_src_port()</code>. Stores <code>1</code> in
+ <var>R</var> if the current packet matches a learned flow, that is,
+ if it is an IP packet that, after network function redirection, is
+ about to be sent back out of the logical input port it originally
+ arrived on. Otherwise stores <code>0</code>.
+ </p>
+
+ <p>
+ <b>Example:</b>
+ <code>
+ reg0[0] = nf_lookup_orig_src_port();
+ </code>
+ </p>
+ </dd>
+
<dt><code>nd_ns { <var>action</var>; </code>...<code> };</code></dt>
<dd>
<p>
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 8744ff6b3..c3d9a50cb 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -1648,5 +1648,6 @@ m4_define([OFTABLE_CT_STATE_SAVE], [85])
m4_define([OFTABLE_CT_ORIG_PROTO_LOAD], [86])
m4_define([OFTABLE_GET_REMOTE_FDB], [87])
m4_define([OFTABLE_LEARN_REMOTE_FDB], [88])
+m4_define([OFTABLE_NF_ORIG_SRC_PORT_LEARN], [89])
m4_define([OFTABLE_SAVE_INPORT_HEX], [m4_eval(OFTABLE_SAVE_INPORT, 16)])
diff --git a/tests/ovn.at b/tests/ovn.at
index 1995a989d..eaf56f3ed 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2151,6 +2151,25 @@ reg1[[1]] = lookup_fdb(outport, ip4.src);
reg1[[1]] = lookup_fdb(ip4.src, eth.src);
Cannot use numeric field ip4.src where string field is required.
+# nf_learn_orig_src_port / nf_lookup_orig_src_port
+nf_learn_orig_src_port(ipv6 = false);
+ encodes as
learn(table=OFTABLE_NF_ORIG_SRC_PORT_LEARN,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],NXM_OF_IP_DST[[]],NXM_NX_REG15[[]]=NXM_NX_REG14[[0..-1]],load:0x1->NXM_NX_REG10[[25]])
+
+nf_learn_orig_src_port(ipv6 = true);
+ encodes as
learn(table=OFTABLE_NF_ORIG_SRC_PORT_LEARN,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],NXM_NX_IPV6_DST[[]],NXM_NX_REG15[[]]=NXM_NX_REG14[[0..-1]],load:0x1->NXM_NX_REG10[[25]])
+
+nf_learn_orig_src_port();
+ Syntax error at `)' invalid parameter.
+
+nf_learn_orig_src_port(ipv6 = maybe);
+ Syntax error at `maybe' expecting true or false.
+
+reg0[[0]] = nf_lookup_orig_src_port();
+ encodes as
set_field:0/0x2000000->reg10,resubmit(,OFTABLE_NF_ORIG_SRC_PORT_LEARN),move:NXM_NX_REG10[[25]]->NXM_NX_XXREG0[[96]]
+
+reg0 = nf_lookup_orig_src_port();
+ Cannot use 32-bit field reg0[[0..31]] where 1-bit field is required.
+
# check_in_port_sec
reg0[[0]] = check_in_port_sec();
encodes as
set_field:0/0x1000->reg10,resubmit(,OFTABLE_CHK_IN_PORT_SEC),move:NXM_NX_REG10[[12]]->NXM_NX_XXREG0[[96]]
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index c09a9041f..d5358a425 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -3603,6 +3603,21 @@ trace_actions(const struct ovnact *ovnacts, size_t
ovnacts_len,
case OVNACT_CT_STATE_SAVE:
execute_ct_save_state(ovnact_get_CT_STATE_SAVE(a), uflow, super);
break;
+ case OVNACT_NF_LEARN_ORIG_SRC_PORT:
+ /* Nothing to do for tracing. */
+ break;
+ case OVNACT_NF_LOOKUP_ORIG_SRC_PORT: {
+ /* Assume a lookup miss and clear the destination subfield. */
+ const struct ovnact_nf_lookup *nf_lookup =
+ ovnact_get_NF_LOOKUP_ORIG_SRC_PORT(a);
+ struct mf_subfield dst = expr_resolve_field(&nf_lookup->dst);
+ union mf_subvalue sv = { .u8_val = 0 };
+ mf_write_subfield_flow(&dst, &sv, uflow);
+ ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
+ "/* nf_lookup_orig_src_port miss "
+ "(learn table not modelled). */");
+ break;
+ }
}
}
ofpbuf_uninit(&stack);
--
2.43.5
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev