From: Ralf Lici <ralf@mandelbit.com>
To: netdev@vger.kernel.org
Cc: "Daniel Gröber" <dxld@darkboxed.org>,
"Ralf Lici" <ralf@mandelbit.com>,
"Antonio Quartulli" <antonio@mandelbit.com>,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@google.com>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
linux-kernel@vger.kernel.org
Subject: [RFC net-next 12/15] ipxlat: add ICMP error translation and quoted-inner handling
Date: Thu, 19 Mar 2026 16:12:21 +0100 [thread overview]
Message-ID: <20260319151230.655687-13-ralf@mandelbit.com> (raw)
In-Reply-To: <20260319151230.655687-1-ralf@mandelbit.com>
Extend ICMP translation with error-path support for both directions,
including quoted-inner packet rewriting and RFC 4884 extension
relayout/squeeze logic.
This adds the ICMP type/code/error-field mappings, inner L3/L4 rewrite
paths, and final checksum handling required for translator ICMP error
processing.
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
drivers/net/ipxlat/icmp.h | 14 +-
drivers/net/ipxlat/icmp_46.c | 467 ++++++++++++++++++++++++++++++++-
drivers/net/ipxlat/icmp_64.c | 453 +++++++++++++++++++++++++++++++-
drivers/net/ipxlat/transport.c | 61 +++++
drivers/net/ipxlat/transport.h | 19 ++
5 files changed, 996 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ipxlat/icmp.h b/drivers/net/ipxlat/icmp.h
index 52d681787d6a..71bd7e20af91 100644
--- a/drivers/net/ipxlat/icmp.h
+++ b/drivers/net/ipxlat/icmp.h
@@ -19,22 +19,24 @@
#include "ipxlpriv.h"
/**
- * ipxlat_46_icmp - translate ICMP informational payload
- * after outer 4->6 rewrite
- * @ipxl: translator private context
+ * ipxlat_46_icmp - translate ICMP payload after outer 4->6 L3 rewrite
+ * @ipxlat: translator private context
* @skb: packet carrying ICMPv4 transport payload
*
+ * Handles both ICMP info translation and ICMP error quoted-inner rewriting.
+ *
* Return: 0 on success, negative errno on translation failure.
*/
-int ipxlat_46_icmp(struct ipxlat_priv *ipxl, struct sk_buff *skb);
+int ipxlat_46_icmp(struct ipxlat_priv *ipxlat, struct sk_buff *skb);
/**
- * ipxlat_64_icmp - translate ICMP informational payload
- * after outer 6->4 rewrite
+ * ipxlat_64_icmp - translate ICMP payload after outer 6->4 L3 rewrite
* @ipxlat: translator private context
* @skb: packet carrying ICMPv6 transport payload
* @in6: snapshot of original outer IPv6 header
*
+ * Handles both ICMP info translation and ICMP error quoted-inner rewriting.
+ *
* Return: 0 on success, negative errno on translation failure.
*/
int ipxlat_64_icmp(struct ipxlat_priv *ipxlat, struct sk_buff *skb,
diff --git a/drivers/net/ipxlat/icmp_46.c b/drivers/net/ipxlat/icmp_46.c
index ad907f60416c..41a91d4bc3dc 100644
--- a/drivers/net/ipxlat/icmp_46.c
+++ b/drivers/net/ipxlat/icmp_46.c
@@ -11,13 +11,49 @@
* Ralf Lici <ralf@mandelbit.com>
*/
-#include <linux/icmp.h>
-#include <linux/icmpv6.h>
-
+#include "address.h"
#include "icmp.h"
#include "packet.h"
+#include "translate_46.h"
#include "transport.h"
+#define IPXLAT_ICMP4_PP_CODE_PTR 0
+#define IPXLAT_ICMP4_PP_CODE_BADLEN 2
+
+/* RFC 7915 Section 4.2, Figure 3 */
+static const u8 ipxlat_46_icmp_param_prob_map[] = { 0, 1, 4, 4, 0xff,
+ 0xff, 0xff, 0xff, 7, 6,
+ 0xff, 0xff, 8, 8, 8,
+ 8, 24, 24, 24, 24 };
+
+/* RFC 1191 plateau table used when ICMPv4 FRAG_NEEDED reports MTU=0 */
+static const u16 ipxlat_46_mtu_plateaus[] = {
+ 65535, 32000, 17914, 8166, 4352, 2002, 1492,
+};
+
+static u8 ipxlat_icmp4_get_param_ptr(const struct icmphdr *ic4)
+{
+ return ntohl(ic4->un.gateway) >> 24;
+}
+
+static int ipxlat_46_map_icmp_param_prob(const struct icmphdr *in,
+ struct icmp6hdr *out)
+{
+ u8 ptr;
+
+ if (unlikely(in->code != IPXLAT_ICMP4_PP_CODE_PTR &&
+ in->code != IPXLAT_ICMP4_PP_CODE_BADLEN))
+ return -EPROTONOSUPPORT;
+
+ ptr = ipxlat_icmp4_get_param_ptr(in);
+ if (unlikely(ptr >= ARRAY_SIZE(ipxlat_46_icmp_param_prob_map) ||
+ ipxlat_46_icmp_param_prob_map[ptr] == 0xff))
+ return -EPROTONOSUPPORT;
+
+ out->icmp6_pointer = cpu_to_be32(ipxlat_46_icmp_param_prob_map[ptr]);
+ return 0;
+}
+
static int ipxlat_46_map_icmp_info_type_code(const struct icmphdr *in,
struct icmp6hdr *out)
{
@@ -39,6 +75,165 @@ static int ipxlat_46_map_icmp_info_type_code(const struct icmphdr *in,
return -EPROTONOSUPPORT;
}
+static __be32 ipxlat_46_compute_icmp_mtu6(unsigned int pkt_mtu,
+ unsigned int nexthop6mtu,
+ unsigned int nexthop4mtu,
+ u16 tot_len_field)
+{
+ unsigned int i;
+ u32 result;
+
+ /* RFC 7915 Section 4.2:
+ * If the IPv4 router set the MTU field to zero, then the translator
+ * MUST use the plateau values specified in RFC 1191 to determine a
+ * likely path MTU and include that path MTU in the ICMPv6 packet.
+ */
+ if (unlikely(pkt_mtu == 0)) {
+ for (i = 0; i < ARRAY_SIZE(ipxlat_46_mtu_plateaus); i++) {
+ if (ipxlat_46_mtu_plateaus[i] < tot_len_field) {
+ pkt_mtu = ipxlat_46_mtu_plateaus[i];
+ break;
+ }
+ }
+ }
+
+ /* RFC 7915 Section 4.2:
+ * max(1280, min(pkt_mtu + 20, mtu6_nexthop, mtu4_nexthop + 20))
+ *
+ * pkt_mtu + 20 converts ICMPv4-reported MTU to IPv6 context.
+ * mtu6_nexthop and mtu4_nexthop + 20 clamp to local next-hop limits.
+ * max(..., 1280) enforces IPv6 minimum MTU.
+ */
+ result = min(pkt_mtu + 20, min(nexthop6mtu, nexthop4mtu + 20));
+ if (result < IPV6_MIN_MTU)
+ result = IPV6_MIN_MTU;
+
+ return cpu_to_be32(result);
+}
+
+static int ipxlat_46_build_icmp_dest_unreach(struct ipxlat_priv *ipxlat,
+ struct sk_buff *skb,
+ const struct icmphdr *in,
+ struct icmp6hdr *out,
+ const struct iphdr *inner4)
+{
+ unsigned int inner4_tot_len, in_frag_mtu, in_mtu, out_mtu;
+
+ switch (in->code) {
+ case ICMP_NET_UNREACH:
+ case ICMP_HOST_UNREACH:
+ case ICMP_SR_FAILED:
+ case ICMP_NET_UNKNOWN:
+ case ICMP_HOST_UNKNOWN:
+ case ICMP_HOST_ISOLATED:
+ case ICMP_NET_UNR_TOS:
+ case ICMP_HOST_UNR_TOS:
+ case ICMP_PORT_UNREACH:
+ case ICMP_NET_ANO:
+ case ICMP_HOST_ANO:
+ case ICMP_PKT_FILTERED:
+ case ICMP_PREC_CUTOFF:
+ out->icmp6_unused = 0;
+ return 0;
+ case ICMP_PROT_UNREACH:
+ out->icmp6_pointer =
+ cpu_to_be32(offsetof(struct ipv6hdr, nexthdr));
+ return 0;
+ case ICMP_FRAG_NEEDED:
+ in_frag_mtu = be16_to_cpu(in->un.frag.mtu);
+ inner4_tot_len = be16_to_cpu(inner4->tot_len);
+ in_mtu = READ_ONCE(ipxlat->dev->mtu);
+ out_mtu = ipxlat_46_lookup_pmtu6(ipxlat, skb, inner4);
+
+ out->icmp6_mtu =
+ ipxlat_46_compute_icmp_mtu6(in_frag_mtu, out_mtu,
+ in_mtu, inner4_tot_len);
+ return 0;
+ }
+
+ return -EPROTONOSUPPORT;
+}
+
+static int ipxlat_46_map_icmp_type_code(struct ipxlat_priv *ipxlat,
+ struct sk_buff *skb,
+ const struct icmphdr *in,
+ struct icmp6hdr *out,
+ const struct iphdr *inner4,
+ bool *ie_forbidden)
+{
+ int err;
+
+ *ie_forbidden = false;
+
+ switch (in->type) {
+ case ICMP_ECHO:
+ case ICMP_ECHOREPLY:
+ return ipxlat_46_map_icmp_info_type_code(in, out);
+ case ICMP_DEST_UNREACH:
+ switch (in->code) {
+ case ICMP_NET_UNREACH:
+ case ICMP_HOST_UNREACH:
+ case ICMP_SR_FAILED:
+ case ICMP_NET_UNKNOWN:
+ case ICMP_HOST_UNKNOWN:
+ case ICMP_HOST_ISOLATED:
+ case ICMP_NET_UNR_TOS:
+ case ICMP_HOST_UNR_TOS:
+ out->icmp6_type = ICMPV6_DEST_UNREACH;
+ out->icmp6_code = ICMPV6_NOROUTE;
+ break;
+ case ICMP_PROT_UNREACH:
+ out->icmp6_type = ICMPV6_PARAMPROB;
+ out->icmp6_code = ICMPV6_UNK_NEXTHDR;
+ *ie_forbidden = true;
+ break;
+ case ICMP_PORT_UNREACH:
+ out->icmp6_type = ICMPV6_DEST_UNREACH;
+ out->icmp6_code = ICMPV6_PORT_UNREACH;
+ break;
+ case ICMP_FRAG_NEEDED:
+ out->icmp6_type = ICMPV6_PKT_TOOBIG;
+ out->icmp6_code = 0;
+ *ie_forbidden = true;
+ break;
+ case ICMP_NET_ANO:
+ case ICMP_HOST_ANO:
+ case ICMP_PKT_FILTERED:
+ case ICMP_PREC_CUTOFF:
+ out->icmp6_type = ICMPV6_DEST_UNREACH;
+ out->icmp6_code = ICMPV6_ADM_PROHIBITED;
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+ return ipxlat_46_build_icmp_dest_unreach(ipxlat,
+ skb, in, out,
+ inner4);
+ case ICMP_TIME_EXCEEDED:
+ out->icmp6_type = ICMPV6_TIME_EXCEED;
+ out->icmp6_code = in->code;
+ out->icmp6_unused = 0;
+ return 0;
+ case ICMP_PARAMETERPROB:
+ out->icmp6_type = ICMPV6_PARAMPROB;
+ *ie_forbidden = true;
+ switch (in->code) {
+ case IPXLAT_ICMP4_PP_CODE_PTR:
+ case IPXLAT_ICMP4_PP_CODE_BADLEN:
+ out->icmp6_code = ICMPV6_HDR_FIELD;
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+ err = ipxlat_46_map_icmp_param_prob(in, out);
+ if (unlikely(err))
+ return err;
+ return 0;
+ }
+
+ return -EPROTONOSUPPORT;
+}
+
static void ipxlat_46_icmp_info_update_csum(const struct icmphdr *icmp4,
struct icmp6hdr *icmp6,
const struct ipv6hdr *ip6,
@@ -86,10 +281,272 @@ static int ipxlat_46_icmp_info_outer(struct sk_buff *skb)
return 0;
}
-int ipxlat_46_icmp(struct ipxlat_priv *ipxl, struct sk_buff *skb)
+static int ipxlat_46_icmp_info_inner(struct sk_buff *skb,
+ unsigned int inner_l4_off,
+ const struct ipv6hdr *inner6)
+{
+ struct icmp6hdr *icmp6;
+ struct icmphdr icmp4;
+ int err;
+
+ /* inner header alignment is not guaranteed */
+ memcpy(&icmp4, skb->data + inner_l4_off, sizeof(icmp4));
+ icmp6 = (struct icmp6hdr *)(skb->data + inner_l4_off);
+
+ err = ipxlat_46_map_icmp_info_type_code(&icmp4, icmp6);
+ if (unlikely(err))
+ return -EINVAL;
+
+ ipxlat_46_icmp_info_update_csum(&icmp4, icmp6, inner6, skb,
+ inner_l4_off);
+ return 0;
+}
+
+static int ipxlat_46_icmp_inner_l4(struct sk_buff *skb,
+ unsigned int inner_l4_off,
+ const struct iphdr *inner4,
+ const struct ipv6hdr *inner6)
+{
+ struct tcphdr *tcp;
+ struct udphdr *udp;
+
+ switch (inner4->protocol) {
+ case IPPROTO_TCP:
+ tcp = (struct tcphdr *)(skb->data + inner_l4_off);
+ return ipxlat_46_inner_tcp(skb, inner4, inner6, tcp);
+ case IPPROTO_UDP:
+ udp = (struct udphdr *)(skb->data + inner_l4_off);
+ return ipxlat_46_inner_udp(skb, inner4, inner6, udp);
+ case IPPROTO_ICMP:
+ return ipxlat_46_icmp_info_inner(skb, inner_l4_off, inner6);
+ default:
+ return 0;
+ }
+}
+
+static int ipxlat_46_icmp_inner(struct ipxlat_priv *ipxlat,
+ struct sk_buff *skb, struct iphdr *inner4,
+ int *inner_delta)
+{
+ unsigned int inner_l3_len, inner_l3_off, inner_l4_off, old_prefix,
+ new_prefix, inner_tot_len, inner_l3_payload, inner_l4_payload;
+ const unsigned int outer_l3_len = skb_transport_offset(skb);
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ struct ipv6hdr outer_ip6_copy, *inner_ip6;
+ struct frag_hdr *fh6;
+ u8 next_hdr;
+ bool has_inner_frag;
+
+ inner_l3_off = cb->inner_l3_offset;
+ inner_l4_off = cb->inner_l4_offset;
+
+ /* inner header alignment is not guaranteed */
+ memcpy(inner4, skb->data + inner_l3_off, sizeof(*inner4));
+ inner_l3_len = inner4->ihl << 2;
+ has_inner_frag = ip_is_fragment(inner4);
+
+ /* save outer IPv6 hdr because pull+push destroys that hdr region */
+ outer_ip6_copy = *ipv6_hdr(skb);
+
+ old_prefix = inner_l3_off + inner_l3_len;
+ new_prefix = inner_l3_off + sizeof(struct ipv6hdr) +
+ (has_inner_frag ? sizeof(struct frag_hdr) : 0);
+ *inner_delta = (int)new_prefix - (int)old_prefix;
+
+ if (unlikely(skb_cow_head(skb, max_t(int, 0, *inner_delta))))
+ return -ENOMEM;
+
+ skb_pull(skb, old_prefix);
+ skb_push(skb, new_prefix);
+ /* outer 4->6 path already set header offsets, but inner relayout
+ * pulls/pushes change skb->data placement. Reinitialize outer header
+ * offsets so ip{,v6}_hdr/icmp{,6}_hdr and skb_transport_offset keep
+ * pointing to the outer packet.
+ */
+ skb_reset_network_header(skb);
+ skb_set_transport_header(skb, outer_l3_len);
+
+ *ipv6_hdr(skb) = outer_ip6_copy;
+ ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + inner_l3_off);
+ /* use quoted IPv4 total-length, not skb->len:
+ * skb->len also includes ICMP extension bytes at the end, which are
+ * not part of the quoted inner IP datagram length.
+ */
+ inner_tot_len = ntohs(inner4->tot_len);
+ if (unlikely(inner_tot_len < inner_l3_len))
+ return -EINVAL;
+
+ inner_l3_payload = inner_tot_len - inner_l3_len +
+ (has_inner_frag ? sizeof(struct frag_hdr) : 0);
+ if (has_inner_frag)
+ next_hdr = NEXTHDR_FRAGMENT;
+ else
+ next_hdr = ipxlat_46_map_proto_to_nexthdr(inner4->protocol);
+
+ ipxlat_46_build_l3(inner_ip6, inner4, inner_l3_payload, next_hdr,
+ inner4->ttl);
+
+ ipxlat_46_convert_addrs(&ipxlat->xlat_prefix6, inner4, inner_ip6);
+
+ if (unlikely(has_inner_frag)) {
+ fh6 = (struct frag_hdr *)(inner_ip6 + 1);
+ ipxlat_46_build_frag_hdr(fh6, inner4, inner4->protocol);
+ }
+
+ if (unlikely(!ipxlat_is_first_frag4(inner4)))
+ return 0;
+
+ inner_l4_payload = new_prefix + ipxlat_l4_min_len(inner4->protocol);
+ if (unlikely(skb_ensure_writable(skb, inner_l4_payload)))
+ return -ENOMEM;
+
+ return ipxlat_46_icmp_inner_l4(skb, new_prefix, inner4, inner_ip6);
+}
+
+/* Adjust ICMP error quoted-datagram/extensions after inner 4->6 translation.
+ * The inner rewrite changes quoted datagram length; this helper recomputes
+ * RFC 4884 delimiter/padding, preserves extensions only when allowed, and
+ * enforces IPv6 minimum-MTU packet size constraints.
+ */
+static int ipxlat_46_icmp_squeeze_ext(struct sk_buff *skb,
+ unsigned int icmp4_ipl, int inner_delta,
+ bool ie_forbidden)
+{
+ unsigned int icmp6_iel_in, icmp6_iel_out, max_iel, outer_hdrs_len,
+ out_pad, payload_len, icmp6_ipl_out_bytes, pkt_len_cap;
+ unsigned int icmp6_ipl_out = 0;
+ int icmp6_ipl_in_bytes, err;
+ struct icmp6hdr *ic6;
+ struct ipv6hdr *iph6;
+
+ /* icmp4_ipl marks where quoted datagram ends and extension area starts
+ */
+ if (likely(!icmp4_ipl))
+ goto no_extensions;
+
+ outer_hdrs_len = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
+ payload_len = skb->len - outer_hdrs_len;
+ icmp6_ipl_in_bytes = icmp4_ipl + inner_delta;
+ if (unlikely(icmp6_ipl_in_bytes < 0 ||
+ icmp6_ipl_in_bytes > payload_len))
+ return -EINVAL;
+
+ if (likely(icmp6_ipl_in_bytes == payload_len))
+ goto no_extensions;
+
+ icmp6_iel_in = payload_len - icmp6_ipl_in_bytes;
+ max_iel = IPV6_MIN_MTU - (outer_hdrs_len + ICMP_EXT_ORIG_DGRAM_MIN_LEN);
+
+ if (unlikely(ie_forbidden || icmp6_iel_in > max_iel)) {
+ pkt_len_cap = min_t(unsigned int, skb->len - icmp6_iel_in,
+ IPV6_MIN_MTU);
+ icmp6_ipl_out_bytes = pkt_len_cap - outer_hdrs_len;
+ out_pad = 0;
+ icmp6_iel_out = 0;
+ icmp6_ipl_out = 0;
+ } else {
+ pkt_len_cap = min_t(unsigned int, skb->len, IPV6_MIN_MTU);
+ icmp6_ipl_out_bytes =
+ round_down(pkt_len_cap - icmp6_iel_in - outer_hdrs_len,
+ sizeof(u64));
+ out_pad = max_t(unsigned int, ICMP_EXT_ORIG_DGRAM_MIN_LEN,
+ icmp6_ipl_out_bytes) -
+ icmp6_ipl_out_bytes;
+ icmp6_iel_out = icmp6_iel_in;
+ icmp6_ipl_out = (icmp6_ipl_out_bytes + out_pad) >> 3;
+ }
+
+ /* if no extension bytes are copied and no pad is written, relayout only
+ * trims/updates lengths and does not require full data writability
+ */
+ if (unlikely(icmp6_iel_out || out_pad)) {
+ err = skb_ensure_writable(skb, skb->len);
+ if (unlikely(err))
+ return err;
+ }
+
+ err = ipxlat_icmp_relayout(skb, outer_hdrs_len, icmp6_ipl_in_bytes,
+ icmp6_iel_in, icmp6_ipl_out_bytes, out_pad,
+ icmp6_iel_out);
+ if (unlikely(err))
+ return err;
+
+ iph6 = ipv6_hdr(skb);
+ iph6->payload_len = htons(skb->len - sizeof(*iph6));
+
+no_extensions:
+ if (unlikely(skb->len > IPV6_MIN_MTU)) {
+ err = pskb_trim(skb, IPV6_MIN_MTU);
+ if (unlikely(err))
+ return err;
+
+ iph6 = ipv6_hdr(skb);
+ iph6->payload_len = htons(skb->len - sizeof(*iph6));
+ }
+
+ ic6 = icmp6_hdr(skb);
+ ic6->icmp6_datagram_len = icmp6_ipl_out;
+ return 0;
+}
+
+/**
+ * ipxlat_46_icmp_error - translate ICMPv4 error payload to ICMPv6 error form
+ * @ipxlat: translator private context
+ * @skb: packet carrying outer ICMPv4 error
+ *
+ * Rewrites the quoted inner datagram in place, maps type/code/fields and
+ * adjusts RFC 4884 datagram/extension layout before recomputing outer checksum.
+ *
+ * Return: 0 on success, negative errno on translation failure.
+ */
+static int ipxlat_46_icmp_error(struct ipxlat_priv *ipxlat, struct sk_buff *skb)
+{
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct icmphdr icmp4 = *icmp_hdr(skb);
+ struct iphdr inner4_ip;
+ int inner_delta, err;
+ bool ie_forbidden;
+
+ if (unlikely(!(cb->is_icmp_err))) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* translate quoted inner packet headers */
+ err = ipxlat_46_icmp_inner(ipxlat, skb, &inner4_ip, &inner_delta);
+ if (unlikely(err))
+ return err;
+
+ err = ipxlat_46_map_icmp_type_code(ipxlat, skb, &icmp4, icmp6_hdr(skb),
+ &inner4_ip, &ie_forbidden);
+ if (unlikely(err))
+ return err;
+
+ err = ipxlat_46_icmp_squeeze_ext(skb, icmp4.un.reserved[1] << 2,
+ inner_delta, ie_forbidden);
+ if (unlikely(err))
+ return err;
+
+ /* error path rewrites quoted packet bytes/lengths, so use full
+ * checksum recomputation instead of incremental update
+ */
+ icmp6_hdr(skb)->icmp6_cksum = 0;
+ icmp6_hdr(skb)->icmp6_cksum =
+ ipxlat_l4_csum_ipv6(&ipv6_hdr(skb)->saddr,
+ &ipv6_hdr(skb)->daddr, skb,
+ skb_transport_offset(skb),
+ ipxlat_skb_datagram_len(skb),
+ IPPROTO_ICMPV6);
+ skb->ip_summed = CHECKSUM_NONE;
+ return 0;
+}
+
+int ipxlat_46_icmp(struct ipxlat_priv *ipxlat, struct sk_buff *skb)
{
if (unlikely(ipxlat_skb_cb(skb)->is_icmp_err))
- return -EPROTONOSUPPORT;
+ return ipxlat_46_icmp_error(ipxlat, skb);
return ipxlat_46_icmp_info_outer(skb);
}
diff --git a/drivers/net/ipxlat/icmp_64.c b/drivers/net/ipxlat/icmp_64.c
index 6b11aa638068..18583620a09a 100644
--- a/drivers/net/ipxlat/icmp_64.c
+++ b/drivers/net/ipxlat/icmp_64.c
@@ -11,12 +11,38 @@
* Ralf Lici <ralf@mandelbit.com>
*/
-#include <linux/icmpv6.h>
+#include <net/route.h>
+#include "address.h"
#include "icmp.h"
#include "packet.h"
+#include "translate_64.h"
#include "transport.h"
+#define IPXLAT_ICMP4_ERROR_MAX_LEN 576U
+
+/* RFC 7915 Section 5.2, Figure 4 */
+static const u8 ipxlat_64_icmp_param_prob_map[] = {
+ 0, 1, 0xff, 0xff, 2, 2, 9, 8, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+};
+
+static int ipxlat_64_map_icmp_param_prob(u32 ptr6, u32 *ptr4)
+{
+ if (unlikely(ptr6 >= ARRAY_SIZE(ipxlat_64_icmp_param_prob_map) ||
+ ipxlat_64_icmp_param_prob_map[ptr6] == 0xff))
+ return -EPROTONOSUPPORT;
+
+ *ptr4 = ipxlat_64_icmp_param_prob_map[ptr6];
+ return 0;
+}
+
+static void ipxlat_icmp4_set_param_ptr(struct icmphdr *ic4, u8 ptr)
+{
+ ic4->un.gateway = htonl((u32)ptr << 24);
+}
+
static int ipxlat_64_map_icmp_info_type_code(const struct icmp6hdr *in,
struct icmphdr *out)
{
@@ -38,10 +64,119 @@ static int ipxlat_64_map_icmp_info_type_code(const struct icmp6hdr *in,
}
}
-static __sum16 ipxlat_64_compute_icmp_info_csum(const struct ipv6hdr *in6,
- const struct icmp6hdr *in_icmp6,
- const struct icmphdr *out_icmp4,
- unsigned int l4_len)
+/* Lookup post-translation IPv4 PMTU for ICMPv6 PTB -> ICMPv4 FRAG_NEEDED.
+ * Falls back to translator MTU on routing failures and clamps route MTU
+ * against translator egress MTU.
+ */
+static unsigned int ipxlat_64_lookup_pmtu4(struct ipxlat_priv *ipxlat,
+ const struct sk_buff *skb)
+{
+ const struct iphdr *iph4;
+ struct flowi4 fl4 = {};
+ unsigned int dev_mtu;
+ struct rtable *rt;
+ unsigned int mtu4;
+
+ dev_mtu = READ_ONCE(ipxlat->dev->mtu);
+ iph4 = ip_hdr(skb);
+
+ fl4.daddr = iph4->daddr;
+ fl4.saddr = iph4->saddr;
+ fl4.flowi4_mark = skb->mark;
+ fl4.flowi4_proto = IPPROTO_ICMP;
+
+ rt = ip_route_output_key(dev_net(ipxlat->dev), &fl4);
+ if (IS_ERR(rt))
+ return dev_mtu;
+
+ /* clamp against translator MTU to avoid oversized local PMTU */
+ mtu4 = min_t(unsigned int, dst_mtu(&rt->dst), dev_mtu);
+ ip_rt_put(rt);
+
+ return mtu4;
+}
+
+static int ipxlat_64_build_icmp4_errhdr(struct ipxlat_priv *ipxlat,
+ struct sk_buff *skb,
+ const struct icmp6hdr *ic6,
+ struct icmphdr *ic4, bool *ie_forbidden)
+{
+ unsigned int in_mtu, out_mtu;
+ u32 ptr6, ptr4;
+ int err;
+
+ switch (ic6->icmp6_type) {
+ case ICMPV6_DEST_UNREACH:
+ ic4->type = ICMP_DEST_UNREACH;
+ switch (ic6->icmp6_code) {
+ case ICMPV6_NOROUTE:
+ case ICMPV6_NOT_NEIGHBOUR:
+ case ICMPV6_ADDR_UNREACH:
+ ic4->code = ICMP_HOST_UNREACH;
+ break;
+ case ICMPV6_ADM_PROHIBITED:
+ ic4->code = ICMP_HOST_ANO;
+ break;
+ case ICMPV6_PORT_UNREACH:
+ ic4->code = ICMP_PORT_UNREACH;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ic4->un.gateway = 0;
+ *ie_forbidden = false;
+ return 0;
+ case ICMPV6_TIME_EXCEED:
+ ic4->type = ICMP_TIME_EXCEEDED;
+ ic4->code = ic6->icmp6_code;
+ ic4->un.gateway = 0;
+ *ie_forbidden = false;
+ return 0;
+ case ICMPV6_PKT_TOOBIG:
+ ic4->type = ICMP_DEST_UNREACH;
+ ic4->code = ICMP_FRAG_NEEDED;
+ ic4->un.frag.__unused = 0;
+ in_mtu = ipxlat_64_lookup_pmtu4(ipxlat, skb);
+ out_mtu = READ_ONCE(ipxlat->dev->mtu);
+ /* RFC 7915 Section 5.2:
+ * min((PTB_mtu - 20), mtu4_nexthop, (mtu6_nexthop - 20))
+ */
+ ic4->un.frag.mtu =
+ cpu_to_be16(min3(be32_to_cpu(ic6->icmp6_mtu) - 20,
+ in_mtu, out_mtu - 20));
+ *ie_forbidden = true;
+ return 0;
+ case ICMPV6_PARAMPROB:
+ ptr6 = be32_to_cpu(ic6->icmp6_dataun.un_data32[0]);
+ switch (ic6->icmp6_code) {
+ case ICMPV6_HDR_FIELD:
+ ic4->type = ICMP_PARAMETERPROB;
+ ic4->code = 0;
+ err = ipxlat_64_map_icmp_param_prob(ptr6, &ptr4);
+ if (unlikely(err))
+ return err;
+ ipxlat_icmp4_set_param_ptr(ic4, ptr4);
+ break;
+ case ICMPV6_UNK_NEXTHDR:
+ ic4->type = ICMP_DEST_UNREACH;
+ ic4->code = ICMP_PROT_UNREACH;
+ ic4->un.gateway = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ *ie_forbidden = true;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static __sum16
+ipxlat_64_compute_icmp_info_csum(const struct ipv6hdr *in6,
+ const struct icmp6hdr *in_icmp6,
+ const struct icmphdr *out_icmp4,
+ unsigned int l4_len)
{
struct icmp6hdr icmp6_zero;
struct icmphdr icmp4_zero;
@@ -82,11 +217,315 @@ static int ipxlat_64_icmp_info(struct sk_buff *skb, const struct ipv6hdr *in6)
return 0;
}
-int ipxlat_64_icmp(struct ipxlat_priv *ipxl, struct sk_buff *skb,
+static int ipxlat_64_icmp_inner_info(struct sk_buff *skb,
+ unsigned int inner_l4_off)
+{
+ struct icmphdr *ic4;
+ struct icmp6hdr ic6;
+ int err;
+
+ /* inner header alignment is not guaranteed */
+ memcpy(&ic6, skb->data + inner_l4_off, sizeof(ic6));
+ ic4 = (struct icmphdr *)(skb->data + inner_l4_off);
+ err = ipxlat_64_map_icmp_info_type_code(&ic6, ic4);
+ if (unlikely(err))
+ return err;
+
+ ic4->checksum = 0;
+ ic4->checksum = csum_fold(skb_checksum(skb, inner_l4_off,
+ skb->len - inner_l4_off, 0));
+ skb->ip_summed = CHECKSUM_NONE;
+ return 0;
+}
+
+static int ipxlat_64_icmp_inner_l4(struct sk_buff *skb,
+ unsigned int inner_l4_off,
+ const struct iphdr *inner4,
+ const struct ipv6hdr *inner6)
+{
+ struct tcphdr *tcp;
+ struct udphdr *udp;
+
+ switch (inner4->protocol) {
+ case IPPROTO_TCP:
+ tcp = (struct tcphdr *)(skb->data + inner_l4_off);
+ return ipxlat_64_inner_tcp(skb, inner6, inner4, tcp);
+ case IPPROTO_UDP:
+ udp = (struct udphdr *)(skb->data + inner_l4_off);
+ return ipxlat_64_inner_udp(skb, inner6, inner4, udp);
+ case IPPROTO_ICMP:
+ return ipxlat_64_icmp_inner_info(skb, inner_l4_off);
+ default:
+ return 0;
+ }
+}
+
+static int ipxlat_64_icmp_inner(struct ipxlat_priv *ipxlat, struct sk_buff *skb,
+ int *inner_delta)
+{
+ unsigned int old_prefix, new_prefix, inner_l3_len, inner_tot_len,
+ inner_l4_payload, outer_prefix, inner_l3_off, inner_l4_old_off;
+ const unsigned int outer_l3_len = skb_transport_offset(skb);
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct iphdr outer4_copy = *ip_hdr(skb);
+ bool has_inner_frag, first_inner_frag, mf, df;
+ struct frag_hdr inner_fragh;
+ struct ipv6hdr inner6;
+ struct iphdr *inner4;
+ __be32 saddr, daddr;
+ u16 frag_off;
+ u8 inner_l4_proto;
+ __be16 frag_id;
+ int err;
+
+ inner_l3_off = cb->inner_l3_offset;
+ inner_l4_old_off = cb->inner_l4_offset;
+ inner_l3_len = inner_l4_old_off - inner_l3_off;
+ outer_prefix = inner_l3_off;
+
+ inner_l4_proto = ipxlat_64_map_nexthdr_proto(cb->inner_l4_proto);
+ has_inner_frag = !!cb->inner_fragh_off;
+
+ /* inner header alignment is not guaranteed */
+ memcpy(&inner6, skb->data + outer_prefix, sizeof(inner6));
+
+ first_inner_frag = true;
+ if (unlikely(has_inner_frag)) {
+ memcpy(&inner_fragh, skb->data + cb->inner_fragh_off,
+ sizeof(inner_fragh));
+ first_inner_frag = ipxlat_is_first_frag6(&inner_fragh);
+ }
+
+ err = ipxlat_64_convert_addrs(&ipxlat->xlat_prefix6, &inner6, false,
+ &saddr, &daddr);
+ if (unlikely(err))
+ return err;
+
+ old_prefix = outer_prefix + inner_l3_len;
+ new_prefix = outer_prefix + sizeof(struct iphdr);
+ *inner_delta = (int)new_prefix - (int)old_prefix;
+
+ /* unlike 46, inner 6->4 always shrinks quoted L3 size */
+ skb_pull(skb, old_prefix);
+ skb_push(skb, new_prefix);
+ /* outer 6->4 translation already set network/transport headers, but
+ * inner relayout pulls/pushes again and changes skb->data placement.
+ * Reinitialize outer header offsets so ip{,v6}_hdr/icmp{,6}_hdr and
+ * skb_transport_offset keep pointing to the outer packet.
+ */
+ skb_reset_network_header(skb);
+ skb_set_transport_header(skb, outer_l3_len);
+
+ *ip_hdr(skb) = outer4_copy;
+
+ inner4 = (struct iphdr *)(skb->data + outer_prefix);
+ inner_tot_len = ntohs(inner6.payload_len) + sizeof(inner6) -
+ inner_l3_len + sizeof(struct iphdr);
+ /* RFC 7915 Section 5.1 */
+ if (likely(!has_inner_frag)) {
+ df = inner_tot_len > (IPV6_MIN_MTU - sizeof(struct iphdr));
+ inner4->frag_off = ipxlat_build_frag4_offset(df, false, 0);
+ } else {
+ mf = !!(be16_to_cpu(inner_fragh.frag_off) & IP6_MF);
+ frag_off = ipxlat_get_frag6_offset(&inner_fragh);
+ inner4->frag_off =
+ ipxlat_build_frag4_offset(false, mf, frag_off);
+ }
+
+ /* keep low 16 bits of IPv6 Fragment ID as numeric value, then re-encode
+ * to network-order IPv4 ID
+ */
+ frag_id = has_inner_frag ?
+ cpu_to_be16(be32_to_cpu(inner_fragh.identification)) :
+ 0;
+ ipxlat_64_build_l3(inner4, &inner6, inner_tot_len, inner4->frag_off,
+ inner_l4_proto, saddr, daddr, inner6.hop_limit,
+ frag_id);
+
+ if (likely(!has_inner_frag)) {
+ inner4->id = 0;
+ __ip_select_ident(dev_net(ipxlat->dev), inner4, 1);
+ inner4->check = 0;
+ inner4->check = ip_fast_csum(inner4, inner4->ihl);
+ }
+
+ if (unlikely(!first_inner_frag))
+ return 0;
+
+ inner_l4_payload = new_prefix + ipxlat_l4_min_len(inner4->protocol);
+ if (unlikely(skb_ensure_writable(skb, inner_l4_payload)))
+ return -ENOMEM;
+
+ return ipxlat_64_icmp_inner_l4(skb, new_prefix, inner4, &inner6);
+}
+
+/* Rebuild ICMPv4 quoted-datagram/extensions after inner 6->4 translation.
+ *
+ * The inner rewrite changes the quoted datagram length. This helper updates
+ * the RFC 4884 delimiter/padding and extension bytes, then enforces the
+ * IPv4 ICMP error size cap.
+ *
+ * This is intentionally not a mirror of ipxlat_46_icmp_squeeze_ext:
+ * - 4->6 always writes icmp6_datagram_len (either computed or 0).
+ * - 6->4 updates ICMPv4 datagram-length only when extensions are allowed.
+ * Some mapped ICMPv6 errors set ie_forbidden, and in that case we keep the
+ * ICMPv4 header semantics for that type/code and only relayout/trim payload.
+ */
+static int ipxlat_64_squeeze_icmp_ext(struct sk_buff *skb,
+ unsigned int icmp6_ipl, int inner_delta,
+ bool ie_forbidden)
+{
+ unsigned int outer_hdrs_len, payload_len, icmp4_iel_in, icmp4_iel_out;
+ unsigned int out_pad, max_iel, pkt_len_cap, icmp4_ipl_out_bytes;
+ unsigned int icmp4_ipl_out = 0, icmp4_ipl_in_bytes;
+ unsigned int new_tot_len;
+ int icmp4_ipl_in, err;
+ struct icmphdr *ic4;
+ struct iphdr *iph4;
+
+ if (likely(!icmp6_ipl))
+ goto finalize;
+
+ outer_hdrs_len = skb_transport_offset(skb) + sizeof(struct icmphdr);
+ if (unlikely(skb->len < outer_hdrs_len))
+ return -EINVAL;
+
+ payload_len = skb->len - outer_hdrs_len;
+ icmp4_ipl_in = (int)icmp6_ipl + inner_delta;
+ if (unlikely(icmp4_ipl_in < 0))
+ return -EINVAL;
+ icmp4_ipl_in_bytes = icmp4_ipl_in;
+ if (unlikely(icmp4_ipl_in_bytes > payload_len))
+ return -EINVAL;
+
+ if (likely(icmp4_ipl_in_bytes == payload_len))
+ goto finalize;
+
+ icmp4_iel_in = payload_len - icmp4_ipl_in_bytes;
+ max_iel = IPXLAT_ICMP4_ERROR_MAX_LEN -
+ (outer_hdrs_len + ICMP_EXT_ORIG_DGRAM_MIN_LEN);
+
+ if (unlikely(ie_forbidden)) {
+ icmp4_ipl_out_bytes = icmp4_ipl_in_bytes;
+ out_pad = 0;
+ icmp4_iel_out = 0;
+ } else if (unlikely(icmp4_iel_in > max_iel)) {
+ pkt_len_cap = min_t(unsigned int, skb->len - icmp4_iel_in,
+ IPXLAT_ICMP4_ERROR_MAX_LEN);
+ icmp4_ipl_out_bytes = pkt_len_cap - outer_hdrs_len;
+ out_pad = 0;
+ icmp4_iel_out = 0;
+ icmp4_ipl_out = 0;
+ } else {
+ pkt_len_cap = min_t(unsigned int, skb->len,
+ IPXLAT_ICMP4_ERROR_MAX_LEN);
+ icmp4_ipl_out_bytes =
+ round_down(pkt_len_cap - icmp4_iel_in - outer_hdrs_len,
+ sizeof(u32));
+ out_pad = max_t(unsigned int, ICMP_EXT_ORIG_DGRAM_MIN_LEN,
+ icmp4_ipl_out_bytes) -
+ icmp4_ipl_out_bytes;
+ icmp4_iel_out = icmp4_iel_in;
+ /* RFC 4884 field is in 32-bit units for ICMPv4 errors */
+ icmp4_ipl_out = (icmp4_ipl_out_bytes + out_pad) >> 2;
+ }
+
+ /* if no extension bytes are copied and no pad is written, relayout only
+ * trims/updates lengths and does not require full data writability
+ */
+ if (unlikely(icmp4_iel_out || out_pad)) {
+ err = skb_ensure_writable(skb, skb->len);
+ if (unlikely(err))
+ return err;
+ }
+
+ err = ipxlat_icmp_relayout(skb, outer_hdrs_len, icmp4_ipl_in_bytes,
+ icmp4_iel_in, icmp4_ipl_out_bytes, out_pad,
+ icmp4_iel_out);
+ if (unlikely(err))
+ return err;
+
+finalize:
+ if (!ie_forbidden) {
+ ic4 = icmp_hdr(skb);
+ ic4->un.reserved[1] = icmp4_ipl_out;
+ }
+
+ if (unlikely(skb->len > IPXLAT_ICMP4_ERROR_MAX_LEN)) {
+ err = pskb_trim(skb, IPXLAT_ICMP4_ERROR_MAX_LEN);
+ if (unlikely(err))
+ return err;
+ }
+
+ iph4 = ip_hdr(skb);
+ new_tot_len = skb->len;
+ if (unlikely(be16_to_cpu(iph4->tot_len) != new_tot_len)) {
+ iph4->tot_len = cpu_to_be16(new_tot_len);
+ /* relayout/trim may invalidate precomputed DF decision */
+ iph4->frag_off &= cpu_to_be16(~IP_DF);
+ iph4->check = 0;
+ iph4->check = ip_fast_csum(iph4, iph4->ihl);
+ }
+
+ return 0;
+}
+
+/**
+ * ipxlat_64_icmp_error - translate ICMPv6 error payload to ICMPv4 error form
+ * @ipxlat: translator private context
+ * @skb: packet carrying outer ICMPv6 error
+ *
+ * Rewrites the quoted inner datagram in place, maps type/code/fields and
+ * adjusts RFC 4884 datagram/extension layout before recomputing outer checksum.
+ *
+ * Return: 0 on success, negative errno on translation failure.
+ */
+static int ipxlat_64_icmp_error(struct ipxlat_priv *ipxlat, struct sk_buff *skb)
+{
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct icmp6hdr ic6 = *icmp6_hdr(skb);
+ unsigned int icmp6_ipl;
+ int inner_delta, err;
+ struct icmphdr *ic4;
+ bool ie_forbidden;
+
+ if (unlikely(!(cb->is_icmp_err))) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* translate quoted inner packet headers */
+ err = ipxlat_64_icmp_inner(ipxlat, skb, &inner_delta);
+ if (unlikely(err))
+ return err;
+
+ /* build outer ICMPv4 error header after inner relayout */
+ ic4 = (struct icmphdr *)(skb->data + skb_transport_offset(skb));
+ err = ipxlat_64_build_icmp4_errhdr(ipxlat, skb, &ic6, ic4,
+ &ie_forbidden);
+ if (unlikely(err))
+ return err;
+
+ icmp6_ipl = ic6.icmp6_datagram_len << 3;
+ err = ipxlat_64_squeeze_icmp_ext(skb, icmp6_ipl, inner_delta,
+ ie_forbidden);
+ if (unlikely(err))
+ return err;
+
+ /* recompute whole ICMPv4 checksum after error-path relayout */
+ ic4->checksum = 0;
+ ic4->checksum = csum_fold(skb_checksum(skb, skb_transport_offset(skb),
+ ipxlat_skb_datagram_len(skb),
+ 0));
+ skb->ip_summed = CHECKSUM_NONE;
+ return 0;
+}
+
+int ipxlat_64_icmp(struct ipxlat_priv *ipxlat, struct sk_buff *skb,
const struct ipv6hdr *in6)
{
if (unlikely(ipxlat_skb_cb(skb)->is_icmp_err))
- return -EPROTONOSUPPORT;
+ return ipxlat_64_icmp_error(ipxlat, skb);
return ipxlat_64_icmp_info(skb, in6);
}
diff --git a/drivers/net/ipxlat/transport.c b/drivers/net/ipxlat/transport.c
index 3aa00c635916..82aedfb0ee48 100644
--- a/drivers/net/ipxlat/transport.c
+++ b/drivers/net/ipxlat/transport.c
@@ -87,6 +87,67 @@ __sum16 ipxlat_l4_csum_ipv6(const struct in6_addr *saddr,
skb_checksum(skb, l4_off, l4_len, 0));
}
+static int ipxlat_ensure_tailroom(struct sk_buff *skb, const unsigned int grow)
+{
+ int err;
+
+ if (!grow || skb_tailroom(skb) >= grow)
+ return 0;
+
+ /* tail growth may reallocate backing storage and move skb data */
+ err = pskb_expand_head(skb, 0, grow - skb_tailroom(skb), GFP_ATOMIC);
+ if (unlikely(err))
+ return err;
+
+ return 0;
+}
+
+/* Rewrite quoted datagram layout after inner translation in ICMP errors.
+ *
+ * Caller provides old/new quoted lengths and extension lengths; this helper
+ * only does byte moves/padding/trim while preserving extension bytes at the
+ * end of the packet when present
+ */
+int ipxlat_icmp_relayout(struct sk_buff *skb, unsigned int outer_len,
+ unsigned int in_ipl, unsigned int in_iel,
+ unsigned int out_ipl, unsigned int out_pad,
+ unsigned int out_iel)
+{
+ const unsigned int in_ie_off = outer_len + in_ipl, old_len = skb->len;
+ const unsigned int new_len = outer_len + out_ipl + out_pad + out_iel;
+ const unsigned int out_ie_off = outer_len + out_ipl + out_pad;
+ unsigned int grow = 0;
+ int err;
+
+ /* new_len > old_len here means "we need extra bytes on top of
+ * already-translated length", mainly due padding/layout decisions
+ * while keeping extensions
+ */
+ if (unlikely(new_len > old_len)) {
+ grow = new_len - old_len;
+
+ err = ipxlat_ensure_tailroom(skb, grow);
+ if (unlikely(err))
+ return err;
+
+ __skb_put(skb, grow);
+ }
+
+ if (unlikely(out_iel))
+ memmove(skb->data + out_ie_off, skb->data + in_ie_off, out_iel);
+
+ if (unlikely(out_pad))
+ memset(skb->data + outer_len + out_ipl, 0, out_pad);
+
+ if (unlikely(new_len < old_len)) {
+ err = pskb_trim(skb, new_len);
+ if (unlikely(err))
+ return err;
+ }
+
+ return 0;
+}
+
/* Normalize checksum/offload metadata after address-family translation.
*
* Translation changes protocol family but keeps transport payload semantics
diff --git a/drivers/net/ipxlat/transport.h b/drivers/net/ipxlat/transport.h
index 9b6fe422b01f..09f522696eea 100644
--- a/drivers/net/ipxlat/transport.h
+++ b/drivers/net/ipxlat/transport.h
@@ -63,6 +63,25 @@ __sum16 ipxlat_l4_csum_ipv6(const struct in6_addr *saddr,
const struct sk_buff *skb, unsigned int l4_off,
unsigned int l4_len, u8 proto);
+/**
+ * ipxlat_icmp_relayout - resize quoted ICMP payload/extensions in place
+ * @skb: packet buffer
+ * @outer_len: offset to quoted datagram start
+ * @in_ipl: input datagram payload length
+ * @in_iel: input extension length
+ * @out_ipl: output datagram payload length
+ * @out_pad: output pad bytes between datagram and extensions
+ * @out_iel: output extension length
+ *
+ * This helper may move payload bytes and adjust skb tail length.
+ *
+ * Return: 0 on success, negative errno on resize/memory failures.
+ */
+int ipxlat_icmp_relayout(struct sk_buff *skb, unsigned int outer_len,
+ unsigned int in_ipl, unsigned int in_iel,
+ unsigned int out_ipl, unsigned int out_pad,
+ unsigned int out_iel);
+
/**
* ipxlat_finalize_offload - normalize checksum/GSO metadata after translation
* @skb: translated packet
--
2.53.0
next prev parent reply other threads:[~2026-03-19 15:13 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-19 15:12 [RFC net-next 00/15] Introducing ipxlat: a stateless IPv4/IPv6 translation device Ralf Lici
2026-03-19 15:12 ` [RFC net-next 01/15] drivers/net: add ipxlat netdevice skeleton and build plumbing Ralf Lici
2026-03-19 15:12 ` [RFC net-next 02/15] ipxlat: add RFC 6052 address conversion helpers Ralf Lici
2026-03-19 15:12 ` [RFC net-next 03/15] ipxlat: add packet metadata control block helpers Ralf Lici
2026-03-19 15:12 ` [RFC net-next 04/15] ipxlat: add IPv4 packet validation path Ralf Lici
2026-03-19 15:12 ` [RFC net-next 05/15] ipxlat: add IPv6 " Ralf Lici
2026-03-19 15:12 ` [RFC net-next 06/15] ipxlat: add transport checksum and offload helpers Ralf Lici
2026-03-19 15:12 ` [RFC net-next 07/15] ipxlat: add 4to6 and 6to4 TCP/UDP translation helpers Ralf Lici
2026-03-19 15:12 ` [RFC net-next 08/15] ipxlat: add translation engine and dispatch core Ralf Lici
2026-03-19 15:12 ` [RFC net-next 09/15] ipxlat: emit translator-generated ICMP errors on drop Ralf Lici
2026-03-19 15:12 ` [RFC net-next 10/15] ipxlat: add 4to6 pre-fragmentation path Ralf Lici
2026-03-19 15:12 ` [RFC net-next 11/15] ipxlat: add ICMP informational translation paths Ralf Lici
2026-03-19 15:12 ` Ralf Lici [this message]
2026-03-19 15:12 ` [RFC net-next 13/15] ipxlat: add netlink control plane and uapi Ralf Lici
2026-03-19 15:12 ` [RFC net-next 14/15] selftests: net: add ipxlat coverage Ralf Lici
2026-03-19 15:12 ` [RFC net-next 15/15] Documentation: networking: add ipxlat translator guide Ralf Lici
2026-03-19 22:11 ` Jonathan Corbet
2026-03-24 9:55 ` 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=20260319151230.655687-13-ralf@mandelbit.com \
--to=ralf@mandelbit.com \
--cc=andrew+netdev@lunn.ch \
--cc=antonio@mandelbit.com \
--cc=davem@davemloft.net \
--cc=dxld@darkboxed.org \
--cc=edumazet@google.com \
--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