History

net: fix possible wrong checksum generation

Pravin Shelar mentioned that GSO could potentially generate wrong TX checksum if skb has fragments that are overwritten by the user between the checksum computation and transmit. He suggested to linearize skbs but this extra copy can be avoided for normal tcp skbs cooked by tcp_sendmsg(). This patch introduces a new SKB_GSO_SHARED_FRAG flag, set in skb_shinfo(skb)->gso_type if at least one frag can be modified by the user. Typical sources of such possible overwrites are {vm}splice(), sendfile(), and macvtap/tun/virtio_net drivers. Tested: $ netperf -H 7.7.8.84 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 7.7.8.84 () port 0 AF_INET Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 87380 16384 16384 10.00 3959.52 $ netperf -H 7.7.8.84 -t TCP_SENDFILE TCP SENDFILE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 7.7.8.84 () port 0 AF_INET Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 87380 16384 16384 10.00 3216.80 Performance of the SENDFILE is impacted by the extra allocation and copy, and because we use order-0 pages, while the TCP_STREAM uses bigger pages.

Eric Dumazet committed 13 years ago

Commit cef401d1 parent 615500213 files · +65 −15Browse files

Unified · Split

13 files
  1. drivers/net/macvtap.c +2 −1
  2. drivers/net/tun.c +8 −4
  3. drivers/net/virtio_net.c +8 −4
  4. include/linux/skbuff.h +19 −0
  5. net/core/dev.c +9 −0
  6. net/core/skbuff.c +4 −0
  7. net/ipv4/af_inet.c +1 −0
  8. net/ipv4/ip_gre.c +3 −1
  9. net/ipv4/ipip.c +3 −1
  10. net/ipv4/tcp.c +3 −0
  11. net/ipv4/tcp_input.c +2 −2
  12. net/ipv4/tcp_output.c +2 −2
  13. net/ipv6/ip6_offload.c +1 −0
drivers/net/macvtap.c+2−1
@@ -543,6 +543,7 @@ static int zerocopy_sg_from_iovec(struct sk_buff *skb, const struct iovec *from, · 542 unmodified lines
543 skb->data_len += len;543 skb->data_len += len;
544 skb->len += len;544 skb->len += len;
545 skb->truesize += truesize;545 skb->truesize += truesize;
546 skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
546 atomic_add(truesize, &skb->sk->sk_wmem_alloc);547 atomic_add(truesize, &skb->sk->sk_wmem_alloc);
547 while (len) {548 while (len) {
548 int off = base & ~PAGE_MASK;549 int off = base & ~PAGE_MASK;
@@ -598,7 +599,7 @@ static int macvtap_skb_from_vnet_hdr(struct sk_buff *skb, · 49 unmodified lines
598 599
599 if (vnet_hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {600 if (vnet_hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
600 skb_shinfo(skb)->gso_size = vnet_hdr->gso_size;601 skb_shinfo(skb)->gso_size = vnet_hdr->gso_size;
601 skb_shinfo(skb)->gso_type = gso_type;602 skb_shinfo(skb)->gso_type |= gso_type;
602 603
603 /* Header must be checked, and gso_segs computed. */604 /* Header must be checked, and gso_segs computed. */
604 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;605 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;
drivers/net/tun.c+8−4
@@ -1005,6 +1005,7 @@ static int zerocopy_sg_from_iovec(struct sk_buff *skb, const struct iovec *from, · 1004 unmodified lines
1005 skb->data_len += len;1005 skb->data_len += len;
1006 skb->len += len;1006 skb->len += len;
1007 skb->truesize += truesize;1007 skb->truesize += truesize;
1008 skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
1008 atomic_add(truesize, &skb->sk->sk_wmem_alloc);1009 atomic_add(truesize, &skb->sk->sk_wmem_alloc);
1009 while (len) {1010 while (len) {
1010 int off = base & ~PAGE_MASK;1011 int off = base & ~PAGE_MASK;
@@ -1150,16 +1151,18 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, · 139 unmodified lines
1150 }1151 }
1151 1152
1152 if (gso.gso_type != VIRTIO_NET_HDR_GSO_NONE) {1153 if (gso.gso_type != VIRTIO_NET_HDR_GSO_NONE) {
1154 unsigned short gso_type = 0;
1155
1153 pr_debug("GSO!\n");1156 pr_debug("GSO!\n");
1154 switch (gso.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {1157 switch (gso.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
1155 case VIRTIO_NET_HDR_GSO_TCPV4:1158 case VIRTIO_NET_HDR_GSO_TCPV4:
1156 skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;1159 gso_type = SKB_GSO_TCPV4;
1157 break;1160 break;
1158 case VIRTIO_NET_HDR_GSO_TCPV6:1161 case VIRTIO_NET_HDR_GSO_TCPV6:
1159 skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;1162 gso_type = SKB_GSO_TCPV6;
1160 break;1163 break;
1161 case VIRTIO_NET_HDR_GSO_UDP:1164 case VIRTIO_NET_HDR_GSO_UDP:
1162 skb_shinfo(skb)->gso_type = SKB_GSO_UDP;1165 gso_type = SKB_GSO_UDP;
1163 break;1166 break;
1164 default:1167 default:
1165 tun->dev->stats.rx_frame_errors++;1168 tun->dev->stats.rx_frame_errors++;
@@ -1168,9 +1171,10 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, · 2 unmodified lines
1168 }1171 }
1169 1172
1170 if (gso.gso_type & VIRTIO_NET_HDR_GSO_ECN)1173 if (gso.gso_type & VIRTIO_NET_HDR_GSO_ECN)
1171 skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN;1174 gso_type |= SKB_GSO_TCP_ECN;
1172 1175
1173 skb_shinfo(skb)->gso_size = gso.gso_size;1176 skb_shinfo(skb)->gso_size = gso.gso_size;
1177 skb_shinfo(skb)->gso_type |= gso_type;
1174 if (skb_shinfo(skb)->gso_size == 0) {1178 if (skb_shinfo(skb)->gso_size == 0) {
1175 tun->dev->stats.rx_frame_errors++;1179 tun->dev->stats.rx_frame_errors++;
1176 kfree_skb(skb);1180 kfree_skb(skb);
drivers/net/virtio_net.c+8−4
@@ -220,6 +220,7 @@ static void set_skb_frag(struct sk_buff *skb, struct page *page, · 219 unmodified lines
220 skb->len += size;220 skb->len += size;
221 skb->truesize += PAGE_SIZE;221 skb->truesize += PAGE_SIZE;
222 skb_shinfo(skb)->nr_frags++;222 skb_shinfo(skb)->nr_frags++;
223 skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
223 *len -= size;224 *len -= size;
224}225}
225 226
@@ -379,16 +380,18 @@ static void receive_buf(struct receive_queue *rq, void *buf, unsigned int len) · 153 unmodified lines
379 ntohs(skb->protocol), skb->len, skb->pkt_type);380 ntohs(skb->protocol), skb->len, skb->pkt_type);
380 381
381 if (hdr->hdr.gso_type != VIRTIO_NET_HDR_GSO_NONE) {382 if (hdr->hdr.gso_type != VIRTIO_NET_HDR_GSO_NONE) {
383 unsigned short gso_type = 0;
384
382 pr_debug("GSO!\n");385 pr_debug("GSO!\n");
383 switch (hdr->hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {386 switch (hdr->hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
384 case VIRTIO_NET_HDR_GSO_TCPV4:387 case VIRTIO_NET_HDR_GSO_TCPV4:
385 skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;388 gso_type = SKB_GSO_TCPV4;
386 break;389 break;
387 case VIRTIO_NET_HDR_GSO_UDP:390 case VIRTIO_NET_HDR_GSO_UDP:
388 skb_shinfo(skb)->gso_type = SKB_GSO_UDP;391 gso_type = SKB_GSO_UDP;
389 break;392 break;
390 case VIRTIO_NET_HDR_GSO_TCPV6:393 case VIRTIO_NET_HDR_GSO_TCPV6:
391 skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;394 gso_type = SKB_GSO_TCPV6;
392 break;395 break;
393 default:396 default:
394 net_warn_ratelimited("%s: bad gso type %u.\n",397 net_warn_ratelimited("%s: bad gso type %u.\n",
@@ -397,7 +400,7 @@ static void receive_buf(struct receive_queue *rq, void *buf, unsigned int len) · 2 unmodified lines
397 }400 }
398 401
399 if (hdr->hdr.gso_type & VIRTIO_NET_HDR_GSO_ECN)402 if (hdr->hdr.gso_type & VIRTIO_NET_HDR_GSO_ECN)
400 skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN;403 gso_type |= SKB_GSO_TCP_ECN;
401 404
402 skb_shinfo(skb)->gso_size = hdr->hdr.gso_size;405 skb_shinfo(skb)->gso_size = hdr->hdr.gso_size;
403 if (skb_shinfo(skb)->gso_size == 0) {406 if (skb_shinfo(skb)->gso_size == 0) {
@@ -405,6 +408,7 @@ static void receive_buf(struct receive_queue *rq, void *buf, unsigned int len) · 1 unmodified line
405 goto frame_err;408 goto frame_err;
406 }409 }
407 410
411 skb_shinfo(skb)->gso_type |= gso_type;
408 /* Header must be checked, and gso_segs computed. */412 /* Header must be checked, and gso_segs computed. */
409 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;413 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;
410 skb_shinfo(skb)->gso_segs = 0;414 skb_shinfo(skb)->gso_segs = 0;
include/linux/skbuff.h+19−0
@@ -307,6 +307,13 @@ enum { · 306 unmodified lines
307 SKB_GSO_TCPV6 = 1 << 4,307 SKB_GSO_TCPV6 = 1 << 4,
308 308
309 SKB_GSO_FCOE = 1 << 5,309 SKB_GSO_FCOE = 1 << 5,
310
311 /* This indicates at least one fragment might be overwritten
312 * (as in vmsplice(), sendfile() ...)
313 * If we need to compute a TX checksum, we'll need to copy
314 * all frags to avoid possible bad checksum
315 */
316 SKB_GSO_SHARED_FRAG = 1 << 6,
310};317};
311 318
312#if BITS_PER_LONG > 32319#if BITS_PER_LONG > 32
@@ -2200,6 +2207,18 @@ static inline int skb_linearize(struct sk_buff *skb) · 1887 unmodified lines
2200 return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;2207 return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
2201}2208}
2202 2209
2210/**
2211 * skb_has_shared_frag - can any frag be overwritten
2212 * @skb: buffer to test
2213 *
2214 * Return true if the skb has at least one frag that might be modified
2215 * by an external entity (as in vmsplice()/sendfile())
2216 */
2217static inline bool skb_has_shared_frag(const struct sk_buff *skb)
2218{
2219 return skb_shinfo(skb)->gso_type & SKB_GSO_SHARED_FRAG;
2220}
2221
2203/**2222/**
2204 * skb_linearize_cow - make sure skb is linear and writable2223 * skb_linearize_cow - make sure skb is linear and writable
2205 * @skb: buffer to process2224 * @skb: buffer to process
net/core/dev.c+9−0
@@ -2271,6 +2271,15 @@ int skb_checksum_help(struct sk_buff *skb) · 2270 unmodified lines
2271 return -EINVAL;2271 return -EINVAL;
2272 }2272 }
2273 2273
2274 /* Before computing a checksum, we should make sure no frag could
2275 * be modified by an external entity : checksum could be wrong.
2276 */
2277 if (skb_has_shared_frag(skb)) {
2278 ret = __skb_linearize(skb);
2279 if (ret)
2280 goto out;
2281 }
2282
2274 offset = skb_checksum_start_offset(skb);2283 offset = skb_checksum_start_offset(skb);
2275 BUG_ON(offset >= skb_headlen(skb));2284 BUG_ON(offset >= skb_headlen(skb));
2276 csum = skb_checksum(skb, offset, skb->len - offset, 0);2285 csum = skb_checksum(skb, offset, skb->len - offset, 0);
net/core/skbuff.c+4−0
@@ -2340,6 +2340,8 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len) · 2339 unmodified lines
2340{2340{
2341 int pos = skb_headlen(skb);2341 int pos = skb_headlen(skb);
2342 2342
2343 skb_shinfo(skb1)->gso_type = skb_shinfo(skb)->gso_type;
2344
2343 if (len < pos) /* Split line is inside header. */2345 if (len < pos) /* Split line is inside header. */
2344 skb_split_inside_header(skb, skb1, len, pos);2346 skb_split_inside_header(skb, skb1, len, pos);
2345 else /* Second chunk has no header, nothing to copy. */2347 else /* Second chunk has no header, nothing to copy. */
@@ -2845,6 +2847,8 @@ struct sk_buff *skb_segment(struct sk_buff *skb, netdev_features_t features) · 499 unmodified lines
2845 skb_copy_from_linear_data_offset(skb, offset,2847 skb_copy_from_linear_data_offset(skb, offset,
2846 skb_put(nskb, hsize), hsize);2848 skb_put(nskb, hsize), hsize);
2847 2849
2850 skb_shinfo(nskb)->gso_type = skb_shinfo(skb)->gso_type;
2851
2848 while (pos < offset + len && i < nfrags) {2852 while (pos < offset + len && i < nfrags) {
2849 *frag = skb_shinfo(skb)->frags[i];2853 *frag = skb_shinfo(skb)->frags[i];
2850 __skb_frag_ref(frag);2854 __skb_frag_ref(frag);
net/ipv4/af_inet.c+1−0
@@ -1306,6 +1306,7 @@ static struct sk_buff *inet_gso_segment(struct sk_buff *skb, · 1305 unmodified lines
1306 SKB_GSO_UDP |1306 SKB_GSO_UDP |
1307 SKB_GSO_DODGY |1307 SKB_GSO_DODGY |
1308 SKB_GSO_TCP_ECN |1308 SKB_GSO_TCP_ECN |
1309 SKB_GSO_SHARED_FRAG |
1309 0)))1310 0)))
1310 goto out;1311 goto out;
1311 1312
net/ipv4/ip_gre.c+3−1
@@ -738,7 +738,7 @@ drop: · 737 unmodified lines
738static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)738static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
739{739{
740 struct ip_tunnel *tunnel = netdev_priv(dev);740 struct ip_tunnel *tunnel = netdev_priv(dev);
741 const struct iphdr *old_iph = ip_hdr(skb);741 const struct iphdr *old_iph;
742 const struct iphdr *tiph;742 const struct iphdr *tiph;
743 struct flowi4 fl4;743 struct flowi4 fl4;
744 u8 tos;744 u8 tos;
@@ -756,6 +756,8 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev · 11 unmodified lines
756 skb_checksum_help(skb))756 skb_checksum_help(skb))
757 goto tx_error;757 goto tx_error;
758 758
759 old_iph = ip_hdr(skb);
760
759 if (dev->type == ARPHRD_ETHER)761 if (dev->type == ARPHRD_ETHER)
760 IPCB(skb)->flags = 0;762 IPCB(skb)->flags = 0;
761 763
net/ipv4/ipip.c+3−1
@@ -472,7 +472,7 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) · 471 unmodified lines
472 __be16 df = tiph->frag_off;472 __be16 df = tiph->frag_off;
473 struct rtable *rt; /* Route to the other host */473 struct rtable *rt; /* Route to the other host */
474 struct net_device *tdev; /* Device to other host */474 struct net_device *tdev; /* Device to other host */
475 const struct iphdr *old_iph = ip_hdr(skb);475 const struct iphdr *old_iph;
476 struct iphdr *iph; /* Our new IP header */476 struct iphdr *iph; /* Our new IP header */
477 unsigned int max_headroom; /* The extra header space needed */477 unsigned int max_headroom; /* The extra header space needed */
478 __be32 dst = tiph->daddr;478 __be32 dst = tiph->daddr;
@@ -486,6 +486,8 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) · 7 unmodified lines
486 skb_checksum_help(skb))486 skb_checksum_help(skb))
487 goto tx_error;487 goto tx_error;
488 488
489 old_iph = ip_hdr(skb);
490
489 if (tos & 1)491 if (tos & 1)
490 tos = old_iph->tos;492 tos = old_iph->tos;
491 493
net/ipv4/tcp.c+3−0
@@ -896,6 +896,8 @@ new_segment: · 895 unmodified lines
896 skb_fill_page_desc(skb, i, page, offset, copy);896 skb_fill_page_desc(skb, i, page, offset, copy);
897 }897 }
898 898
899 skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
900
899 skb->len += copy;901 skb->len += copy;
900 skb->data_len += copy;902 skb->data_len += copy;
901 skb->truesize += copy;903 skb->truesize += copy;
@@ -3032,6 +3034,7 @@ struct sk_buff *tcp_tso_segment(struct sk_buff *skb, · 2130 unmodified lines
3032 SKB_GSO_DODGY |3034 SKB_GSO_DODGY |
3033 SKB_GSO_TCP_ECN |3035 SKB_GSO_TCP_ECN |
3034 SKB_GSO_TCPV6 |3036 SKB_GSO_TCPV6 |
3037 SKB_GSO_SHARED_FRAG |
3035 0) ||3038 0) ||
3036 !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))3039 !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
3037 goto out;3040 goto out;
net/ipv4/tcp_input.c+2−2
@@ -1240,13 +1240,13 @@ static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *skb, · 1239 unmodified lines
1240 */1240 */
1241 if (!skb_shinfo(prev)->gso_size) {1241 if (!skb_shinfo(prev)->gso_size) {
1242 skb_shinfo(prev)->gso_size = mss;1242 skb_shinfo(prev)->gso_size = mss;
1243 skb_shinfo(prev)->gso_type = sk->sk_gso_type;1243 skb_shinfo(prev)->gso_type |= sk->sk_gso_type;
1244 }1244 }
1245 1245
1246 /* CHECKME: To clear or not to clear? Mimics normal skb currently */1246 /* CHECKME: To clear or not to clear? Mimics normal skb currently */
1247 if (skb_shinfo(skb)->gso_segs <= 1) {1247 if (skb_shinfo(skb)->gso_segs <= 1) {
1248 skb_shinfo(skb)->gso_size = 0;1248 skb_shinfo(skb)->gso_size = 0;
1249 skb_shinfo(skb)->gso_type = 0;1249 skb_shinfo(skb)->gso_type &= SKB_GSO_SHARED_FRAG;
1250 }1250 }
1251 1251
1252 /* Difference in this won't matter, both ACKed by the same cumul. ACK */1252 /* Difference in this won't matter, both ACKed by the same cumul. ACK */
net/ipv4/tcp_output.c+2−2
@@ -1133,6 +1133,7 @@ static void tcp_queue_skb(struct sock *sk, struct sk_buff *skb) · 1132 unmodified lines
1133static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb,1133static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb,
1134 unsigned int mss_now)1134 unsigned int mss_now)
1135{1135{
1136 skb_shinfo(skb)->gso_type &= SKB_GSO_SHARED_FRAG;
1136 if (skb->len <= mss_now || !sk_can_gso(sk) ||1137 if (skb->len <= mss_now || !sk_can_gso(sk) ||
1137 skb->ip_summed == CHECKSUM_NONE) {1138 skb->ip_summed == CHECKSUM_NONE) {
1138 /* Avoid the costly divide in the normal1139 /* Avoid the costly divide in the normal
@@ -1140,11 +1141,10 @@ static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb, · 1 unmodified line
1140 */1141 */
1141 skb_shinfo(skb)->gso_segs = 1;1142 skb_shinfo(skb)->gso_segs = 1;
1142 skb_shinfo(skb)->gso_size = 0;1143 skb_shinfo(skb)->gso_size = 0;
1143 skb_shinfo(skb)->gso_type = 0;
1144 } else {1144 } else {
1145 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss_now);1145 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss_now);
1146 skb_shinfo(skb)->gso_size = mss_now;1146 skb_shinfo(skb)->gso_size = mss_now;
1147 skb_shinfo(skb)->gso_type = sk->sk_gso_type;1147 skb_shinfo(skb)->gso_type |= sk->sk_gso_type;
1148 }1148 }
1149}1149}
1150 1150
net/ipv6/ip6_offload.c+1−0
@@ -100,6 +100,7 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, · 99 unmodified lines
100 SKB_GSO_DODGY |100 SKB_GSO_DODGY |
101 SKB_GSO_TCP_ECN |101 SKB_GSO_TCP_ECN |
102 SKB_GSO_TCPV6 |102 SKB_GSO_TCPV6 |
103 SKB_GSO_SHARED_FRAG |
103 0)))104 0)))
104 goto out;105 goto out;
105 106

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t