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 15/18] net: ipxlat: add ICMP error and quoted-packet translation
Date: Sat, 24 Oct 2026 11:25:25 +0200 [thread overview]
Message-ID: <20261024092532.172005-16-ralf@mandelbit.com> (raw)
In-Reply-To: <20261024092532.172005-1-ralf@mandelbit.com>
Map ICMP error types, codes, pointers and MTUs; translate quoted
inner headers and checksums; and rebuild RFC 4884 quote, padding and
extension layouts in both directions.
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
Changes since RFC v1 https://lore.kernel.org/netdev/20260319151230.655687-13-ralf@mandelbit.com/
- Accept the minimum eight-byte ICMP quote without requiring a complete
quoted TCP header, and update quoted TCP and UDP checksums only when
enough bytes are present. (Sashiko)
- Preserve CSUM_MANGLED_0 if an incremental quoted-inner UDP checksum
update produces zero. (Sashiko)
- Do not retain quoted-header pointers across operations which can
reallocate packet storage.
net/ipxlat/icmp_46.c | 490 ++++++++++++++++++++++++++++++++++++++++-
net/ipxlat/icmp_64.c | 513 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 993 insertions(+), 10 deletions(-)
diff --git a/net/ipxlat/icmp_46.c b/net/ipxlat/icmp_46.c
index bb3952256162..793d8151a859 100644
--- a/net/ipxlat/icmp_46.c
+++ b/net/ipxlat/icmp_46.c
@@ -13,10 +13,50 @@
#include <net/ip6_checksum.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_icmp_map_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_code = ICMPV6_HDR_FIELD;
+ out->icmp6_pointer = cpu_to_be32(ipxlat_46_icmp_param_prob_map[ptr]);
+ return 0;
+}
+
static int ipxlat_46_icmp_map_info_type_code(const struct icmphdr *in,
struct icmp6hdr *out)
{
@@ -38,18 +78,153 @@ static int ipxlat_46_icmp_map_info_type_code(const struct icmphdr *in,
return -EPROTONOSUPPORT;
}
+static __be32 ipxlat_46_compute_icmp_mtu6(unsigned int pkt_mtu,
+ u16 tot_len_field)
+{
+ unsigned int i;
+ u32 mtu6;
+
+ /* 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;
+ }
+ }
+ }
+
+ /* The engine has no authoritative next-hop routes for the quoted flow.
+ * Preserve the reported path information, adjust for the larger IPv6
+ * header and enforce the IPv6 minimum MTU. The output path handles its
+ * own route PMTU after translation.
+ */
+ mtu6 = pkt_mtu + sizeof(struct ipv6hdr) - sizeof(struct iphdr);
+ if (mtu6 < IPV6_MIN_MTU)
+ mtu6 = IPV6_MIN_MTU;
+
+ return cpu_to_be32(mtu6);
+}
+
+static int ipxlat_46_build_icmp_dest_unreach(const struct icmphdr *in,
+ struct icmp6hdr *out,
+ const struct iphdr *inner4,
+ bool *ie_forbidden)
+{
+ unsigned int inner4_tot_len, in_frag_mtu;
+
+ *ie_forbidden = false;
+ 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;
+ out->icmp6_unused = 0;
+ return 0;
+ case ICMP_PORT_UNREACH:
+ out->icmp6_type = ICMPV6_DEST_UNREACH;
+ out->icmp6_code = ICMPV6_PORT_UNREACH;
+ out->icmp6_unused = 0;
+ return 0;
+ 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;
+ out->icmp6_unused = 0;
+ return 0;
+ case ICMP_PROT_UNREACH:
+ out->icmp6_type = ICMPV6_PARAMPROB;
+ out->icmp6_code = ICMPV6_UNK_NEXTHDR;
+ out->icmp6_pointer =
+ cpu_to_be32(offsetof(struct ipv6hdr, nexthdr));
+ *ie_forbidden = true;
+ return 0;
+ case ICMP_FRAG_NEEDED:
+ out->icmp6_type = ICMPV6_PKT_TOOBIG;
+ out->icmp6_code = 0;
+ in_frag_mtu = be16_to_cpu(in->un.frag.mtu);
+ inner4_tot_len = be16_to_cpu(inner4->tot_len);
+ out->icmp6_mtu =
+ ipxlat_46_compute_icmp_mtu6(in_frag_mtu,
+ inner4_tot_len);
+ *ie_forbidden = true;
+ return 0;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+}
+
+static int ipxlat_46_icmp_map_error_type_code(const struct icmphdr *in,
+ struct icmp6hdr *out,
+ const struct iphdr *inner4,
+ bool *ie_forbidden)
+{
+ int err;
+
+ *ie_forbidden = false;
+
+ switch (in->type) {
+ case ICMP_DEST_UNREACH:
+ return ipxlat_46_build_icmp_dest_unreach(in, out, inner4,
+ ie_forbidden);
+ 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;
+ err = ipxlat_46_icmp_map_param_prob(in, out);
+ if (unlikely(err))
+ return err;
+ return 0;
+ }
+
+ return -EPROTONOSUPPORT;
+}
+
int ipxlat_46_icmp_preflight(struct sk_buff *skb)
{
const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
const struct icmphdr *icmp4;
struct icmp6hdr mapped = {};
+ struct icmphdr inner_icmp4;
+ struct iphdr inner4;
+ bool ie_forbidden;
int err;
- if (cb->is_icmp_err)
+ icmp4 = (const struct icmphdr *)(skb->data + cb->l4_off);
+ if (!cb->is_icmp_err) {
+ err = ipxlat_46_icmp_map_info_type_code(icmp4, &mapped);
+ return err ? -EOPNOTSUPP : 0;
+ }
+
+ /* quoted headers are not guaranteed to be naturally aligned */
+ memcpy(&inner4, skb->data + cb->inner_l3_offset, sizeof(inner4));
+ err = ipxlat_46_icmp_map_error_type_code(icmp4, &mapped, &inner4,
+ &ie_forbidden);
+ if (err)
return -EOPNOTSUPP;
- icmp4 = (const struct icmphdr *)(skb->data + cb->l4_off);
- err = ipxlat_46_icmp_map_info_type_code(icmp4, &mapped);
+ if (!ipxlat_is_first_frag4(&inner4) || inner4.protocol != IPPROTO_ICMP)
+ return 0;
+
+ memcpy(&inner_icmp4, skb->data + cb->inner_l4_offset,
+ sizeof(inner_icmp4));
+ err = ipxlat_46_icmp_map_info_type_code(&inner_icmp4, &mapped);
return err ? -EOPNOTSUPP : 0;
}
@@ -97,10 +272,317 @@ static int ipxlat_46_icmp_info_outer(struct sk_buff *skb)
return 0;
}
+struct ipxlat_46_quote {
+ struct iphdr ip4;
+ unsigned int l3_off;
+ unsigned int l4_off;
+ unsigned int l4_len;
+ unsigned int end;
+ bool is_frag;
+};
+
+static int ipxlat_46_icmp_inner_l3(const struct ipxlat_config *cfg,
+ struct sk_buff *skb,
+ struct ipxlat_46_quote *quote,
+ int *inner_delta)
+{
+ unsigned int inner_l3_payload, inner_l3_len, inner_tot_len;
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ unsigned int input_quote_end, input_quote_len;
+ unsigned int old_l4_off, new_l4_off;
+ struct ipv6hdr outer_ip6_copy;
+ struct ipv6hdr *inner_ip6;
+ int adjusted_quote_end;
+ struct frag_hdr *fh6;
+ u8 next_hdr;
+
+ /* inner header alignment is not guaranteed */
+ quote->l3_off = cb->inner_l3_offset;
+ memcpy("e->ip4, skb->data + quote->l3_off, sizeof(quote->ip4));
+ inner_l3_len = quote->ip4.ihl << 2;
+ quote->is_frag = ip_is_fragment("e->ip4);
+ input_quote_len = icmp_hdr(skb)->un.reserved[1] << 2;
+ input_quote_end = input_quote_len ? cb->payload_off + input_quote_len :
+ skb->len;
+
+ /* save outer IPv6 hdr because pull+push destroys that hdr region */
+ outer_ip6_copy = *ipv6_hdr(skb);
+
+ old_l4_off = quote->l3_off + inner_l3_len;
+ new_l4_off = quote->l3_off + sizeof(struct ipv6hdr) +
+ (quote->is_frag ? sizeof(struct frag_hdr) : 0);
+ *inner_delta = (int)new_l4_off - (int)old_l4_off;
+ adjusted_quote_end = (int)input_quote_end + *inner_delta;
+ quote->end = adjusted_quote_end;
+
+ if (unlikely(skb_cow_head(skb, max_t(int, 0, *inner_delta))))
+ return -ENOMEM;
+
+ ipxlat_replace_l3(skb, old_l4_off, new_l4_off,
+ skb_transport_offset(skb));
+
+ *ipv6_hdr(skb) = outer_ip6_copy;
+ ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + quote->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(quote->ip4.tot_len);
+ quote->l4_len = inner_tot_len - inner_l3_len;
+ quote->l4_off = new_l4_off;
+ quote->end = min(quote->end, quote->l4_off + quote->l4_len);
+ inner_l3_payload = inner_tot_len - inner_l3_len +
+ (quote->is_frag ? sizeof(struct frag_hdr) : 0);
+ if (quote->is_frag)
+ next_hdr = NEXTHDR_FRAGMENT;
+ else
+ next_hdr = ipxlat_46_map_proto_to_nexthdr(quote->ip4.protocol);
+
+ ipxlat_46_build_l3_hdr(inner_ip6, "e->ip4, inner_l3_payload,
+ next_hdr, quote->ip4.ttl);
+
+ ipxlat_46_convert_endpoints(cfg, "e->ip4, &inner_ip6->saddr,
+ &inner_ip6->daddr);
+
+ if (quote->is_frag) {
+ fh6 = (struct frag_hdr *)(inner_ip6 + 1);
+ ipxlat_46_build_frag_hdr(fh6, "e->ip4, quote->ip4.protocol);
+ }
+
+ return 0;
+}
+
+static int ipxlat_46_icmp_inner_l4(struct sk_buff *skb,
+ const struct ipxlat_46_quote *quote)
+{
+ struct icmp6hdr *inner_icmp6;
+ struct icmphdr inner_icmp4;
+ struct ipv6hdr *inner_ip6;
+ unsigned int write_end;
+ struct tcphdr *tcp;
+ struct udphdr *udp;
+ int err;
+
+ if (unlikely(!ipxlat_is_first_frag4("e->ip4)))
+ return 0;
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + quote->l3_off);
+ switch (quote->ip4.protocol) {
+ case IPPROTO_TCP:
+ /* Only the pseudo-header addresses change. If the quote reaches
+ * the checksum field, the rest of the TCP message is not needed
+ * for an incremental update.
+ */
+ write_end = quote->l4_off + offsetof(struct tcphdr, check) +
+ sizeof(tcp->check);
+ if (write_end > quote->end)
+ return 0;
+ if (unlikely(skb_ensure_writable(skb, write_end)))
+ return -ENOMEM;
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + quote->l3_off);
+ tcp = (struct tcphdr *)(skb->data + quote->l4_off);
+ tcp->check = ipxlat_46_update_l4_csum(tcp->check, "e->ip4,
+ inner_ip6);
+ return 0;
+ case IPPROTO_UDP:
+ write_end = quote->l4_off + sizeof(*udp);
+ if (unlikely(write_end > quote->end))
+ return -EINVAL;
+ if (unlikely(skb_ensure_writable(skb, write_end)))
+ return -ENOMEM;
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + quote->l3_off);
+ udp = (struct udphdr *)(skb->data + quote->l4_off);
+ if (!udp->check)
+ return 0;
+ udp->check = ipxlat_46_update_l4_csum(udp->check, "e->ip4,
+ inner_ip6);
+ if (!udp->check)
+ udp->check = CSUM_MANGLED_0;
+ return 0;
+ case IPPROTO_ICMP:
+ write_end = quote->l4_off + sizeof(inner_icmp4);
+ if (unlikely(write_end > quote->end))
+ return -EINVAL;
+ if (unlikely(skb_ensure_writable(skb, write_end)))
+ return -ENOMEM;
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + quote->l3_off);
+ /* inner header alignment is not guaranteed */
+ memcpy(&inner_icmp4, skb->data + quote->l4_off,
+ sizeof(inner_icmp4));
+ inner_icmp6 = (struct icmp6hdr *)(skb->data + quote->l4_off);
+ err = ipxlat_46_icmp_map_info_type_code(&inner_icmp4,
+ inner_icmp6);
+ if (unlikely(err))
+ return -EINVAL;
+ if (quote->is_frag) {
+ /* a first fragment contains enough bytes to map the
+ * ICMP header, but not enough to rebuild its checksum
+ * over the full datagram
+ */
+ return 0;
+ }
+ ipxlat_46_icmp_info_update_csum(&inner_icmp4, inner_icmp6,
+ inner_ip6, quote->l4_len);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+/* 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 max_ext_len, output_quote_units = 0;
+ unsigned int packet_len_cap, payload_len;
+ struct ipxlat_icmp_layout layout = {};
+ int input_quote_len, err;
+ struct icmp6hdr *ic6;
+ struct ipv6hdr *iph6;
+
+ /* icmp4_ipl marks where quoted dgram ends and extension area starts */
+ if (likely(!icmp4_ipl))
+ goto no_extensions;
+
+ layout.outer_len = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
+ payload_len = skb->len - layout.outer_len;
+ input_quote_len = icmp4_ipl + inner_delta;
+ if (unlikely(input_quote_len < 0 || input_quote_len > payload_len))
+ return -EINVAL;
+ layout.input_quote_len = input_quote_len;
+
+ if (likely(layout.input_quote_len == payload_len))
+ goto no_extensions;
+
+ layout.input_ext_len = payload_len - layout.input_quote_len;
+ max_ext_len = IPV6_MIN_MTU -
+ (layout.outer_len + ICMP_EXT_ORIG_DGRAM_MIN_LEN);
+
+ if (unlikely(ie_forbidden || layout.input_ext_len > max_ext_len)) {
+ packet_len_cap = min_t(unsigned int,
+ skb->len - layout.input_ext_len,
+ IPV6_MIN_MTU);
+ layout.output_quote_len = packet_len_cap - layout.outer_len;
+ } else {
+ packet_len_cap = min_t(unsigned int, skb->len, IPV6_MIN_MTU);
+ layout.output_quote_len =
+ round_down(packet_len_cap - layout.input_ext_len -
+ layout.outer_len,
+ sizeof(u64));
+ layout.output_pad_len =
+ max_t(unsigned int, ICMP_EXT_ORIG_DGRAM_MIN_LEN,
+ layout.output_quote_len) -
+ layout.output_quote_len;
+ layout.output_ext_len = layout.input_ext_len;
+ output_quote_units = (layout.output_quote_len +
+ layout.output_pad_len) >> 3;
+ }
+
+ /* if no ext bytes are copied and no pad is written, relayout only
+ * trims/updates lengths and does not require full data writability
+ */
+ if (unlikely(layout.output_ext_len || layout.output_pad_len)) {
+ err = skb_ensure_writable(skb, skb->len);
+ if (unlikely(err))
+ return err;
+ }
+
+ err = ipxlat_icmp_relayout(skb, &layout);
+ 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 = output_quote_units;
+ return 0;
+}
+
+/**
+ * ipxlat_46_icmp_error - translate ICMPv4 error payload to ICMPv6 error form
+ * @cfg: immutable route translation configuration
+ * @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(const struct ipxlat_config *cfg,
+ struct sk_buff *skb)
+{
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct icmphdr icmp4 = *icmp_hdr(skb);
+ struct ipxlat_46_quote quote;
+ unsigned int l4_len, l4_off;
+ struct icmp6hdr *icmp6;
+ struct ipv6hdr *iph6;
+ int inner_delta, err;
+ bool ie_forbidden;
+
+ if (unlikely(!cb->is_icmp_err)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* rewrite the quoted IP hdr, then repair transport data when present */
+ err = ipxlat_46_icmp_inner_l3(cfg, skb, "e, &inner_delta);
+ if (unlikely(err))
+ return err;
+ err = ipxlat_46_icmp_inner_l4(skb, "e);
+ if (unlikely(err))
+ return err;
+
+ err = ipxlat_46_icmp_map_error_type_code(&icmp4, icmp6_hdr(skb),
+ "e.ip4, &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
+ */
+ iph6 = ipv6_hdr(skb);
+ icmp6 = icmp6_hdr(skb);
+ l4_off = skb_transport_offset(skb);
+ l4_len = ipxlat_skb_l4_len(skb);
+ icmp6->icmp6_cksum = 0;
+ icmp6->icmp6_cksum = ipxlat_l4_csum_ipv6(&iph6->saddr, &iph6->daddr,
+ skb, l4_off, l4_len,
+ IPPROTO_ICMPV6);
+ skb->ip_summed = CHECKSUM_NONE;
+ return 0;
+}
+
int ipxlat_46_icmp(const struct ipxlat_config *cfg, struct sk_buff *skb)
{
if (unlikely(ipxlat_skb_cb(skb)->is_icmp_err))
- return -EINVAL;
+ return ipxlat_46_icmp_error(cfg, skb);
return ipxlat_46_icmp_info_outer(skb);
}
diff --git a/net/ipxlat/icmp_64.c b/net/ipxlat/icmp_64.c
index ce3131ffc6be..db3a2c784e61 100644
--- a/net/ipxlat/icmp_64.c
+++ b/net/ipxlat/icmp_64.c
@@ -12,11 +12,39 @@
*/
#include <net/ip6_checksum.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_icmp_map_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_icmp_map_info_type_code(const struct icmp6hdr *in,
struct icmphdr *out)
{
@@ -38,6 +66,81 @@ static int ipxlat_64_icmp_map_info_type_code(const struct icmp6hdr *in,
}
}
+static int ipxlat_64_icmp_map_error_type_code(const struct icmp6hdr *ic6,
+ struct icmphdr *ic4,
+ bool *ie_forbidden)
+{
+ const u32 header_delta = sizeof(struct ipv6hdr) - sizeof(struct iphdr);
+ u32 mtu4, mtu6, ptr6, ptr4;
+ int err;
+
+ *ie_forbidden = false;
+
+ 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;
+ return 0;
+ case ICMPV6_TIME_EXCEED:
+ ic4->type = ICMP_TIME_EXCEEDED;
+ ic4->code = ic6->icmp6_code;
+ ic4->un.gateway = 0;
+ return 0;
+ case ICMPV6_PKT_TOOBIG:
+ ic4->type = ICMP_DEST_UNREACH;
+ ic4->code = ICMP_FRAG_NEEDED;
+ ic4->un.frag.__unused = 0;
+ /* The engine has no authoritative next-hop routes for the
+ * quoted flow so we preserve the received path information and
+ * adjust it for the smaller IPv4 header. The output path
+ * handles its own route PMTU.
+ */
+ mtu6 = be32_to_cpu(ic6->icmp6_mtu);
+ mtu4 = mtu6 > header_delta ? mtu6 - header_delta : 0;
+ ic4->un.frag.mtu = cpu_to_be16(min_t(u32, mtu4, IP_MAX_MTU));
+ *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_icmp_map_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,
@@ -91,24 +194,422 @@ static int ipxlat_64_icmp_info_outer(struct sk_buff *skb,
int ipxlat_64_icmp_preflight(const struct ipxlat_config *cfg,
struct sk_buff *skb)
{
- const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
- const struct icmp6hdr *icmp6;
+ unsigned int inner_datagram_len, inner_l3_len, inner_payload_len;
+ struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct icmp6hdr *icmp6 =
+ (const struct icmp6hdr *)(skb->data + cb->l4_off);
+ struct frag_hdr inner_fragh;
+ struct icmp6hdr inner_icmp6;
struct icmphdr mapped = {};
+ struct ipv6hdr inner6;
+ bool ie_forbidden;
int err;
- if (cb->is_icmp_err)
+ if (!cb->is_icmp_err) {
+ err = ipxlat_64_icmp_map_info_type_code(icmp6, &mapped);
+ return err ? -EOPNOTSUPP : 0;
+ }
+
+ err = ipxlat_64_icmp_map_error_type_code(icmp6, &mapped, &ie_forbidden);
+ if (err)
+ return -EOPNOTSUPP;
+
+ /* quoted headers are not guaranteed to be naturally aligned */
+ memcpy(&inner6, skb->data + cb->inner_l3_offset, sizeof(inner6));
+ inner_l3_len = cb->inner_l4_offset - cb->inner_l3_offset;
+ inner_payload_len = ntohs(inner6.payload_len);
+ inner_datagram_len = sizeof(inner6) + inner_payload_len;
+ if (unlikely(inner_l3_len > inner_datagram_len))
+ return -EINVAL;
+
+ if (inner_datagram_len - inner_l3_len + sizeof(struct iphdr) >
+ IP_MAX_MTU)
+ return -EOPNOTSUPP;
+
+ err = ipxlat_64_convert_endpoints(cfg, &inner6, false,
+ &cb->inner_saddr4, &cb->inner_daddr4);
+ if (err)
return -EOPNOTSUPP;
- icmp6 = (const struct icmp6hdr *)(skb->data + cb->l4_off);
- err = ipxlat_64_icmp_map_info_type_code(icmp6, &mapped);
+ if (cb->inner_l4_proto != NEXTHDR_ICMP)
+ return 0;
+
+ if (cb->inner_fragh_off) {
+ memcpy(&inner_fragh, skb->data + cb->inner_fragh_off,
+ sizeof(inner_fragh));
+ if (!ipxlat_is_first_frag6(&inner_fragh))
+ return 0;
+ }
+
+ memcpy(&inner_icmp6, skb->data + cb->inner_l4_offset,
+ sizeof(inner_icmp6));
+ err = ipxlat_64_icmp_map_info_type_code(&inner_icmp6, &mapped);
return err ? -EOPNOTSUPP : 0;
}
+struct ipxlat_64_quote {
+ struct ipv6hdr ip6;
+ struct frag_hdr frag6;
+ unsigned int l3_off;
+ unsigned int l4_off;
+ unsigned int l4_len;
+ unsigned int end;
+ u16 frag_offset;
+ bool has_frag_hdr;
+ bool more_fragments;
+ bool has_l4;
+ bool l4_len_known;
+};
+
+static void ipxlat_64_icmp_inner_frag(const struct sk_buff *skb,
+ const struct ipxlat_cb *cb,
+ struct ipxlat_64_quote *quote)
+{
+ quote->has_frag_hdr = !!cb->inner_fragh_off;
+ quote->more_fragments = false;
+ quote->frag_offset = 0;
+ quote->has_l4 = true;
+ quote->l4_len_known = true;
+ if (!quote->has_frag_hdr)
+ return;
+
+ memcpy("e->frag6, skb->data + cb->inner_fragh_off,
+ sizeof(quote->frag6));
+ quote->frag_offset = ipxlat_get_frag6_offset("e->frag6);
+ quote->more_fragments = !!(be16_to_cpu(quote->frag6.frag_off) & IP6_MF);
+ /* An atomic fragment has offset zero and M clear, so its L4 header and
+ * complete length are available. A first real fragment exposes L4 but
+ * not the complete length; later fragments expose neither.
+ */
+ quote->has_l4 = !quote->frag_offset;
+ quote->l4_len_known = quote->has_l4 && !quote->more_fragments;
+}
+
+static int ipxlat_64_icmp_inner_l3(struct net *net, struct sk_buff *skb,
+ struct ipxlat_64_quote *quote,
+ int *inner_delta)
+{
+ unsigned int inner_datagram_len, inner_l3_len, inner_tot_len;
+ unsigned int inner_payload_len, input_quote_end, input_quote_len;
+ unsigned int new_l4_off, old_l4_off;
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct iphdr outer4_copy = *ip_hdr(skb);
+ struct iphdr *inner4;
+ u8 inner_l4_proto;
+ __be16 frag_off4;
+ bool df;
+ int adjusted_quote_end;
+
+ quote->l3_off = cb->inner_l3_offset;
+ old_l4_off = cb->inner_l4_offset;
+ inner_l3_len = old_l4_off - quote->l3_off;
+
+ inner_l4_proto = ipxlat_64_map_nexthdr_proto(cb->inner_l4_proto);
+
+ /* inner header alignment is not guaranteed */
+ memcpy("e->ip6, skb->data + quote->l3_off, sizeof(quote->ip6));
+ input_quote_len = icmp6_hdr(skb)->icmp6_datagram_len << 3;
+ input_quote_end = input_quote_len ? cb->payload_off + input_quote_len :
+ skb->len;
+
+ /* the quoted IPv6 length is the full datagram length, not the number
+ * of bytes present in this ICMP quote
+ */
+ inner_payload_len = ntohs(quote->ip6.payload_len);
+ inner_datagram_len = sizeof(quote->ip6) + inner_payload_len;
+ if (unlikely(inner_l3_len > inner_datagram_len))
+ return -EINVAL;
+ inner_tot_len = inner_datagram_len - inner_l3_len +
+ sizeof(struct iphdr);
+ if (unlikely(inner_tot_len > IP_MAX_MTU))
+ return -EMSGSIZE;
+ quote->l4_len = inner_tot_len - sizeof(struct iphdr);
+ ipxlat_64_icmp_inner_frag(skb, cb, quote);
+
+ new_l4_off = quote->l3_off + sizeof(struct iphdr);
+ *inner_delta = (int)new_l4_off - (int)old_l4_off;
+ adjusted_quote_end = (int)input_quote_end + *inner_delta;
+ quote->end = adjusted_quote_end;
+ quote->end = min(quote->end, quote->l3_off + inner_tot_len);
+ quote->l4_off = new_l4_off;
+
+ /* unlike 46, inner 6->4 always shrinks quoted L3 size */
+ ipxlat_replace_l3(skb, old_l4_off, new_l4_off,
+ skb_transport_offset(skb));
+
+ *ip_hdr(skb) = outer4_copy;
+
+ inner4 = (struct iphdr *)(skb->data + quote->l3_off);
+ /* RFC 7915 Section 5.1 */
+ if (likely(!quote->has_frag_hdr)) {
+ df = ipxlat_64_should_set_df(inner_tot_len);
+ frag_off4 = ipxlat_64_encode_frag_off(df, false, 0);
+ } else {
+ frag_off4 = ipxlat_64_encode_frag_off(false,
+ quote->more_fragments,
+ quote->frag_offset);
+ }
+
+ ipxlat_64_build_l3_hdr(net, inner4, "e->ip6, inner_tot_len,
+ frag_off4, inner_l4_proto, cb->inner_saddr4,
+ cb->inner_daddr4, quote->ip6.hop_limit,
+ quote->has_frag_hdr ? "e->frag6 : NULL);
+
+ return 0;
+}
+
+static int ipxlat_64_icmp_inner_l4(struct sk_buff *skb,
+ const struct ipxlat_64_quote *quote)
+{
+ struct icmp6hdr inner_icmp6;
+ struct icmphdr *inner_icmp4;
+ unsigned int write_end;
+ struct iphdr *inner4;
+ struct tcphdr *tcp;
+ struct udphdr *udp;
+ int err;
+
+ if (unlikely(!quote->has_l4))
+ return 0;
+
+ inner4 = (struct iphdr *)(skb->data + quote->l3_off);
+ switch (inner4->protocol) {
+ case IPPROTO_TCP:
+ /* Only the pseudo-header addresses change. If the quote reaches
+ * the checksum field, the rest of the TCP message is not needed
+ * for an incremental update.
+ */
+ write_end = quote->l4_off + offsetof(struct tcphdr, check) +
+ sizeof(tcp->check);
+ if (write_end > quote->end)
+ return 0;
+ if (unlikely(skb_ensure_writable(skb, write_end)))
+ return -ENOMEM;
+
+ inner4 = (struct iphdr *)(skb->data + quote->l3_off);
+ tcp = (struct tcphdr *)(skb->data + quote->l4_off);
+ tcp->check = ipxlat_64_update_l4_csum(tcp->check, "e->ip6,
+ inner4);
+ return 0;
+ case IPPROTO_UDP:
+ write_end = quote->l4_off + sizeof(*udp);
+ if (unlikely(write_end > quote->end))
+ return -EINVAL;
+ if (unlikely(skb_ensure_writable(skb, write_end)))
+ return -ENOMEM;
+
+ inner4 = (struct iphdr *)(skb->data + quote->l3_off);
+ udp = (struct udphdr *)(skb->data + quote->l4_off);
+ if (!udp->check)
+ return 0;
+ udp->check = ipxlat_64_update_l4_csum(udp->check, "e->ip6,
+ inner4);
+ if (!udp->check)
+ udp->check = CSUM_MANGLED_0;
+ return 0;
+ case IPPROTO_ICMP:
+ write_end = quote->l4_off + sizeof(inner_icmp6);
+ if (unlikely(write_end > quote->end))
+ return -EINVAL;
+ if (unlikely(skb_ensure_writable(skb, write_end)))
+ return -ENOMEM;
+
+ inner4 = (struct iphdr *)(skb->data + quote->l3_off);
+ /* inner header alignment is not guaranteed */
+ memcpy(&inner_icmp6, skb->data + quote->l4_off,
+ sizeof(inner_icmp6));
+ inner_icmp4 = (struct icmphdr *)(skb->data + quote->l4_off);
+ err = ipxlat_64_icmp_map_info_type_code(&inner_icmp6,
+ inner_icmp4);
+ if (unlikely(err))
+ return err;
+ if (!quote->l4_len_known) {
+ /* a first fragment contains enough bytes to map the
+ * ICMP header, but not enough to rebuild its checksum
+ * over the full datagram
+ */
+ return 0;
+ }
+ inner_icmp4->checksum =
+ ipxlat_64_compute_icmp_info_csum("e->ip6,
+ &inner_icmp6,
+ inner_icmp4,
+ quote->l4_len);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+/* 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_icmp_squeeze_ext(struct sk_buff *skb,
+ unsigned int icmp6_ipl, int inner_delta,
+ bool ie_forbidden)
+{
+ struct ipxlat_icmp_layout layout = {};
+ unsigned int max_ext_len, output_quote_units = 0;
+ unsigned int packet_len_cap, payload_len;
+ unsigned int new_tot_len;
+ int input_quote_len, err;
+ struct icmphdr *ic4;
+ struct iphdr *iph4;
+
+ if (likely(!icmp6_ipl))
+ goto finalize;
+
+ layout.outer_len = skb_transport_offset(skb) + sizeof(struct icmphdr);
+ if (unlikely(skb->len < layout.outer_len))
+ return -EINVAL;
+
+ payload_len = skb->len - layout.outer_len;
+ input_quote_len = (int)icmp6_ipl + inner_delta;
+ if (unlikely(input_quote_len < 0))
+ return -EINVAL;
+ layout.input_quote_len = input_quote_len;
+ if (unlikely(layout.input_quote_len > payload_len))
+ return -EINVAL;
+
+ if (likely(layout.input_quote_len == payload_len))
+ goto finalize;
+
+ layout.input_ext_len = payload_len - layout.input_quote_len;
+ max_ext_len = IPXLAT_ICMP4_ERROR_MAX_LEN -
+ (layout.outer_len + ICMP_EXT_ORIG_DGRAM_MIN_LEN);
+
+ if (unlikely(ie_forbidden)) {
+ layout.output_quote_len = layout.input_quote_len;
+ } else if (unlikely(layout.input_ext_len > max_ext_len)) {
+ packet_len_cap = min_t(unsigned int,
+ skb->len - layout.input_ext_len,
+ IPXLAT_ICMP4_ERROR_MAX_LEN);
+ layout.output_quote_len = packet_len_cap - layout.outer_len;
+ } else {
+ packet_len_cap = min_t(unsigned int, skb->len,
+ IPXLAT_ICMP4_ERROR_MAX_LEN);
+ layout.output_quote_len =
+ round_down(packet_len_cap - layout.input_ext_len -
+ layout.outer_len,
+ sizeof(u32));
+ layout.output_pad_len =
+ max_t(unsigned int, ICMP_EXT_ORIG_DGRAM_MIN_LEN,
+ layout.output_quote_len) -
+ layout.output_quote_len;
+ layout.output_ext_len = layout.input_ext_len;
+ /* RFC 4884 field is in 32-bit units for ICMPv4 errors */
+ output_quote_units = (layout.output_quote_len +
+ layout.output_pad_len) >> 2;
+ }
+
+ /* if no extension bytes are copied and no pad is written, relayout
+ * only trims/updates lengths and doesn't require full data writability
+ */
+ if (unlikely(layout.output_ext_len || layout.output_pad_len)) {
+ err = skb_ensure_writable(skb, skb->len);
+ if (unlikely(err))
+ return err;
+ }
+
+ err = ipxlat_icmp_relayout(skb, &layout);
+ if (unlikely(err))
+ return err;
+
+finalize:
+ if (!ie_forbidden) {
+ ic4 = icmp_hdr(skb);
+ ic4->un.reserved[1] = output_quote_units;
+ }
+
+ 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
+ * @net: namespace owning translated IPv4 fragment-ID state
+ * @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 net *net, struct sk_buff *skb)
+{
+ const struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ const struct icmp6hdr ic6 = *icmp6_hdr(skb);
+ unsigned int icmp6_ipl, l4_len, l4_off;
+ struct ipxlat_64_quote quote;
+ int inner_delta, err;
+ struct icmphdr *ic4;
+ bool ie_forbidden;
+
+ if (unlikely(!cb->is_icmp_err)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* rewrite the quoted IP hdr, then repair transport data when present */
+ err = ipxlat_64_icmp_inner_l3(net, skb, "e, &inner_delta);
+ if (unlikely(err))
+ return err;
+ err = ipxlat_64_icmp_inner_l4(skb, "e);
+ if (unlikely(err))
+ return err;
+
+ /* build outer ICMPv4 error header after inner relayout */
+ ic4 = icmp_hdr(skb);
+ err = ipxlat_64_icmp_map_error_type_code(&ic6, ic4, &ie_forbidden);
+ if (unlikely(err))
+ return err;
+
+ icmp6_ipl = ic6.icmp6_datagram_len << 3;
+ err = ipxlat_64_icmp_squeeze_ext(skb, icmp6_ipl, inner_delta,
+ ie_forbidden);
+ if (unlikely(err))
+ return err;
+
+ /* relayout may reallocate the skb, so reload the header pointer */
+ ic4 = icmp_hdr(skb);
+ /* recompute whole ICMPv4 checksum after error-path relayout */
+ l4_off = skb_transport_offset(skb);
+ l4_len = ipxlat_skb_l4_len(skb);
+ ic4->checksum = 0;
+ ic4->checksum = csum_fold(skb_checksum(skb, l4_off, l4_len, 0));
+ skb->ip_summed = CHECKSUM_NONE;
+ return 0;
+}
+
int ipxlat_64_icmp(struct net *net, struct sk_buff *skb,
const struct ipv6hdr *in6)
{
if (unlikely(ipxlat_skb_cb(skb)->is_icmp_err))
- return -EINVAL;
+ return ipxlat_64_icmp_error(net, skb);
return ipxlat_64_icmp_info_outer(skb, in6);
}
--
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 ` [RFC net-next v2 11/18] net: ipxlat: add IPv4/IPv6 header and transport translation Ralf Lici
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 ` Ralf Lici [this message]
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-16-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.