Skip to content

Neigh suppression#588

Draft
rjarry wants to merge 19 commits intoDPDK:mainfrom
rjarry:neigh-suppression
Draft

Neigh suppression#588
rjarry wants to merge 19 commits intoDPDK:mainfrom
rjarry:neigh-suppression

Conversation

@rjarry
Copy link
Copy Markdown
Collaborator

@rjarry rjarry commented Apr 5, 2026

Depends on #566

Neighbor suppression and EVPN L3VNI implementation

L2 neighbor suppression datapath

  • Added bridge_neigh_suppress node that answers ARP requests and ICMPv6 Neighbor Solicitations for known remote nexthops:
    • ARP: in-place rewrite from request->reply using nexthop MAC/IP; forwards to iface_output.
    • NDP: rebuilds Ethernet+IPv6+ICMPv6 NA with Target Link-Layer Address option and correct checksum; forwards to iface_output.
  • Bridge input now routes non-unicast traffic to the suppression node when GR_BRIDGE_F_NEIGH_SUPPRESS is set.
  • FDB learning for unicast source addresses is gated by GR_BRIDGE_F_LEARN.
  • Added GR_BRIDGE_F_NEIGH_SUPPRESS flag and converted bridge flags to positive semantics (FLOOD/LEARN).

Neighbor snooping and learning

  • Added per-interface GR_IFACE_F_NEIGH_SNOOP; unsolicited ARP/NDP on interfaces with this flag are enqueued to control and can create nexthops.
  • ARP and NDP input processing now avoid dropping packets on missing nexthops when neigh_snoop is enabled, enabling learning from unsolicited packets.

EVPN L3VNI support and RMAC caching

  • New frr/l3vni_map module: dplane-thread caches for
    • VRF→VXLAN interface mapping (l3vni_set/del/get_vxlan),
    • (vrf, vtep IPv4) → RMAC cache (l3vni_rmac_set/del/get) used to populate L3 nexthop MACs.
  • VRF state extended with router MAC (gr_iface_info_vrf.mac) and GR_VRF_SET_MAC reconfiguration flag; kernel VRF device programmed with stored MAC.
  • VXLAN reconfig now inherits MAC from encap VRF when not explicitly set and synchronizes MAC to VRF when configuring L3VNI.

FRR integration and neighbor-driven nexthop lifecycle

  • Added grout_neigh_update_ctx(ctx) and wired zebra dplane to handle DPLANE_OP_NEIGH_INSTALL/UPDATE/DELETE routed from the FRR plugin.
  • grout_nexthop_change() calls grout_neigh_notify(), which enqueues neighbor install/delete dplane operations for eligible non-local, non-multicast neighbors with resolved MACs.
  • Neighbor updates cache/evict RMACs via l3vni_rmac_set/del (IPv4) and install/delete corresponding remote nexthops (neigh_install_nexthop/neigh_delete_nexthop).
  • EVPN Type-2 MAC+IP entries are installed as remote nexthops.

Nexthop API and control-plane changes

  • Introduced GR_NH_F_REMOTE and GR_NH_ORIGIN_NEIGH; nexthops learned from neighbors are treated like internal origin for ID allocation (nh_id forced 0).
  • Nexthop delete requests switched from ID-based deletes to key-based deletes (struct gr_nh_del_req carries struct gr_nexthop).
  • Added GR_NH_FLUSH request and nh_flush API handler to flush nexthops filtered by origin (skips L3 nexthops with local-address flags).
  • Nexthop lookup/creation paths updated to accept GR_NH_ORIGIN_NEIGH for ARP/NDP probe senders and dynamic L3 resolution.

Datapath metadata and VTEP propagation

  • Extended eth_output_mbuf_data with ip4_addr_t vtep; eth_output_process and ip{,6}_output set/propage vtep for VXLAN outputs so bridge/neigh suppression and VXLAN handling see the VTEP address.
  • Fixed mbuf private-data handling for VTEP addresses to avoid clobbering.

L4 parsing and tracing

  • Local L4 input node extended to parse TCP ports in addition to UDP; sport/dport recorded in trace metadata with trace_l4_format.

CLI and configuration

  • Added interface attribute neigh_snoop on/off and VRF mac attribute; VRF init generates a random MAC if not set.
  • Bridge CLI: flood on/off, learn on/off, neigh_suppress on/off; bridge creation defaults to FLOOD|LEARN.
  • Nexthop CLI: l3 add accepts optional remote flag to set GR_NH_F_REMOTE; added nexthop flush origin {neigh|static} command.
  • Introduced generic gr_flags_foreach macro and replaced per-type foreach macros where used.

