> 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 <[email protected]>
Fixing the nits spotted by Dumitru: Acked-by: Lorenzo Bianconi <[email protected]> Regards, Lorenzo > --- > controller/lflow.c | 1 + > controller/lflow.h | 1 + > include/ovn/actions.h | 10 +++++ > include/ovn/logical-fields.h | 4 ++ > lib/actions.c | 78 ++++++++++++++++++++++++++++++++++++ > lib/ovn-util.c | 2 +- > ovn-sb.xml | 41 +++++++++++++++++++ > tests/ovn-macros.at | 1 + > tests/ovn.at | 32 +++++++++++++++ > tests/test-ovn.c | 1 + > utilities/ovn-trace.c | 37 +++++++++++++++++ > 11 files changed, 207 insertions(+), 1 deletion(-) > > diff --git a/controller/lflow.c b/controller/lflow.c > index 382c2aecb..89ad6831f 100644 > --- a/controller/lflow.c > +++ b/controller/lflow.c > @@ -950,6 +950,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow > *lflow, > .ct_proto_load_table = OFTABLE_CT_ORIG_PROTO_LOAD, > .flood_remote_table = OFTABLE_FLOOD_REMOTE_CHASSIS, > .ct_state_save_table = OFTABLE_CT_STATE_SAVE, > + .evpn_arp_ptable = OFTABLE_EVPN_ARP_LOOKUP, > .ctrl_meter_id = ctrl_meter_id, > .common_nat_ct_zone = get_common_nat_zone(ldp), > }; > diff --git a/controller/lflow.h b/controller/lflow.h > index 786cf974e..0b7e53a10 100644 > --- a/controller/lflow.h > +++ b/controller/lflow.h > @@ -104,6 +104,7 @@ struct uuid; > #define OFTABLE_CT_ORIG_PROTO_LOAD 110 > #define OFTABLE_GET_REMOTE_FDB 111 > #define OFTABLE_LEARN_REMOTE_FDB 112 > +#define OFTABLE_EVPN_ARP_LOOKUP 113 > > /* Verify that table regions do not overlap. */ > BUILD_ASSERT_DECL(OFTABLE_LOG_INGRESS_PIPELINE + LOG_PIPELINE_INGRESS_LEN > diff --git a/include/ovn/actions.h b/include/ovn/actions.h > index 2ca8dac8f..4473e3297 100644 > --- a/include/ovn/actions.h > +++ b/include/ovn/actions.h > @@ -140,6 +140,7 @@ struct collector_set_ids; > OVNACT(FLOOD_REMOTE, ovnact_null) \ > OVNACT(CT_STATE_SAVE, ovnact_result) \ > OVNACT(MIRROR, ovnact_mirror) \ > + OVNACT(CHK_EVPN_ARP, ovnact_chk_evpn_arp) \ > > /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ > enum OVS_PACKED_ENUM ovnact_type { > @@ -549,6 +550,13 @@ struct ovnact_commit_lb_aff { > uint16_t timeout; > }; > > +/* OVNACT_CHK_EVPN_ARP. */ > +struct ovnact_chk_evpn_arp { > + struct ovnact ovnact; > + struct expr_field dst; /* 1-bit destination field. */ > + struct expr_field ip; /* 32-bit or 128-bit IP address. */ > +}; > + > /* OVNACT_MIRROR. */ > struct ovnact_mirror { > struct ovnact ovnact; > @@ -972,6 +980,8 @@ struct ovnact_encode_params { > * 'get_remote_fdb' to resubmit. */ > uint8_t fdb_lookup_ptable; /* OpenFlow table for > * 'lookup_fdb' to resubmit. */ > + uint8_t evpn_arp_ptable; /* OpenFlow table for > + * 'chk_evpn_arp' to resubmit. */ > uint8_t in_port_sec_ptable; /* OpenFlow table for > * 'check_in_port_sec' to resubmit. */ > uint8_t out_port_sec_ptable; /* OpenFlow table for > diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h > index 3c0fb22e7..8b4876530 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_EVPN_LOOKUP_BIT = 25, > MLF_NETWORK_ID_START_BIT = 28, > MLF_NETWORK_ID_END_BIT = 31, > }; > @@ -225,6 +226,9 @@ enum mff_log_flags { > /* Indicate the packet has been processed by LOCAL table once before. */ > MLF_RECIRC = (1 << MLF_RECIRC_BIT), > > + /* Indicate that the lookup in the EVPN ARP table was successful. */ > + MLF_EVPN_LOOKUP = (1 << MLF_EVPN_LOOKUP_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..3b58bca0c 100644 > --- a/lib/actions.c > +++ b/lib/actions.c > @@ -2600,6 +2600,80 @@ ovnact_lookup_mac_bind_ip_free( > > } > > +/* chk_evpn_arp action. */ > + > +static void > +format_CHK_EVPN_ARP(const struct ovnact_chk_evpn_arp *chk, struct ds *s) > +{ > + expr_field_format(&chk->dst, s); > + ds_put_cstr(s, " = chk_evpn_arp("); > + expr_field_format(&chk->ip, s); > + ds_put_cstr(s, ");"); > +} > + > +static void > +encode_CHK_EVPN_ARP(const struct ovnact_chk_evpn_arp *chk, > + const struct ovnact_encode_params *ep, > + struct ofpbuf *ofpacts) > +{ > + struct mf_subfield ip_sf = expr_resolve_field(&chk->ip); > + const struct arg args[] = { > + { ip_sf, ip_sf.n_bits == 32 ? MFF_REG0 : MFF_XXREG0 }, > + }; > + encode_setup_args(args, ARRAY_SIZE(args), ofpacts); > + > + put_load(0, MFF_LOG_FLAGS, MLF_EVPN_LOOKUP_BIT, 1, ofpacts); > + emit_resubmit(ofpacts, ep->evpn_arp_ptable); > + > + struct mf_subfield dst = expr_resolve_field(&chk->dst); > + 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_EVPN_LOOKUP_BIT; > + orm->src.n_bits = 1; > + > + encode_restore_args(args, ARRAY_SIZE(args), ofpacts); > +} > + > +static void > +parse_chk_evpn_arp(struct action_context *ctx, > + const struct expr_field *dst, > + struct ovnact_chk_evpn_arp *chk) > +{ > + /* Validate 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; > + } > + chk->dst = *dst; > + > + lexer_get(ctx->lexer); /* Skip "chk_evpn_arp". */ > + lexer_get(ctx->lexer); /* Skip '('. * */ > + > + if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, > + &chk->ip, &ctx->prereqs)) { > + return; > + } > + > + /* Validate IP width: must be 32 (IPv4) or 128 (IPv6). */ > + struct mf_subfield ip_sf = expr_resolve_field(&chk->ip); > + if (ip_sf.n_bits != 32 && ip_sf.n_bits != 128) { > + lexer_error(ctx->lexer, > + "chk_evpn_arp requires a 32-bit or " > + "128-bit IP field"); > + return; > + } > + > + lexer_force_match(ctx->lexer, LEX_T_RPAREN); > +} > + > +static void > +ovnact_chk_evpn_arp_free(struct ovnact_chk_evpn_arp *chk OVS_UNUSED) > +{ > +} > + > > static void > parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o, > @@ -5877,6 +5951,10 @@ parse_set_action(struct action_context *ctx) > && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { > parse_lookup_mac_bind_ip(ctx, &lhs, 128, > ovnact_put_LOOKUP_ND_IP(ctx->ovnacts)); > + } else if (!strcmp(ctx->lexer->token.s, "chk_evpn_arp") > + && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { > + parse_chk_evpn_arp(ctx, &lhs, > + ovnact_put_CHK_EVPN_ARP(ctx->ovnacts)); > } else if (!strcmp(ctx->lexer->token.s, "chk_lb_hairpin") > && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { > parse_chk_lb_hairpin(ctx, &lhs, > diff --git a/lib/ovn-util.c b/lib/ovn-util.c > index e6143d7a9..cc5431a11 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 "951247664 11305" > #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..9b453a372 100644 > --- a/ovn-sb.xml > +++ b/ovn-sb.xml > @@ -1696,6 +1696,47 @@ > </p> > </dd> > > + <dt> > + <code><var>R</var> = chk_evpn_arp(<var>A</var>);</code> > + </dt> > + > + <dd> > + <p> > + <b>Parameters</b>: 32-bit or 128-bit IP address field > + <var>A</var>. > + </p> > + > + <p> > + <b>Result</b>: stored to a 1-bit subfield <var>R</var>. > + </p> > + > + <p> > + Looks up <var>A</var> in the EVPN ARP side table for the > + current logical datapath. If a matching entry is found, > + stores <code>1</code> in the 1-bit subfield <var>R</var> > + and loads the resolved MAC address into > + <code>eth.dst</code>. Otherwise, stores > + <code>0</code> in <var>R</var> and <code>eth.dst</code> > + is left unchanged. > + </p> > + > + <p> > + This action is used for EVPN ARP/ND suppression on > + logical switches. When an ARP request or ND solicitation > + targets an IP address that was learned via EVPN, the > + switch can proxy-reply using the MAC from > + <code>eth.dst</code> instead of flooding the request > + to remote VTEPs. > + </p> > + > + <p> > + <b>Example:</b> > + <code> > + reg9[5] = chk_evpn_arp(arp.tpa); > + </code> > + </p> > + </dd> > + > <dt><code><var>P</var> = get_fdb(<var>A</var>);</code></dt> > > <dd> > diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at > index c4f80642d..d96e8e61f 100644 > --- a/tests/ovn-macros.at > +++ b/tests/ovn-macros.at > @@ -1649,5 +1649,6 @@ m4_define([OFTABLE_CT_STATE_SAVE], [109]) > m4_define([OFTABLE_CT_ORIG_PROTO_LOAD], [110]) > m4_define([OFTABLE_GET_REMOTE_FDB], [111]) > m4_define([OFTABLE_LEARN_REMOTE_FDB], [112]) > +m4_define([OFTABLE_EVPN_ARP_LOOKUP], [113]) > > m4_define([OFTABLE_SAVE_INPORT_HEX], [m4_eval(OFTABLE_SAVE_INPORT, 16)]) > diff --git a/tests/ovn.at b/tests/ovn.at > index 272346dd7..2d2dcb385 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -2217,6 +2217,38 @@ commit_lb_aff(vip = "[[::1]]:8080", backend = > "[[::2]]:8080", proto = tcp, timeo > reg9[[6]] = chk_lb_aff(); > encodes as > set_field:0/0x4000->reg10,resubmit(,OFTABLE_CHK_LB_AFFINITY),move:NXM_NX_REG10[[14]]->OXM_OF_PKT_REG4[[6]] > > +# chk_evpn_arp > +reg0[[0]] = chk_evpn_arp(arp.tpa); > + 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[[]] > + has prereqs eth.type == 0x806 > + > +reg2[[2]] = chk_evpn_arp(arp.tpa); > + 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[[]] > + has prereqs eth.type == 0x806 > + > +reg0[[0]] = chk_evpn_arp(ip6.dst); > + 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[[]] > + has prereqs eth.type == 0x86dd > + > +reg0[[0]] = chk_evpn_arp(nd.target); > + 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[[]] > + 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) > + > +reg0 = chk_evpn_arp(arp.tpa); > + Cannot use 32-bit field reg0[[0..31]] where 1-bit field is required. > + > +reg0[[0]] = chk_evpn_arp(eth.src); > + chk_evpn_arp requires a 32-bit or 128-bit IP field > + > +reg0[[0]] = chk_evpn_arp(); > + Syntax error at `)' expecting field name. > + > +reg0[[0]] = chk_evpn_arp(arp.tpa, ip4.src); > + Syntax error at `,' expecting `)'. > + > +chk_evpn_arp; > + Syntax error at `chk_evpn_arp' expecting action. > + > # push/pop > > push(xxreg0);push(xxreg1[[10..20]]);push(eth.src);pop(xxreg0[[0..47]]);pop(xxreg0[[48..57]]);pop(xxreg1); > formats as push(xxreg0); push(xxreg1[[10..20]]); push(eth.src); > pop(xxreg0[[0..47]]); pop(xxreg0[[48..57]]); pop(xxreg1); > diff --git a/tests/test-ovn.c b/tests/test-ovn.c > index 114e60d65..f88d44d3d 100644 > --- a/tests/test-ovn.c > +++ b/tests/test-ovn.c > @@ -1392,6 +1392,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx > OVS_UNUSED) > .ct_proto_load_table = OFTABLE_CT_ORIG_PROTO_LOAD, > .flood_remote_table = OFTABLE_FLOOD_REMOTE_CHASSIS, > .ct_state_save_table = OFTABLE_CT_STATE_SAVE, > + .evpn_arp_ptable = OFTABLE_EVPN_ARP_LOOKUP, > .lflow_uuid.parts = > { 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd}, > .dp_key = 0xabcdef, > diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c > index c09a9041f..c0e04262c 100644 > --- a/utilities/ovn-trace.c > +++ b/utilities/ovn-trace.c > @@ -2242,6 +2242,39 @@ execute_lookup_mac_bind_ip(const struct > ovnact_lookup_mac_bind_ip *bind, > mf_write_subfield_flow(&dst, &sv, uflow); > } > > +static void > +execute_chk_evpn_arp(const struct ovnact_chk_evpn_arp *chk, > + const struct ovntrace_datapath *dp OVS_UNUSED, > + struct flow *uflow, > + struct ovs_list *super) > +{ > + /* Get IP address. */ > + struct mf_subfield ip_sf = expr_resolve_field(&chk->ip); > + ovs_assert(ip_sf.n_bits == 32 || ip_sf.n_bits == 128); > + union mf_subvalue ip_sv; > + mf_read_subfield(&ip_sf, uflow, &ip_sv); > + struct in6_addr ip = (ip_sf.n_bits == 32 > + ? in6_addr_mapped_ipv4(ip_sv.ipv4) > + : ip_sv.ipv6); > + > + struct ds ip_s = DS_EMPTY_INITIALIZER; > + ipv6_format_mapped(&ip, &ip_s); > + > + /* EVPN entries only exist in ovn-controller's OpenFlow tables > + * (OFTABLE_EVPN_ARP_LOOKUP), not in the SB database, so > + * ovn-trace cannot resolve them. Report the miss and set > + * the result to 0. */ > + ovntrace_node_append(super, OVNTRACE_NODE_ACTION, > + "/* EVPN ARP lookup for %s: " > + "not available in trace. */", ds_cstr(&ip_s)); > + ds_destroy(&ip_s); > + > + /* Set the result bit to 0 (miss). */ > + struct mf_subfield dst = expr_resolve_field(&chk->dst); > + union mf_subvalue sv = { .u8_val = 0 }; > + mf_write_subfield_flow(&dst, &sv, uflow); > +} > + > static void > execute_lookup_fdb(const struct ovnact_lookup_fdb *lookup_fdb, > const struct ovntrace_datapath *dp, > @@ -3603,6 +3636,10 @@ 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_CHK_EVPN_ARP: > + execute_chk_evpn_arp(ovnact_get_CHK_EVPN_ARP(a), dp, uflow, > + super); > + break; > } > } > ofpbuf_uninit(&stack); > -- > 2.54.0 > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
_______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
