Merge tag 'lsk-v4.4-16.05-android'
[firefly-linux-kernel-4.4.55.git] / net / ipv6 / ip6_flowlabel.c
index 46e88433ec7d229550eecadc29f8eacc139fe5b2..dc2db4f7b182c4ebc1a8a51487a3d2e893955df9 100644 (file)
@@ -15,9 +15,7 @@
 #include <linux/socket.h>
 #include <linux/net.h>
 #include <linux/netdevice.h>
-#include <linux/if_arp.h>
 #include <linux/in6.h>
-#include <linux/route.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <net/sock.h>
 
 #include <net/ipv6.h>
-#include <net/ndisc.h>
-#include <net/protocol.h>
-#include <net/ip6_route.h>
-#include <net/addrconf.h>
 #include <net/rawv6.h>
-#include <net/icmp.h>
 #include <net/transp_v6.h>
 
 #include <asm/uaccess.h>
@@ -41,7 +34,7 @@
 #define FL_MIN_LINGER  6       /* Minimal linger. It is set to 6sec specified
                                   in old IPv6 RFC. Well, it was reasonable value.
                                 */
-#define FL_MAX_LINGER  60      /* Maximal linger timeout */
+#define FL_MAX_LINGER  150     /* Maximal linger timeout */
 
 /* FL hash table */
 
@@ -107,7 +100,6 @@ static void fl_free(struct ip6_flowlabel *fl)
        if (fl) {
                if (fl->share == IPV6_FL_S_PROCESS)
                        put_pid(fl->owner.pid);
-               release_net(fl->fl_net);
                kfree(fl->opt);
                kfree_rcu(fl, rcu);
        }
@@ -143,7 +135,7 @@ static void ip6_fl_gc(unsigned long dummy)
 
        spin_lock(&ip6_fl_lock);
 
