Skip to content

Commit a6b38af

Browse files
naveen-yerramnenidceara
authored andcommitted
actions: Add nf_learn/nf_lookup_orig_inport actions.
This commit introduces two NF-related logical actions, nf_learn_orig_inport() and nf_lookup_orig_inport(), used to store and retrieve a packet's original ingress port. These actions will be used by northd to identify and drop post-NF loopback copies in logical pipeline stages. - "nf_learn_orig_inport(ipv6 = true|false)" installs a learned flow in a new OFTABLE_NF_ORIG_INPORT_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_POST_NF_LOOP_BACK set. The learned flow has a 60s idle timeout. - "R = nf_lookup_orig_inport()" resubmits to the learn table and stores the result of the lookup (MLF_POST_NF_LOOP_BACK) into the 1-bit field R. Acked-by: Aditya Mehakare <aditya.mehakare@nutanix.com> CC: Sragdhara Datta Chaudhuri <sragdha.chaudhu@nutanix.com> Assisted-by: Claude Opus 4.7, Cursor Signed-off-by: Naveen Yerramneni <naveen.yerramneni@nutanix.com> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
1 parent f1dda4b commit a6b38af

9 files changed

Lines changed: 306 additions & 2 deletions

File tree

controller/lflow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ struct uuid;
105105
#define OFTABLE_GET_REMOTE_FDB 111
106106
#define OFTABLE_LEARN_REMOTE_FDB 112
107107
#define OFTABLE_EVPN_ARP_LOOKUP 113
108+
#define OFTABLE_NF_ORIG_INPORT_LEARN 114
108109

