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]> --- 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
