ipv4: fix nexthop attlen check in fib_nh_match
[firefly-linux-kernel-4.4.55.git] / net / ipv6 / ip6_output.c
index dae1949019d7b8dc77d14c39478571e86ad89f34..071edcba4158ede71b98c6c3380e85a210b8b240 100644 (file)
@@ -130,7 +130,7 @@ static int ip6_finish_output2(struct sk_buff *skb)
        }
 
        rcu_read_lock_bh();
-       nexthop = rt6_nexthop((struct rt6_info *)dst, &ipv6_hdr(skb)->daddr);
+       nexthop = rt6_nexthop((struct rt6_info *)dst);
        neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
        if (unlikely(!neigh))
                neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
@@ -141,8 +141,8 @@ static int ip6_finish_output2(struct sk_buff *skb)
        }
        rcu_read_unlock_bh();
 
-       IP6_INC_STATS_BH(dev_net(dst->dev),
-                        ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
+       IP6_INC_STATS(dev_net(dst->dev),
+                     ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
        kfree_skb(skb);
        return -EINVAL;
 }
@@ -150,7 +150,8 @@ static int ip6_finish_output2(struct sk_buff *skb)
 static int ip6_finish_output(struct sk_buff *skb)
 {
        if ((skb->len > ip6_skb_dst_mtu(skb) && !skb_is_gso(skb)) ||
-           dst_allfrag(skb_dst(skb)))
+           dst_allfrag(skb_dst(skb)) ||
+           (IP6CB(skb)->frag_max_size && skb->len > IP6CB(skb)->frag_max_size))
                return ip6_fragment(skb, ip6_finish_output2);
        else
                return ip6_finish_output2(skb);
@@ -344,6 +345,24 @@ static inline int ip6_forward_finish(struct sk_buff *skb)
        return dst_output(skb);
 }
 
+static bool ip6_pkt_too_big(const struct sk_buff *skb, unsigned int mtu)
+{
+       if (skb->len <= mtu)
+               return false;
+
+       /* ipv6 conntrack defrag sets max_frag_size + local_df */
+       if (IP6CB(skb)->frag_max_size && IP6CB(skb)->frag_max_size > mtu)
+               return true;
+
+       if (skb->local_df)
+               return false;
+
+       if (skb_is_gso(skb) && skb_gso_network_seglen(skb) <= mtu)
+               return false;
+
+       return true;
+}
+
 int ip6_forward(struct sk_buff *skb)
 {
        struct dst_entry *dst = skb_dst(skb);
@@ -381,9 +400,8 @@ int ip6_forward(struct sk_buff *skb)
         *      cannot be fragmented, because there is no warranty
         *      that different fragments will go along one path. --ANK
         */
-       if (opt->ra) {
-               u8 *ptr = skb_network_header(skb) + opt->ra;
-               if (ip6_call_ra_chain(skb, (ptr[2]<<8) + ptr[3]))
+       if (unlikely(opt->flags & IP6SKB_ROUTERALERT)) {
+               if (ip6_call_ra_chain(skb, ntohs(opt->ra)))
                        return 0;
        }
 
@@ -467,8 +485,7 @@ int ip6_forward(struct sk_buff *skb)
        if (mtu < IPV6_MIN_MTU)
                mtu = IPV6_MIN_MTU;
 
-       if ((!skb->local_df && skb->len > mtu && !skb_is_gso(skb)) ||
-           (IP6CB(skb)->frag_max_size && IP6CB(skb)->frag_max_size > mtu)) {
+       if (ip6_pkt_too_big(skb, mtu)) {
                /* Again, force OUTPUT device used as source address */
                skb->dev = dst->dev;
                icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
@@ -523,6 +540,23 @@ static void ip6_copy_metadata(struct sk_buff *to, struct sk_buff *from)
        skb_copy_secmark(to, from);
 }
 
+static void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt)
+{
+       static u32 ip6_idents_hashrnd __read_mostly;
+       static bool hashrnd_initialized = false;
+       u32 hash, id;
+
+       if (unlikely(!hashrnd_initialized)) {
+               hashrnd_initialized = true;
+               get_random_bytes(&ip6_idents_hashrnd, sizeof(ip6_idents_hashrnd));
+       }
+       hash = __ipv6_addr_jhash(&rt->rt6i_dst.addr, ip6_idents_hashrnd);
+       hash = __ipv6_addr_jhash(&rt->rt6i_src.addr, hash);
+
+       id = ip_idents_reserve(hash, 1);
+       fhdr->identification = htonl(id);
+}
+
 int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 {
        struct sk_buff *frag;
@@ -822,11 +856,17 @@ static struct dst_entry *ip6_sk_dst_check(struct sock *sk,
                                          const struct flowi6 *fl6)
 {
        struct ipv6_pinfo *np = inet6_sk(sk);
-       struct rt6_info *rt = (struct rt6_info *)dst;
+       struct rt6_info *rt;
 
        if (!dst)
                goto out;
 
+       if (dst->ops->family != AF_INET6) {
+               dst_release(dst);
+               return NULL;
+       }
+
+       rt = (struct rt6_info *)dst;
        /* Yes, checking route validity in not connected
         * case is not very simple. Take into account,
         * that we do not support routing by source, TOS,
@@ -893,7 +933,7 @@ static int ip6_dst_lookup_tail(struct sock *sk,
         */
        rt = (struct rt6_info *) *dst;
        rcu_read_lock_bh();
-       n = __ipv6_neigh_lookup_noref(rt->dst.dev, rt6_nexthop(rt, &fl6->daddr));
+       n = __ipv6_neigh_lookup_noref(rt->dst.dev, rt6_nexthop(rt));
        err = n && !(n->nud_state & NUD_VALID) ? -EINVAL : 0;
        rcu_read_unlock_bh();
 
@@ -1034,6 +1074,8 @@ static inline int ip6_ufo_append_data(struct sock *sk,
         * udp datagram
         */
        if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL) {
+               struct frag_hdr fhdr;
+
                skb = sock_alloc_send_skb(sk,
                        hh_len + fragheaderlen + transhdrlen + 20,
                        (flags & MSG_DONTWAIT), &err);
@@ -1054,12 +1096,6 @@ static inline int ip6_ufo_append_data(struct sock *sk,
 
                skb->ip_summed = CHECKSUM_PARTIAL;
                skb->csum = 0;
-       }
-
-       err = skb_append_datato_frags(sk,skb, getfrag, from,
-                                     (length - transhdrlen));
-       if (!err) {
-               struct frag_hdr fhdr;
 
                /* Specify the length of each IPv6 datagram fragment.
                 * It has to be a multiple of 8.
@@ -1070,15 +1106,10 @@ static inline int ip6_ufo_append_data(struct sock *sk,
                ipv6_select_ident(&fhdr, rt);
                skb_shinfo(skb)->ip6_frag_id = fhdr.identification;
                __skb_queue_tail(&sk->sk_write_queue, skb);
-
-               return 0;
        }
-       /* There is not enough support do UPD LSO,
-        * so follow normal path
-        */
-       kfree_skb(skb);
 
-       return err;
+       return skb_append_datato_frags(sk, skb, getfrag, from,
+                                      (length - transhdrlen));
 }
 
 static inline struct ipv6_opt_hdr *ip6_opt_dup(struct ipv6_opt_hdr *src,
@@ -1093,23 +1124,24 @@ static inline struct ipv6_rt_hdr *ip6_rthdr_dup(struct ipv6_rt_hdr *src,
        return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
 }
 
-static void ip6_append_data_mtu(int *mtu,
+static void ip6_append_data_mtu(unsigned int *mtu,
                                int *maxfraglen,
                                unsigned int fragheaderlen,
                                struct sk_buff *skb,
-                               struct rt6_info *rt)
+                               struct rt6_info *rt,
+                               unsigned int orig_mtu)
 {
        if (!(rt->dst.flags & DST_XFRM_TUNNEL)) {
                if (skb == NULL) {
                        /* first fragment, reserve header_len */
-                       *mtu = *mtu - rt->dst.header_len;
+                       *mtu = orig_mtu - rt->dst.header_len;
 
                } else {
                        /*
                         * this fragment is not first, the headers
                         * space is regarded as data space.
                         */
-                       *mtu = dst_mtu(rt->dst.path);
+                       *mtu = orig_mtu;
                }
                *maxfraglen = ((*mtu - fragheaderlen) & ~7)
                              + fragheaderlen - sizeof(struct frag_hdr);
@@ -1126,11 +1158,10 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
        struct ipv6_pinfo *np = inet6_sk(sk);
        struct inet_cork *cork;
        struct sk_buff *skb, *skb_prev = NULL;
-       unsigned int maxfraglen, fragheaderlen;
+       unsigned int maxfraglen, fragheaderlen, mtu, orig_mtu;
        int exthdrlen;
        int dst_exthdrlen;
        int hh_len;
-       int mtu;
        int copy;
        int err;
        int offset = 0;
@@ -1209,6 +1240,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
                dst_exthdrlen = 0;
                mtu = cork->fragsize;
        }
+       orig_mtu = mtu;
 
        hh_len = LL_RESERVED_SPACE(rt->dst.dev);
 
@@ -1243,27 +1275,27 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
         * --yoshfuji
         */
 
-       cork->length += length;
-       if (length > mtu) {
-               int proto = sk->sk_protocol;
-               if (dontfrag && (proto == IPPROTO_UDP || proto == IPPROTO_RAW)){
-                       ipv6_local_rxpmtu(sk, fl6, mtu-exthdrlen);
-                       return -EMSGSIZE;
-               }
-
-               if (proto == IPPROTO_UDP &&
-                   (rt->dst.dev->features & NETIF_F_UFO)) {
+       if ((length > mtu) && dontfrag && (sk->sk_protocol == IPPROTO_UDP ||
+                                          sk->sk_protocol == IPPROTO_RAW)) {
+               ipv6_local_rxpmtu(sk, fl6, mtu-exthdrlen);
+               return -EMSGSIZE;
+       }
 
-                       err = ip6_ufo_append_data(sk, getfrag, from, length,
-                                                 hh_len, fragheaderlen,
-                                                 transhdrlen, mtu, flags, rt);
-                       if (err)
-                               goto error;
-                       return 0;
-               }
+       skb = skb_peek_tail(&sk->sk_write_queue);
+       cork->length += length;
+       if (((length > mtu) ||
+            (skb && skb_has_frags(skb))) &&
+           (sk->sk_protocol == IPPROTO_UDP) &&
+           (rt->dst.dev->features & NETIF_F_UFO)) {
+               err = ip6_ufo_append_data(sk, getfrag, from, length,
+                                         hh_len, fragheaderlen,
+                                         transhdrlen, mtu, flags, rt);
+               if (err)
+                       goto error;
+               return 0;
        }
 
-       if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL)
+       if (!skb)
                goto alloc_new_skb;
 
        while (length > 0) {
@@ -1287,7 +1319,8 @@ alloc_new_skb:
                        /* update mtu and maxfraglen if necessary */
                        if (skb == NULL || skb_prev == NULL)
                                ip6_append_data_mtu(&mtu, &maxfraglen,
-                                                   fragheaderlen, skb, rt);
+                                                   fragheaderlen, skb, rt,
+                                                   orig_mtu);
 
                        skb_prev = skb;
 
@@ -1542,8 +1575,8 @@ int ip6_push_pending_frames(struct sock *sk)
        if (proto == IPPROTO_ICMPV6) {
                struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
 
-               ICMP6MSGOUT_INC_STATS_BH(net, idev, icmp6_hdr(skb)->icmp6_type);
-               ICMP6_INC_STATS_BH(net, idev, ICMP6_MIB_OUTMSGS);
+               ICMP6MSGOUT_INC_STATS(net, idev, icmp6_hdr(skb)->icmp6_type);
+               ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS);
        }
 
        err = ip6_local_out(skb);