Tests

  • Added smoke tests:
    • bridge_neigh_suppress_test.sh: validates ARP suppression on a bridge with a configured remote nexthop and ensures suppressed ARP replies contain the remote MAC and do not flood.
    • evpn_l3vpn_frr_test.sh: exercises EVPN Type-5 (L3 prefix) over L3VNI, verifies RMAC on route nexthops and overlay connectivity.
    • evpn_neigh_suppress_frr_test.sh: exercises EVPN Type-2 learning, ensures grout installs remote nexthops from Type-2 routes and verifies ARP suppression behavior end-to-end.

rjarry added 14 commits April 5, 2026 23:11
Add trace output showing source and destination ports for UDP and TCP
packets in l4_input_local. Also parse TCP headers to extract ports
before falling through to management plane forwarding.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
VRF interfaces now get a random MAC address at creation time. This MAC
will be used as the Router MAC for EVPN L3VNI type-5 routes.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
VXLAN interfaces now inherit from their VTEP VRF (encap_vrf_id) MAC
address instead of a random one. Some EVPN endpoints make the assumption
that there is a unique RMAC per VTEP.

When a VXLAN interface is in VRF mode (or moves to one), also
synchronize that MAC address to the VRF interface. This ensures the
VXLAN interface's MAC matches the Router MAC that FRR advertises in EVPN
type-5 routes, so incoming L3VPN packets pass eth_input's check.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
All GR_MBUF_PRIV_DATA_TYPE types overlay the same memory region. When
ip_output resolves a nexthop on a VXLAN interface, it writes
eth_output_mbuf_data->dst and ->ether_type which clobber
iface_mbuf_data->vtep. By the time vxlan_output reads the vtep field,
it contains garbage.

Add a vtep field to eth_output_mbuf_data and set it from the nexthop
gateway IP when the output interface is a VXLAN. In eth_output, save
the vtep value before writing iface_mbuf_data (which shares the same
memory) and restore it after.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Add a flag to mark nexthops learned from remote VTEPs via EVPN. These
nexthops carry a known IP+MAC pair from the control plane and are set
to GR_NH_S_REACHABLE with GR_NH_F_STATIC so they bypass ARP/ND
probing and aging.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
EVPN type-5 routes require two pieces of state that FRR delivers out
of order: the remote router MAC arrives via DPLANE_OP_NEIGH_INSTALL
before the nexthop via DPLANE_OP_NH_INSTALL. Both need to be combined
when grout_add_nexthop builds the GR_NH_ADD request.

