Skip to content

bgpd: fix IPv6 nexthop for IPv4 peers to use IPv4-mapped address#22168

Open
Jay779 wants to merge 1 commit into
FRRouting:masterfrom
Jay779:fix/bgp-ipv6-over-ipv4-nexthop
Open

bgpd: fix IPv6 nexthop for IPv4 peers to use IPv4-mapped address#22168
Jay779 wants to merge 1 commit into
FRRouting:masterfrom
Jay779:fix/bgp-ipv6-over-ipv4-nexthop

Conversation

@Jay779
Copy link
Copy Markdown

@Jay779 Jay779 commented Jun 2, 2026

Summary

When an IPv4 BGP peer advertises IPv6 prefixes (RFC 5549), the nexthop in UPDATE messages must be an IPv4-mapped IPv6 address (::ffff:x.x.x.x).

Currently, bgp_zebra_nexthop_set() calls if_get_ipv6_global() which stores the first non-link-local IPv6 address found on the interface into peer->nexthop.v6_global. In bpacket_reformat_for_peer(), the existing synthesis path (ipv4_to_ipv4_mapped_ipv6) only fires when mod_v6nhg is all-zeros, and the IS_MAPPED_IPV6 override only fires when v6_global is already IPv4-mapped. Neither triggers when v6_global contains a real global IPv6 address from the interface.

Result: When the outgoing interface has both a global IPv6 address and an IPv4 address (e.g., a loopback with 2001:db8::1/128 and 172.32.0.13/32), the UPDATE is sent with the wrong nexthop (the global IPv6 address instead of ::ffff:172.32.0.13), causing the receiver to fail recursive nexthop resolution in the IPv4 RIB.

Fix

Always synthesize the IPv4-mapped IPv6 nexthop from peer->nexthop.v4 when the peer connection is AF_INET, regardless of what v6_global contains. The existing fallback path for IPv6 peers is preserved as an else-if branch.

Receive side

Note that the receive side already correctly handles IPv4-mapped IPv6 nexthops:

  • bgp_find_or_add_nexthop() in bgp_nht.c detects IS_MAPPED_IPV6() and forces AFI_IP for resolution
  • make_prefix() extracts the IPv4 address via ipv4_mapped_ipv6_to_ipv4()
  • Zebra's nexthop_active() also handles v4-mapped addresses correctly

Only the send side was broken.

Signed-off-by: Jia Chen jchen1@paloaltonetworks.com

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR fixes a send-side bug in RFC 5549 (IPv4 BGP peer advertising IPv6 prefixes) where an interface with both a global IPv6 address and an IPv4 address would produce UPDATE messages carrying the wrong nexthop (2001:db8::1 instead of ::ffff:172.32.0.13), breaking recursive nexthop resolution on the receiver.

  • Adds a new branch in bpacket_reformat_for_peer() that detects an IPv4 transport peer (su_local->sa_family == AF_INET) and unconditionally synthesizes the IPv4-mapped nexthop from peer->nexthop.v4, replacing the previous all-zeros fallback path.
  • The existing else if (IN6_IS_ADDR_UNSPECIFIED) and IS_MAPPED_IPV6 branches are preserved for non-IPv4-transport peers.

Confidence Score: 3/5

The fix correctly addresses the described nexthop mismatch, but calls ipv4_to_ipv4_mapped_ipv6 through mod_v6nhg, which may point into the peer's session-lifetime nexthop structure rather than the local stack variable, potentially overwriting peer->nexthop.v6_global.

The EBGP multiaccess-check and route-map NH_PEER_ADDRESS branches redirect mod_v6nhg to &peer->nexthop.v6_global before the new code runs. Writing the IPv4-mapped address there mutates a per-session structure set by bgp_zebra_nexthop_set() and later read by bgp_updgrp.c for hashing and peer grouping.

bgpd/bgp_updgrp_packet.c — the interaction between the new branch and the preceding mod_v6nhg redirection logic.

Important Files Changed

Filename Overview
bgpd/bgp_updgrp_packet.c Adds an RFC 5549 IPv4-mapped nexthop synthesis path, but the implementation may call ipv4_to_ipv4_mapped_ipv6 through a pointer already redirected to &peer->nexthop.v6_global, permanently mutating a session-lifetime field used in update-group hashing and comparison.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
bgpd/bgp_updgrp_packet.c:534-540
When the EBGP multiaccess-check branch (lines 517-527) or the route-map `NH_PEER_ADDRESS` branch (lines 508-512) fires before this point, `mod_v6nhg` has already been redirected to `&peer->nexthop.v6_global`. Calling `ipv4_to_ipv4_mapped_ipv6(mod_v6nhg, ...)` then permanently overwrites that per-session field in place. `peer->nexthop.v6_global` is written once at session establishment by `bgp_zebra_nexthop_set()` and is later read by the update-group hash (`bgp_updgrp.c:473`) and update-group equality check (`bgp_updgrp.c:695`). Mutating it mid-send silently corrupts those lookups for the rest of the session. The fix should always target the stack-local `v6nhglobal` and reset the pointer, keeping the peer structure untouched.

```suggestion
		if (peer->su_local &&
		    peer->su_local->sa.sa_family == AF_INET &&
		    peer->nexthop.v4.s_addr != INADDR_ANY) {
			ipv4_to_ipv4_mapped_ipv6(&v6nhglobal,
						 peer->nexthop.v4);
			mod_v6nhg = &v6nhglobal;
			gnh_modified = 1;
		} else if (IN6_IS_ADDR_UNSPECIFIED(mod_v6nhg)) {
```

