Carry IPv4 routes over IPv6 sessions with IPv6 next hops (RFC 8950)
For a link that has only IPv6 addresses. - The Extended Next Hop capability is offered on every session over IPv6 whose families include IPv4 unicast, and used if the peer offers it too. Nothing to configure. - On such a session our IPv4 routes go out in MP_REACH_NLRI with the session's IPv6 address as next hop, and are withdrawn in MP_UNREACH_NLRI. Without the agreement they are not sent, as before. - IPv4 routes are read with an IPv6 next hop, global or global with link-local. - The kernel installer resolves an IPv4 route's IPv6 next hop like any other and installs it: by way of an IPv6 nexthop object, or with RTA_VIA where routes carry their own gateway. Tested against a real kernel in both modes, with a link-local next hop too, and in the lab against FRR and BIRD: the new `v6only` phase takes the IPv4 addresses off the exchange LAN, runs one IPv6 session with each router for both families, and pings IPv4 across. Sessions between link-local addresses are not supported yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
321af51e4b
commit
92e013ca01
14 files changed
+511
-23
No files matched your search
+24
-4
@@ -28,7 +28,7 @@ pub struct Target {
|
||||
pub enum Unresolved {
|
||||
/// The route has no next hop.
|
||||
Missing,
|
||||
/// The next hop is of the other IP version than the prefix.
|
||||
/// An IPv6 route with an IPv4 next hop.
|
||||
OtherFamily,
|
||||
/// The next hop is one of this machine's own addresses.
|
||||
Ourselves,
|
||||
@@ -49,7 +49,7 @@ impl Unresolved {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Missing => "no next hop",
|
||||
Self::OtherFamily => "next hop of the other IP version",
|
||||
Self::OtherFamily => "IPv4 next hop for an IPv6 route",
|
||||
Self::Ourselves => "next hop is this machine",
|
||||
Self::NotConnected => "next hop not directly connected",
|
||||
}
|
||||
@@ -77,7 +77,10 @@ pub fn resolve(
|
||||
NlriNextHop::Ipv4(_) => IpVersion::Ipv4,
|
||||
NlriNextHop::Ipv6(_) | NlriNextHop::Ipv6WithLl { .. } => IpVersion::Ipv6,
|
||||
};
|
||||
if version != prefix.address_family() {
|
||||
// An IPv4 route may have an IPv6 next hop: that is how routes go over
|
||||
// a link with only IPv6 addresses (RFC 8950), and the kernel takes
|
||||
// such a route. The other way about means nothing.
|
||||
if version != prefix.address_family() && version == IpVersion::Ipv4 {
|
||||
return Err(Unresolved::OtherFamily);
|
||||
}
|
||||
let direct = |gateway: IpAddr| {
|
||||
@@ -427,7 +430,6 @@ mod tests {
|
||||
for (hop, why) in [
|
||||
(Some(v4_hop("10.9.9.9")), Unresolved::NotConnected),
|
||||
(Some(v4_hop("192.0.2.1")), Unresolved::Ourselves),
|
||||
(Some(v6_hop("2001:db8::2")), Unresolved::OtherFamily),
|
||||
(None, Unresolved::Missing),
|
||||
] {
|
||||
assert_eq!(resolve(&interfaces, &v4, hop.as_ref(), peer), Err(why));
|
||||
@@ -436,6 +438,24 @@ mod tests {
|
||||
resolve(&interfaces, &v6, Some(&v4_hop("192.0.2.2")), peer),
|
||||
Err(Unresolved::OtherFamily)
|
||||
);
|
||||
// An IPv4 route by way of an IPv6 neighbor: a link with only IPv6
|
||||
// addresses (RFC 8950).
|
||||
assert_eq!(
|
||||
resolve(&interfaces, &v4, Some(&v6_hop("2001:db8::2")), peer),
|
||||
Ok(target("2001:db8::2", 2))
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(
|
||||
&interfaces,
|
||||
&v4,
|
||||
Some(&NlriNextHop::Ipv6WithLl {
|
||||
global: "2001:db8:ffff::2".parse().unwrap(),
|
||||
link_local: "fe80::2".parse().unwrap(),
|
||||
}),
|
||||
Some("2001:db8::2".parse().unwrap()),
|
||||
),
|
||||
Ok(target("fe80::2", 2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user