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
next prev 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®