Reviews (1): Last reviewed commit: "bgpd: fix IPv6 nexthop for IPv4 peers to..." | Re-trigger Greptile

Comment thread bgpd/bgp_updgrp_packet.c
Comment on lines +534 to +540
if (peer->su_local &&
peer->su_local->sa.sa_family == AF_INET &&
peer->nexthop.v4.s_addr != INADDR_ANY) {
ipv4_to_ipv4_mapped_ipv6(mod_v6nhg,
peer->nexthop.v4);
gnh_modified = 1;
} else if (IN6_IS_ADDR_UNSPECIFIED(mod_v6nhg)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 When the EBGP multiaccess-check branch (lines 517-527) or the route-map NH_PEER_ADDRESS branch (lines 508-512) fires before this point, mod_v6nhg has already been redirected to &peer->nexthop.v6_global. Calling ipv4_to_ipv4_mapped_ipv6(mod_v6nhg, ...) then permanently overwrites that per-session field in place. peer->nexthop.v6_global is written once at session establishment by bgp_zebra_nexthop_set() and is later read by the update-group hash (bgp_updgrp.c:473) and update-group equality check (bgp_updgrp.c:695). Mutating it mid-send silently corrupts those lookups for the rest of the session. The fix should always target the stack-local v6nhglobal and reset the pointer, keeping the peer structure untouched.

Suggested change
if (peer->su_local &&
peer->su_local->sa.sa_family == AF_INET &&
peer->nexthop.v4.s_addr != INADDR_ANY) {
ipv4_to_ipv4_mapped_ipv6(mod_v6nhg,
peer->nexthop.v4);
gnh_modified = 1;
} else if (IN6_IS_ADDR_UNSPECIFIED(mod_v6nhg)) {
if (peer->su_local &&
peer->su_local->sa.sa_family == AF_INET &&
peer->nexthop.v4.s_addr != INADDR_ANY) {
ipv4_to_ipv4_mapped_ipv6(&v6nhglobal,
peer->nexthop.v4);
mod_v6nhg = &v6nhglobal;
gnh_modified = 1;
} else if (IN6_IS_ADDR_UNSPECIFIED(mod_v6nhg)) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: bgpd/bgp_updgrp_packet.c
Line: 534-540

Comment:
When the EBGP multiaccess-check branch (lines 517-527) or the route-map `NH_PEER_ADDRESS` branch (lines 508-512) fires before this point, `mod_v6nhg` has already been redirected to `&peer->nexthop.v6_global`. Calling `ipv4_to_ipv4_mapped_ipv6(mod_v6nhg, ...)` then permanently overwrites that per-session field in place. `peer->nexthop.v6_global` is written once at session establishment by `bgp_zebra_nexthop_set()` and is later read by the update-group hash (`bgp_updgrp.c:473`) and update-group equality check (`bgp_updgrp.c:695`). Mutating it mid-send silently corrupts those lookups for the rest of the session. The fix should always target the stack-local `v6nhglobal` and reset the pointer, keeping the peer structure untouched.

```suggestion
		if (peer->su_local &&
		    peer->su_local->sa.sa_family == AF_INET &&
		    peer->nexthop.v4.s_addr != INADDR_ANY) {
			ipv4_to_ipv4_mapped_ipv6(&v6nhglobal,
						 peer->nexthop.v4);
			mod_v6nhg = &v6nhglobal;
			gnh_modified = 1;
		} else if (IN6_IS_ADDR_UNSPECIFIED(mod_v6nhg)) {
```

How can I resolve this? If you propose a fix, please make it concise.

When an IPv4 BGP peer advertises IPv6 prefixes (RFC 5549), the
nexthop in UPDATE messages must be an IPv4-mapped IPv6 address
(::ffff:x.x.x.x). Currently, bgp_zebra_nexthop_set() calls
if_get_ipv6_global() which stores the first non-link-local IPv6
address found on the interface into peer->nexthop.v6_global.

In bpacket_reformat_for_peer(), this value is used as the nexthop.
The existing synthesis path (ipv4_to_ipv4_mapped_ipv6) only fires
when mod_v6nhg is all-zeros, and the IS_MAPPED_IPV6 override only
fires when v6_global is already IPv4-mapped. Neither triggers when
v6_global contains a real global IPv6 address from the interface.

This causes incorrect nexthops when the outgoing interface has both
a global IPv6 address and an IPv4 address (e.g., a loopback with
2001:db8::1/128 and 172.32.0.13/32). The UPDATE is sent with the
global IPv6 address instead of ::ffff:172.32.0.13, causing the
receiver to fail recursive nexthop resolution in the IPv4 RIB.

Fix this by always synthesizing the IPv4-mapped IPv6 nexthop from
peer->nexthop.v4 when the peer connection is AF_INET, regardless
of what v6_global contains. The existing fallback path for IPv6
peers is preserved as an else-if branch.

Signed-off-by: Jia Chen <jchen1@paloaltonetworks.com>
@Jay779 Jay779 force-pushed the fix/bgp-ipv6-over-ipv4-nexthop branch from fbe0c00 to 1a421c6 Compare June 2, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants