mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-26 15:30:27 +01:00
bpf: clat: translate inner headers of incoming ICMPv6 errors
ICMPv6 error messages contain a copy of the original packet that caused the error. In a 464XLAT deployment, this inner packet is an IPv6 packet (as translated by the PLAT), while the local host expects to see the original IPv4 packet it generated. Without translation, the local host can't match the error to an active socket. This breaks functionality like Path MTU Discovery (PMTUD), traceroute, and error reporting for connected UDP sockets. This commit implements the translation of the inner headers from IPv6 to IPv4 for incoming ICMPv6 errors. Some implementation notes: - this only handles incoming ICMPv6; outgoing ICMPv4 is not yet implemented, but it seems less important. - the program uses different functions for rewriting the outer and inner header. I tried using recursion but the verifier didn't seem to like it. - after rewriting the inner headers, the ICMP checksum is incrementally updated based on difference of all the individual modifications done to the inner headers. This has the advantage that all the operations are fixed-size. But probably it would be easier and faster to just calculate the checksum from scratch.
This commit is contained in:
parent
f40f7f9223
commit
e822db9c78
1 changed files with 321 additions and 75 deletions
|
|
@ -1,6 +1,7 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright 2021 Toke Høiland-Jørgensen <toke@toke.dk> */
|
||||
/* Copyright 2025 Mary Strodl <mstrodl@csh.rit.edu> */
|
||||
/* Copyright 2025 Beniamino Galvani <bgalvani@redhat.com> */
|
||||
|
||||
/**
|
||||
* This is an implementation of a CLAT in eBPF. BPF is a different environment
|
||||
|
|
@ -67,16 +68,20 @@ struct icmpv6_pseudo {
|
|||
__u8 nh;
|
||||
} __attribute__((packed));
|
||||
|
||||
static void
|
||||
/* This function must be declared as inline because the BPF calling
|
||||
* convention only supports up to 5 function arguments. */
|
||||
static __always_inline void
|
||||
update_l4_checksum(struct __sk_buff *skb,
|
||||
struct ipv6hdr *ip6h,
|
||||
struct iphdr *iph,
|
||||
int ip_type,
|
||||
bool v4to6)
|
||||
bool v4to6,
|
||||
bool is_inner,
|
||||
__u32 *csum_diff)
|
||||
{
|
||||
int flags = BPF_F_PSEUDO_HDR;
|
||||
__u16 offset;
|
||||
__u32 csum;
|
||||
int ip_type;
|
||||
|
||||
if (v4to6) {
|
||||
void *from_ptr = &iph->saddr;
|
||||
|
|
@ -84,12 +89,18 @@ update_l4_checksum(struct __sk_buff *skb,
|
|||
|
||||
csum = bpf_csum_diff(from_ptr, 2 * sizeof(__u32), to_ptr, 2 * sizeof(struct in6_addr), 0);
|
||||
offset = sizeof(struct ethhdr) + sizeof(struct iphdr);
|
||||
ip_type = ip6h->nexthdr;
|
||||
} else {
|
||||
void *from_ptr = &ip6h->saddr;
|
||||
void *to_ptr = &iph->saddr;
|
||||
|
||||
csum = bpf_csum_diff(from_ptr, 2 * sizeof(struct in6_addr), to_ptr, 2 * sizeof(__u32), 0);
|
||||
offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
|
||||
ip_type = iph->protocol;
|
||||
|
||||
if (is_inner) {
|
||||
offset = offset + sizeof(struct icmp6hdr) + sizeof(struct ipv6hdr);
|
||||
}
|
||||
}
|
||||
|
||||
switch (ip_type) {
|
||||
|
|
@ -105,14 +116,20 @@ update_l4_checksum(struct __sk_buff *skb,
|
|||
}
|
||||
|
||||
bpf_l4_csum_replace(skb, offset, 0, csum, flags);
|
||||
|
||||
if (csum_diff) {
|
||||
*csum_diff = bpf_csum_diff((__be32 *) &csum, sizeof(csum), 0, 0, *csum_diff);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
static __always_inline void
|
||||
update_icmp_checksum(struct __sk_buff *skb,
|
||||
const struct ipv6hdr *ip6h,
|
||||
void *icmp_before,
|
||||
void *icmp_after,
|
||||
bool v4to6)
|
||||
bool v4to6,
|
||||
bool is_inner,
|
||||
__u32 seed)
|
||||
{
|
||||
struct icmpv6_pseudo ph = {.nh = IPPROTO_ICMPV6, .len = ip6h->payload_len};
|
||||
__u16 h_before;
|
||||
|
|
@ -138,9 +155,15 @@ update_icmp_checksum(struct __sk_buff *skb,
|
|||
v4to6 ? 0 : sizeof(ph),
|
||||
(__be32 *) &ph,
|
||||
v4to6 ? sizeof(ph) : 0,
|
||||
0);
|
||||
seed);
|
||||
|
||||
offset = sizeof(struct ethhdr) + (v4to6 ? sizeof(struct iphdr) : sizeof(struct ipv6hdr)) + 2;
|
||||
if (v4to6) {
|
||||
offset = sizeof(struct ethhdr) + sizeof(struct iphdr) + 2;
|
||||
} else {
|
||||
offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + 2;
|
||||
if (is_inner)
|
||||
offset += sizeof(struct icmp6hdr) + sizeof(struct ipv6hdr);
|
||||
}
|
||||
|
||||
/* first two bytes of ICMP header, type and code */
|
||||
h_before = *(__u16 *) icmp_before;
|
||||
|
|
@ -272,7 +295,7 @@ rewrite_icmp(struct __sk_buff *skb, const struct ipv6hdr *ip6h)
|
|||
}
|
||||
|
||||
*icmp6 = icmp6_buf;
|
||||
update_icmp_checksum(skb, ip6h, &icmp_buf, icmp6, true);
|
||||
update_icmp_checksum(skb, ip6h, &icmp_buf, icmp6, true, false, 0);
|
||||
|
||||
/* FIXME: also need to rewrite IP header embedded in ICMP error */
|
||||
|
||||
|
|
@ -505,7 +528,7 @@ clat_handle_v4(struct __sk_buff *skb)
|
|||
break;
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_UDP:
|
||||
update_l4_checksum(skb, &dst_hdr, iph, dst_hdr.nexthdr, true);
|
||||
update_l4_checksum(skb, &dst_hdr, iph, true, false, NULL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -554,93 +577,102 @@ v6addr_equal(const struct in6_addr *a, const struct in6_addr *b)
|
|||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
rewrite_icmpv6(struct __sk_buff *skb)
|
||||
static __always_inline void
|
||||
translate_ipv6_header(const struct ipv6hdr *ip6, struct iphdr *ip, __be32 saddr, __be32 daddr)
|
||||
{
|
||||
void *data_end = SKB_DATA_END(skb);
|
||||
void *data = SKB_DATA(skb);
|
||||
struct icmp6hdr *icmp6;
|
||||
struct icmphdr *icmp;
|
||||
struct icmphdr icmp_buf; /* buffer for the new ICMPv4 header */
|
||||
struct icmp6hdr icmp6_buf; /* copy of the old ICMPv6 header */
|
||||
struct ipv6hdr *ip6h;
|
||||
__u32 mtu;
|
||||
__u32 ptr;
|
||||
*ip = (struct iphdr) {
|
||||
.version = 4,
|
||||
.ihl = 5,
|
||||
.tos = ip6->priority << 4 | (ip6->flow_lbl[0] >> 4),
|
||||
.frag_off = bpf_htons(1 << 14),
|
||||
.ttl = ip6->hop_limit,
|
||||
.protocol = ip6->nexthdr == IPPROTO_ICMPV6 ? IPPROTO_ICMP : ip6->nexthdr,
|
||||
.saddr = saddr,
|
||||
.daddr = daddr,
|
||||
.tot_len = bpf_htons(bpf_ntohs(ip6->payload_len) + sizeof(struct iphdr)),
|
||||
};
|
||||
|
||||
icmp6 = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
|
||||
if (icmp6 + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
icmp6_buf = *icmp6;
|
||||
icmp = (void *) icmp6;
|
||||
icmp_buf = *icmp;
|
||||
ip->check =
|
||||
csum_fold_helper(bpf_csum_diff((__be32 *) ip, 0, (__be32 *) ip, sizeof(struct iphdr), 0));
|
||||
}
|
||||
|
||||
static __always_inline int
|
||||
translate_icmpv6_header(const struct icmp6hdr *icmp6, struct icmphdr *icmp)
|
||||
{
|
||||
/* These translations are defined in RFC6145 section 5.2 */
|
||||
switch (icmp6->icmp6_type) {
|
||||
case ICMPV6_ECHO_REQUEST:
|
||||
icmp_buf.type = ICMP_ECHO;
|
||||
icmp->type = ICMP_ECHO;
|
||||
break;
|
||||
case ICMPV6_ECHO_REPLY:
|
||||
icmp_buf.type = ICMP_ECHOREPLY;
|
||||
icmp->type = ICMP_ECHOREPLY;
|
||||
break;
|
||||
case ICMPV6_DEST_UNREACH:
|
||||
icmp_buf.type = ICMP_DEST_UNREACH;
|
||||
icmp->type = ICMP_DEST_UNREACH;
|
||||
switch (icmp6->icmp6_code) {
|
||||
case ICMPV6_NOROUTE:
|
||||
case ICMPV6_NOT_NEIGHBOUR:
|
||||
case ICMPV6_ADDR_UNREACH:
|
||||
icmp_buf.code = ICMP_HOST_UNREACH;
|
||||
icmp->code = ICMP_HOST_UNREACH;
|
||||
break;
|
||||
case ICMPV6_ADM_PROHIBITED:
|
||||
icmp_buf.code = ICMP_HOST_ANO;
|
||||
icmp->code = ICMP_HOST_ANO;
|
||||
break;
|
||||
case ICMPV6_PORT_UNREACH:
|
||||
icmp_buf.code = ICMP_PORT_UNREACH;
|
||||
icmp->code = ICMP_PORT_UNREACH;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case ICMPV6_PKT_TOOBIG:
|
||||
icmp_buf.type = ICMP_DEST_UNREACH;
|
||||
icmp_buf.code = ICMP_FRAG_NEEDED;
|
||||
{
|
||||
__u32 mtu;
|
||||
|
||||
icmp->type = ICMP_DEST_UNREACH;
|
||||
icmp->code = ICMP_FRAG_NEEDED;
|
||||
|
||||
mtu = bpf_ntohl(icmp6->icmp6_mtu) - 20;
|
||||
if (mtu > 0xffff)
|
||||
return -1;
|
||||
icmp_buf.un.frag.mtu = bpf_htons(mtu);
|
||||
icmp->un.frag.mtu = bpf_htons(mtu);
|
||||
break;
|
||||
}
|
||||
case ICMPV6_TIME_EXCEED:
|
||||
icmp_buf.type = ICMP_TIME_EXCEEDED;
|
||||
icmp->type = ICMP_TIME_EXCEEDED;
|
||||
break;
|
||||
case ICMPV6_PARAMPROB:
|
||||
switch (icmp6->icmp6_code) {
|
||||
case 0:
|
||||
icmp_buf.type = ICMP_PARAMETERPROB;
|
||||
icmp_buf.code = 0;
|
||||
{
|
||||
__u32 ptr;
|
||||
|
||||
icmp->type = ICMP_PARAMETERPROB;
|
||||
icmp->code = 0;
|
||||
|
||||
ptr = bpf_ntohl(icmp6->icmp6_pointer);
|
||||
/* Figure 6 in RFC6145 - using if statements b/c of
|
||||
* range at the bottom
|
||||
*/
|
||||
ptr = bpf_ntohl(icmp6->icmp6_pointer);
|
||||
if (ptr == 0 || ptr == 1)
|
||||
icmp_buf.un.reserved[0] = ptr;
|
||||
icmp->un.reserved[0] = ptr;
|
||||
else if (ptr == 4 || ptr == 5)
|
||||
icmp_buf.un.reserved[0] = 2;
|
||||
icmp->un.reserved[0] = 2;
|
||||
else if (ptr == 6)
|
||||
icmp_buf.un.reserved[0] = 9;
|
||||
icmp->un.reserved[0] = 9;
|
||||
else if (ptr == 7)
|
||||
icmp_buf.un.reserved[0] = 8;
|
||||
icmp->un.reserved[0] = 8;
|
||||
else if (ptr >= 8 && ptr <= 23)
|
||||
icmp_buf.un.reserved[0] = 12;
|
||||
icmp->un.reserved[0] = 12;
|
||||
else if (ptr >= 24 && ptr <= 39)
|
||||
icmp_buf.un.reserved[0] = 16;
|
||||
icmp->un.reserved[0] = 16;
|
||||
else
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
icmp_buf.type = ICMP_DEST_UNREACH;
|
||||
icmp_buf.code = ICMP_PROT_UNREACH;
|
||||
icmp->type = ICMP_DEST_UNREACH;
|
||||
icmp->code = ICMP_PROT_UNREACH;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
|
|
@ -650,11 +682,214 @@ rewrite_icmpv6(struct __sk_buff *skb)
|
|||
return -1;
|
||||
}
|
||||
|
||||
*icmp = icmp_buf;
|
||||
ip6h = data + sizeof(struct ethhdr);
|
||||
update_icmp_checksum(skb, ip6h, &icmp6_buf, icmp, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FIXME: also need to rewrite IP header embedded in ICMP error */
|
||||
static int
|
||||
rewrite_icmpv6_inner(struct __sk_buff *skb, __u32 *csum_diff)
|
||||
{
|
||||
void *data_end = SKB_DATA_END(skb);
|
||||
void *data = SKB_DATA(skb);
|
||||
struct icmphdr *icmp;
|
||||
struct icmp6hdr *icmp6;
|
||||
struct icmphdr icmp_buf; /* buffer for the new ICMPv4 header */
|
||||
struct icmp6hdr icmp6_buf; /* copy of the old ICMPv6 header */
|
||||
|
||||
/*
|
||||
* icmp6: v
|
||||
* -------------------------------------------------------------------------
|
||||
* | Ethernet | IPv6 | ICMPv6 | IPv6 | ICMPv6 | ...
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
icmp6 = data + sizeof(struct ethhdr) + 2 * sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr);
|
||||
if (icmp6 + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
icmp6_buf = *icmp6;
|
||||
icmp = (void *) icmp6;
|
||||
icmp_buf = *icmp;
|
||||
|
||||
if (translate_icmpv6_header(icmp6, &icmp_buf))
|
||||
return -1;
|
||||
|
||||
*icmp = icmp_buf;
|
||||
update_icmp_checksum(skb,
|
||||
(struct ipv6hdr *) (data + sizeof(struct ethhdr)),
|
||||
&icmp6_buf,
|
||||
icmp,
|
||||
false,
|
||||
true,
|
||||
0);
|
||||
|
||||
if (csum_diff) {
|
||||
data_end = SKB_DATA_END(skb);
|
||||
data = SKB_DATA(skb);
|
||||
|
||||
icmp = data + sizeof(struct ethhdr) + 2 * sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr);
|
||||
if (icmp + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
/* Compute the checksum difference between the old ICMPv6 header and the new ICMPv4 one */
|
||||
*csum_diff = bpf_csum_diff((__be32 *) &icmp6_buf,
|
||||
sizeof(struct icmp6hdr),
|
||||
(__be32 *) &icmp6_buf,
|
||||
0,
|
||||
*csum_diff);
|
||||
*csum_diff =
|
||||
bpf_csum_diff((__be32 *) icmp, 0, (__be32 *) icmp, sizeof(struct icmphdr), *csum_diff);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rewrite_ipv6_inner(struct __sk_buff *skb, struct iphdr *dst_hdr, __u32 *csum_diff)
|
||||
{
|
||||
void *data_end = SKB_DATA_END(skb);
|
||||
void *data = SKB_DATA(skb);
|
||||
struct ipv6hdr *ip6h;
|
||||
__be32 addr4;
|
||||
struct in6_addr subnet_v6;
|
||||
|
||||
/*
|
||||
* ip6h: v
|
||||
* ----------------------------------------------------------------
|
||||
* | Ethernet | IPv6 | ICMPv6 | IPv6 | ...
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
ip6h = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr);
|
||||
if (ip6h + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
if (!v6addr_equal(&ip6h->saddr, &config.local_v6))
|
||||
return -1;
|
||||
if (!v6addr_to_v4(&ip6h->daddr, config.pref64_len, &addr4, &subnet_v6))
|
||||
return -1;
|
||||
if (!v6addr_equal(&subnet_v6, &config.pref64))
|
||||
return -1;
|
||||
|
||||
translate_ipv6_header(ip6h, dst_hdr, config.local_v4.s_addr, addr4);
|
||||
|
||||
if (csum_diff) {
|
||||
/* Checksum difference between the old IPv6 header and the new IPv4 one */
|
||||
*csum_diff =
|
||||
bpf_csum_diff((__be32 *) ip6h, sizeof(struct ipv6hdr), (__be32 *) ip6h, 0, *csum_diff);
|
||||
|
||||
*csum_diff = bpf_csum_diff((__be32 *) dst_hdr,
|
||||
0,
|
||||
(__be32 *) dst_hdr,
|
||||
sizeof(struct iphdr),
|
||||
*csum_diff);
|
||||
}
|
||||
|
||||
switch (dst_hdr->protocol) {
|
||||
case IPPROTO_ICMP:
|
||||
if (rewrite_icmpv6_inner(skb, csum_diff))
|
||||
return -1;
|
||||
break;
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_UDP:
|
||||
update_l4_checksum(skb, ip6h, dst_hdr, false, true, csum_diff);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rewrite_icmpv6(struct __sk_buff *skb, int *out_length_diff)
|
||||
{
|
||||
void *data_end = SKB_DATA_END(skb);
|
||||
void *data = SKB_DATA(skb);
|
||||
struct iphdr *ip;
|
||||
struct icmp6hdr *icmp6;
|
||||
struct icmphdr *icmp;
|
||||
struct icmphdr icmp_buf; /* buffer for the new ICMPv4 header */
|
||||
struct icmp6hdr icmp6_buf; /* copy of the old ICMPv6 header */
|
||||
struct iphdr ip_in_buf; /* buffer for the new inner IPv4 header */
|
||||
__u32 csum_diff = 0;
|
||||
|
||||
/*
|
||||
* icmp6: v
|
||||
* ---------------------------------------------
|
||||
* | Ethernet | IPv6 | ICMPv6 | ...
|
||||
* ---------------------------------------------
|
||||
*/
|
||||
|
||||
icmp6 = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
|
||||
if (icmp6 + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
icmp6_buf = *icmp6;
|
||||
icmp = (void *) icmp6;
|
||||
icmp_buf = *icmp;
|
||||
|
||||
if (translate_icmpv6_header(icmp6, &icmp_buf))
|
||||
return -1;
|
||||
|
||||
if (icmp6->icmp6_type >= 128) {
|
||||
/* ICMPv6 non-error message: only translate the header */
|
||||
*icmp = icmp_buf;
|
||||
update_icmp_checksum(skb,
|
||||
(struct ipv6hdr *) (data + sizeof(struct ethhdr)),
|
||||
&icmp6_buf,
|
||||
icmp,
|
||||
false,
|
||||
false,
|
||||
0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ICMPv6 error messages: we need to rewrite the headers in the inner packet.
|
||||
* Track in csum_diff the incremental changes to the checksum for the ICMPv4
|
||||
* header. */
|
||||
|
||||
if (rewrite_ipv6_inner(skb, &ip_in_buf, &csum_diff))
|
||||
return -1;
|
||||
|
||||
/* The inner IP header shrinks from 40 (IPv6) to 20 (IPv4) bytes; we need to move
|
||||
* the L4 header and payload. BPF programs don't have an easy way to move a variable
|
||||
* amount of packet data; use bpf_skb_adjust_room() which can add or remove data
|
||||
* inside a packet. It doesn't support arbitrary offsets, but we can use BPF_ADJ_ROOM_NET
|
||||
* to remove the bytes just after the L3 header, and rewrite the ICMP and the inner
|
||||
* IP headers.
|
||||
*/
|
||||
if (bpf_skb_adjust_room(skb,
|
||||
(int) sizeof(struct iphdr) - (int) sizeof(struct ipv6hdr),
|
||||
BPF_ADJ_ROOM_NET,
|
||||
0))
|
||||
return -1;
|
||||
|
||||
*out_length_diff = (int) sizeof(struct iphdr) - (int) sizeof(struct ipv6hdr);
|
||||
|
||||
data_end = SKB_DATA_END(skb);
|
||||
data = SKB_DATA(skb);
|
||||
|
||||
/* Rewrite the ICMPv6 header with the translated ICMPv4 one */
|
||||
icmp = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
|
||||
if (icmp + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
*icmp = icmp_buf;
|
||||
|
||||
/* Rewrite the inner IPv6 header with the translated IPv4 one */
|
||||
ip = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct icmphdr);
|
||||
if (ip + 1 > data_end)
|
||||
return -1;
|
||||
|
||||
*ip = ip_in_buf;
|
||||
|
||||
/* Update the ICMPv4 checksum according to all the changes in headers */
|
||||
update_icmp_checksum(skb,
|
||||
(struct ipv6hdr *) (data + sizeof(struct ethhdr)),
|
||||
&icmp6_buf,
|
||||
icmp,
|
||||
false,
|
||||
false,
|
||||
csum_diff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -666,16 +901,20 @@ clat_handle_v6(struct __sk_buff *skb)
|
|||
int ret = TC_ACT_OK;
|
||||
void *data_end = SKB_DATA_END(skb);
|
||||
void *data = SKB_DATA(skb);
|
||||
__be32 saddr4;
|
||||
struct in6_addr subnet_v6;
|
||||
struct ethhdr *eth;
|
||||
struct iphdr *iph;
|
||||
struct ipv6hdr *ip6h;
|
||||
struct iphdr dst_hdr = {
|
||||
.version = 4,
|
||||
.ihl = 5,
|
||||
.frag_off = bpf_htons(1 << 14), /* set Don't Fragment bit */
|
||||
};
|
||||
struct iphdr *iph;
|
||||
struct iphdr dst_hdr;
|
||||
struct in6_addr subnet_v6;
|
||||
__be32 addr4;
|
||||
int length_diff = 0;
|
||||
|
||||
/*
|
||||
* ip6h: v
|
||||
* ------------------------------------
|
||||
* | Ethernet | IPv6 | ...
|
||||
* ------------------------------------
|
||||
*/
|
||||
|
||||
ip6h = data + sizeof(struct ethhdr);
|
||||
if (ip6h + 1 > data_end)
|
||||
|
|
@ -683,10 +922,8 @@ clat_handle_v6(struct __sk_buff *skb)
|
|||
|
||||
if (!v6addr_equal(&ip6h->daddr, &config.local_v6))
|
||||
goto out;
|
||||
|
||||
if (!v6addr_to_v4(&ip6h->saddr, config.pref64_len, &saddr4, &subnet_v6))
|
||||
if (!v6addr_to_v4(&ip6h->saddr, config.pref64_len, &addr4, &subnet_v6))
|
||||
goto out;
|
||||
|
||||
if (!v6addr_equal(&subnet_v6, &config.pref64))
|
||||
goto out;
|
||||
|
||||
|
|
@ -700,29 +937,38 @@ clat_handle_v6(struct __sk_buff *skb)
|
|||
&& ip6h->nexthdr != IPPROTO_ICMPV6)
|
||||
goto out;
|
||||
|
||||
dst_hdr.daddr = config.local_v4.s_addr;
|
||||
dst_hdr.saddr = saddr4;
|
||||
dst_hdr.protocol = ip6h->nexthdr;
|
||||
dst_hdr.ttl = ip6h->hop_limit;
|
||||
dst_hdr.tos = ip6h->priority << 4 | (ip6h->flow_lbl[0] >> 4);
|
||||
dst_hdr.tot_len = bpf_htons(bpf_ntohs(ip6h->payload_len) + sizeof(struct iphdr));
|
||||
translate_ipv6_header(ip6h, &dst_hdr, addr4, config.local_v4.s_addr);
|
||||
|
||||
switch (dst_hdr.protocol) {
|
||||
case IPPROTO_ICMPV6:
|
||||
if (rewrite_icmpv6(skb))
|
||||
case IPPROTO_ICMP:
|
||||
if (rewrite_icmpv6(skb, &length_diff))
|
||||
goto out;
|
||||
dst_hdr.protocol = IPPROTO_ICMP;
|
||||
break;
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_UDP:
|
||||
update_l4_checksum(skb, ip6h, &dst_hdr, dst_hdr.protocol, false);
|
||||
update_l4_checksum(skb, ip6h, &dst_hdr, false, false, NULL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dst_hdr.check = csum_fold_helper(
|
||||
bpf_csum_diff((__be32 *) &dst_hdr, 0, (__be32 *) &dst_hdr, sizeof(dst_hdr), 0));
|
||||
/* rewrite_icmpv6() can change the payload length when it rewrites the content of
|
||||
* an ICMPv6 error packet. Update the length and the checksum. */
|
||||
if (length_diff != 0) {
|
||||
data = SKB_DATA(skb);
|
||||
data_end = SKB_DATA_END(skb);
|
||||
|
||||
ip6h = data + sizeof(struct ethhdr);
|
||||
if (ip6h + 1 > data_end)
|
||||
goto out;
|
||||
|
||||
dst_hdr.tot_len =
|
||||
bpf_htons(bpf_ntohs(ip6h->payload_len) + length_diff + sizeof(struct iphdr));
|
||||
|
||||
dst_hdr.check = 0;
|
||||
dst_hdr.check = csum_fold_helper(
|
||||
bpf_csum_diff((__be32 *) &dst_hdr, 0, (__be32 *) &dst_hdr, sizeof(struct iphdr), 0));
|
||||
}
|
||||
|
||||
if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IP), 0))
|
||||
goto out;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue