Commit 72a2cad7 authored by Michal Kubeček's avatar Michal Kubeček Committed by Ben Hutchings

net: account for current skb length when deciding about UFO

commit a5cb659b upstream.

Our customer encountered stuck NFS writes for blocks starting at specific
offsets w.r.t. page boundary caused by networking stack sending packets via
UFO enabled device with wrong checksum. The problem can be reproduced by
composing a long UDP datagram from multiple parts using MSG_MORE flag:

  sendto(sd, buff, 1000, MSG_MORE, ...);
  sendto(sd, buff, 1000, MSG_MORE, ...);
  sendto(sd, buff, 3000, 0, ...);

Assume this packet is to be routed via a device with MTU 1500 and
NETIF_F_UFO enabled. When second sendto() gets into __ip_append_data(),
this condition is tested (among others) to decide whether to call
ip_ufo_append_data():

  ((length + fragheaderlen) > mtu) || (skb && skb_is_gso(skb))

At the moment, we already have skb with 1028 bytes of data which is not
marked for GSO so that the test is false (fragheaderlen is usually 20).
Thus we append second 1000 bytes to this skb without invoking UFO. Third
sendto(), however, has sufficient length to trigger the UFO path so that we
end up with non-UFO skb followed by a UFO one. Later on, udp_send_skb()
uses udp_csum() to calculate the checksum but that assumes all fragments
have correct checksum in skb->csum which is not true for UFO fragments.

When checking against MTU, we need to add skb->len to length of new segment
if we already have a partially filled skb and fragheaderlen only if there
isn't one.

In the IPv6 case, skb can only be null if this is the first segment so that
we have to use headersize (length of the first IPv6 header) rather than
fragheaderlen (length of IPv6 header of further fragments) for skb == NULL.

Fixes: e89e9cf5 ("[IPv4/IPv6]: UFO Scatter-gather approach")
Fixes: e4c5e13a ("ipv6: Should use consistent conditional judgement for
	ip6 fragment between __ip6_append_data and ip6_finish_output")
Signed-off-by: default avatarMichal Kubecek <mkubecek@suse.cz>
Acked-by: default avatarVlad Yasevich <vyasevic@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
[bwh: Backported to 3.16:
 - Move headersize out of the if-statement in ip6_append_data() so it can be
   used for this
 - Adjust context to apply after "udp: consistently apply ufo or fragmentation"]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent c52f61de
...@@ -886,7 +886,7 @@ static int __ip_append_data(struct sock *sk, ...@@ -886,7 +886,7 @@ static int __ip_append_data(struct sock *sk,
cork->length += length; cork->length += length;
if ((skb && skb_is_gso(skb)) || if ((skb && skb_is_gso(skb)) ||
(((length + fragheaderlen) > mtu) && (((length + (skb ? skb->len : fragheaderlen)) > mtu) &&
(skb_queue_len(queue) <= 1) && (skb_queue_len(queue) <= 1) &&
(sk->sk_protocol == IPPROTO_UDP) && (sk->sk_protocol == IPPROTO_UDP) &&
(rt->dst.dev->features & NETIF_F_UFO) && !dst_xfrm(&rt->dst) && (rt->dst.dev->features & NETIF_F_UFO) && !dst_xfrm(&rt->dst) &&
......
...@@ -1146,7 +1146,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to, ...@@ -1146,7 +1146,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_pinfo *np = inet6_sk(sk);
struct inet_cork *cork; struct inet_cork *cork;
struct sk_buff *skb, *skb_prev = NULL; struct sk_buff *skb, *skb_prev = NULL;
unsigned int maxfraglen, fragheaderlen, mtu, orig_mtu; unsigned int maxfraglen, fragheaderlen, mtu, orig_mtu, headersize;
int exthdrlen; int exthdrlen;
int dst_exthdrlen; int dst_exthdrlen;
int hh_len; int hh_len;
...@@ -1236,15 +1236,14 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to, ...@@ -1236,15 +1236,14 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
(opt ? opt->opt_nflen : 0); (opt ? opt->opt_nflen : 0);
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen - maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen -
sizeof(struct frag_hdr); sizeof(struct frag_hdr);
headersize = sizeof(struct ipv6hdr) +
(opt ? opt->opt_flen + opt->opt_nflen : 0) +
(dst_allfrag(&rt->dst) ?
sizeof(struct frag_hdr) : 0) +
rt->rt6i_nfheader_len;
if (mtu <= sizeof(struct ipv6hdr) + IPV6_MAXPLEN) { if (mtu <= sizeof(struct ipv6hdr) + IPV6_MAXPLEN) {
unsigned int maxnonfragsize, headersize; unsigned int maxnonfragsize;
headersize = sizeof(struct ipv6hdr) +
(opt ? opt->opt_flen + opt->opt_nflen : 0) +
(dst_allfrag(&rt->dst) ?
sizeof(struct frag_hdr) : 0) +
rt->rt6i_nfheader_len;
if (ip6_sk_ignore_df(sk)) if (ip6_sk_ignore_df(sk))
maxnonfragsize = sizeof(struct ipv6hdr) + IPV6_MAXPLEN; maxnonfragsize = sizeof(struct ipv6hdr) + IPV6_MAXPLEN;
...@@ -1292,7 +1291,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to, ...@@ -1292,7 +1291,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
skb = skb_peek_tail(&sk->sk_write_queue); skb = skb_peek_tail(&sk->sk_write_queue);
cork->length += length; cork->length += length;
if ((skb && skb_is_gso(skb)) || if ((skb && skb_is_gso(skb)) ||
(((length + fragheaderlen) > mtu) && (((length + (skb ? skb->len : headersize)) > mtu) &&
(skb_queue_len(&sk->sk_write_queue) <= 1) && (skb_queue_len(&sk->sk_write_queue) <= 1) &&
(sk->sk_protocol == IPPROTO_UDP) && (sk->sk_protocol == IPPROTO_UDP) &&
(rt->dst.dev->features & NETIF_F_UFO) && !dst_xfrm(&rt->dst) && (rt->dst.dev->features & NETIF_F_UFO) && !dst_xfrm(&rt->dst) &&
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment