Skip to content

Commit cc6a66e

Browse files
committed
controller: Populate EVPN ARP side table for logical switches.
Extend physical_consider_evpn_arp() to also install flows in the dedicated EVPN ARP side table (OFTABLE_EVPN_ARP_LOOKUP, table 113) for the switch datapath. These flows are matched by the chk_evpn_arp() action added in the previous commit. Each flow matches on metadata (switch dp_key) and IP address (reg0 for IPv4, xxreg0 for IPv6). On a hit, it loads the resolved MAC into eth.dst and sets MLF_EVPN_LOOKUP_BIT. The flows use the same UUID as the router-side neighbor flows, so ofctrl_remove_flows() in physical_handle_evpn_arp_changes() automatically removes both router-side and switch-side flows when an EVPN ARP entry is deleted. Assisted-by: Claude Opus 4.6, Claude Code Signed-off-by: Ales Musil <amusil@redhat.com>
1 parent 2db8e2f commit cc6a66e

2 files changed

Lines changed: 102 additions & 2 deletions

File tree

controller/physical.c

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3689,6 +3689,8 @@ physical_consider_evpn_fdb(const struct evpn_fdb *fdb,
36893689
static void
36903690
physical_consider_evpn_arp(const struct hmap *local_datapaths,
36913691
const struct evpn_arp *arp,
3692+
struct ofpbuf *ofpacts,
3693+
struct match *match,
36923694
struct ovn_desired_flow_table *flow_table)
36933695
{
36943696
/* Walk connected OVN routers and install neighbor flows for the ARPs
@@ -3706,6 +3708,38 @@ physical_consider_evpn_arp(const struct hmap *local_datapaths,
37063708
consider_neighbor_flow(remote_pb, &arp->flow_uuid, &arp->ip, arp->mac,
37073709
flow_table, arp->priority, false);
37083710
}
3711+
3712+
/* Install EVPN ARP lookup flows in the dedicated side table for the
3713+
* switch datapath. These flows are matched by the chk_evpn_arp()
3714+
* action. On a hit they load the resolved MAC into eth.dst
3715+
* and set MLF_EVPN_LOOKUP_BIT. */
3716+
const struct in6_addr *ip = &arp->ip;
3717+
struct eth_addr mac = arp->mac;
3718+
const struct local_datapath *dp = arp->ldp;
3719+
3720+
match_init_catchall(match);
3721+
match_set_metadata(match, htonll(dp->datapath->tunnel_key));
3722+
3723+
if (IN6_IS_ADDR_V4MAPPED(ip)) {
3724+
ovs_be32 ip4 = in6_addr_get_mapped_ipv4(ip);
3725+
match_set_reg(match, 0, ntohl(ip4));
3726+
} else {
3727+
ovs_be128 value;
3728+
memcpy(&value, ip, sizeof(value));
3729+
match_set_xxreg(match, 0, ntoh128(value));
3730+
}
3731+
3732+
ofpbuf_clear(ofpacts);
3733+
3734+
/* Load resolved MAC into eth.dst. */
3735+
put_load_bytes(mac.ea, sizeof mac.ea,
3736+
MFF_ETH_DST, 0, 48, ofpacts);
3737+
/* Set the EVPN lookup result bit. */
3738+
put_load(1, MFF_LOG_FLAGS, MLF_EVPN_LOOKUP_BIT, 1, ofpacts);
3739+
3740+
ofctrl_add_flow(flow_table, OFTABLE_EVPN_ARP_LOOKUP, arp->priority,
3741+
arp->flow_uuid.parts[0], match, ofpacts,
3742+
&arp->flow_uuid);
37093743
}
37103744

37113745
static void
@@ -3753,7 +3787,8 @@ physical_eval_evpn_flows(const struct physical_ctx *ctx,
37533787

37543788
const struct evpn_arp *arp;
37553789
HMAP_FOR_EACH (arp, hmap_node, ctx->evpn_arps) {
3756-
physical_consider_evpn_arp(ctx->local_datapaths, arp, flow_table);
3790+
physical_consider_evpn_arp(ctx->local_datapaths, arp, ofpacts,
3791+
&match, flow_table);
37573792
}
37583793
}
37593794

@@ -3967,14 +4002,19 @@ physical_handle_evpn_arp_changes(const struct hmap *local_datapaths,
39674002
const struct hmapx *updated_arps,
39684003
const struct uuidset *removed_arps)
39694004
{
4005+
struct ofpbuf ofpacts;
4006+
ofpbuf_init(&ofpacts, 0);
4007+
struct match match;
39704008

39714009
const struct hmapx_node *node;
39724010
HMAPX_FOR_EACH (node, updated_arps) {
39734011
const struct evpn_arp *arp = node->data;
39744012

39754013
ofctrl_remove_flows(flow_table, &arp->flow_uuid);
3976-
physical_consider_evpn_arp(local_datapaths, arp, flow_table);
4014+
physical_consider_evpn_arp(local_datapaths, arp, &ofpacts,
4015+
&match, flow_table);
39774016
}
4017+
ofpbuf_uninit(&ofpacts);
39784018

39794019
const struct uuidset_node *uuidset_node;
39804020
UUIDSET_FOR_EACH (uuidset_node, removed_arps) {

tests/system-ovn.at

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18540,6 +18540,66 @@ ovn-appctl evpn/vtep-arp-list > arp_after
1854018540

1854118541
check diff -q arp_before arp_after
1854218542

18543+
AS_BOX([EVPN ARP/ND side table flows])
18544+
dnl Verify that EVPN ARP entries also produce flows in the dedicated
18545+
dnl side table (OFTABLE_EVPN_ARP_LOOKUP) for the switch datapath.
18546+
dnl These flows match on metadata+IP and load the resolved MAC into
18547+
dnl eth.dst while setting the EVPN lookup bit (reg10[25]).
18548+
dnl Verify we have 6 flows: 3 IPv4 + 3 IPv6.
18549+
AT_CHECK([test "$(ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18550+
grep -c priority)" = "6"])
18551+
18552+
dnl Verify each IPv4 EVPN entry has a side table flow.
18553+
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18554+
grep "reg0=0xac100132" | grep -q "metadata=0x$dp_key"])
18555+
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18556+
grep "reg0=0xac10013c" | grep -q "metadata=0x$dp_key"])
18557+
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18558+
grep "reg0=0xac100146" | grep -q "metadata=0x$dp_key"])
18559+
18560+
dnl Verify each IPv6 EVPN entry has a side table flow.
18561+
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18562+
grep "reg3=0x50" | grep -q "metadata=0x$dp_key"])
18563+
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18564+
grep "reg3=0x60" | grep -q "metadata=0x$dp_key"])
18565+
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18566+
grep "reg3=0x70" | grep -q "metadata=0x$dp_key"])
18567+
18568+
dnl Verify recompute stability for side table flows.
18569+
ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18570+
grep priority | awk '{print $[7], $[8]}' | sort > evpn_side_before
18571+
18572+
check ovn-appctl inc-engine/recompute
18573+
check ovn-nbctl --wait=hv sync
18574+
18575+
ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18576+
grep priority | awk '{print $[7], $[8]}' | sort > evpn_side_after
18577+
AT_CHECK([diff -q evpn_side_before evpn_side_after])
18578+
18579+
dnl Verify side table flows are removed when EVPN entries are removed.
18580+
check ip neigh del dev $BR_NAME 172.16.1.50
18581+
check ip neigh del dev $BR_NAME 172.16.1.60
18582+
check ip neigh del dev $BR_NAME 172.16.1.70
18583+
18584+
check ip -6 neigh del dev $BR_NAME 172:16::50
18585+
check ip -6 neigh del dev $BR_NAME 172:16::60
18586+
check ip -6 neigh del dev $BR_NAME 172:16::70
18587+
18588+
OVS_WAIT_UNTIL([test "$(ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18589+
grep -c priority)" = "0"])
18590+
18591+
dnl Re-add the EVPN entries for subsequent tests.
18592+
check ip neigh add dev $BR_NAME 172.16.1.50 lladdr f0:00:0f:16:10:50 nud noarp extern_learn
18593+
check ip neigh add dev $BR_NAME 172.16.1.60 lladdr f0:00:0f:16:10:60 nud noarp extern_learn
18594+
check ip neigh add dev $BR_NAME 172.16.1.70 lladdr f0:00:0f:16:10:70 nud noarp extern_learn
18595+
18596+
check ip -6 neigh add dev $BR_NAME 172:16::50 lladdr f0:00:0f:16:10:50 nud noarp extern_learn
18597+
check ip -6 neigh add dev $BR_NAME 172:16::60 lladdr f0:00:0f:16:10:60 nud noarp extern_learn
18598+
check ip -6 neigh add dev $BR_NAME 172:16::70 lladdr f0:00:0f:16:10:70 nud noarp extern_learn
18599+
18600+
OVS_WAIT_UNTIL([test "$(ovs-ofctl dump-flows br-int table=OFTABLE_EVPN_ARP_LOOKUP | \
18601+
grep -c priority)" = "6"])
18602+
1854318603
# Remove remote workload ARP entries and check ovn-controller's state.
1854418604
check ip neigh del dev $BR_NAME 172.16.1.50
1854518605
check ip neigh del dev $BR_NAME 172.16.1.60

0 commit comments

Comments
 (0)