[RFC net-next v2 06/18] net: ipxlat: add RFC 6052 and EAM address translation
flat view
COOLING4d
From: Ralf Lici <ralf@mandelbit.com>
Date: 2026-10-24 09:26:17
Also in:
lkml
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Translate each endpoint through a matching RFC 7757 EAM first and use the configured RFC 6052 prefix as fallback. Permit the RFC 7915 dummy IPv4 source only for otherwise unmappable ICMPv6 errors. Signed-off-by: Ralf Lici <ralf@mandelbit.com> --- Changes since RFC v1 https://lore.kernel.org/netdev/20260319151230.655687-3-ralf@mandelbit.com/ (local) - Extend the v1 symmetric RFC 6052 conversion with an optional RFC 7757 EAM. Each endpoint independently uses the EAM on a match and RFC 6052 otherwise, supporting both single-address and delegated-prefix CLAT. (Beniamino) - Validate the reserved RFC 6052 u octet during 6-to-4 extraction. net/ipxlat/Makefile | 1 + net/ipxlat/address.c | 218 +++++++++++++++++++++++++++++++++++++++++++ net/ipxlat/address.h | 57 +++++++++++ 3 files changed, 276 insertions(+) create mode 100644 net/ipxlat/address.c create mode 100644 net/ipxlat/address.h
diff --git a/net/ipxlat/Makefile b/net/ipxlat/Makefile
index 47d00b7b05bb..013c338a2576 100644
--- a/net/ipxlat/Makefile
+++ b/net/ipxlat/Makefile@@ -6,3 +6,4 @@ obj-$(CONFIG_NET_IPXLAT) += ipxlat.o ipxlat-objs += config.o ipxlat-objs += core.o +ipxlat-objs += address.o
diff --git a/net/ipxlat/address.c b/net/ipxlat/address.c
new file mode 100644
index 000000000000..e1ff81daf7dc
--- /dev/null
+++ b/net/ipxlat/address.c@@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* IPXLAT - Stateless IP/ICMP Translation (SIIT) engine + * + * Copyright (C) 2024- Alberto Leiva Popper <ydahhrk@gmail.com> + * Copyright (C) 2026- Mandelbit SRL + * Copyright (C) 2026- Daniel Gröber <dxld@darkboxed.org> + * + * Author: Alberto Leiva Popper <ydahhrk@gmail.com> + * Antonio Quartulli <antonio@mandelbit.com> + * Daniel Gröber <dxld@darkboxed.org> + * Ralf Lici <ralf@mandelbit.com> + */ + +#include <linux/errno.h> +#include <linux/inetdevice.h> +#include <linux/kernel.h> + +#include "address.h" + +static bool ipxlat_prefix4_contains(const struct ipxlat_prefix4 *prefix, + __be32 addr) +{ + return (addr & inet_make_mask(prefix->len)) == prefix->addr; +} + +static bool ipxlat_prefix6_contains(const struct ipxlat_prefix6 *prefix, + const struct in6_addr *addr) +{ + return ipv6_prefix_equal(&prefix->addr, addr, prefix->len); +} + +static void ipxlat_rfc6052_46_embed(__be32 addr4, struct in6_addr *dst, + unsigned int q1, unsigned int q2, + unsigned int q3, unsigned int q4) +{ + const u32 src = ntohl(addr4); + + dst->s6_addr[q1] = src >> 24; + dst->s6_addr[q2] = src >> 16; + dst->s6_addr[q3] = src >> 8; + dst->s6_addr[q4] = src; +} + +static void ipxlat_rfc6052_46(const struct ipxlat_prefix6 *rfc6052_prefix6, + __be32 addr4, struct in6_addr *addr6) +{ + *addr6 = rfc6052_prefix6->addr; + + /* always skip the 9th (index 8) byte which is the reserve "u" octet */ + switch (rfc6052_prefix6->len) { + case 96: + addr6->s6_addr32[3] = addr4; + break; + case 64: + ipxlat_rfc6052_46_embed(addr4, addr6, 9, 10, 11, 12); + break; + case 56: + ipxlat_rfc6052_46_embed(addr4, addr6, 7, 9, 10, 11); + break; + case 48: + ipxlat_rfc6052_46_embed(addr4, addr6, 6, 7, 9, 10); + break; + case 40: + ipxlat_rfc6052_46_embed(addr4, addr6, 5, 6, 7, 9); + break; + case 32: + addr6->s6_addr32[1] = addr4; + break; + default: + DEBUG_NET_WARN_ON_ONCE(1); + break; + } +} + +static __be32 ipxlat_rfc6052_64_extract(const struct in6_addr *src, + unsigned int q1, unsigned int q2, + unsigned int q3, unsigned int q4) +{ + const u32 a = src->s6_addr[q1]; + const u32 b = src->s6_addr[q2]; + const u32 c = src->s6_addr[q3]; + const u32 d = src->s6_addr[q4]; + + return htonl((a << 24) | (b << 16) | (c << 8) | d); +} + +static int ipxlat_rfc6052_64(const struct ipxlat_prefix6 *rfc6052_prefix6, + const struct in6_addr *addr6, __be32 *addr4) +{ + if (!ipxlat_prefix6_contains(rfc6052_prefix6, addr6) || + addr6->s6_addr[8]) + return -EINVAL; + + /* always skip the 9th (index 8) byte which is the reserve "u" octet */ + switch (rfc6052_prefix6->len) { + case 96: + *addr4 = addr6->s6_addr32[3]; + break; + case 64: + *addr4 = ipxlat_rfc6052_64_extract(addr6, 9, 10, 11, 12); + break; + case 56: + *addr4 = ipxlat_rfc6052_64_extract(addr6, 7, 9, 10, 11); + break; + case 48: + *addr4 = ipxlat_rfc6052_64_extract(addr6, 6, 7, 9, 10); + break; + case 40: + *addr4 = ipxlat_rfc6052_64_extract(addr6, 5, 6, 7, 9); + break; + case 32: + *addr4 = addr6->s6_addr32[1]; + break; + default: + return -EINVAL; + } + return 0; +} + +static void ipxlat_set_bit(struct in6_addr *addr, unsigned int bit, bool value) +{ + /* IPv6 prefix bits are numbered from the most significant bit */ + u8 *p = &addr->s6_addr[bit >> 3]; + u8 mask = BIT(7 - (bit & 7)); + + if (value) + *p |= mask; + else + *p &= ~mask; +} + +static bool ipxlat_get_bit(const struct in6_addr *addr, unsigned int bit) +{ + return addr->s6_addr[bit >> 3] & BIT(7 - (bit & 7)); +} + +static void ipxlat_eam_46(__be32 addr4, const struct ipxlat_eam *eam, + struct in6_addr *addr6) +{ + const u8 host_bits = 32 - eam->prefix4.len; + const u32 host = ntohl(addr4); + unsigned int i; + + /* append the IPv4 suffix to EAM6 and leave the unused tail zero */ + *addr6 = eam->prefix6.addr; + for (i = 0; i < host_bits; ++i) + ipxlat_set_bit(addr6, eam->prefix6.len + i, + host & BIT(host_bits - 1 - i)); +} + +static void ipxlat_eam_64(const struct in6_addr *addr6, + const struct ipxlat_eam *eam, __be32 *addr4) +{ + const u8 host_bits = 32 - eam->prefix4.len; + unsigned int i; + u32 host = 0; + + /* RFC 7757 discards IPv6 suffix bits which do not fit in IPv4 */ + for (i = 0; i < host_bits; ++i) + host = (host << 1) | + ipxlat_get_bit(addr6, eam->prefix6.len + i); + + *addr4 = eam->prefix4.addr | htonl(host); +} + +static void ipxlat_46_convert_addr(const struct ipxlat_config *cfg, + __be32 addr4, struct in6_addr *addr6) +{ + const struct ipxlat_eam *eam = &cfg->eam; + + if (cfg->has_eam && ipxlat_prefix4_contains(&eam->prefix4, addr4)) { + ipxlat_eam_46(addr4, eam, addr6); + return; + } + + ipxlat_rfc6052_46(&cfg->rfc6052_prefix6, addr4, addr6); +} + +static int ipxlat_64_convert_addr(const struct ipxlat_config *cfg, + const struct in6_addr *addr6, __be32 *addr4) +{ + const struct ipxlat_eam *eam = &cfg->eam; + + if (cfg->has_eam && ipxlat_prefix6_contains(&eam->prefix6, addr6)) { + ipxlat_eam_64(addr6, eam, addr4); + return 0; + } + + return ipxlat_rfc6052_64(&cfg->rfc6052_prefix6, addr6, addr4); +} + +void ipxlat_46_convert_endpoints(const struct ipxlat_config *cfg, + const struct iphdr *iph4, struct in6_addr *src, + struct in6_addr *dst) +{ + ipxlat_46_convert_addr(cfg, iph4->saddr, src); + ipxlat_46_convert_addr(cfg, iph4->daddr, dst); +} + +int ipxlat_64_convert_endpoints(const struct ipxlat_config *cfg, + const struct ipv6hdr *hdr6, + bool allow_dummy_src, __be32 *src, __be32 *dst) +{ + int err; + + err = ipxlat_64_convert_addr(cfg, &hdr6->saddr, src); + if (err) { + if (!allow_dummy_src) + return err; + + /* ICMPv6 errors may come from a router whose source address has + * no configured IPv4 mapping + */ + *src = htonl(INADDR_DUMMY); + } + + return ipxlat_64_convert_addr(cfg, &hdr6->daddr, dst); +}
diff --git a/net/ipxlat/address.h b/net/ipxlat/address.h
new file mode 100644
index 000000000000..5f43c729bc94
--- /dev/null
+++ b/net/ipxlat/address.h@@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* IPXLAT - Stateless IP/ICMP Translation (SIIT) engine + * + * Copyright (C) 2024- Alberto Leiva Popper <ydahhrk@gmail.com> + * Copyright (C) 2026- Mandelbit SRL + * Copyright (C) 2026- Daniel Gröber <dxld@darkboxed.org> + * + * Author: Alberto Leiva Popper <ydahhrk@gmail.com> + * Antonio Quartulli <antonio@mandelbit.com> + * Daniel Gröber <dxld@darkboxed.org> + * Ralf Lici <ralf@mandelbit.com> + */ + +#ifndef _NET_IPXLAT_ADDRESS_H_ +#define _NET_IPXLAT_ADDRESS_H_ + +#include <linux/ip.h> +#include <net/ipv6.h> + +#include <net/ipxlat.h> + +/** + * ipxlat_46_convert_endpoints - convert both endpoints of an IPv4 header + * @cfg: validated translation configuration + * @iph4: original IPv4 header + * @src: translated IPv6 source + * @dst: translated IPv6 destination + * + * A matching RFC 7757 EAM takes precedence for each endpoint. Endpoints not + * covered by the EAM are converted with the RFC 6052 prefix. + */ +void ipxlat_46_convert_endpoints(const struct ipxlat_config *cfg, + const struct iphdr *iph4, struct in6_addr *src, + struct in6_addr *dst); + +/** + * ipxlat_64_convert_endpoints - convert both endpoints of an IPv6 header + * @cfg: validated translation configuration + * @hdr6: original IPv6 header + * @allow_dummy_src: permit the RFC 7915 dummy IPv4 source (for an ICMP error) + * @src: translated IPv4 source + * @dst: translated IPv4 destination + * + * A matching RFC 7757 EAM takes precedence for each endpoint. Endpoints not + * covered by the EAM are converted with the RFC 6052 prefix. + * + * If the source address cannot be converted and @allow_dummy_src is true, + * INADDR_DUMMY will be used as source. + * + * Return: zero on success or a negative errno when an endpoint cannot be + * represented in IPv4. + */ +int ipxlat_64_convert_endpoints(const struct ipxlat_config *cfg, + const struct ipv6hdr *hdr6, + bool allow_dummy_src, __be32 *src, __be32 *dst); + +#endif /* _NET_IPXLAT_ADDRESS_H_ */
--
2.55.0