clat: support all pref64 lengths

Support all the prefix lengths defined in RFC 6052.
This commit is contained in:
Beniamino Galvani 2025-12-15 19:21:22 +01:00
parent 5dba386055
commit 131ef566ef
4 changed files with 179 additions and 16 deletions

View file

@ -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);

View file

@ -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

View file

@ -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) {

View file

@ -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) {