Skip to content

ip6: punt received router advertisements to control plane - #658

Open
chdxD1 wants to merge 3 commits into
DPDK:mainfrom
chdxD1:icmp6-router-advert-punt
Open

ip6: punt received router advertisements to control plane#658
chdxD1 wants to merge 3 commits into
DPDK:mainfrom
chdxD1:icmp6-router-advert-punt

Conversation

@chdxD1

@chdxD1 chdxD1 commented Jul 23, 2026

Copy link
Copy Markdown

ip6: BGP unnumbered peering via Router Advertisement punt

Problem

BGP unnumbered peering (RFC 5549) relies on IPv6 Router Advertisements to
discover the peer's link-local next hop. grout was dropping all received RAs
in icmp6_input (dispatched to the unsupported edge), so a routing daemon
such as FRR running on top of grout would never receive them. The BGP session
would remain stuck in Idle (Waiting for peer address) indefinitely.

A second issue caused zebra to crash when programming a link-local neighbor
learned over such a session: if_nbr_mac_to_ipv4ll_neigh_update() calls
kernel_neigh_update() directly, bypassing the dplane abstraction, which
does not work with dplane_grout.

Solution

ip6: punt received router advertisements to control plane

Add a new ndp_ra_input graph node that validates incoming Router
Advertisements per RFC 4861 §6.1.2:

  • IP Hop Limit must be 255 (not forwarded by a router)
  • ICMPv6 Code must be 0
  • Packet length ≥ 4-byte ICMPv6 header + 12-byte RA fields
  • IP source address must be link-local

Valid packets are punted to the control plane via the existing
l4_loopback_output path. Invalid packets are dropped via
ndp_ra_input_inval. A smoke test verifies an unnumbered eBGP session comes
up end-to-end between an FRR+grout instance and a pure FRR peer.

frr: patch zebra neighbor update crash with dplane_grout

Carry patches for FRR 10.5–10.7+ that route link-local neighbor updates
through the dplane API instead of calling kernel_neigh_update() directly.
(based on FRRouting/frr#22773)

Closes

Closes #657

Summary

  • Add validated IPv6 Router Advertisement input handling. Forward valid packets to the control-plane loopback and drop invalid packets.
  • Apply FRR zebra patches for versions 10.5–10.7 and master. Route synthetic IPv4 link-local neighbor updates through the dplane API.
  • Add a smoke test for unnumbered eBGP peering between grout and FRR.

@rjarry rjarry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @chdxD1,

sorry not to have replied earlier.

I will close my PR #660 since we are basically doing the same thing.

Could you rebase your code on top of the latest main?

Thanks!

Comment on lines +84 to +92
// Grout does not process router advertisements itself.
// Punt them to the control plane so that a routing daemon
// (e.g. FRR) running on top of grout can consume them. BGP
// unnumbered peering relies on received RAs to discover the
// peer link-local next hop; without this the packets would
// be dropped as unsupported and the session would never come
// up.
next = ROUTER_ADVERT;
break;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Your approach is fine but I would prefer if we could add an intermediate node to check for the RA packets compliance with RFCs. See my other PR for reference:

f5309fe#diff-ae0101fec65d9c9f9e7645dff184ef9454a808762ad3162dbe93d129874eb37cR41-R55

Comment thread smoke/bgp_unnumbered_frr_test.sh Outdated
Comment on lines +5 to +26
# This test checks BGP unnumbered (RFC 5549) peering between an FRR+grout setup
# and a pure FRR BGP peer. Unnumbered peering relies on IPv6 Router
# Advertisements to discover the peer link-local next hop. grout must punt the
# RAs it receives on a port to that port's control plane so the FRR instance
# running on top of grout can consume them; otherwise the session never leaves
# the Idle state. At the end of the configuration phase grout must be able to
# ping the loopback advertised by the "bgp-peer" namespace over the route
# learned through the unnumbered session.
#
# .-------..-------------. .-------------------.
# | zebra || grout | | netns "bgp-peer" |
# '-------'| | | .------. |
# .------. | .------------. .------------. | bgpd | |
# | bgpd | | | p0 | net_tap | x-p0 | '------' |
# '------' | | (LL) +-------------+ (LL) | .-------. |
# .------. '------------' '------------' | zebra | |
# | main | | | '-------' |
# '------' | | .----------. |
# | ping <------------------------------------>| lo | |
# '------------' | | 16.0.0.1 | |
# | '----------' |
# '-------------------'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also, the issue we have at the moment is that the test you added will fail all the time until there is a proper fix in zebra.

I think my approach in FRRouting/frr#22773 is correct but I'd like some upstream reviews.

In the meantime, we "could" keep a local patch for FRR 487aac5 so that we can test it in CI.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sounds good, I can cherry pick your commit after rebasing :)

