From 131ef566ef05d0c1f76ee0c52bb62116f943c767 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 15 Dec 2025 19:21:22 +0100 Subject: [PATCH] clat: support all pref64 lengths Support all the prefix lengths defined in RFC 6052. --- src/core/bpf/clat.bpf.c | 183 +++++++++++++++++++++++++++++++-- src/core/bpf/clat.h | 3 +- src/core/ndisc/nm-lndp-ndisc.c | 8 +- src/core/nm-l3cfg.c | 1 + 4 files changed, 179 insertions(+), 16 deletions(-) diff --git a/src/core/bpf/clat.bpf.c b/src/core/bpf/clat.bpf.c index 69f116b911..4f96e1e256 100644 --- a/src/core/bpf/clat.bpf.c +++ b/src/core/bpf/clat.bpf.c @@ -273,6 +273,170 @@ rewrite_icmp(struct __sk_buff *skb, const struct ipv6hdr *ip6h) return 0; } +/* + * Convert an IPv4 address to the corresponding "IPv4-Embedded IPv6 Address" + * according to RFC 6052 2.2. + * + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------| + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |32| prefix |v4(32) | u | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |40| prefix |v4(24) | u |(8)| suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |48| prefix |v4(16) | u | (16) | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |56| prefix |(8)| u | v4(24) | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |64| prefix | u | v4(32) | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |96| prefix | v4(32) | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + */ +static __always_inline bool +v4addr_to_v6(__be32 addr4, struct in6_addr *addr6, const struct in6_addr *pref64, int pref64_len) +{ + union { + __be32 a32; + __u8 a8[4]; + } u; + + u.a32 = addr4; + + addr6->s6_addr32[0] = 0; + addr6->s6_addr32[1] = 0; + addr6->s6_addr32[2] = 0; + addr6->s6_addr32[3] = 0; + + switch (pref64_len) { + case 96: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = pref64->s6_addr32[1]; + addr6->s6_addr32[2] = pref64->s6_addr32[2]; + addr6->s6_addr32[3] = addr4; + break; + case 64: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = pref64->s6_addr32[1]; + addr6->s6_addr[9] = u.a8[0]; + addr6->s6_addr[10] = u.a8[1]; + addr6->s6_addr[11] = u.a8[2]; + addr6->s6_addr[12] = u.a8[3]; + break; + case 56: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = pref64->s6_addr32[1]; + addr6->s6_addr[7] = u.a8[0]; + addr6->s6_addr[9] = u.a8[1]; + addr6->s6_addr[10] = u.a8[2]; + addr6->s6_addr[11] = u.a8[3]; + break; + case 48: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr16[2] = pref64->s6_addr16[2]; + addr6->s6_addr[6] = u.a8[0]; + addr6->s6_addr[7] = u.a8[1]; + addr6->s6_addr[9] = u.a8[2]; + addr6->s6_addr[10] = u.a8[3]; + break; + case 40: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr[4] = pref64->s6_addr[4]; + addr6->s6_addr[5] = u.a8[0]; + addr6->s6_addr[6] = u.a8[1]; + addr6->s6_addr[7] = u.a8[2]; + addr6->s6_addr[9] = u.a8[3]; + break; + case 32: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = addr4; + break; + default: + return false; + } + return true; +} + +/* + * Extract the IPv4 address @addr4 and the NAT64 prefix @pref64 from an IPv6 address, + * given the known prefix length @pref64_len. See the table above. + */ +static __always_inline bool +v6addr_to_v4(const struct in6_addr *addr6, int pref64_len, __be32 *addr4, struct in6_addr *pref64) +{ + union { + __be32 a32; + __u8 a8[4]; + } u; + + pref64->s6_addr32[0] = 0; + pref64->s6_addr32[1] = 0; + pref64->s6_addr32[2] = 0; + pref64->s6_addr32[3] = 0; + + switch (pref64_len) { + case 96: + u.a32 = addr6->s6_addr32[3]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr32[2] = addr6->s6_addr32[2]; + break; + case 64: + u.a8[0] = addr6->s6_addr[9]; + u.a8[1] = addr6->s6_addr[10]; + u.a8[2] = addr6->s6_addr[11]; + u.a8[3] = addr6->s6_addr[12]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + break; + case 56: + u.a8[0] = addr6->s6_addr[7]; + u.a8[1] = addr6->s6_addr[9]; + u.a8[2] = addr6->s6_addr[10]; + u.a8[3] = addr6->s6_addr[11]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr[7] = 0; + break; + case 48: + u.a8[0] = addr6->s6_addr[6]; + u.a8[1] = addr6->s6_addr[7]; + u.a8[2] = addr6->s6_addr[9]; + u.a8[3] = addr6->s6_addr[10]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr16[3] = 0; + break; + case 40: + u.a8[0] = addr6->s6_addr[5]; + u.a8[1] = addr6->s6_addr[6]; + u.a8[2] = addr6->s6_addr[7]; + u.a8[3] = addr6->s6_addr[9]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr16[3] = 0; + pref64->s6_addr[5] = 0; + + break; + case 32: + u.a32 = addr6->s6_addr32[1]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + break; + default: + return false; + } + + *addr4 = u.a32; + return true; +} + /* ipv4 traffic in from application on this device, needs to be translated to v6 and sent to PLAT */ static int clat_handle_v4(struct __sk_buff *skb) @@ -314,12 +478,12 @@ clat_handle_v4(struct __sk_buff *skb) goto out; } - /* src v4 as last octet of clat address */ - dst_hdr.saddr = config.local_v6; - dst_hdr.daddr = config.pref64; - dst_hdr.daddr.s6_addr32[3] = iph->daddr; - dst_hdr.nexthdr = iph->protocol; - dst_hdr.hop_limit = iph->ttl; + if (!v4addr_to_v6(iph->daddr, &dst_hdr.daddr, &config.pref64, config.pref64_len)) + goto out; + + dst_hdr.saddr = config.local_v6; + dst_hdr.nexthdr = iph->protocol; + dst_hdr.hop_limit = iph->ttl; /* weird definition in ipv6hdr */ dst_hdr.priority = (iph->tos & 0x70) >> 4; dst_hdr.flow_lbl[0] = iph->tos << 4; @@ -495,6 +659,7 @@ clat_handle_v6(struct __sk_buff *skb) int ret = TC_ACT_OK; void *data_end = SKB_DATA_END(skb); void *data = SKB_DATA(skb); + __be32 saddr4; struct in6_addr subnet_v6; struct ethhdr *eth; struct iphdr *iph; @@ -512,8 +677,8 @@ clat_handle_v6(struct __sk_buff *skb) if (!v6addr_equal(&ip6h->daddr, &config.local_v6)) goto out; - subnet_v6 = ip6h->saddr; - subnet_v6.s6_addr32[3] = 0; /* prefix len is always 96 for now */ + if (!v6addr_to_v4(&ip6h->saddr, config.pref64_len, &saddr4, &subnet_v6)) + goto out; if (!v6addr_equal(&subnet_v6, &config.pref64)) goto out; @@ -529,7 +694,7 @@ clat_handle_v6(struct __sk_buff *skb) goto out; dst_hdr.daddr = config.local_v4.s_addr; - dst_hdr.saddr = ip6h->saddr.s6_addr32[3]; + dst_hdr.saddr = saddr4; dst_hdr.protocol = ip6h->nexthdr; dst_hdr.ttl = ip6h->hop_limit; dst_hdr.tos = ip6h->priority << 4 | (ip6h->flow_lbl[0] >> 4); diff --git a/src/core/bpf/clat.h b/src/core/bpf/clat.h index 2748946392..3d926b527f 100644 --- a/src/core/bpf/clat.h +++ b/src/core/bpf/clat.h @@ -7,7 +7,8 @@ struct clat_config { struct in6_addr local_v6; struct in6_addr pref64; - struct in_addr local_v4; + struct in_addr local_v4; + unsigned pref64_len; }; #endif diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c index 980bc597bb..69cb733a77 100644 --- a/src/core/ndisc/nm-lndp-ndisc.c +++ b/src/core/ndisc/nm-lndp-ndisc.c @@ -409,12 +409,8 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) gint64 expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_pref64_lifetime(msg, offset)); - /* Currently, only /96 is supported */ - if (pref64_length != 96) { - _LOGW("Ignored PREF64 for unsupported prefix length: %d (only /96 is supported)", - pref64_length); - continue; - } + /* libndp should only return lengths defined in RFC 8781 */ + nm_assert(NM_IN_SET(pref64_length, 96, 64, 56, 48, 40, 32)); /* Newer RA has more up to date information, prefer it: */ if (!pref64_found) { diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 5c3f92080f..21e196c6d0 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -5689,6 +5689,7 @@ _l3_commit_pref64(NML3Cfg *self, NML3CfgCommitType commit_type) clat_config.local_v4.s_addr = self->priv.p->clat_address_4->addr; clat_config.local_v6 = self->priv.p->clat_address_6.address; clat_config.pref64 = *l3cd_pref64; + clat_config.pref64_len = l3cd_pref64_plen; self->priv.p->clat_bpf->bss->config = clat_config; if (self->priv.p->clat_socket < 0) {