Add l3vni_map to maintain two hash tables on the dplane thread:

  vrf_id -> vxlan_iface_id:  used to redirect nexthops from the VRF
  interface (FRR's SVI model) to the VXLAN interface so that ip_output
  routes packets into the tunnel.

  (vrf_id, vtep) -> RMAC:  caches the remote router MAC until the
  matching nexthop install arrives.

Both tables run exclusively on the dplane thread so no locking is
needed.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Handle EVPN type-5 (IP prefix) routes with symmetric IRB over VXLAN.

Present VRF-mode VXLAN interfaces to FRR as bridge slaves of the VRF
interface. FRR requires an SVI derived from a bridge master to bring the
L3VNI up and compute the Router MAC for type-5 routes. The VRF MAC (set
in a previous commit) serves as the Router MAC.

Handle DPLANE_OP_NEIGH_INSTALL/DELETE to cache remote router MACs
delivered by FRR before the corresponding nexthop install arrives. When
grout_add_nexthop processes an L3 nexthop in a VRF with an L3VNI, it
redirects the interface from VRF to VXLAN and applies the cached RMAC.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Verify EVPN type-5 IP prefix route exchange and L3 connectivity over
VXLAN between FRR+grout and a standalone FRR+Linux peer. Each side
has a VRF with an L3 VNI (1000) and a host on a local subnet. BGP
EVPN advertises connected prefixes as type-5 routes.

Grout uses a VXLAN in VRF mode (no bridge needed). The peer uses the
standard Linux bridge+SVI model. The test verifies L3VNI recognition,
type-5 route exchange, route installation in the VRF, RMAC presence
on the route nexthop, and end-to-end ping through the overlay.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Add a per-interface flag to control neighbor snooping. Configurable via
grcli: interface set port p0 neigh_snoop on/off.

When GR_IFACE_F_NEIGH_SNOOP is enabled on an interface, forward
unsolicited ARP replies and Neighbor Advertisements to the control plane
instead of dropping them. The existing arp_probe_input_cb and
ndp_probe_input_cb callbacks already handle creating nexthops for
unknown senders, so no control plane changes are needed.

This allows grout to passively learn IP+MAC bindings from ARP/NDP
traffic observed on an interface, similar to Linux's arp_accept sysctl.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
ARP/NDP-learned nexthops were created with GR_NH_ORIGIN_INTERNAL which
suppresses event generation. Add GR_NH_ORIGIN_NEIGH so these nexthops
produce GR_EVENT_NEXTHOP_NEW and GR_EVENT_NEXTHOP_UPDATE events that
the FRR plugin can receive and translate into neighbor entries for zebra.

The associated host routes (/32, /128) remain GR_NH_ORIGIN_INTERNAL
since they are internal plumbing and should not be visible to FRR.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Add GR_NH_FLUSH API request and grcli command to delete all nexthops
matching a given origin. This is useful for clearing ARP/NDP-learned
neighbors (origin neigh) which don't have IDs.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Change GR_NH_DEL to accept a full struct gr_nexthop instead of just an
ID. The handler uses nexthop_lookup() which first tries the ID if
specified or falls back to the type-specific key-based lookup (e.g.
vrf+addr for L3). This will allow the FRR plugin to delete remote
nexthops installed from EVPN neighbor entries without tracking their
auto-allocated IDs.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
When a grout nexthop event arrives for an L3 nexthop with a resolved
MAC, push a DPLANE_OP_NEIGH_INSTALL to zebra. On deletion, push
DPLANE_OP_NEIGH_DELETE. This makes FRR aware of locally learned
neighbors so it can advertise them as Type-2 MAC+IP routes via BGP EVPN.

The notification is called before the nh_id check in
grout_nexthop_change so that nexthops with nh_id=0 (auto-allocated by
the ARP/NDP control plane) still produce neighbor entries in zebra even
though they are not registered as zebra nexthop groups.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
When the dplane plugin receives DPLANE_OP_NEIGH_INSTALL for a remote
MAC/IP (from EVPN Type-2 routes), install the entry as a grout nexthop
with GR_NH_F_REMOTE. The iface_id is resolved from the FRR ifindex which
points to the bridge (SVI) interface. This allows the bridge suppress
node to look up the nexthop and answer ARP/ND requests locally.

Both IPv4 and IPv6 neighbors are supported. The existing L3VNI RMAC
caching for Type-5 routes is preserved alongside.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

This pull request adds EVPN L3VNI symmetric IRB support, including an in-memory VRF→VXLAN mapping and remote VTEP MAC cache, VRF MAC management synced with VXLAN interfaces, neighbor snooping and bridge-based neighbor suppression datapath, nexthop extensions for remote/neigh origins and flush operations, bridge flag semantics switched to positive logic, CLI/API additions for neigh_snoop and nexthop flush, datapath metadata VTEP propagation, and multiple smoke tests exercising EVPN L3VPN and neighbor suppression scenarios.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
modules/l2/cli/bridge.c (1)

87-93: Minor inconsistency in CLI syntax for neigh_suppress.

The neigh_suppress flag uses neigh_suppress/no_neigh_suppress tokens while flood and learn use on/off values. This inconsistency in the CLI interface isn't a bug, but the help text at lines 179-182 suggests on/off which doesn't match the actual token parsing here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/l2/cli/bridge.c` around lines 87 - 93, The CLI parsing for the
neigh_suppress option is inconsistent with flood/learn: arg_str(...) currently
checks literal tokens "neigh_suppress"/"no_neigh_suppress" while the help text
and other options use on/off values; update the parsing in the function that
reads args (the arg_str(...) checks around bridge->flags,
GR_BRIDGE_F_NEIGH_SUPPRESS and set_attrs |= GR_BRIDGE_SET_FLAGS) to accept the
same "on"/"off" style (or adjust the help text to match) so the token parsing
and help text are consistent; modify the arg_* check to parse "on"/"off" for
neigh_suppress and set/clear GR_BRIDGE_F_NEIGH_SUPPRESS and set_attrs
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frr/rt_grout.c`:
- Around line 676-681: The current code unconditionally retargets any L3 nexthop
in a VRF with an L3VNI to the VXLAN iface; change it to only rewrite when the
nexthop's original egress is the VRF/SVI interface. Concretely: call whatever
helper returns the VRF's SVI iface (e.g., vrf_get_svi_iface(req->nh.vrf_id) or
equivalent), then only assign req->nh.iface_id = vxlan_iface_id when
vxlan_iface_id != GR_IFACE_ID_UNDEF and req->nh.iface_id equals that VRF/SVI
iface id; leave other nexthops (static, self-originated, etc.) untouched. Ensure
you reference l3vni_get_vxlan(req->nh.vrf_id), req->nh.vrf_id, req->nh.iface_id
and GR_IFACE_ID_UNDEF in the change.
- Around line 1040-1045: The remote neighbor install/delete helpers are not
setting req->nh.vrf_id, causing lookups in bridge_neigh_suppress (which is
VRF-scoped) to miss entries keyed with GR_VRF_ID_UNDEF; set req->nh.vrf_id to
the correct VRF (the same VRF used for iface_id/neighbor) when building the key
(i.e., assign the VRF identifier into req->nh.vrf_id alongside
req->nh.type/origin/iface_id), and apply the identical fix in the other helper
block around the similar code at the referenced second site (lines shown
1083-1086) so both install and delete use the real VRF instead of leaving
GR_VRF_ID_UNDEF.

