Skip to content

Commit 2db8e2f

Browse files
committed
actions: Add chk_evpn_arp action and EVPN side table.
Add the chk_evpn_arp(ip) action that performs an EVPN ARP/ND lookup in a dedicated side table (OFTABLE_EVPN_ARP_LOOKUP, table 113). The action takes one IP field argument (32-bit for IPv4 or 128-bit for IPv6) and sets a 1-bit destination regbit to indicate whether a match was found. On a hit, the side table loads the resolved MAC address into eth.dst and sets the MLF_EVPN_LOOKUP_BIT (bit 25 of MFF_LOG_FLAGS). The caller reads the MAC from eth.dst directly in response flows. For IPv4, the IP argument is stored in MFF_REG0. For IPv6 it is stored in MFF_XXREG0. This commit adds the infrastructure only: OVNACT definition, parse/format/encode/free functions, OFTABLE definition, and ovn-trace stub. No flows use the action yet. Assisted-by: Claude Opus 4.6, Claude Code Signed-off-by: Ales Musil <amusil@redhat.com>
1 parent fe1d38f commit 2db8e2f

11 files changed

Lines changed: 207 additions & 1 deletion

File tree

controller/lflow.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
950950
.ct_proto_load_table = OFTABLE_CT_ORIG_PROTO_LOAD,
951951
.flood_remote_table = OFTABLE_FLOOD_REMOTE_CHASSIS,
952952
.ct_state_save_table = OFTABLE_CT_STATE_SAVE,
953+
.evpn_arp_ptable = OFTABLE_EVPN_ARP_LOOKUP,
953954
.ctrl_meter_id = ctrl_meter_id,
954955
.common_nat_ct_zone = get_common_nat_zone(ldp),
955956
};

controller/lflow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ struct uuid;
104104
#define OFTABLE_CT_ORIG_PROTO_LOAD 110
105105
#define OFTABLE_GET_REMOTE_FDB 111
106106
#define OFTABLE_LEARN_REMOTE_FDB 112
107+
#define OFTABLE_EVPN_ARP_LOOKUP 113
107108

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

include/ovn/actions.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ struct collector_set_ids;
139139
OVNACT(CT_ORIG_PROTO, ovnact_result) \
140140
OVNACT(FLOOD_REMOTE, ovnact_null) \
141141
OVNACT(CT_STATE_SAVE, ovnact_result) \
142+
OVNACT(CHK_EVPN_ARP, ovnact_chk_evpn_arp) \
142143
OVNACT(MIRROR, ovnact_mirror) \
143144

144145
/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
@@ -549,6 +550,13 @@ struct ovnact_commit_lb_aff {
549550
uint16_t timeout;
550551
};
551552

553+
/* OVNACT_CHK_EVPN_ARP. */
554+
struct ovnact_chk_evpn_arp {
555+
struct ovnact ovnact;
556+
struct expr_field dst; /* 1-bit destination field. */
557+
struct expr_field ip; /* 32-bit or 128-bit IP address. */
558+
};
559+
552560
/* OVNACT_MIRROR. */
553561
struct ovnact_mirror {
554562
struct ovnact ovnact;
@@ -972,6 +980,8 @@ struct ovnact_encode_params {
972980
* 'get_remote_fdb' to resubmit. */
973981
uint8_t fdb_lookup_ptable; /* OpenFlow table for
974982
* 'lookup_fdb' to resubmit. */
983+
uint8_t evpn_arp_ptable; /* OpenFlow table for
984+
* 'chk_evpn_arp' to resubmit. */
975985
uint8_t in_port_sec_ptable; /* OpenFlow table for
976986
* 'check_in_port_sec' to resubmit. */
977987
uint8_t out_port_sec_ptable; /* OpenFlow table for

include/ovn/logical-fields.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ enum mff_log_flags_bits {
149149
MLF_IGMP_IGMP_SNOOP_INJECT_BIT = 22,
150150
MLF_PKT_SAMPLED_BIT = 23,
151151
MLF_RECIRC_BIT = 24,
152+
MLF_EVPN_LOOKUP_BIT = 25,
152153
MLF_NETWORK_ID_START_BIT = 28,
153154
MLF_NETWORK_ID_END_BIT = 31,
154155
};
@@ -225,6 +226,9 @@ enum mff_log_flags {
225226
/* Indicate the packet has been processed by LOCAL table once before. */
226227
MLF_RECIRC = (1 << MLF_RECIRC_BIT),
227228

229+
/* Indicate that the lookup in the EVPN ARP table was successful. */
230+
MLF_EVPN_LOOKUP = (1 << MLF_EVPN_LOOKUP_BIT),
231+
228232
/* Assign network ID to packet to choose correct network for snat when
229233
* lb_force_snat_ip=router_ip. */
230234
MLF_NETWORK_ID = (OVN_MAX_NETWORK_ID << MLF_NETWORK_ID_START_BIT),

lib/actions.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2600,6 +2600,80 @@ ovnact_lookup_mac_bind_ip_free(
26002600

26012601
}
26022602

2603+
/* chk_evpn_arp action. */
2604+
2605+
static void
2606+
format_CHK_EVPN_ARP(const struct ovnact_chk_evpn_arp *chk, struct ds *s)
2607+
{
2608+
expr_field_format(&chk->dst, s);
2609+
ds_put_cstr(s, " = chk_evpn_arp(");
2610+
expr_field_format(&chk->ip, s);
2611+
ds_put_cstr(s, ");");
2612+
}
2613+
2614+
static void
2615+
encode_CHK_EVPN_ARP(const struct ovnact_chk_evpn_arp *chk,
2616+
const struct ovnact_encode_params *ep,
2617+
struct ofpbuf *ofpacts)
2618+
{
2619+
struct mf_subfield ip_sf = expr_resolve_field(&chk->ip);
2620+
const struct arg args[] = {
2621+
{ ip_sf, ip_sf.n_bits == 32 ? MFF_REG0 : MFF_XXREG0 },
2622+
};
2623+
encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
2624+
2625+
put_load(0, MFF_LOG_FLAGS, MLF_EVPN_LOOKUP_BIT, 1, ofpacts);
2626+
emit_resubmit(ofpacts, ep->evpn_arp_ptable);
2627+
2628+
struct mf_subfield dst = expr_resolve_field(&chk->dst);
2629+
struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
2630+
orm->dst = dst;
2631+
orm->src.field = mf_from_id(MFF_LOG_FLAGS);
2632+
orm->src.ofs = MLF_EVPN_LOOKUP_BIT;
2633+
orm->src.n_bits = 1;
2634+
2635+
encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
2636+
}
2637+
2638+
static void
2639+
parse_chk_evpn_arp(struct action_context *ctx,
2640+
const struct expr_field *dst,
2641+
struct ovnact_chk_evpn_arp *chk)
2642+
{
2643+
/* Validate destination is a 1-bit modifiable field. */
2644+
char *error = expr_type_check(dst, 1, true, ctx->scope);
2645+
if (error) {
2646+
lexer_error(ctx->lexer, "%s", error);
2647+
free(error);
2648+
return;
2649+
}
2650+
chk->dst = *dst;
2651+
2652+
lexer_get(ctx->lexer); /* Skip "chk_evpn_arp". */
2653+
lexer_get(ctx->lexer); /* Skip "(". */
2654+
2655+
if (!expr_field_parse(ctx->lexer, ctx->pp->symtab,
2656+
&chk->ip, &ctx->prereqs)) {
2657+
return;
2658+
}
2659+
2660+
/* Validate IP width: must be 32 (IPv4) or 128 (IPv6). */
2661+
struct mf_subfield ip_sf = expr_resolve_field(&chk->ip);
2662+
if (ip_sf.n_bits != 32 && ip_sf.n_bits != 128) {
2663+
lexer_error(ctx->lexer,
2664+
"chk_evpn_arp requires a 32-bit or "
2665+
"128-bit IP field");
2666+
return;
2667+
}
2668+
2669+
lexer_force_match(ctx->lexer, LEX_T_RPAREN);
2670+
}
2671+
2672+
static void
2673+
ovnact_chk_evpn_arp_free(struct ovnact_chk_evpn_arp *chk OVS_UNUSED)
2674+
{
2675+
}
2676+
26032677

26042678
static void
26052679
parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o,
@@ -5877,6 +5951,10 @@ parse_set_action(struct action_context *ctx)
58775951
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
58785952
parse_lookup_mac_bind_ip(ctx, &lhs, 128,
58795953
ovnact_put_LOOKUP_ND_IP(ctx->ovnacts));
5954+
} else if (!strcmp(ctx->lexer->token.s, "chk_evpn_arp")
5955+
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
5956+
parse_chk_evpn_arp(ctx, &lhs,
5957+
ovnact_put_CHK_EVPN_ARP(ctx->ovnacts));
58805958
} else if (!strcmp(ctx->lexer->token.s, "chk_lb_hairpin")
58815959
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
58825960
parse_chk_lb_hairpin(ctx, &lhs,

lib/ovn-util.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ 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 "3760014456 11249"
1010+
#define OVN_NORTHD_PIPELINE_CSUM "2700382548 11305"
10111011
#define OVN_INTERNAL_MINOR_VER 14
10121012

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

ovn-sb.xml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,47 @@
16961696
</p>
16971697
</dd>
16981698

1699+
<dt>
1700+
<code><var>R</var> = chk_evpn_arp(<var>A</var>);</code>
1701+
</dt>
1702+
1703+
<dd>
1704+
<p>
1705+
<b>Parameters</b>: 32-bit or 128-bit IP address field
1706+
<var>A</var>.
1707+
</p>
1708+
1709+
<p>
1710+
<b>Result</b>: stored to a 1-bit subfield <var>R</var>.
1711+
</p>
1712+
1713+
<p>
1714+
Looks up <var>A</var> in the EVPN ARP side table for the
1715+
current logical datapath. If a matching entry is found,
1716+
stores <code>1</code> in the 1-bit subfield <var>R</var>
1717+
and loads the resolved MAC address into
1718+
<code>eth.dst</code>. Otherwise, stores
1719+
<code>0</code> in <var>R</var> and <code>eth.dst</code>
1720+
is left unchanged.
1721+
</p>
1722+
1723+
<p>
1724+
This action is used for EVPN ARP/ND suppression on
1725+
logical switches. When an ARP request or ND solicitation
1726+
targets an IP address that was learned via EVPN, the
1727+
switch can proxy-reply using the MAC from
1728+
<code>eth.dst</code> instead of flooding the request
1729+
to remote VTEPs.
1730+
</p>
1731+
1732+
<p>
1733+
<b>Example:</b>
1734+
<code>
1735+
reg9[5] = chk_evpn_arp(arp.tpa);
1736+
</code>
1737+
</p>
1738+
</dd>
1739+
16991740
<dt><code><var>P</var> = get_fdb(<var>A</var>);</code></dt>
17001741

17011742
<dd>

tests/ovn-macros.at

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,5 +1649,6 @@ m4_define([OFTABLE_CT_STATE_SAVE], [109])
16491649
m4_define([OFTABLE_CT_ORIG_PROTO_LOAD], [110])
16501650
m4_define([OFTABLE_GET_REMOTE_FDB], [111])
16511651
m4_define([OFTABLE_LEARN_REMOTE_FDB], [112])
1652+
m4_define([OFTABLE_EVPN_ARP_LOOKUP], [113])
16521653

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

tests/ovn.at

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,38 @@ commit_lb_aff(vip = "[[::1]]:8080", backend = "[[::2]]:8080", proto = tcp, timeo
22172217
reg9[[6]] = chk_lb_aff();
22182218
encodes as set_field:0/0x4000->reg10,resubmit(,OFTABLE_CHK_LB_AFFINITY),move:NXM_NX_REG10[[14]]->OXM_OF_PKT_REG4[[6]]
22192219

2220+
# chk_evpn_arp
2221+
reg0[[0]] = chk_evpn_arp(arp.tpa);
2222+
encodes as push:NXM_NX_REG0[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_NX_REG0[[]],set_field:0/0x2000000->reg10,resubmit(,OFTABLE_EVPN_ARP_LOOKUP),move:NXM_NX_REG10[[25]]->NXM_NX_XXREG0[[96]],pop:NXM_NX_REG0[[]]
2223+
has prereqs eth.type == 0x806
2224+
2225+
reg2[[2]] = chk_evpn_arp(arp.tpa);
2226+
encodes as push:NXM_NX_REG0[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_NX_REG0[[]],set_field:0/0x2000000->reg10,resubmit(,OFTABLE_EVPN_ARP_LOOKUP),move:NXM_NX_REG10[[25]]->NXM_NX_XXREG0[[34]],pop:NXM_NX_REG0[[]]
2227+
has prereqs eth.type == 0x806
2228+
2229+
reg0[[0]] = chk_evpn_arp(ip6.dst);
2230+
encodes as push:NXM_NX_XXREG0[[]],push:NXM_NX_IPV6_DST[[]],pop:NXM_NX_XXREG0[[]],set_field:0/0x2000000->reg10,resubmit(,OFTABLE_EVPN_ARP_LOOKUP),move:NXM_NX_REG10[[25]]->NXM_NX_XXREG0[[96]],pop:NXM_NX_XXREG0[[]]
2231+
has prereqs eth.type == 0x86dd
2232+
2233+
reg0[[0]] = chk_evpn_arp(nd.target);
2234+
encodes as push:NXM_NX_XXREG0[[]],push:NXM_NX_ND_TARGET[[]],pop:NXM_NX_XXREG0[[]],set_field:0/0x2000000->reg10,resubmit(,OFTABLE_EVPN_ARP_LOOKUP),move:NXM_NX_REG10[[25]]->NXM_NX_XXREG0[[96]],pop:NXM_NX_XXREG0[[]]
2235+
has prereqs (icmp6.type == 0x87 || icmp6.type == 0x88) && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
2236+
2237+
reg0 = chk_evpn_arp(arp.tpa);
2238+
Cannot use 32-bit field reg0[[0..31]] where 1-bit field is required.
2239+
2240+
reg0[[0]] = chk_evpn_arp(eth.src);
2241+
chk_evpn_arp requires a 32-bit or 128-bit IP field
2242+
2243+
reg0[[0]] = chk_evpn_arp();
2244+
Syntax error at `)' expecting field name.
2245+
2246+
reg0[[0]] = chk_evpn_arp(arp.tpa, ip4.src);
2247+
Syntax error at `,' expecting `)'.
2248+
2249+
chk_evpn_arp;
2250+
Syntax error at `chk_evpn_arp' expecting action.
2251+
22202252
# push/pop
22212253
push(xxreg0);push(xxreg1[[10..20]]);push(eth.src);pop(xxreg0[[0..47]]);pop(xxreg0[[48..57]]);pop(xxreg1);
22222254
formats as push(xxreg0); push(xxreg1[[10..20]]); push(eth.src); pop(xxreg0[[0..47]]); pop(xxreg0[[48..57]]); pop(xxreg1);

tests/test-ovn.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
13921392
.ct_proto_load_table = OFTABLE_CT_ORIG_PROTO_LOAD,
13931393
.flood_remote_table = OFTABLE_FLOOD_REMOTE_CHASSIS,
13941394
.ct_state_save_table = OFTABLE_CT_STATE_SAVE,
1395+
.evpn_arp_ptable = OFTABLE_EVPN_ARP_LOOKUP,
13951396
.lflow_uuid.parts =
13961397
{ 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd},
13971398
.dp_key = 0xabcdef,

0 commit comments

Comments
 (0)