-       for (i=0; i<=FL_HASH_MASK; i++) {
+       for (i = 0; i <= FL_HASH_MASK; i++) {
                struct ip6_flowlabel *fl;
                struct ip6_flowlabel __rcu **flp;
 
@@ -179,7 +171,7 @@ static void __net_exit ip6_fl_purge(struct net *net)
 {
        int i;
 
-       spin_lock(&ip6_fl_lock);
+       spin_lock_bh(&ip6_fl_lock);
        for (i = 0; i <= FL_HASH_MASK; i++) {
                struct ip6_flowlabel *fl;
                struct ip6_flowlabel __rcu **flp;
@@ -197,7 +189,7 @@ static void __net_exit ip6_fl_purge(struct net *net)
                        flp = &fl->next;
                }
        }
-       spin_unlock(&ip6_fl_lock);
+       spin_unlock_bh(&ip6_fl_lock);
 }
 
 static struct ip6_flowlabel *fl_intern(struct net *net,
@@ -210,10 +202,10 @@ static struct ip6_flowlabel *fl_intern(struct net *net,
        spin_lock_bh(&ip6_fl_lock);
        if (label == 0) {
                for (;;) {
-                       fl->label = htonl(net_random())&IPV6_FLOWLABEL_MASK;
+                       fl->label = htonl(prandom_u32())&IPV6_FLOWLABEL_MASK;
                        if (fl->label) {
                                lfl = __fl_lookup(net, fl->label);
-                               if (lfl == NULL)
+                               if (!lfl)
                                        break;
                        }
                }
@@ -227,7 +219,7 @@ static struct ip6_flowlabel *fl_intern(struct net *net,
                 * with the same label can only appear on another sock
                 */
                lfl = __fl_lookup(net, fl->label);
-               if (lfl != NULL) {
+               if (lfl) {
                        atomic_inc(&lfl->users);
                        spin_unlock_bh(&ip6_fl_lock);
                        return lfl;
@@ -246,7 +238,7 @@ static struct ip6_flowlabel *fl_intern(struct net *net,
 
 /* Socket flowlabel lists */
 
-struct ip6_flowlabel * fl6_sock_lookup(struct sock *sk, __be32 label)
+struct ip6_flowlabel *fl6_sock_lookup(struct sock *sk, __be32 label)
 {
        struct ipv6_fl_socklist *sfl;
        struct ipv6_pinfo *np = inet6_sk(sk);
@@ -266,7 +258,6 @@ struct ip6_flowlabel * fl6_sock_lookup(struct sock *sk, __be32 label)
        rcu_read_unlock_bh();
        return NULL;
 }
-
 EXPORT_SYMBOL_GPL(fl6_sock_lookup);
 
 void fl6_free_socklist(struct sock *sk)
@@ -300,16 +291,16 @@ void fl6_free_socklist(struct sock *sk)
    following rthdr.
  */
 
-struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
-                                        struct ip6_flowlabel * fl,
-                                        struct ipv6_txoptions * fopt)
+struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions *opt_space,
+                                        struct ip6_flowlabel *fl,
+                                        struct ipv6_txoptions *fopt)
 {
-       struct ipv6_txoptions * fl_opt = fl->opt;
+       struct ipv6_txoptions *fl_opt = fl->opt;
 
-       if (fopt == NULL || fopt->opt_flen == 0)
+       if (!fopt || fopt->opt_flen == 0)
                return fl_opt;
 
-       if (fl_opt != NULL) {
+       if (fl_opt) {
                opt_space->hopopt = fl_opt->hopopt;
                opt_space->dst0opt = fl_opt->dst0opt;
                opt_space->srcrt = fl_opt->srcrt;
@@ -345,6 +336,8 @@ static int fl6_renew(struct ip6_flowlabel *fl, unsigned long linger, unsigned lo
        expires = check_linger(expires);
        if (!expires)
                return -EPERM;
+
+       spin_lock_bh(&ip6_fl_lock);
        fl->lastuse = jiffies;
        if (time_before(fl->linger, linger))
                fl->linger = linger;
@@ -352,6 +345,8 @@ static int fl6_renew(struct ip6_flowlabel *fl, unsigned long linger, unsigned lo
                expires = fl->linger;
        if (time_before(fl->expires, fl->lastuse + expires))
                fl->expires = fl->lastuse + expires;
+       spin_unlock_bh(&ip6_fl_lock);
+
        return 0;
 }
 
@@ -371,7 +366,7 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq,
 
        err = -ENOMEM;
        fl = kzalloc(sizeof(*fl), GFP_KERNEL);
-       if (fl == NULL)
+       if (!fl)
                goto done;
 
        if (olen > 0) {
@@ -381,7 +376,7 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq,
 
                err = -ENOMEM;
                fl->opt = kmalloc(sizeof(*fl->opt) + olen, GFP_KERNEL);
-               if (fl->opt == NULL)
+               if (!fl->opt)
                        goto done;
 
                memset(fl->opt, 0, sizeof(*fl->opt));
@@ -391,7 +386,7 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq,
                        goto done;
 
                msg.msg_controllen = olen;
-               msg.msg_control = (void*)(fl->opt+1);
+               msg.msg_control = (void *)(fl->opt+1);
                memset(&flowi6, 0, sizeof(flowi6));
 
                err = ip6_datagram_send_ctl(net, sk, &msg, &flowi6, fl->opt,
@@ -407,7 +402,7 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq,
                }
        }
 
-       fl->fl_net = hold_net(net);
+       fl->fl_net = net;
        fl->expires = jiffies;
        err = fl6_renew(fl, freq->flr_linger, freq->flr_expires);
        if (err)
@@ -453,8 +448,10 @@ static int mem_check(struct sock *sk)
        if (room > FL_MAX_SIZE - FL_MAX_PER_SOCK)
                return 0;
 
+       rcu_read_lock_bh();
        for_each_sk_fl_rcu(np, sfl)
                count++;
+       rcu_read_unlock_bh();
 
        if (room <= 0 ||
            ((count >= FL_MAX_PER_SOCK ||
@@ -465,34 +462,6 @@ static int mem_check(struct sock *sk)
        return 0;
 }
 
-static bool ipv6_hdr_cmp(struct ipv6_opt_hdr *h1, struct ipv6_opt_hdr *h2)
-{
-       if (h1 == h2)
-               return false;
-       if (h1 == NULL || h2 == NULL)
-               return true;
-       if (h1->hdrlen != h2->hdrlen)
-               return true;
-       return memcmp(h1+1, h2+1, ((h1->hdrlen+1)<<3) - sizeof(*h1));
-}
-
-static bool ipv6_opt_cmp(struct ipv6_txoptions *o1, struct ipv6_txoptions *o2)
-{
-       if (o1 == o2)
-               return false;
-       if (o1 == NULL || o2 == NULL)
-               return true;
-       if (o1->opt_nflen != o2->opt_nflen)
-               return true;
-       if (ipv6_hdr_cmp(o1->hopopt, o2->hopopt))
-               return true;
-       if (ipv6_hdr_cmp(o1->dst0opt, o2->dst0opt))
-               return true;
-       if (ipv6_hdr_cmp((struct ipv6_opt_hdr *)o1->srcrt, (struct ipv6_opt_hdr *)o2->srcrt))
-               return true;
-       return false;
-}
-
 static inline void fl_link(struct ipv6_pinfo *np, struct ipv6_fl_socklist *sfl,
                struct ip6_flowlabel *fl)
 {
@@ -503,13 +472,50 @@ static inline void fl_link(struct ipv6_pinfo *np, struct ipv6_fl_socklist *sfl,
        spin_unlock_bh(&ip6_sk_fl_lock);
 }
 
+int ipv6_flowlabel_opt_get(struct sock *sk, struct in6_flowlabel_req *freq,
+                          int flags)
+{
+       struct ipv6_pinfo *np = inet6_sk(sk);
+       struct ipv6_fl_socklist *sfl;
+
+       if (flags & IPV6_FL_F_REMOTE) {
+               freq->flr_label = np->rcv_flowinfo & IPV6_FLOWLABEL_MASK;
+               return 0;
+       }
+
+       if (np->repflow) {
+               freq->flr_label = np->flow_label;
+               return 0;
+       }
+
+       rcu_read_lock_bh();
+
+       for_each_sk_fl_rcu(np, sfl) {
+               if (sfl->fl->label == (np->flow_label & IPV6_FLOWLABEL_MASK)) {
+                       spin_lock_bh(&ip6_fl_lock);
+                       freq->flr_label = sfl->fl->label;
+                       freq->flr_dst = sfl->fl->dst;
+                       freq->flr_share = sfl->fl->share;
+                       freq->flr_expires = (sfl->fl->expires - jiffies) / HZ;
+                       freq->flr_linger = sfl->fl->linger / HZ;
+
+                       spin_unlock_bh(&ip6_fl_lock);
+                       rcu_read_unlock_bh();
+                       return 0;
+               }
+       }
+       rcu_read_unlock_bh();
+
+       return -ENOENT;
+}
+
 int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
 {
        int uninitialized_var(err);
        struct net *net = sock_net(sk);
        struct ipv6_pinfo *np = inet6_sk(sk);
        struct in6_flowlabel_req freq;
-       struct ipv6_fl_socklist *sfl1=NULL;
+       struct ipv6_fl_socklist *sfl1 = NULL;
        struct ipv6_fl_socklist *sfl;
        struct ipv6_fl_socklist __rcu **sflp;
        struct ip6_flowlabel *fl, *fl1 = NULL;
@@ -523,14 +529,24 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
 
        switch (freq.flr_action) {
        case IPV6_FL_A_PUT:
+               if (freq.flr_flags & IPV6_FL_F_REFLECT) {
+                       if (sk->sk_protocol != IPPROTO_TCP)
+                               return -ENOPROTOOPT;
+                       if (!np->repflow)
+                               return -ESRCH;
+                       np->flow_label = 0;
+                       np->repflow = 0;
+                       return 0;
+               }
                spin_lock_bh(&ip6_sk_fl_lock);
                for (sflp = &np->ipv6_fl_list;
-                    (sfl = rcu_dereference(*sflp))!=NULL;
+                    (sfl = rcu_dereference_protected(*sflp,
+                                                     lockdep_is_held(&ip6_sk_fl_lock))) != NULL;
                     sflp = &sfl->next) {
                        if (sfl->fl->label == freq.flr_label) {
                                if (freq.flr_label == (np->flow_label&IPV6_FLOWLABEL_MASK))
                                        np->flow_label &= ~IPV6_FLOWLABEL_MASK;
-                               *sflp = rcu_dereference(sfl->next);
+                               *sflp = sfl->next;
                                spin_unlock_bh(&ip6_sk_fl_lock);
                                fl_release(sfl->fl);
                                kfree_rcu(sfl, rcu);
@@ -563,11 +579,29 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
                return -ESRCH;
 
        case IPV6_FL_A_GET:
+               if (freq.flr_flags & IPV6_FL_F_REFLECT) {
+                       struct net *net = sock_net(sk);
+                       if (net->ipv6.sysctl.flowlabel_consistency) {
+                               net_info_ratelimited("Can not set IPV6_FL_F_REFLECT if flowlabel_consistency sysctl is enable\n");
+                               return -EPERM;
+                       }
+
+                       if (sk->sk_protocol != IPPROTO_TCP)
+                               return -ENOPROTOOPT;
+
+                       np->repflow = 1;
+                       return 0;
+               }
+
                if (freq.flr_label & ~IPV6_FLOWLABEL_MASK)
                        return -EINVAL;
 
+               if (net->ipv6.sysctl.flowlabel_state_ranges &&
+                   (freq.flr_label & IPV6_FLOWLABEL_STATELESS_FLAG))
+                       return -ERANGE;
+
                fl = fl_create(net, sk, &freq, optval, optlen, &err);
-               if (fl == NULL)
+               if (!fl)
                        return err;
                sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
 
@@ -587,7 +621,7 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
                        }
                        rcu_read_unlock_bh();
 
-                       if (fl1 == NULL)
+                       if (!fl1)
                                fl1 = fl_lookup(net, freq.flr_label);
                        if (fl1) {
 recheck:
@@ -603,13 +637,8 @@ recheck:
                                     uid_eq(fl1->owner.uid, fl->owner.uid)))
                                        goto release;
 
-                               err = -EINVAL;
-                               if (!ipv6_addr_equal(&fl1->dst, &fl->dst) ||
-                                   ipv6_opt_cmp(fl1->opt, fl->opt))
-                                       goto release;
-
                                err = -ENOMEM;
-                               if (sfl1 == NULL)
+                               if (!sfl1)
                                        goto release;
                                if (fl->linger > fl1->linger)
                                        fl1->linger = fl->linger;
@@ -629,11 +658,15 @@ release:
                        goto done;
 
                err = -ENOMEM;
-               if (sfl1 == NULL || (err = mem_check(sk)) != 0)
+               if (!sfl1)
+                       goto done;
+
+               err = mem_check(sk);
+               if (err != 0)
                        goto done;
 
                fl1 = fl_intern(net, fl, freq.flr_label);
-               if (fl1 != NULL)
+               if (fl1)
                        goto recheck;
 
                if (!freq.flr_label) {
@@ -744,10 +777,9 @@ static void ip6fl_seq_stop(struct seq_file *seq, void *v)
 static int ip6fl_seq_show(struct seq_file *seq, void *v)
 {
        struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
-       if (v == SEQ_START_TOKEN)
-               seq_printf(seq, "%-5s %-1s %-6s %-6s %-6s %-8s %-32s %s\n",
-                          "Label", "S", "Owner", "Users", "Linger", "Expires", "Dst", "Opt");
-       else {
+       if (v == SEQ_START_TOKEN) {
+               seq_puts(seq, "Label S Owner  Users  Linger Expires  Dst                              Opt\n");
+       } else {
                struct ip6_flowlabel *fl = v;
                seq_printf(seq,
                           "%05X %-1d %-6d %-6d %-6ld %-8ld %pi6 %-4d\n",