All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ralf Lici <ralf@mandelbit.com>
To: netdev@vger.kernel.org
Cc: "David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@kernel.org>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Simon Horman" <horms@kernel.org>,
	"Daniel Gröber" <dxld@darkboxed.org>,
	"Antonio Quartulli" <antonio@mandelbit.com>,
	linux-kernel@vger.kernel.org
Subject: [RFC net-next v2 11/18] net: ipxlat: add IPv4/IPv6 header and transport translation
Date: Sat, 24 Oct 2026 11:25:21 +0200	[thread overview]
Message-ID: <20261024092532.172005-12-ralf@mandelbit.com> (raw)
In-Reply-To: <20261024092532.172005-1-ralf@mandelbit.com>

Rewrite IPv4 and IPv6 headers in place, map Fragment Headers and update
TCP and UDP checksums for ordinary, partial-checksum and GSO packets.

Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
Changes since RFC v1 https://lore.kernel.org/netdev/20260319151230.655687-8-ralf@mandelbit.com/
- Drive the L3 and TCP/UDP rewrite paths only from prepared metadata and
  configuration, without routing or device policy.
- Base 6-to-4 GSO DF selection on each eventual IPv4 segment and leave
  DF clear for legacy UFO.
- Make the 6-to-4 skb writable before moving or changing headers.
  (Sashiko)
- Reacquire header pointers after operations which can move packet
  storage.
- Use the kernel's zero length-field convention for oversized GSO
  packets instead of truncating translated lengths, and reject
  unrepresentable non-GSO packets. (Sashiko)
- Preserve CSUM_MANGLED_0 when an outer 4-to-6 UDP checksum update
  produces zero. (Sashiko)

 net/ipxlat/Makefile       |   2 +
 net/ipxlat/translate_46.c | 213 ++++++++++++++++++++++++++++++++++++
 net/ipxlat/translate_46.h |  77 +++++++++++++
 net/ipxlat/translate_64.c | 222 ++++++++++++++++++++++++++++++++++++++
 net/ipxlat/translate_64.h |  96 +++++++++++++++++
 5 files changed, 610 insertions(+)
 create mode 100644 net/ipxlat/translate_46.c
 create mode 100644 net/ipxlat/translate_46.h
 create mode 100644 net/ipxlat/translate_64.c
 create mode 100644 net/ipxlat/translate_64.h

diff --git a/net/ipxlat/Makefile b/net/ipxlat/Makefile
index 91baab41d5eb..b0fab158c7b3 100644
--- a/net/ipxlat/Makefile
+++ b/net/ipxlat/Makefile
@@ -10,3 +10,5 @@ ipxlat-objs += address.o
 ipxlat-objs += packet4.o
 ipxlat-objs += packet6.o
 ipxlat-objs += transport.o
+ipxlat-objs += translate_46.o
+ipxlat-objs += translate_64.o
diff --git a/net/ipxlat/translate_46.c b/net/ipxlat/translate_46.c
new file mode 100644
index 000000000000..add29d9ef0a7
--- /dev/null
+++ b/net/ipxlat/translate_46.c
@@ -0,0 +1,213 @@
+// 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 <net/ip6_checksum.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#include <net/ipxlat.h>
+
+#include "address.h"
+#include "packet.h"
+#include "transport.h"
+#include "translate_46.h"
+
+u8 ipxlat_46_map_proto_to_nexthdr(u8 protocol)
+{
+	return (protocol == IPPROTO_ICMP) ? NEXTHDR_ICMP : protocol;
+}
+
+static __be16 ipxlat_46_build_frag_off(u16 frag_offset, bool mf)
+{
+	return cpu_to_be16((frag_offset & IP6_OFFSET) | (mf ? IP6_MF : 0));
+}
+
+void ipxlat_46_build_frag_hdr(struct frag_hdr *fh6, const struct iphdr *hdr4,
+			      u8 l4_proto)
+{
+	const u16 frag_off4 = ipxlat_get_frag4_offset(hdr4);
+	const bool mf = !!(be16_to_cpu(hdr4->frag_off) & IP_MF);
+
+	fh6->nexthdr = ipxlat_46_map_proto_to_nexthdr(l4_proto);
+	fh6->reserved = 0;
+	fh6->frag_off = ipxlat_46_build_frag_off(frag_off4, mf);
+	fh6->identification = cpu_to_be32(be16_to_cpu(hdr4->id));
+}
+
+void ipxlat_46_build_l3_hdr(struct ipv6hdr *iph6, const struct iphdr *iph4,
+			    unsigned int l3_payload_len, u8 nexthdr,
+			    u8 hop_limit)
+{
+	iph6->version = 6;
+	iph6->priority = iph4->tos >> 4;
+	iph6->flow_lbl[0] = (iph4->tos & 0x0F) << 4;
+	iph6->flow_lbl[1] = 0;
+	iph6->flow_lbl[2] = 0;
+	ipv6_set_payload_len(iph6, l3_payload_len);
+	iph6->nexthdr = nexthdr;
+	iph6->hop_limit = hop_limit;
+}
+
+int ipxlat_46_prepare_udp_csum(struct sk_buff *skb)
+{
+	const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	struct udphdr *udp = udp_hdr(skb);
+	struct iphdr *iph4 = ip_hdr(skb);
+	unsigned int udp_len;
+	__sum16 csum;
+
+	if (!ipxlat_is_first_frag4(iph4) || skb->ip_summed == CHECKSUM_PARTIAL)
+		return 0;
+	if (likely(udp->check))
+		return 0;
+
+	udp_len = ntohs(udp->len);
+	if (unlikely(skb_ensure_writable(skb, cb->l4_off + sizeof(*udp))))
+		return -ENOMEM;
+
+	/* skb_ensure_writable might move pointers so re-acquire the headers */
+	iph4 = ip_hdr(skb);
+	udp = udp_hdr(skb);
+	udp->check = 0;
+	csum = csum_tcpudp_magic(iph4->saddr, iph4->daddr, udp_len, IPPROTO_UDP,
+				 skb_checksum(skb, cb->l4_off, udp_len, 0));
+	udp->check = csum ? csum : CSUM_MANGLED_0;
+	return 0;
+}
+
+static int ipxlat_46_translate_tcp_csum(struct sk_buff *skb,
+					const struct iphdr *in4)
+{
+	const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	const struct ipv6hdr *out6 = ipv6_hdr(skb);
+	struct tcphdr *tcp = tcp_hdr(skb);
+
+	/* CHECKSUM_PARTIAL contains a pseudo-header seed rather than a final
+	 * checksum. Replace it with the translated IPv6 pseudo-header and
+	 * leave completion to the later output path.
+	 */
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		tcp->check = ~tcp_v6_check(ipxlat_skb_l4_seg_len(skb, cb),
+					   &out6->saddr, &out6->daddr, 0);
+		return ipxlat_set_partial_csum(skb,
+					       offsetof(struct tcphdr, check));
+	}
+
+	tcp->check = ipxlat_46_update_l4_csum(tcp->check, in4, out6);
+	skb->ip_summed = CHECKSUM_NONE;
+	return 0;
+}
+
+static int ipxlat_46_translate_udp_csum(struct sk_buff *skb,
+					const struct iphdr *in4)
+{
+	const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	const struct ipv6hdr *out6 = ipv6_hdr(skb);
+	struct udphdr *udp = udp_hdr(skb);
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		udp->check = ~udp_v6_check(ipxlat_skb_l4_seg_len(skb, cb),
+					   &out6->saddr, &out6->daddr, 0);
+		return ipxlat_set_partial_csum(skb,
+					       offsetof(struct udphdr, check));
+	}
+
+	udp->check = ipxlat_46_update_l4_csum(udp->check, in4, out6);
+	if (unlikely(!udp->check))
+		udp->check = CSUM_MANGLED_0;
+	skb->ip_summed = CHECKSUM_NONE;
+	return 0;
+}
+
+int ipxlat_46_translate(const struct ipxlat_config *cfg, struct sk_buff *skb)
+{
+	unsigned int l3_payload_len, min_l4_len, old_l3_len, new_l3_len;
+	struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	const struct iphdr outer4 = *ip_hdr(skb);
+	bool has_frag, first_frag;
+	u8 l4_proto4, l4_proto6;
+	struct frag_hdr *fh6;
+	struct ipv6hdr *iph6;
+	int l3_delta, err;
+
+	/* snapshot the original IPv4 header fields before skb layout changes */
+	l4_proto4 = cb->l4_proto;
+	l4_proto6 = ipxlat_46_map_proto_to_nexthdr(l4_proto4);
+	has_frag = ip_is_fragment(&outer4);
+	first_frag = ipxlat_is_first_frag4(&outer4);
+
+	old_l3_len = cb->l3_hdr_len;
+	new_l3_len = sizeof(struct ipv6hdr) +
+		     (has_frag ? sizeof(struct frag_hdr) : 0);
+	l3_delta = (int)new_l3_len - (int)old_l3_len;
+
+	/* reserve headroom and make the transport header writable */
+	if (unlikely(skb_cow_head(skb, max_t(int, 0, l3_delta))))
+		return -ENOMEM;
+	if (first_frag) {
+		min_l4_len = ipxlat_l4_min_len(l4_proto4);
+		if (unlikely(skb_ensure_writable(skb, cb->l4_off + min_l4_len)))
+			return -ENOMEM;
+	}
+
+	/* replace outer L3 area: drop IPv4 hdr, reserve IPv6(+Frag) hdr */
+	ipxlat_replace_l3(skb, old_l3_len, new_l3_len, new_l3_len);
+	skb->protocol = htons(ETH_P_IPV6);
+
+	/* parsed offsets include the old L3 length, which the delta replaces */
+	ipxlat_cb_rebase_offsets(cb, l3_delta);
+	DEBUG_NET_WARN_ON_ONCE(!ipxlat_cb_offsets_valid(cb));
+
+	cb->l3_hdr_len = new_l3_len;
+	cb->fragh_off = has_frag ? sizeof(struct ipv6hdr) : 0;
+	cb->l4_proto = l4_proto6;
+
+	/* build outer IPv6 base hdr from translated IPv4 fields */
+	iph6 = ipv6_hdr(skb);
+	l3_payload_len = skb->len - skb_network_offset(skb) - sizeof(*iph6);
+	ipxlat_46_build_l3_hdr(iph6, &outer4, l3_payload_len, l4_proto6,
+			       outer4.ttl);
+	ipxlat_46_convert_endpoints(cfg, &outer4, &iph6->saddr, &iph6->daddr);
+
+	/* add IPv6 fragment hdr when the IPv4 packet carried fragmentation */
+	if (unlikely(has_frag)) {
+		iph6->nexthdr = NEXTHDR_FRAGMENT;
+
+		fh6 = (struct frag_hdr *)(iph6 + 1);
+		ipxlat_46_build_frag_hdr(fh6, &outer4, l4_proto4);
+	}
+
+	/* non-first fragments have no transport header to translate */
+	if (unlikely(!first_frag))
+		goto out;
+
+	/* translate transport hdr and pseudohdr dependent checksums */
+	switch (l4_proto4) {
+	case IPPROTO_TCP:
+		err = ipxlat_46_translate_tcp_csum(skb, &outer4);
+		break;
+	case IPPROTO_UDP:
+		err = ipxlat_46_translate_udp_csum(skb, &outer4);
+		break;
+	default:
+		err = 0;
+		break;
+	}
+	if (unlikely(err))
+		return err;
+
+out:
+	/* normalize checksum/offload metadata for the translated frame */
+	return ipxlat_finalize_offload(skb, l4_proto4, has_frag, SKB_GSO_TCPV4,
+				       SKB_GSO_TCPV6);
+}
diff --git a/net/ipxlat/translate_46.h b/net/ipxlat/translate_46.h
new file mode 100644
index 000000000000..b3a63284e641
--- /dev/null
+++ b/net/ipxlat/translate_46.h
@@ -0,0 +1,77 @@
+/* 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_TRANSLATE_46_H_
+#define _NET_IPXLAT_TRANSLATE_46_H_
+
+#include <net/ipxlat.h>
+
+struct iphdr;
+struct ipv6hdr;
+struct frag_hdr;
+struct sk_buff;
+
+/**
+ * ipxlat_46_map_proto_to_nexthdr - map IPv4 L4 protocol to IPv6 nexthdr
+ * @protocol: IPv4 L4 protocol
+ *
+ * Return: IPv6 next-header value corresponding to @protocol.
+ */
+u8 ipxlat_46_map_proto_to_nexthdr(u8 protocol);
+
+/**
+ * ipxlat_46_build_frag_hdr - build IPv6 Fragment Header from IPv4 fragment info
+ * @fh6: output IPv6 fragment header
+ * @hdr4: source IPv4 header
+ * @l4_proto: original IPv4 L4 protocol
+ */
+void ipxlat_46_build_frag_hdr(struct frag_hdr *fh6, const struct iphdr *hdr4,
+			      u8 l4_proto);
+
+/**
+ * ipxlat_46_build_l3_hdr - build translated IPv6 header from IPv4 metadata
+ * @iph6: output IPv6 header
+ * @iph4: source IPv4 header
+ * @l3_payload_len: IPv6 payload length
+ * @nexthdr: resulting IPv6 nexthdr
+ * @hop_limit: resulting IPv6 hop limit
+ */
+void ipxlat_46_build_l3_hdr(struct ipv6hdr *iph6, const struct iphdr *iph4,
+			    unsigned int l3_payload_len, u8 nexthdr,
+			    u8 hop_limit);
+
+/**
+ * ipxlat_46_prepare_udp_csum - checksum a validated zero-checksum UDP packet
+ * @skb: original IPv4 packet
+ *
+ * This runs after translation eligibility is known. Packets which already
+ * carry a checksum are unchanged.
+ *
+ * Return: 0 on success or %-ENOMEM when the UDP header cannot be made
+ * writable.
+ */
+int ipxlat_46_prepare_udp_csum(struct sk_buff *skb);
+
+/**
+ * ipxlat_46_translate - translate outer packet from IPv4 to IPv6 in place
+ * @cfg: immutable route translation configuration
+ * @skb: packet to translate
+ *
+ * Rewrites outer L3 in place, rebases cached offsets and translates L4 on
+ * first fragments only.
+ *
+ * Return: 0 on success, negative errno on translation failure.
+ */
+int ipxlat_46_translate(const struct ipxlat_config *cfg, struct sk_buff *skb);
+
+#endif /* _NET_IPXLAT_TRANSLATE_46_H_ */
diff --git a/net/ipxlat/translate_64.c b/net/ipxlat/translate_64.c
new file mode 100644
index 000000000000..2448d3da32e4
--- /dev/null
+++ b/net/ipxlat/translate_64.c
@@ -0,0 +1,222 @@
+// 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/icmpv6.h>
+#include <net/ip.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#include "translate_64.h"
+#include "packet.h"
+#include "transport.h"
+
+u8 ipxlat_64_map_nexthdr_proto(u8 nexthdr)
+{
+	return (nexthdr == NEXTHDR_ICMP) ? IPPROTO_ICMP : nexthdr;
+}
+
+void ipxlat_64_build_l3_hdr(struct net *net, struct iphdr *iph4,
+			    const struct ipv6hdr *iph6,
+			    unsigned int tot_len, __be16 frag_off, u8 protocol,
+			    __be32 saddr, __be32 daddr, u8 ttl,
+			    const struct frag_hdr *frag6)
+{
+	iph4->version = 4;
+	iph4->ihl = 5;
+	iph4->tos = ipxlat_get_ipv6_tclass(iph6);
+	iph_set_totlen(iph4, tot_len);
+	iph4->frag_off = frag_off;
+	iph4->ttl = ttl;
+	iph4->protocol = protocol;
+	iph4->saddr = saddr;
+	iph4->daddr = daddr;
+	iph4->id = 0;
+	if (frag6)
+		iph4->id = ipxlat_64_map_frag_id(frag6);
+	else
+		__ip_select_ident(net, iph4, 1);
+	iph4->check = 0;
+	iph4->check = ip_fast_csum(iph4, iph4->ihl);
+}
+
+static int ipxlat_64_translate_tcp_csum(struct sk_buff *skb,
+					const struct ipv6hdr *in6)
+{
+	const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	const struct iphdr *out4 = ip_hdr(skb);
+	struct tcphdr *tcp = tcp_hdr(skb);
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		tcp->check = ~tcp_v4_check(ipxlat_skb_l4_seg_len(skb, cb),
+					   out4->saddr, out4->daddr, 0);
+		return ipxlat_set_partial_csum(skb,
+					       offsetof(struct tcphdr, check));
+	}
+
+	tcp->check = ipxlat_64_update_l4_csum(tcp->check, in6, out4);
+	skb->ip_summed = CHECKSUM_NONE;
+	return 0;
+}
+
+static int ipxlat_64_translate_udp_csum(struct sk_buff *skb,
+					const struct ipv6hdr *in6)
+{
+	const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	const struct iphdr *out4 = ip_hdr(skb);
+	struct udphdr *udp = udp_hdr(skb);
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		udp->check = ~udp_v4_check(ipxlat_skb_l4_seg_len(skb, cb),
+					   out4->saddr, out4->daddr, 0);
+		return ipxlat_set_partial_csum(skb,
+					       offsetof(struct udphdr, check));
+	}
+	/* A zero checksum is invalid on ordinary IPv6 UDP, but specialized
+	 * receive paths can allow it. It is a valid checksum choice in IPv4.
+	 */
+	if (!udp->check) {
+		skb->ip_summed = CHECKSUM_NONE;
+		return 0;
+	}
+
+	udp->check = ipxlat_64_update_l4_csum(udp->check, in6, out4);
+	if (unlikely(!udp->check))
+		udp->check = CSUM_MANGLED_0;
+	skb->ip_summed = CHECKSUM_NONE;
+	return 0;
+}
+
+static __be16 ipxlat_64_build_frag_off(const struct sk_buff *skb,
+				       const struct frag_hdr *frag6)
+{
+	const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	unsigned int ipv4_seg_len, l4_seg_len;
+	bool df, is_gso, mf;
+	u16 frag_offset;
+
+	/* an IPv6 Fragment Header maps to IPv4 fragmentation with DF clear */
+	if (frag6) {
+		mf = !!(be16_to_cpu(frag6->frag_off) & IP6_MF);
+		frag_offset = ipxlat_get_frag6_offset(frag6);
+		return ipxlat_64_encode_frag_off(false, mf, frag_offset);
+	}
+
+	/* legacy UFO represents one UDP datagram that will be materialized as
+	 * fragments, so its IPv4 header must leave DF clear
+	 */
+	is_gso = skb_is_gso(skb);
+	if (is_gso && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP))
+		return ipxlat_64_encode_frag_off(false, false, 0);
+
+	/* IPv6 cannot use ordinary PMTU discovery below 1280 bytes. The
+	 * corresponding IPv4 size is 1260 bytes because its base header is 20
+	 * bytes smaller. Clear DF at or below that floor so low-MTU IPv4 links
+	 * can fragment the packet; above it, set DF so the IPv6 sender can
+	 * learn a smaller usable PMTU.
+	 */
+	if (is_gso) {
+		/* TCP and UDP-L4 GSO contain independent datagrams. Decide DF
+		 * from the IPv4 packet each eventual segment will become, not
+		 * from the size of the coalesced skb.
+		 */
+		l4_seg_len = ipxlat_skb_l4_seg_len(skb, cb);
+		ipv4_seg_len = sizeof(struct iphdr) + l4_seg_len;
+		df = ipxlat_64_should_set_df(ipv4_seg_len);
+		return ipxlat_64_encode_frag_off(df, false, 0);
+	}
+
+	df = ipxlat_64_should_set_df(skb->len);
+	return ipxlat_64_encode_frag_off(df, false, 0);
+}
+
+int ipxlat_64_translate(const struct ipxlat_config *cfg, struct net *net,
+			struct sk_buff *skb)
+{
+	unsigned int min_l4_len, old_l3_len, new_l3_len, tot_len4;
+	const struct ipv6hdr outer6 = *ipv6_hdr(skb);
+	struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+	struct frag_hdr *frag6 = NULL;
+	struct frag_hdr frag_copy;
+	u8 l4_proto4, l4_proto6;
+	struct iphdr *iph4;
+	int l3_delta, err;
+	__be16 frag_off;
+	bool first_frag;
+
+	/* snapshot original outer IPv6 fields before L3 rewrite */
+	l4_proto6 = cb->l4_proto;
+	l4_proto4 = ipxlat_64_map_nexthdr_proto(l4_proto6);
+	if (unlikely(cb->fragh_off)) {
+		frag_copy = *(struct frag_hdr *)(skb->data + cb->fragh_off);
+		frag6 = &frag_copy;
+	}
+	first_frag = ipxlat_is_first_frag6(frag6);
+
+	old_l3_len = cb->l3_hdr_len;
+	new_l3_len = sizeof(struct iphdr);
+	l3_delta = (int)new_l3_len - (int)old_l3_len;
+
+	/* reserve headroom and make the transport header writable */
+	if (unlikely(skb_cow_head(skb, max_t(int, 0, l3_delta))))
+		return -ENOMEM;
+	if (first_frag) {
+		min_l4_len = ipxlat_l4_min_len(l4_proto4);
+		if (unlikely(skb_ensure_writable(skb, cb->l4_off + min_l4_len)))
+			return -ENOMEM;
+	}
+
+	/* replace outer IPv6 header with IPv4 in-place */
+	ipxlat_replace_l3(skb, old_l3_len, new_l3_len, new_l3_len);
+	skb->protocol = htons(ETH_P_IP);
+
+	/* parsed offsets include the old L3 length, which the delta replaces */
+	ipxlat_cb_rebase_offsets(cb, l3_delta);
+	DEBUG_NET_WARN_ON_ONCE(!ipxlat_cb_offsets_valid(cb));
+
+	cb->l3_hdr_len = sizeof(struct iphdr);
+	cb->fragh_off = 0;
+	cb->l4_proto = l4_proto4;
+
+	/* build outer IPv4 base hdr from translated IPv6 fields */
+	iph4 = ip_hdr(skb);
+	frag_off = ipxlat_64_build_frag_off(skb, frag6);
+	tot_len4 = skb->len - skb_network_offset(skb);
+	ipxlat_64_build_l3_hdr(net, iph4, &outer6, tot_len4, frag_off,
+			       l4_proto4, cb->saddr4, cb->daddr4,
+			       outer6.hop_limit, frag6);
+
+	/* non-first fragments have no transport header to translate */
+	if (unlikely(!first_frag))
+		goto out;
+
+	/* translate transport hdr and pseudohdr dependent checksums */
+	switch (l4_proto4) {
+	case IPPROTO_TCP:
+		err = ipxlat_64_translate_tcp_csum(skb, &outer6);
+		break;
+	case IPPROTO_UDP:
+		err = ipxlat_64_translate_udp_csum(skb, &outer6);
+		break;
+	default:
+		err = 0;
+		break;
+	}
+	if (unlikely(err))
+		return err;
+
+out:
+	/* normalize checksum/offload metadata for the translated frame */
+	return ipxlat_finalize_offload(skb, l4_proto4,
+				       ip_is_fragment(ip_hdr(skb)),
+				       SKB_GSO_TCPV6, SKB_GSO_TCPV4);
+}
diff --git a/net/ipxlat/translate_64.h b/net/ipxlat/translate_64.h
new file mode 100644
index 000000000000..f0ed37e6ad91
--- /dev/null
+++ b/net/ipxlat/translate_64.h
@@ -0,0 +1,96 @@
+/* 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_TRANSLATE_64_H_
+#define _NET_IPXLAT_TRANSLATE_64_H_
+
+#include <linux/ipv6.h>
+
+#include <net/ipxlat.h>
+
+struct sk_buff;
+struct iphdr;
+struct ipv6hdr;
+
+/**
+ * ipxlat_64_should_set_df - choose DF for IPv6 packets without a Fragment
+ *                           Header
+ * @pkt_len4: size of the resulting IPv4 packet
+ *
+ * Return: true when the IPv4 packet is above the IPv6-derived PMTU floor.
+ */
+static inline bool ipxlat_64_should_set_df(unsigned int pkt_len4)
+{
+	return pkt_len4 > IPV6_MIN_MTU - sizeof(struct ipv6hdr) +
+			  sizeof(struct iphdr);
+}
+
+/**
+ * ipxlat_64_map_frag_id - derive an IPv4 ID from an IPv6 Fragment Header
+ * @frag6: source IPv6 Fragment Header
+ *
+ * Return: the low 16 bits of the IPv6 identification in network byte order.
+ */
+static inline __be16 ipxlat_64_map_frag_id(const struct frag_hdr *frag6)
+{
+	return cpu_to_be16(be32_to_cpu(frag6->identification));
+}
+
+static inline __be16 ipxlat_64_encode_frag_off(bool df, bool mf,
+					       u16 frag_offset)
+{
+	return cpu_to_be16((df ? IP_DF : 0) | (mf ? IP_MF : 0) |
+			   (frag_offset >> 3));
+}
+
+/**
+ * ipxlat_64_build_l3_hdr - build translated IPv4 header from IPv6 metadata
+ * @net: namespace owning IPv4 identification state
+ * @iph4: output IPv4 header
+ * @iph6: source IPv6 header
+ * @tot_len: resulting IPv4 total length
+ * @frag_off: resulting IPv4 fragment offset/flags
+ * @protocol: resulting IPv4 L4 protocol
+ * @saddr: resulting IPv4 source address
+ * @daddr: resulting IPv4 destination address
+ * @ttl: resulting IPv4 TTL
+ * @frag6: source Fragment Header, or NULL to allocate a fresh IPv4 ID
+ */
+void ipxlat_64_build_l3_hdr(struct net *net, struct iphdr *iph4,
+			    const struct ipv6hdr *iph6, unsigned int tot_len,
+			    __be16 frag_off, u8 protocol, __be32 saddr,
+			    __be32 daddr, u8 ttl, const struct frag_hdr *frag6);
+
+/**
+ * ipxlat_64_translate - translate outer packet from IPv6 to IPv4 in place
+ * @cfg: immutable route translation configuration
+ * @net: namespace owning translated IPv4 fragment-ID state
+ * @skb: packet to translate
+ *
+ * Rewrites outer L3 in place, rebases cached offsets and translates L4 on
+ * first fragments only.
+ *
+ * Return: 0 on success, negative errno on translation failure.
+ */
+int ipxlat_64_translate(const struct ipxlat_config *cfg, struct net *net,
+			struct sk_buff *skb);
+
+/**
+ * ipxlat_64_map_nexthdr_proto - map IPv6 nexthdr to IPv4 L4 protocol
+ * @nexthdr: IPv6 next-header value
+ *
+ * Return: IPv4 protocol value corresponding to @nexthdr.
+ */
+u8 ipxlat_64_map_nexthdr_proto(u8 nexthdr);
+
+#endif /* _NET_IPXLAT_TRANSLATE_64_H_ */
-- 
2.55.0


  parent reply	other threads:[~2026-10-24  9:26 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24  9:25 [RFC net-next v2 00/18] net: introduce ipxlat stateless IPv4/IPv6 translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 01/18] lwtunnel: track callback dispatch separately from return value Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 02/18] ipv4/ipv6: add LWT receive helpers Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 03/18] ipv6: allow output fragmentation to reuse a Fragment Header Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 04/18] netfilter: preserve IPv6 refragmentation state across reassembly Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 05/18] net: ipxlat: add engine skeleton and configuration model Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 06/18] net: ipxlat: add RFC 6052 and EAM address translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 07/18] net: ipxlat: add packet metadata and header helpers Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 08/18] net: ipxlat: add IPv4 packet validation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 09/18] net: ipxlat: add IPv6 " Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 10/18] net: ipxlat: add checksum and offload helpers Ralf Lici
2026-10-24  9:25 ` Ralf Lici [this message]
2026-10-24  9:25 ` [RFC net-next v2 12/18] net: ipxlat: add translation preparation and dispatch Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 13/18] net: ipxlat: add translator-generated ICMP errors Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 14/18] net: ipxlat: add ICMP informational translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 15/18] net: ipxlat: add ICMP error and quoted-packet translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 16/18] net: ipxlat: add lightweight tunnel attachment Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 17/18] selftests: net: add ipxlat coverage Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 18/18] docs: networking: add ipxlat guide Ralf Lici

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261024092532.172005-12-ralf@mandelbit.com \
    --to=ralf@mandelbit.com \
    --cc=antonio@mandelbit.com \
    --cc=davem@davemloft.net \
    --cc=dxld@darkboxed.org \
    --cc=edumazet@kernel.org \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.