@rjarry rjarry mentioned this pull request Aug 3, 2026
@chdxD1
chdxD1 force-pushed the icmp6-router-advert-punt branch from afa9a6d to e8de0cd Compare August 4, 2026 13:55
@chdxD1
chdxD1 marked this pull request as ready for review August 4, 2026 13:57
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 333f9e0c-1b10-4966-b13d-37f4f2c7a4ef

📥 Commits

Reviewing files that changed from the base of the PR and between 500babf and e91d212.

📒 Files selected for processing (1)
  • smoke/bgp_unnumbered_frr_test.sh

📝 Walkthrough

Walkthrough

This PR adds an IPv6 ndp_ra_input node and wires ICMPv6 Router Advertisements to it. The node validates Router Advertisement fields and routes valid and invalid packets separately. A smoke test covers IPv4 unnumbered BGP peering with FRR. FRR wrap configurations apply zebra patches that route synthetic IPv4 link-local neighbor updates through the dataplane abstraction.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/ip6/datapath/ndp_ra_input.c`:
- Around line 43-57: Update the Router Advertisement validation in the RA input
handler before l4_loopback_output is selected: parse the option sequence
following the fixed RA header, reject any option with a zero Length, and reject
any option whose encoded size exceeds the remaining payload so invalid RAs are
dropped rather than delivered. Add packet tests covering both malformed option
cases.

In
`@subprojects/packagefiles/frr/10.5-zebra-route-IPv4-link-local-neighbor-updates-through.patch`:
- Around line 58-70: The neighbor delete operation uses
dplane_rem_neigh_delete() which marks the delete as remote, but the subsequent
add operation uses dplane_local_neigh_add() which installs a local neighbor.
This class mismatch can leave the local neighbor state installed after refresh
or delete. In both
subprojects/packagefiles/frr/10.5-zebra-route-IPv4-link-local-neighbor-updates-through.patch
(lines 58-70) and
subprojects/packagefiles/frr/10.7-zebra-route-IPv4-link-local-neighbor-updates-through.patch
(lines 58-70), replace the dplane_rem_neigh_delete() call with a local or
class-neutral neighbor delete operation that matches the local neighbor class
used in the dplane_local_neigh_add() call below it.
🪄 Autofix

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 Plus

Run ID: d1b20a51-3cd9-4742-9ece-7b7581ad4e85

📥 Commits

Reviewing files that changed from the base of the PR and between 1f98b66 and 0483fcc.

⛔ Files ignored due to path filters (1)
  • docs/graph.svg is excluded by !**/*.svg
📒 Files selected for processing (10)
  • modules/ip6/datapath/icmp6_input.c
  • modules/ip6/datapath/meson.build
  • modules/ip6/datapath/ndp_ra_input.c
  • smoke/bgp_unnumbered_frr_test.sh
  • subprojects/frr-10.5.wrap
  • subprojects/frr-10.6.wrap
  • subprojects/frr-10.7.wrap
  • subprojects/frr-master.wrap
  • subprojects/packagefiles/frr/10.5-zebra-route-IPv4-link-local-neighbor-updates-through.patch
  • subprojects/packagefiles/frr/10.7-zebra-route-IPv4-link-local-neighbor-updates-through.patch

Comment on lines +43 to +57
// Validation of Router Advertisements
// https://www.rfc-editor.org/rfc/rfc4861#section-6.1.2
//
// - The IP Hop Limit field has a value of 255, i.e., the packet
// could not possibly have been forwarded by a router.
ASSERT_NDP(d->hop_limit == 255);
// - ICMP Checksum is valid. (already checked in icmp6_input)
//
// - ICMP Code is 0.
ASSERT_NDP(icmp6->code == 0);
// - ICMP length (derived from the IP length) is 16 or more
// octets: 4 bytes ICMP header + 12 bytes RA fields.
ASSERT_NDP(d->len >= sizeof(*icmp6) + sizeof(struct icmp6_router_advert));
// - The IP source address is a link-local address.
ASSERT_NDP(rte_ipv6_addr_is_linklocal(&d->src));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject invalid RA options before control-plane delivery.

An RA with a valid 16-byte fixed header followed by an option whose Length is zero passes all current checks and reaches l4_loopback_output. RFC 4861 requires receivers to discard RAs with a zero-length option. Parse the option sequence after the RA header. Reject a zero Length and an option that exceeds the remaining payload before selecting LOOPBACK. Add packet tests for both cases. (rfc-editor.org)

The PR objective requires invalid RAs to be dropped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/ip6/datapath/ndp_ra_input.c` around lines 43 - 57, Update the Router
Advertisement validation in the RA input handler before l4_loopback_output is
selected: parse the option sequence following the fixed RA header, reject any
option with a zero Length, and reject any option whose encoded size exceeds the
remaining payload so invalid RAs are dropped rather than delivered. Add packet
tests covering both malformed option cases.

@rjarry rjarry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you split in 3 commits? That way, the source tree always succeeds build/test at every step.

  1. Punt RAs to control plane representors
  2. Add zebra patch
  3. Add smoke test

Thanks!

@chdxD1

chdxD1 commented Aug 4, 2026

Copy link
Copy Markdown
Author

Could you split in 3 commits? That way, the source tree always succeeds build/test at every step.

  1. Punt RAs to control plane representors
  2. Add zebra patch
  3. Add smoke test

Thanks!

And I also noticed that I need to re-generated the svg. Will do!

chdxD1 and others added 2 commits August 4, 2026 17:21
grout does not process IPv6 router advertisements itself. Until now the
icmp6_input node dispatched them to the unsupported edge, so they were
dropped in the data path.

A routing daemon running on top of grout needs to see these packets. BGP
unnumbered peering (RFC 5549) in particular relies on received router
advertisements to discover the peer's link-local next hop; when the RAs
are dropped the session never leaves the "Idle (Waiting for peer
address)" state.

Add a ndp_ra_input node that validates received router advertisements
as described in RFC 4861 section 6.1.2 (hop limit 255, ICMP code 0,
minimum length and link-local source address) and punts the valid ones
to the port control plane representor via l4_loopback_output. Invalid
advertisements are dropped in ndp_ra_input_inval.

Closes: DPDK#657
Co-authored-by: Robin Jarry <rjarry@redhat.com>
Assisted-by: GitHub Copilot CLI:claude-opus-5
Signed-off-by: Christopher Dziomba <christopher.dziomba@telekom.de>
FRR's if_nbr_mac_to_ipv4ll_neigh_update() calls
kernel_neigh_update() directly, bypassing the dplane abstraction.
This crashes zebra when using dplane_grout because the netlink
socket is not initialized.

Carry patches for FRR 10.5-10.6 and 10.7+ that route these
neighbor updates through the dplane API instead.

Link: FRRouting/frr#22773
Signed-off-by: Robin Jarry <rjarry@redhat.com>
@chdxD1
chdxD1 force-pushed the icmp6-router-advert-punt branch from 0483fcc to 500babf Compare August 4, 2026 15:23
Check BGP unnumbered peering (RFC 5549) between an FRR+grout instance
and a pure FRR peer over a net_tap port.

Unnumbered peering relies on IPv6 router advertisements to discover the
peer link-local next hop, so the session can only be established once
grout punts the received advertisements to the port control plane
representor. Advertising IPv4 prefixes over such a session additionally
requires the zebra neighbor update patch.

Both routers only advertise an IPv4 loopback prefix, the ports carry no
IPv4 address at all. Assert that the prefix advertised by the peer is
installed in the FIB with an IPv6 link-local next hop and that IPv4
traffic is forwarded between both loopbacks over that next hop.

Co-authored-by: Robin Jarry <rjarry@redhat.com>
Assisted-by: GitHub Copilot CLI:claude-opus-5
Signed-off-by: Christopher Dziomba <christopher.dziomba@telekom.de>
@chdxD1

chdxD1 commented Aug 4, 2026

Copy link
Copy Markdown
Author

The CI failure here is not caused by this series. The smoke test added in the last commit is the first one to configure an address on the main VRF loopback, which uncovers a pre-existing leak in iface_vrf_fini():

Direct leak of 128 byte(s) in 1 object(s) allocated from:
    #1 in event_new
    #2 in iface_loopback_create modules/infra/control/loopback.c:257

netlink_vrf_del() fails with ESRCH at teardown (delete route on main failed: No such process) and iface_vrf_fini() returns early, so iface_loopback_destroy() never runs.

Fixed separately in #681.

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.

Received IPv6 Router Advertisements are dropped, breaking FRR BGP unnumbered over grout ports

2 participants