]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/blobdiff - net/ipv6/ip6_output.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next
[mirror_ubuntu-focal-kernel.git] / net / ipv6 / ip6_output.c
index cec49e137dbbd13b9d4ceb86049a66a24aa1ede3..7f4493080df68d010e3ec656fc651bf30a402bdf 100644 (file)
@@ -940,16 +940,21 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
         * that's why we try it again later.
         */
        if (ipv6_addr_any(&fl6->saddr) && (!*dst || !(*dst)->error)) {
+               struct fib6_info *from;
                struct rt6_info *rt;
                bool had_dst = *dst != NULL;
 
                if (!had_dst)
                        *dst = ip6_route_output(net, sk, fl6);
                rt = (*dst)->error ? NULL : (struct rt6_info *)*dst;
-               err = ip6_route_get_saddr(net, rt ? rt->from : NULL,
-                                         &fl6->daddr,
+
+               rcu_read_lock();
+               from = rt ? rcu_dereference(rt->from) : NULL;
+               err = ip6_route_get_saddr(net, from, &fl6->daddr,
                                          sk ? inet6_sk(sk)->srcprefs : 0,
                                          &fl6->saddr);
+               rcu_read_unlock();
+
                if (err)
                        goto out_err_release;
 
@@ -1213,6 +1218,8 @@ static int ip6_setup_cork(struct sock *sk, struct inet_cork_full *cork,
        if (mtu < IPV6_MIN_MTU)
                return -EINVAL;
        cork->base.fragsize = mtu;
+       cork->base.gso_size = sk->sk_type == SOCK_DGRAM ? ipc6->gso_size : 0;
+
        if (dst_allfrag(xfrm_dst_path(&rt->dst)))
                cork->base.flags |= IPCORK_ALLFRAG;
        cork->base.length = 0;
@@ -1247,6 +1254,7 @@ static int __ip6_append_data(struct sock *sk,
        int csummode = CHECKSUM_NONE;
        unsigned int maxnonfragsize, headersize;
        unsigned int wmem_alloc_delta = 0;
+       bool paged;
 
        skb = skb_peek_tail(queue);
        if (!skb) {
@@ -1254,7 +1262,8 @@ static int __ip6_append_data(struct sock *sk,
                dst_exthdrlen = rt->dst.header_len - rt->rt6i_nfheader_len;
        }
 
-       mtu = cork->fragsize;
+       paged = !!cork->gso_size;
+       mtu = cork->gso_size ? IP6_MAX_MTU : cork->fragsize;
        orig_mtu = mtu;
 
        hh_len = LL_RESERVED_SPACE(rt->dst.dev);
@@ -1302,7 +1311,7 @@ emsgsize:
        if (transhdrlen && sk->sk_protocol == IPPROTO_UDP &&
            headersize == sizeof(struct ipv6hdr) &&
            length <= mtu - headersize &&
-           !(flags & MSG_MORE) &&
+           (!(flags & MSG_MORE) || cork->gso_size) &&
            rt->dst.dev->features & (NETIF_F_IPV6_CSUM | NETIF_F_HW_CSUM))
                csummode = CHECKSUM_PARTIAL;
 
@@ -1345,6 +1354,7 @@ emsgsize:
                        unsigned int fraglen;
                        unsigned int fraggap;
                        unsigned int alloclen;
+                       unsigned int pagedlen = 0;
 alloc_new_skb:
                        /* There's no room in the current skb */
                        if (skb)
@@ -1367,11 +1377,17 @@ alloc_new_skb:
 
                        if (datalen > (cork->length <= mtu && !(cork->flags & IPCORK_ALLFRAG) ? mtu : maxfraglen) - fragheaderlen)
                                datalen = maxfraglen - fragheaderlen - rt->dst.trailer_len;
+                       fraglen = datalen + fragheaderlen;
+
                        if ((flags & MSG_MORE) &&
                            !(rt->dst.dev->features&NETIF_F_SG))
                                alloclen = mtu;
-                       else
-                               alloclen = datalen + fragheaderlen;
+                       else if (!paged)
+                               alloclen = fraglen;
+                       else {
+                               alloclen = min_t(int, fraglen, MAX_HEADER);
+                               pagedlen = fraglen - alloclen;
+                       }
 
                        alloclen += dst_exthdrlen;
 
@@ -1393,7 +1409,7 @@ alloc_new_skb:
                         */
                        alloclen += sizeof(struct frag_hdr);
 
-                       copy = datalen - transhdrlen - fraggap;
+                       copy = datalen - transhdrlen - fraggap - pagedlen;
                        if (copy < 0) {
                                err = -EINVAL;
                                goto error;
@@ -1432,7 +1448,7 @@ alloc_new_skb:
                        /*
                         *      Find where to start putting bytes
                         */
-                       data = skb_put(skb, fraglen);
+                       data = skb_put(skb, fraglen - pagedlen);
                        skb_set_network_header(skb, exthdrlen);
                        data += fragheaderlen;
                        skb->transport_header = (skb->network_header +
@@ -1455,7 +1471,7 @@ alloc_new_skb:
                        }
 
                        offset += copy;
-                       length -= datalen - fraggap;
+                       length -= copy + transhdrlen;
                        transhdrlen = 0;
                        exthdrlen = 0;
                        dst_exthdrlen = 0;
@@ -1728,9 +1744,9 @@ struct sk_buff *ip6_make_skb(struct sock *sk,
                             void *from, int length, int transhdrlen,
                             struct ipcm6_cookie *ipc6, struct flowi6 *fl6,
                             struct rt6_info *rt, unsigned int flags,
+                            struct inet_cork_full *cork,
                             const struct sockcm_cookie *sockc)
 {
-       struct inet_cork_full cork;
        struct inet6_cork v6_cork;
        struct sk_buff_head queue;
        int exthdrlen = (ipc6->opt ? ipc6->opt->opt_flen : 0);
@@ -1741,27 +1757,27 @@ struct sk_buff *ip6_make_skb(struct sock *sk,
 
        __skb_queue_head_init(&queue);
 
-       cork.base.flags = 0;
-       cork.base.addr = 0;
-       cork.base.opt = NULL;
-       cork.base.dst = NULL;
+       cork->base.flags = 0;
+       cork->base.addr = 0;
+       cork->base.opt = NULL;
+       cork->base.dst = NULL;
        v6_cork.opt = NULL;
-       err = ip6_setup_cork(sk, &cork, &v6_cork, ipc6, rt, fl6);
+       err = ip6_setup_cork(sk, cork, &v6_cork, ipc6, rt, fl6);
        if (err) {
-               ip6_cork_release(&cork, &v6_cork);
+               ip6_cork_release(cork, &v6_cork);
                return ERR_PTR(err);
        }
        if (ipc6->dontfrag < 0)
                ipc6->dontfrag = inet6_sk(sk)->dontfrag;
 
-       err = __ip6_append_data(sk, fl6, &queue, &cork.base, &v6_cork,
+       err = __ip6_append_data(sk, fl6, &queue, &cork->base, &v6_cork,
                                &current->task_frag, getfrag, from,
                                length + exthdrlen, transhdrlen + exthdrlen,
                                flags, ipc6, sockc);
        if (err) {
-               __ip6_flush_pending_frames(sk, &queue, &cork, &v6_cork);
+               __ip6_flush_pending_frames(sk, &queue, cork, &v6_cork);
                return ERR_PTR(err);
        }
 
-       return __ip6_make_skb(sk, &queue, &cork, &v6_cork);
+       return __ip6_make_skb(sk, &queue, cork, &v6_cork);
 }