In `@modules/infra/cli/vrf.c`:
- Around line 85-86: Replace the incorrect VLAN flag with the VRF flag so the
MAC attr is actually applied: in the arg_eth_addr handling block where set_attrs
is updated (look for arg_eth_addr(p, "MAC", &info->mac) and the subsequent
set_attrs |= GR_VLAN_SET_MAC), change the flag to GR_VRF_SET_MAC so the daemon’s
VRF config check matches the set_attrs bit used when applying MACs.

In `@modules/infra/control/vrf.c`:
- Line 154: The call to netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac)
ignores its return value causing kernel and in-memory MAC to diverge; change
this to check the return int, and on failure log a descriptive error including
vrf->vrf_ifindex and the target MAC (use processLogger or existing logging
facility) and propagate or handle the error (e.g., return an error code from the
surrounding function or rollback the vrf creation) so state stays consistent;
ensure you reference netlink_link_set_mac, vrf->vrf_ifindex and vrf->mac when
implementing the check and error handling.

In `@modules/l2/control/vxlan.c`:
- Around line 155-163: The code reads conf->vrf_id even when iface_vxlan_init
passes a partially initialized conf, causing an indeterminate VRF; change the
logic in iface_vxlan_reconfig to avoid dereferencing conf->vrf_id unless the
caller explicitly provided VRF in set_attrs: compute VRF id from conf only when
(set_attrs & GR_IFACE_SET_VRF) is set, otherwise use iface->vrf_id (or skip
syncing), then call get_vrf_iface with that trusted VRF id and only call
iface_set_eth_addr(vrf, &mac) when vrf != NULL; update the branch around
get_vrf_iface/vrf to use this safe vrf_id selection so startup no longer reads
uninitialized conf->vrf_id.

---

Nitpick comments:
In `@modules/l2/cli/bridge.c`:
- Around line 87-93: The CLI parsing for the neigh_suppress option is
inconsistent with flood/learn: arg_str(...) currently checks literal tokens
"neigh_suppress"/"no_neigh_suppress" while the help text and other options use
on/off values; update the parsing in the function that reads args (the
arg_str(...) checks around bridge->flags, GR_BRIDGE_F_NEIGH_SUPPRESS and
set_attrs |= GR_BRIDGE_SET_FLAGS) to accept the same "on"/"off" style (or adjust
the help text to match) so the token parsing and help text are consistent;
modify the arg_* check to parse "on"/"off" for neigh_suppress and set/clear
GR_BRIDGE_F_NEIGH_SUPPRESS and set_attrs accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5c7bdb9d-5f15-48d9-a7e6-2f52cad4c334

📥 Commits

Reviewing files that changed from the base of the PR and between 5a23198 and 8f47020.

📒 Files selected for processing (38)
  • .clang-format
  • api/gr_macro.h
  • frr/if_grout.c
  • frr/l3vni_map.c
  • frr/l3vni_map.h
  • frr/meson.build
  • frr/rt_grout.c
  • frr/rt_grout.h
  • frr/zebra_dplane_grout.c
  • modules/infra/api/gr_infra.h
  • modules/infra/api/gr_nexthop.h
  • modules/infra/api/nexthop.c
  • modules/infra/cli/cli_iface.h
  • modules/infra/cli/iface.c
  • modules/infra/cli/nexthop.c
  • modules/infra/cli/vrf.c
  • modules/infra/control/nexthop.c
  • modules/infra/control/vrf.c
  • modules/infra/datapath/eth.h
  • modules/infra/datapath/eth_output.c
  • modules/ip/control/nexthop.c
  • modules/ip/datapath/arp_input_reply.c
  • modules/ip/datapath/ip_output.c
  • modules/ip6/control/address.c
  • modules/ip6/control/nexthop.c
  • modules/ip6/datapath/ip6_output.c
  • modules/ip6/datapath/ndp_na_input.c
  • modules/l2/api/gr_l2.h
  • modules/l2/cli/bridge.c
  • modules/l2/cli/fdb.c
  • modules/l2/control/vxlan.c
  • modules/l2/datapath/bridge_input.c
  • modules/l2/datapath/bridge_neigh_suppress.c
  • modules/l2/datapath/meson.build
  • modules/l4/l4_input_local.c
  • smoke/bridge_neigh_suppress_test.sh
  • smoke/evpn_l3vpn_frr_test.sh
  • smoke/evpn_neigh_suppress_frr_test.sh

Comment on lines +676 to +681
// For L3 nexthops in VRFs with an L3VNI, redirect the iface from
// the VRF (SVI in FRR's model) to the VXLAN interface. Grout
// routes packets directly through the VXLAN tunnel.
vxlan_iface_id = l3vni_get_vxlan(req->nh.vrf_id);
if (vxlan_iface_id != GR_IFACE_ID_UNDEF)
req->nh.iface_id = vxlan_iface_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't retarget every L3 nexthop in an L3VNI VRF to the VXLAN iface.

This is keyed only on the VRF having an L3VNI, so any self-originated L3 route in that VRF—static routes included—gets forced onto the tunnel. Restrict the rewrite to nexthops whose original FRR egress is the VRF/SVI interface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frr/rt_grout.c` around lines 676 - 681, The current code unconditionally
retargets any L3 nexthop in a VRF with an L3VNI to the VXLAN iface; change it to
only rewrite when the nexthop's original egress is the VRF/SVI interface.
Concretely: call whatever helper returns the VRF's SVI iface (e.g.,
vrf_get_svi_iface(req->nh.vrf_id) or equivalent), then only assign
req->nh.iface_id = vxlan_iface_id when vxlan_iface_id != GR_IFACE_ID_UNDEF and
req->nh.iface_id equals that VRF/SVI iface id; leave other nexthops (static,
self-originated, etc.) untouched. Ensure you reference
l3vni_get_vxlan(req->nh.vrf_id), req->nh.vrf_id, req->nh.iface_id and
GR_IFACE_ID_UNDEF in the change.

Comment on lines +1040 to +1045
req->exist_ok = true;
req->nh.type = GR_NH_T_L3;
req->nh.origin = GR_NH_ORIGIN_BGP;
req->nh.iface_id = iface_id;
l3 = (struct gr_nexthop_info_l3 *)req->nh.info;
l3->flags = GR_NH_F_REMOTE;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Carry vrf_id in the key-based remote neighbor install/delete.

Both helpers leave req->nh.vrf_id unset. bridge_neigh_suppress does VRF-scoped neighbor lookups, so installs keyed with GR_VRF_ID_UNDEF won't be found for the bridge's actual VRF, and the delete helper repeats the same wrong key.

Possible fix
 	req->exist_ok = true;
 	req->nh.type = GR_NH_T_L3;
 	req->nh.origin = GR_NH_ORIGIN_BGP;
 	req->nh.iface_id = iface_id;
+	req->nh.vrf_id = vrf_frr_to_grout(dplane_ctx_get_vrf(ctx));
 	l3 = (struct gr_nexthop_info_l3 *)req->nh.info;
@@
 	req->missing_ok = true;
 	req->nh.type = GR_NH_T_L3;
 	req->nh.iface_id = iface_id;
+	req->nh.vrf_id = vrf_frr_to_grout(dplane_ctx_get_vrf(ctx));
 	l3 = (struct gr_nexthop_info_l3 *)req->nh.info;

Also applies to: 1083-1086

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frr/rt_grout.c` around lines 1040 - 1045, The remote neighbor install/delete
helpers are not setting req->nh.vrf_id, causing lookups in bridge_neigh_suppress
(which is VRF-scoped) to miss entries keyed with GR_VRF_ID_UNDEF; set
req->nh.vrf_id to the correct VRF (the same VRF used for iface_id/neighbor) when
building the key (i.e., assign the VRF identifier into req->nh.vrf_id alongside
req->nh.type/origin/iface_id), and apply the identical fix in the other helper
block around the similar code at the referenced second site (lines shown
1083-1086) so both install and delete use the real VRF instead of leaving
GR_VRF_ID_UNDEF.

Comment on lines +85 to +86
if (arg_eth_addr(p, "MAC", &info->mac) == 0)
set_attrs |= GR_VLAN_SET_MAC;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrong flag constant: GR_VLAN_SET_MAC should be GR_VRF_SET_MAC.

This uses the VLAN MAC flag (GR_BIT64(34)) instead of the VRF MAC flag (GR_BIT64(33)). The daemon checks for GR_VRF_SET_MAC when applying VRF configuration, so the MAC will never be applied.

🐛 Proposed fix
 	if (arg_eth_addr(p, "MAC", &info->mac) == 0)
-		set_attrs |= GR_VLAN_SET_MAC;
+		set_attrs |= GR_VRF_SET_MAC;
 	else if (errno != ENOENT)
 		return 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (arg_eth_addr(p, "MAC", &info->mac) == 0)
set_attrs |= GR_VLAN_SET_MAC;
if (arg_eth_addr(p, "MAC", &info->mac) == 0)
set_attrs |= GR_VRF_SET_MAC;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/infra/cli/vrf.c` around lines 85 - 86, Replace the incorrect VLAN
flag with the VRF flag so the MAC attr is actually applied: in the arg_eth_addr
handling block where set_attrs is updated (look for arg_eth_addr(p, "MAC",
&info->mac) and the subsequent set_attrs |= GR_VLAN_SET_MAC), change the flag to
GR_VRF_SET_MAC so the daemon’s VRF config check matches the set_attrs bit used
when applying MACs.

strerror(errno));
return ret;
}
netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unchecked return value from netlink_link_set_mac.

If setting the MAC on the kernel VRF device fails, grout will have a different MAC stored than what the kernel has, potentially causing ARP/routing inconsistencies.

Proposed fix
-		netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac);
+		if (netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac) < 0) {
+			LOG(WARNING, "failed to set MAC on VRF %s: %s", iface->name, strerror(errno));
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac);
if (netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac) < 0) {
LOG(WARNING, "failed to set MAC on VRF: %s", strerror(errno));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/infra/control/vrf.c` at line 154, The call to
netlink_link_set_mac(vrf->vrf_ifindex, &vrf->mac) ignores its return value
causing kernel and in-memory MAC to diverge; change this to check the return
int, and on failure log a descriptive error including vrf->vrf_ifindex and the
target MAC (use processLogger or existing logging facility) and propagate or
handle the error (e.g., return an error code from the surrounding function or
rollback the vrf creation) so state stays consistent; ensure you reference
netlink_link_set_mac, vrf->vrf_ifindex and vrf->mac when implementing the check
and error handling.

Comment on lines +155 to +163
// If configured for EVPN L3VNI, also synchronize the MAC on the interface VRF.
// So it will be advertised as RMAC by FRR.
vrf = NULL;
if (set_attrs & GR_IFACE_SET_VRF)
vrf = get_vrf_iface(conf->vrf_id);
else if (iface->mode == GR_IFACE_MODE_VRF)
vrf = get_vrf_iface(iface->vrf_id);
if (vrf != NULL && iface_set_eth_addr(vrf, &mac) < 0)
goto err;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid reading conf->vrf_id from the partially initialized init-time config.

iface_vxlan_init() still builds a stack struct gr_iface conf and only fills conf.base before calling iface_vxlan_reconfig(IFACE_SET_ALL, ...). This new branch now dereferences conf->vrf_id, so startup can sync the MAC into an indeterminate VRF.

Possible fix
-static int iface_vxlan_init(struct iface *iface, const void *api_info) {
-	struct gr_iface conf;
+static int iface_vxlan_init(struct iface *iface, const void *api_info) {
+	struct gr_iface conf = {0};
 	int ret;
@@
-	conf.base = iface->base;
+	conf.base = iface->base;
+	conf.vrf_id = iface->vrf_id;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/l2/control/vxlan.c` around lines 155 - 163, The code reads
conf->vrf_id even when iface_vxlan_init passes a partially initialized conf,
causing an indeterminate VRF; change the logic in iface_vxlan_reconfig to avoid
dereferencing conf->vrf_id unless the caller explicitly provided VRF in
set_attrs: compute VRF id from conf only when (set_attrs & GR_IFACE_SET_VRF) is
set, otherwise use iface->vrf_id (or skip syncing), then call get_vrf_iface with
that trusted VRF id and only call iface_set_eth_addr(vrf, &mac) when vrf !=
NULL; update the branch around get_vrf_iface/vrf to use this safe vrf_id
selection so startup no longer reads uninitialized conf->vrf_id.

rjarry added 4 commits April 6, 2026 19:40
All interface/nexthop flags use "presence" semantics. Having flags to
signify the absence of a feature is confusing. Change the semantics of
bridge flags to follow the same style.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
This macro will be useful to format all flag masks.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Use gr_flags_foreach to simplify formatting them.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Add flag to allow enabling ARP/ND suppression on bridge interfaces.

When a bridge has GR_BRIDGE_F_NEIGH_SUPPRESS enabled, broadcast and
multicast traffic is routed through bridge_neigh_suppress before
flooding. The node intercepts ARP requests and IPv6 Neighbor
Solicitations, looks up the target IP in the bridge's VRF nexthop table,
and replies on behalf of known remote hosts (GR_NH_F_REMOTE with
a reachable MAC).

For ARP, the Ethernet and ARP headers are rewritten in-place. For ND,
the packet is rebuilt from scratch since a Neighbor Advertisement has
a different structure than a Neighbor Solicitation. In both cases, the
reply is sent directly to iface_output, bypassing eth_output to avoid
double Ethernet header prepending.

Packets that cannot be suppressed (unknown target, non-ARP/ND, or
non-remote nexthop) fall through to bridge_flood as usual.

Also add a "remote" flag to the nexthop add l3 CLI command so remote
nexthops can be configured manually for testing.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
@rjarry rjarry force-pushed the neigh-suppression branch from 8f47020 to fab7eb3 Compare April 6, 2026 17:53
@rjarry rjarry marked this pull request as draft April 6, 2026 17:53
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@modules/l2/cli/bridge.c`:
- Around line 87-94: The CLI parsing block uses arg_str(p, "neigh_suppress") but
the argument was registered as "NEIGH_SUPPRESS", so the lookup always fails;
update the arg_str call to use the exact registered name "NEIGH_SUPPRESS" (or
change the registration to "neigh_suppress" if you prefer lowercase) so the
value is retrieved correctly, then keep the existing flag handling using
GR_BRIDGE_F_NEIGH_SUPPRESS and set_attrs |= GR_BRIDGE_SET_FLAGS.

In `@smoke/bridge_neigh_suppress_test.sh`:
- Around line 25-26: The test invocation for the bridge neighbor suppression
flag is missing the required value argument; update the command "grcli interface
add bridge br0 neigh_suppress" to supply the "on" argument (i.e., use
"neigh_suppress on") so the CLI schema's on/off parameter is satisfied before
running the subsequent "grcli address add 10.0.0.1/24 iface br0" step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6310ae9-f7b5-4ae8-aaeb-5e05b9d73be6

📥 Commits

Reviewing files that changed from the base of the PR and between 8f47020 and fab7eb3.

📒 Files selected for processing (12)
  • .clang-format
  • api/gr_macro.h
  • modules/infra/api/gr_nexthop.h
  • modules/infra/cli/nexthop.c
  • modules/l2/api/gr_l2.h
  • modules/l2/cli/bridge.c
  • modules/l2/cli/fdb.c
  • modules/l2/datapath/bridge_input.c
  • modules/l2/datapath/bridge_neigh_suppress.c
  • modules/l2/datapath/meson.build
  • smoke/bridge_neigh_suppress_test.sh
  • smoke/evpn_neigh_suppress_frr_test.sh
✅ Files skipped from review due to trivial changes (3)
  • .clang-format
  • modules/l2/cli/fdb.c
  • modules/l2/datapath/meson.build
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/gr_macro.h
  • modules/infra/cli/nexthop.c

Comment on lines +87 to 94
on_off = arg_str(p, "neigh_suppress");
if (on_off != NULL && strcmp(on_off, "on") == 0) {
bridge->flags |= GR_BRIDGE_F_NEIGH_SUPPRESS;
set_attrs |= GR_BRIDGE_SET_FLAGS;
} else if (on_off != NULL && strcmp(on_off, "off") == 0) {
bridge->flags &= ~GR_BRIDGE_F_NEIGH_SUPPRESS;
set_attrs |= GR_BRIDGE_SET_FLAGS;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Argument name mismatch causes neigh_suppress parsing to fail.

The argument is registered as "NEIGH_SUPPRESS" (uppercase) in the schema at line 181, but arg_str() looks up "neigh_suppress" (lowercase). This will never match, silently ignoring the CLI option.

🐛 Proposed fix
-	on_off = arg_str(p, "neigh_suppress");
+	on_off = arg_str(p, "NEIGH_SUPPRESS");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on_off = arg_str(p, "neigh_suppress");
if (on_off != NULL && strcmp(on_off, "on") == 0) {
bridge->flags |= GR_BRIDGE_F_NEIGH_SUPPRESS;
set_attrs |= GR_BRIDGE_SET_FLAGS;
} else if (on_off != NULL && strcmp(on_off, "off") == 0) {
bridge->flags &= ~GR_BRIDGE_F_NEIGH_SUPPRESS;
set_attrs |= GR_BRIDGE_SET_FLAGS;
}
on_off = arg_str(p, "NEIGH_SUPPRESS");
if (on_off != NULL && strcmp(on_off, "on") == 0) {
bridge->flags |= GR_BRIDGE_F_NEIGH_SUPPRESS;
set_attrs |= GR_BRIDGE_SET_FLAGS;
} else if (on_off != NULL && strcmp(on_off, "off") == 0) {
bridge->flags &= ~GR_BRIDGE_F_NEIGH_SUPPRESS;
set_attrs |= GR_BRIDGE_SET_FLAGS;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/l2/cli/bridge.c` around lines 87 - 94, The CLI parsing block uses
arg_str(p, "neigh_suppress") but the argument was registered as
"NEIGH_SUPPRESS", so the lookup always fails; update the arg_str call to use the
exact registered name "NEIGH_SUPPRESS" (or change the registration to
"neigh_suppress" if you prefer lowercase) so the value is retrieved correctly,
then keep the existing flag handling using GR_BRIDGE_F_NEIGH_SUPPRESS and
set_attrs |= GR_BRIDGE_SET_FLAGS.

Comment on lines +25 to +26
grcli interface add bridge br0 neigh_suppress
grcli address add 10.0.0.1/24 iface br0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing on argument for neigh_suppress.

The CLI schema (line 167 of bridge.c) defines neigh_suppress as taking an on/off value argument. Without it, the flag won't be enabled.

🐛 Proposed fix
-grcli interface add bridge br0 neigh_suppress
+grcli interface add bridge br0 neigh_suppress on
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
grcli interface add bridge br0 neigh_suppress
grcli address add 10.0.0.1/24 iface br0
grcli interface add bridge br0 neigh_suppress on
grcli address add 10.0.0.1/24 iface br0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@smoke/bridge_neigh_suppress_test.sh` around lines 25 - 26, The test
invocation for the bridge neighbor suppression flag is missing the required
value argument; update the command "grcli interface add bridge br0
neigh_suppress" to supply the "on" argument (i.e., use "neigh_suppress on") so
the CLI schema's on/off parameter is satisfied before running the subsequent
"grcli address add 10.0.0.1/24 iface br0" step.

bridge_neigh_suppress_test verifies suppression with a manually
configured remote nexthop: an ARP request for a known remote IP gets
answered locally by grout with the correct MAC, and is not flooded to
other bridge ports.

evpn_neigh_suppress_frr_test verifies end-to-end suppression with FRR
EVPN. The Linux peer has neigh_suppress on the VXLAN interface and
advertise-svi-ip in BGP. Both sides have bridge SVIs and hosts with
default routes through them. When hosts ARP for their gateways, VTEPs
learn the IP+MAC bindings and advertise Type-2 MAC+IP routes. The
remote VTEP installs the nexthops and the suppress node answers ARPs
locally.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
@rjarry rjarry force-pushed the neigh-suppression branch from fab7eb3 to 96d65cc Compare April 7, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant