Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv4: ipv6: better estimate tunnel header cut for correct ufo handling

Currently the UFO fragmentation process does not correctly handle inner
UDP frames.

(The following tcpdumps are captured on the parent interface with ufo
disabled while tunnel has ufo enabled, 2000 bytes payload, mtu 1280,
both sit device):

IPv6:
16:39:10.031613 IP (tos 0x0, ttl 64, id 3208, offset 0, flags [DF], proto IPv6 (41), length 1300)
192.168.122.151 > 1.1.1.1: IP6 (hlim 64, next-header Fragment (44) payload length: 1240) 2001::1 > 2001::8: frag (0x00000001:0|1232) 44883 > distinct: UDP, length 2000
16:39:10.031709 IP (tos 0x0, ttl 64, id 3209, offset 0, flags [DF], proto IPv6 (41), length 844)
192.168.122.151 > 1.1.1.1: IP6 (hlim 64, next-header Fragment (44) payload length: 784) 2001::1 > 2001::8: frag (0x00000001:0|776) 58979 > 46366: UDP, length 5471

We can see that fragmentation header offset is not correctly updated.
(fragmentation id handling is corrected by 916e4cf46d0204 ("ipv6: reuse
ip6_frag_id from ip6_ufo_append_data")).

IPv4:
16:39:57.737761 IP (tos 0x0, ttl 64, id 3209, offset 0, flags [DF], proto IPIP (4), length 1296)
192.168.122.151 > 1.1.1.1: IP (tos 0x0, ttl 64, id 57034, offset 0, flags [none], proto UDP (17), length 1276)
192.168.99.1.35961 > 192.168.99.2.distinct: UDP, length 2000
16:39:57.738028 IP (tos 0x0, ttl 64, id 3210, offset 0, flags [DF], proto IPIP (4), length 792)
192.168.122.151 > 1.1.1.1: IP (tos 0x0, ttl 64, id 57035, offset 0, flags [none], proto UDP (17), length 772)
192.168.99.1.13531 > 192.168.99.2.20653: UDP, length 51109

In this case fragmentation id is incremented and offset is not updated.

First, I aligned inet_gso_segment and ipv6_gso_segment:
* align naming of flags
* ipv6_gso_segment: setting skb->encapsulation is unnecessary, as we
always ensure that the state of this flag is left untouched when
returning from upper gso segmenation function
* ipv6_gso_segment: move skb_reset_inner_headers below updating the
fragmentation header data, we don't care for updating fragmentation
header data
* remove currently unneeded comment indicating skb->encapsulation might
get changed by upper gso_segment callback (gre and udp-tunnel reset
encapsulation after segmentation on each fragment)

If we encounter an IPIP or SIT gso skb we now check for the protocol ==
IPPROTO_UDP and that we at least have already traversed another ip(6)
protocol header.

The reason why we have to special case GSO_IPIP and GSO_SIT is that
we reset skb->encapsulation to 0 while skb_mac_gso_segment the inner
protocol of GSO_UDP_TUNNEL or GSO_GRE packets.

Reported-by: Wolfgang Walter <linux@stwm.de>
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Cc: Tom Herbert <therbert@google.com>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Hannes Frederic Sowa and committed by
David S. Miller
91a48a2e 169a1d85

+17 -10
+5 -2
net/ipv4/af_inet.c
··· 1296 1296 1297 1297 segs = ERR_PTR(-EPROTONOSUPPORT); 1298 1298 1299 - /* Note : following gso_segment() might change skb->encapsulation */ 1300 - udpfrag = !skb->encapsulation && proto == IPPROTO_UDP; 1299 + if (skb->encapsulation && 1300 + skb_shinfo(skb)->gso_type & (SKB_GSO_SIT|SKB_GSO_IPIP)) 1301 + udpfrag = proto == IPPROTO_UDP && encap; 1302 + else 1303 + udpfrag = proto == IPPROTO_UDP && !skb->encapsulation; 1301 1304 1302 1305 ops = rcu_dereference(inet_offloads[proto]); 1303 1306 if (likely(ops && ops->callbacks.gso_segment))
+12 -8
net/ipv6/ip6_offload.c
··· 89 89 unsigned int unfrag_ip6hlen; 90 90 u8 *prevhdr; 91 91 int offset = 0; 92 - bool tunnel; 92 + bool encap, udpfrag; 93 93 int nhoff; 94 94 95 95 if (unlikely(skb_shinfo(skb)->gso_type & ··· 110 110 if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h)))) 111 111 goto out; 112 112 113 - tunnel = SKB_GSO_CB(skb)->encap_level > 0; 114 - if (tunnel) 113 + encap = SKB_GSO_CB(skb)->encap_level > 0; 114 + if (encap) 115 115 features = skb->dev->hw_enc_features & netif_skb_features(skb); 116 116 SKB_GSO_CB(skb)->encap_level += sizeof(*ipv6h); 117 117 ··· 120 120 segs = ERR_PTR(-EPROTONOSUPPORT); 121 121 122 122 proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr); 123 + 124 + if (skb->encapsulation && 125 + skb_shinfo(skb)->gso_type & (SKB_GSO_SIT|SKB_GSO_IPIP)) 126 + udpfrag = proto == IPPROTO_UDP && encap; 127 + else 128 + udpfrag = proto == IPPROTO_UDP && !skb->encapsulation; 123 129 124 130 ops = rcu_dereference(inet6_offloads[proto]); 125 131 if (likely(ops && ops->callbacks.gso_segment)) { ··· 139 133 for (skb = segs; skb; skb = skb->next) { 140 134 ipv6h = (struct ipv6hdr *)(skb_mac_header(skb) + nhoff); 141 135 ipv6h->payload_len = htons(skb->len - nhoff - sizeof(*ipv6h)); 142 - if (tunnel) { 143 - skb_reset_inner_headers(skb); 144 - skb->encapsulation = 1; 145 - } 146 136 skb->network_header = (u8 *)ipv6h - skb->head; 147 137 148 - if (!tunnel && proto == IPPROTO_UDP) { 138 + if (udpfrag) { 149 139 unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr); 150 140 fptr = (struct frag_hdr *)((u8 *)ipv6h + unfrag_ip6hlen); 151 141 fptr->frag_off = htons(offset); ··· 150 148 offset += (ntohs(ipv6h->payload_len) - 151 149 sizeof(struct frag_hdr)); 152 150 } 151 + if (encap) 152 + skb_reset_inner_headers(skb); 153 153 } 154 154 155 155 out: