ipv6: no CHECKSUM_PARTIAL on MSG_MORE corked sockets
authorHannes Frederic Sowa <hannes@stressinduktion.org>
Tue, 27 Oct 2015 21:40:41 +0000 (22:40 +0100)
committerDavid S. Miller <davem@davemloft.net>
Sun, 1 Nov 2015 17:01:27 +0000 (12:01 -0500)
We cannot reliable calculate packet size on MSG_MORE corked sockets
and thus cannot decide if they are going to be fragmented later on,
so better not use CHECKSUM_PARTIAL in the first place.

The IPv6 code also intended to protect and not use CHECKSUM_PARTIAL in
the existence of IPv6 extension headers, but the condition was wrong. Fix
it up, too. Also the condition to check whether the packet fits into
one fragment was wrong and has been corrected.

Fixes: commit 32dce968dd987 ("ipv6: Allow for partial checksums on non-ufo packets")
See-also: commit 72e843bb09d45 ("ipv6: ip6_fragment() should check CHECKSUM_PARTIAL")
Cc: Eric Dumazet <edumazet@google.com>
Cc: Vlad Yasevich <vyasevich@gmail.com>
Cc: Benjamin Coddington <bcodding@redhat.com>
Cc: Tom Herbert <tom@herbertland.com>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv6/ip6_output.c

index 3c8e9d8795cdbfff8f46408614e05853fae5cafb..81ac5a970e433f3acd86f8ce64f8e2f1b48e54e3 100644 (file)
@@ -1270,6 +1270,7 @@ static int __ip6_append_data(struct sock *sk,
        struct rt6_info *rt = (struct rt6_info *)cork->dst;
        struct ipv6_txoptions *opt = v6_cork->opt;
        int csummode = CHECKSUM_NONE;
+       unsigned int maxnonfragsize, headersize;
 
        skb = skb_peek_tail(queue);
        if (!skb) {
@@ -1287,38 +1288,43 @@ static int __ip6_append_data(struct sock *sk,
        maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen -
                     sizeof(struct frag_hdr);
 
-       if (mtu <= sizeof(struct ipv6hdr) + IPV6_MAXPLEN) {
-               unsigned int maxnonfragsize, headersize;
-
-               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))
-                       maxnonfragsize = sizeof(struct ipv6hdr) + IPV6_MAXPLEN;
-               else
-                       maxnonfragsize = mtu;
+       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 (cork->length + length > mtu - headersize && dontfrag &&
+           (sk->sk_protocol == IPPROTO_UDP ||
+            sk->sk_protocol == IPPROTO_RAW)) {
+               ipv6_local_rxpmtu(sk, fl6, mtu - headersize +
+                               sizeof(struct ipv6hdr));
+               goto emsgsize;
+       }
 
-               /* dontfrag active */
-               if ((cork->length + length > mtu - headersize) && dontfrag &&
-                   (sk->sk_protocol == IPPROTO_UDP ||
-                    sk->sk_protocol == IPPROTO_RAW)) {
-                       ipv6_local_rxpmtu(sk, fl6, mtu - headersize +
-                                                  sizeof(struct ipv6hdr));
-                       goto emsgsize;
-               }
+       if (ip6_sk_ignore_df(sk))
+               maxnonfragsize = sizeof(struct ipv6hdr) + IPV6_MAXPLEN;
+       else
+               maxnonfragsize = mtu;
 
-               if (cork->length + length > maxnonfragsize - headersize) {
+       if (cork->length + length > maxnonfragsize - headersize) {
 emsgsize:
-                       ipv6_local_error(sk, EMSGSIZE, fl6,
-                                        mtu - headersize +
-                                        sizeof(struct ipv6hdr));
-                       return -EMSGSIZE;
-               }
+               ipv6_local_error(sk, EMSGSIZE, fl6,
+                                mtu - headersize +
+                                sizeof(struct ipv6hdr));
+               return -EMSGSIZE;
        }
 
+       /* CHECKSUM_PARTIAL only with no extension headers and when
+        * we are not going to fragment
+        */
+       if (transhdrlen && sk->sk_protocol == IPPROTO_UDP &&
+           headersize == sizeof(struct ipv6hdr) &&
+           length < mtu - headersize &&
+           !(flags & MSG_MORE) &&
+           rt->dst.dev->features & NETIF_F_V6_CSUM)
+               csummode = CHECKSUM_PARTIAL;
+
        if (sk->sk_type == SOCK_DGRAM || sk->sk_type == SOCK_RAW) {
                sock_tx_timestamp(sk, &tx_flags);
                if (tx_flags & SKBTX_ANY_SW_TSTAMP &&
@@ -1326,16 +1332,6 @@ emsgsize:
                        tskey = sk->sk_tskey++;
        }
 
-       /* If this is the first and only packet and device
-        * supports checksum offloading, let's use it.
-        * Use transhdrlen, same as IPv4, because partial
-        * sums only work when transhdrlen is set.
-        */
-       if (transhdrlen && sk->sk_protocol == IPPROTO_UDP &&
-           length + fragheaderlen < mtu &&
-           rt->dst.dev->features & NETIF_F_V6_CSUM &&
-           !exthdrlen)
-               csummode = CHECKSUM_PARTIAL;
        /*
         * Let's try using as much space as possible.
         * Use MTU if total length of the message fits into the MTU.