109110
/* Verify that table regions do not overlap. */
110111
BUILD_ASSERT_DECL(OFTABLE_LOG_INGRESS_PIPELINE + LOG_PIPELINE_INGRESS_LEN

include/ovn/actions.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ struct collector_set_ids;
140140
OVNACT(CT_STATE_SAVE, ovnact_result) \
141141
OVNACT(MIRROR, ovnact_mirror) \
142142
OVNACT(CHK_EVPN_ARP, ovnact_chk_evpn_arp) \
143+
OVNACT(NF_LEARN_ORIG_INPORT, ovnact_nf_learn) \
144+
OVNACT(NF_LOOKUP_ORIG_INPORT, ovnact_nf_lookup) \
143145

144146
/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
145147
enum OVS_PACKED_ENUM ovnact_type {
@@ -513,6 +515,18 @@ struct ovnact_lookup_fdb {
513515
struct expr_field dst; /* 1-bit destination field. */
514516
};
515517

518+
/* OVNACT_NF_LEARN_ORIG_INPORT. */
519+
struct ovnact_nf_learn {
520+
struct ovnact ovnact;
521+
bool ipv6;
522+
};
523+
524+
/* OVNACT_NF_LOOKUP_ORIG_INPORT. */
525+
struct ovnact_nf_lookup {
526+
struct ovnact ovnact;
527+
struct expr_field dst; /* 1-bit destination field. */
528+
};
529+
516530
/* OVNACT_SAMPLE */
517531
struct ovnact_sample {
518532
struct ovnact ovnact;

include/ovn/logical-fields.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ enum mff_log_flags_bits {
140140
MLF_PKT_SAMPLED_BIT = 23,
141141
MLF_RECIRC_BIT = 24,
142142
MLF_EVPN_LOOKUP_BIT = 25,
143+
MLF_POST_NF_LOOP_BACK_BIT = 26,
143144
MLF_NETWORK_ID_START_BIT = 28,
144145
MLF_NETWORK_ID_END_BIT = 31,
145146
};
@@ -219,6 +220,9 @@ enum mff_log_flags {
219220
/* Indicate that the lookup in the EVPN ARP table was successful. */
220221
MLF_EVPN_LOOKUP = (1 << MLF_EVPN_LOOKUP_BIT),
221222

223+
/* Set on a post-NF packet going back to the original ingress port. */
224+
MLF_POST_NF_LOOP_BACK = (1 << MLF_POST_NF_LOOP_BACK_BIT),
225+
222226
/* Assign network ID to packet to choose correct network for snat when
223227
* lb_force_snat_ip=router_ip. */
224228
MLF_NETWORK_ID = (OVN_MAX_NETWORK_ID << MLF_NETWORK_ID_START_BIT),

lib/actions.c

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4718,6 +4718,189 @@ ovnact_lookup_fdb_free(struct ovnact_lookup_fdb *get_fdb OVS_UNUSED)
47184718
{
47194719
}
47204720

4721+
static void
4722+
format_NF_LEARN_ORIG_INPORT(const struct ovnact_nf_learn *nf_learn,
4723+
struct ds *s)
4724+
{
4725+
ds_put_format(s, "nf_learn_orig_inport(ipv6 = %s);",
4726+
nf_learn->ipv6 ? "true" : "false");
4727+
}
4728+
4729+
/* Idle timeout, in seconds, of the flow installed by
4730+
* nf_learn_orig_inport(). */
4731+
#define NF_LEARN_ORIG_INPORT_IDLE_TIMEOUT_S 60
4732+
4733+
/* Adds a NXAST_LEARN spec matching the value of field 'id' (as it is in the
4734+
* packet that triggers the learn) on the same field of future packets. */
4735+
static void
4736+
nf_learn_put_match_field(struct ofpbuf *ofpacts, enum mf_field_id id)
4737+
{
4738+
struct ofpact_learn_spec *ol_spec =
4739+
ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
4740+
ol_spec->dst.field = mf_from_id(id);
4741+
ol_spec->dst.ofs = 0;
4742+
ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
4743+
ol_spec->n_bits = ol_spec->dst.n_bits;
4744+
ol_spec->dst_type = NX_LEARN_DST_MATCH;
4745+
ol_spec->src_type = NX_LEARN_SRC_FIELD;
4746+
ol_spec->src.field = mf_from_id(id);
4747+
}
4748+
4749+
static void
4750+
encode_NF_LEARN_ORIG_INPORT(const struct ovnact_nf_learn *nf_learn,
4751+
const struct ovnact_encode_params *ep,
4752+
struct ofpbuf *ofpacts)
4753+
{
4754+
size_t ol_offset = ofpacts->size;
4755+
struct ofpact_learn *ol = ofpact_put_LEARN(ofpacts);
4756+
struct match match = MATCH_CATCHALL_INITIALIZER;
4757+
struct ofpact_learn_spec *ol_spec;
4758+
unsigned int imm_bytes;
4759+
uint8_t *src_imm;
4760+
4761+
ol->flags = NX_LEARN_F_DELETE_LEARNED;
4762+
ol->idle_timeout = NF_LEARN_ORIG_INPORT_IDLE_TIMEOUT_S;
4763+
ol->hard_timeout = OFP_FLOW_PERMANENT;
4764+
ol->priority = OFP_DEFAULT_PRIORITY;
4765+
ol->table_id = OFTABLE_NF_ORIG_INPORT_LEARN;
4766+
ol->cookie = htonll(ep->lflow_uuid.parts[0]);
4767+
4768+
/* Match on the logical datapath. */
4769+
nf_learn_put_match_field(ofpacts, MFF_METADATA);
4770+
4771+
/* Match on the same ETH type as the packet that created the flow. */
4772+
ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
4773+
ol_spec->dst.field = mf_from_id(MFF_ETH_TYPE);
4774+
ol_spec->dst.ofs = 0;
4775+
ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
4776+
ol_spec->n_bits = ol_spec->dst.n_bits;
4777+
ol_spec->dst_type = NX_LEARN_DST_MATCH;
4778+
ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
4779+
union mf_value imm_eth_type = {
4780+
.be16 = nf_learn->ipv6 ? htons(ETH_TYPE_IPV6) : htons(ETH_TYPE_IP)
4781+
};
4782+
mf_write_subfield_value(&ol_spec->dst, &imm_eth_type, &match);
4783+
/* Push value last, as this may reallocate 'ol_spec'. */
4784+
imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
4785+
src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
4786+
memcpy(src_imm, &imm_eth_type, imm_bytes);
4787+
4788+
/* Match on the IP source and destination addresses. */
4789+
nf_learn_put_match_field(ofpacts,
4790+
nf_learn->ipv6 ? MFF_IPV6_SRC : MFF_IPV4_SRC);
4791+
nf_learn_put_match_field(ofpacts,
4792+
nf_learn->ipv6 ? MFF_IPV6_DST : MFF_IPV4_DST);
4793+
4794+
/* Match future packets whose logical output port equals the logical
4795+
* input port of the packet that created this flow. */
4796+
ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
4797+
ol_spec->dst.field = mf_from_id(MFF_LOG_OUTPORT);
4798+
ol_spec->dst.ofs = 0;
4799+
ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
4800+
ol_spec->n_bits = ol_spec->dst.n_bits;
4801+
ol_spec->dst_type = NX_LEARN_DST_MATCH;
4802+
ol_spec->src_type = NX_LEARN_SRC_FIELD;
4803+
ol_spec->src.field = mf_from_id(MFF_LOG_INPORT);
4804+
4805+
/* Set MLF_POST_NF_LOOP_BACK on matching packets. */
4806+
ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
4807+
ol_spec->dst.field = mf_from_id(MFF_LOG_FLAGS);
4808+
ol_spec->dst.ofs = MLF_POST_NF_LOOP_BACK_BIT;
4809+
ol_spec->dst.n_bits = 1;
4810+
ol_spec->n_bits = ol_spec->dst.n_bits;
4811+
ol_spec->dst_type = NX_LEARN_DST_LOAD;
4812+
ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
4813+
union mf_value imm_hit = { .u8 = 1 };
4814+
mf_write_subfield_value(&ol_spec->dst, &imm_hit, &match);
4815+
/* Push value last, as this may reallocate 'ol_spec'. */
4816+
imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
4817+
src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
4818+
memcpy(src_imm, &imm_hit, imm_bytes);
4819+
4820+
ol = ofpbuf_at_assert(ofpacts, ol_offset, sizeof *ol);
4821+
ofpact_finish_LEARN(ofpacts, &ol);
4822+
}
4823+
4824+
static void
4825+
parse_nf_learn_orig_inport(struct action_context *ctx,
4826+
struct ovnact_nf_learn *nf_learn)
4827+
{
4828+
lexer_force_match(ctx->lexer, LEX_T_LPAREN);
4829+
if (!lexer_match_id(ctx->lexer, "ipv6")) {
4830+
lexer_syntax_error(ctx->lexer, "invalid parameter");
4831+
return;
4832+
}
4833+
if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
4834+
return;
4835+
}
4836+
if (lexer_match_id(ctx->lexer, "true")) {
4837+
nf_learn->ipv6 = true;
4838+
} else if (lexer_match_id(ctx->lexer, "false")) {
4839+
nf_learn->ipv6 = false;
4840+
} else {
4841+
lexer_syntax_error(ctx->lexer, "expecting true or false");
4842+
return;
4843+
}
4844+
lexer_force_match(ctx->lexer, LEX_T_RPAREN);
4845+
}
4846+
4847+
static void
4848+
ovnact_nf_learn_free(struct ovnact_nf_learn *nf_learn OVS_UNUSED)
4849+
{
4850+
}
4851+
4852+
static void
4853+
format_NF_LOOKUP_ORIG_INPORT(const struct ovnact_nf_lookup *nf_lookup,
4854+
struct ds *s)
4855+
{
4856+
expr_field_format(&nf_lookup->dst, s);
4857+
ds_put_cstr(s, " = nf_lookup_orig_inport();");
4858+
}
4859+
4860+
static void
4861+
encode_NF_LOOKUP_ORIG_INPORT(
4862+
const struct ovnact_nf_lookup *nf_lookup,
4863+
const struct ovnact_encode_params *ep OVS_UNUSED,
4864+
struct ofpbuf *ofpacts)
4865+
{
4866+
struct mf_subfield dst = expr_resolve_field(&nf_lookup->dst);
4867+
ovs_assert(dst.field);
4868+
4869+
put_load(0, MFF_LOG_FLAGS, MLF_POST_NF_LOOP_BACK_BIT, 1, ofpacts);
4870+
emit_resubmit(ofpacts, OFTABLE_NF_ORIG_INPORT_LEARN);
4871+
4872+
struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
4873+
orm->dst = dst;
4874+
orm->src.field = mf_from_id(MFF_LOG_FLAGS);
4875+
orm->src.ofs = MLF_POST_NF_LOOP_BACK_BIT;
4876+
orm->src.n_bits = 1;
4877+
}
4878+
4879+
static void
4880+
parse_nf_lookup_orig_inport(struct action_context *ctx,
4881+
struct expr_field *dst,
4882+
struct ovnact_nf_lookup *nf_lookup)
4883+
{
4884+
lexer_get(ctx->lexer); /* Skip nf_lookup_orig_inport. */
4885+
lexer_force_match(ctx->lexer, LEX_T_LPAREN);
4886+
4887+
/* Validate that the destination is a 1-bit, modifiable field. */
4888+
char *error = expr_type_check(dst, 1, true, ctx->scope);
4889+
if (error) {
4890+
lexer_error(ctx->lexer, "%s", error);
4891+
free(error);
4892+
return;
4893+
}
4894+
nf_lookup->dst = *dst;
4895+
4896+
lexer_force_match(ctx->lexer, LEX_T_RPAREN);
4897+
}
4898+
4899+
static void
4900+
ovnact_nf_lookup_free(struct ovnact_nf_lookup *nf_lookup OVS_UNUSED)
4901+
{
4902+
}
4903+
47214904
static void
47224905
parse_check_in_port_sec(struct action_context *ctx,
47234906
const struct expr_field *dst,
@@ -5977,6 +6160,10 @@ parse_set_action(struct action_context *ctx)
59776160
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
59786161
parse_lookup_fdb(
59796162
ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts));
6163+
} else if (!strcmp(ctx->lexer->token.s, "nf_lookup_orig_inport")
6164+
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
6165+
parse_nf_lookup_orig_inport(
6166+
ctx, &lhs, ovnact_put_NF_LOOKUP_ORIG_INPORT(ctx->ovnacts));
59806167
} else if (!strcmp(ctx->lexer->token.s, "check_in_port_sec")
59816168
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
59826169
parse_check_in_port_sec(
@@ -6148,6 +6335,9 @@ parse_action(struct action_context *ctx)
61486335
ovnact_put_FLOOD_REMOTE(ctx->ovnacts);
61496336
} else if (lexer_match_id(ctx->lexer, "mirror")) {
61506337
parse_MIRROR_action(ctx);
6338+
} else if (lexer_match_id(ctx->lexer, "nf_learn_orig_inport")) {
6339+
parse_nf_learn_orig_inport(
6340+
ctx, ovnact_put_NF_LEARN_ORIG_INPORT(ctx->ovnacts));
61516341
} else {
61526342
lexer_syntax_error(ctx->lexer, "expecting action");
61536343
}

lib/ovn-util.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,8 +1007,8 @@ ip_address_and_port_from_lb_key(const char *key, char **ip_address,
10071007
*
10081008
* NOTE: If OVN_NORTHD_PIPELINE_CSUM is updated make sure to double check
10091009
* whether an update of OVN_INTERNAL_MINOR_VER is required. */
1010-
#define OVN_NORTHD_PIPELINE_CSUM "118971668 11318"
1011-
#define OVN_INTERNAL_MINOR_VER 15
1010+
#define OVN_NORTHD_PIPELINE_CSUM "1167787232 11430"
1011+
#define OVN_INTERNAL_MINOR_VER 16
10121012

10131013
/* Returns the OVN version. The caller must free the returned value. */
10141014
char *

ovn-sb.xml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,63 @@
18321832
</p>
18331833
</dd>
18341834

1835+
<dt>
1836+
<code>nf_learn_orig_inport(ipv6 = <var>B</var>);</code>
1837+
</dt>
1838+
1839+
<dd>
1840+
<p>
1841+
Records the logical input port that the current IP packet
1842+
entered the logical switch on by learning a flow. The learned
1843+
flow matches a later packet of the same flow (same logical
1844+
datapath and IP source and destination addresses) when its
1845+
logical output port equals that input port, that is, when the
1846+
packet is about to be sent back out of the port it originally
1847+
arrived on.
1848+
</p>
1849+
1850+
<p>
1851+
<var>B</var> must be <code>true</code> to learn an IPv6 flow or
1852+
<code>false</code> to learn an IPv4 flow. The learned flow has a
1853+
60 second idle timeout. This action is used together with
1854+
<code>nf_lookup_orig_inport()</code> to drop the duplicate
1855+
copies that a network function (NF) redirection can produce when
1856+
the destination MAC address is still unknown, which would
1857+
otherwise cause MAC address flaps or L2 loops.
1858+
</p>
1859+
1860+
<p>
1861+
<b>Example:</b>
1862+
<code>nf_learn_orig_inport(ipv6 = false);</code>
1863+
</p>
1864+
</dd>
1865+
1866+
<dt>
1867+
<code><var>R</var> = nf_lookup_orig_inport();</code>
1868+
</dt>
1869+
1870+
<dd>
1871+
<p>
1872+
<b>Result</b>: stored to a 1-bit subfield <var>R</var>.
1873+
</p>
1874+
1875+
<p>
1876+
Looks up the flow learned by
1877+
<code>nf_learn_orig_inport()</code>. Stores <code>1</code> in
1878+
<var>R</var> if the current packet matches a learned flow, that is,
1879+
if it is an IP packet that, after network function redirection, is
1880+
about to be sent back out of the logical input port it originally
1881+
arrived on. Otherwise stores <code>0</code>.
1882+
</p>
1883+
1884+
<p>
1885+
<b>Example:</b>
1886+
<code>
1887+
reg0[0] = nf_lookup_orig_inport();
1888+
</code>
1889+
</p>
1890+
</dd>
1891+
18351892
<dt><code>nd_ns { <var>action</var>; </code>...<code> };</code></dt>
18361893
<dd>
18371894
<p>

tests/ovn-macros.at

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,5 +1655,6 @@ m4_define([OFTABLE_CT_ORIG_PROTO_LOAD], [110])
16551655
m4_define([OFTABLE_GET_REMOTE_FDB], [111])
16561656
m4_define([OFTABLE_LEARN_REMOTE_FDB], [112])
16571657
m4_define([OFTABLE_EVPN_ARP_LOOKUP], [113])
1658+
m4_define([OFTABLE_NF_ORIG_INPORT_LEARN], [114])
16581659

16591660
m4_define([OFTABLE_SAVE_INPORT_HEX], [m4_eval(OFTABLE_SAVE_INPORT, 16)])

tests/ovn.at

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,25 @@ reg1[[1]] = lookup_fdb(outport, ip4.src);
21292129
reg1[[1]] = lookup_fdb(ip4.src, eth.src);
21302130
Cannot use numeric field ip4.src where string field is required.
21312131

2132+
# nf_learn_orig_inport / nf_lookup_orig_inport
2133+
nf_learn_orig_inport(ipv6 = false);
2134+
encodes as learn(table=OFTABLE_NF_ORIG_INPORT_LEARN,idle_timeout=60,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[[26]])
2135+
2136+
nf_learn_orig_inport(ipv6 = true);
2137+
encodes as learn(table=OFTABLE_NF_ORIG_INPORT_LEARN,idle_timeout=60,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[[26]])
2138+
2139+
nf_learn_orig_inport();
2140+
Syntax error at `)' invalid parameter.
2141+
2142+
nf_learn_orig_inport(ipv6 = maybe);
2143+
Syntax error at `maybe' expecting true or false.
2144+
2145+
reg0[[0]] = nf_lookup_orig_inport();
2146+
encodes as set_field:0/0x4000000->reg10,resubmit(,OFTABLE_NF_ORIG_INPORT_LEARN),move:NXM_NX_REG10[[26]]->NXM_NX_XXREG0[[96]]
2147+
2148+
reg0 = nf_lookup_orig_inport();
2149+
Cannot use 32-bit field reg0[[0..31]] where 1-bit field is required.
2150+
21322151
# check_in_port_sec
21332152
reg0[[0]] = check_in_port_sec();
21342153
encodes as set_field:0/0x1000->reg10,resubmit(,OFTABLE_CHK_IN_PORT_SEC),move:NXM_NX_REG10[[12]]->NXM_NX_XXREG0[[96]]

0 commit comments

Comments
 (0)