Merge branch 'stable-3.17' of git://git.infradead.org/users/pcmoore/selinux
[firefly-linux-kernel-4.4.55.git] / net / ipv6 / addrconf.c
index 5667b3003af9b51779ff322717e999282113c4b7..0b239fc1816ed828862f19cdde0f08720a0b43ca 100644 (file)
@@ -108,11 +108,12 @@ static inline u32 cstamp_delta(unsigned long cstamp)
 }
 
 #ifdef CONFIG_SYSCTL
-static void addrconf_sysctl_register(struct inet6_dev *idev);
+static int addrconf_sysctl_register(struct inet6_dev *idev);
 static void addrconf_sysctl_unregister(struct inet6_dev *idev);
 #else
-static inline void addrconf_sysctl_register(struct inet6_dev *idev)
+static inline int addrconf_sysctl_register(struct inet6_dev *idev)
 {
+       return 0;
 }
 
 static inline void addrconf_sysctl_unregister(struct inet6_dev *idev)
@@ -186,6 +187,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
        .max_desync_factor      = MAX_DESYNC_FACTOR,
        .max_addresses          = IPV6_MAX_ADDRESSES,
        .accept_ra_defrtr       = 1,
+       .accept_ra_from_local   = 0,
        .accept_ra_pinfo        = 1,
 #ifdef CONFIG_IPV6_ROUTER_PREF
        .accept_ra_rtr_pref     = 1,
@@ -222,6 +224,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
        .max_desync_factor      = MAX_DESYNC_FACTOR,
        .max_addresses          = IPV6_MAX_ADDRESSES,
        .accept_ra_defrtr       = 1,
+       .accept_ra_from_local   = 0,
        .accept_ra_pinfo        = 1,
 #ifdef CONFIG_IPV6_ROUTER_PREF
        .accept_ra_rtr_pref     = 1,
@@ -308,16 +311,16 @@ err_ip:
 static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 {
        struct inet6_dev *ndev;
+       int err = -ENOMEM;
 
        ASSERT_RTNL();
 
        if (dev->mtu < IPV6_MIN_MTU)
-               return NULL;
+               return ERR_PTR(-EINVAL);
 
        ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
-
        if (ndev == NULL)
-               return NULL;
+               return ERR_PTR(err);
 
        rwlock_init(&ndev->lock);
        ndev->dev = dev;
@@ -330,7 +333,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
        ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
        if (ndev->nd_parms == NULL) {
                kfree(ndev);
-               return NULL;
+               return ERR_PTR(err);
        }
        if (ndev->cnf.forwarding)
                dev_disable_lro(dev);
@@ -344,17 +347,14 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
                neigh_parms_release(&nd_tbl, ndev->nd_parms);
                dev_put(dev);
                kfree(ndev);
-               return NULL;
+               return ERR_PTR(err);
        }
 
        if (snmp6_register_dev(ndev) < 0) {
                ADBG(KERN_WARNING
                        "%s: cannot create /proc/net/dev_snmp6/%s\n",
                        __func__, dev->name);
-               neigh_parms_release(&nd_tbl, ndev->nd_parms);
-               ndev->dead = 1;
-               in6_dev_finish_destroy(ndev);
-               return NULL;
+               goto err_release;
        }
 
        /* One reference from device.  We must do this before
@@ -392,7 +392,12 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 
        ipv6_mc_init_dev(ndev);
        ndev->tstamp = jiffies;
-       addrconf_sysctl_register(ndev);
+       err = addrconf_sysctl_register(ndev);
+       if (err) {
+               ipv6_mc_destroy_dev(ndev);
+               del_timer(&ndev->regen_timer);
+               goto err_release;
+       }
        /* protected by rtnl_lock */
        rcu_assign_pointer(dev->ip6_ptr, ndev);
 
@@ -407,6 +412,12 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
                ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters);
 
        return ndev;
+
+err_release:
+       neigh_parms_release(&nd_tbl, ndev->nd_parms);
+       ndev->dead = 1;
+       in6_dev_finish_destroy(ndev);
+       return ERR_PTR(err);
 }
 
 static struct inet6_dev *ipv6_find_idev(struct net_device *dev)
@@ -418,7 +429,7 @@ static struct inet6_dev *ipv6_find_idev(struct net_device *dev)
        idev = __in6_dev_get(dev);
        if (!idev) {
                idev = ipv6_add_dev(dev);
-               if (!idev)
+               if (IS_ERR(idev))
                        return NULL;
        }
 
@@ -2728,9 +2739,25 @@ static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr
        }
 }
 
+static void addrconf_addr_gen(struct inet6_dev *idev, bool prefix_route)
+{
+       if (idev->addr_gen_mode == IN6_ADDR_GEN_MODE_EUI64) {
+               struct in6_addr addr;
+
+               ipv6_addr_set(&addr,  htonl(0xFE800000), 0, 0, 0);
+               /* addrconf_add_linklocal also adds a prefix_route and we
+                * only need to care about prefix routes if ipv6_generate_eui64
+                * couldn't generate one.
+                */
+               if (ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) == 0)
+                       addrconf_add_linklocal(idev, &addr);
+               else if (prefix_route)
+                       addrconf_prefix_route(&addr, 64, idev->dev, 0, 0);
+       }
+}
+
 static void addrconf_dev_config(struct net_device *dev)
 {
-       struct in6_addr addr;
        struct inet6_dev *idev;
 
        ASSERT_RTNL();
@@ -2751,11 +2778,7 @@ static void addrconf_dev_config(struct net_device *dev)
        if (IS_ERR(idev))
                return;
 
-       memset(&addr, 0, sizeof(struct in6_addr));
-       addr.s6_addr32[0] = htonl(0xFE800000);
-
-       if (ipv6_generate_eui64(addr.s6_addr + 8, dev) == 0)
-               addrconf_add_linklocal(idev, &addr);
+       addrconf_addr_gen(idev, false);
 }
 
 #if IS_ENABLED(CONFIG_IPV6_SIT)
@@ -2777,11 +2800,7 @@ static void addrconf_sit_config(struct net_device *dev)
        }
 
        if (dev->priv_flags & IFF_ISATAP) {
-               struct in6_addr addr;
-
-               ipv6_addr_set(&addr,  htonl(0xFE800000), 0, 0, 0);
-               if (!ipv6_generate_eui64(addr.s6_addr + 8, dev))
-                       addrconf_add_linklocal(idev, &addr);
+               addrconf_addr_gen(idev, false);
                return;
        }
 
@@ -2796,7 +2815,6 @@ static void addrconf_sit_config(struct net_device *dev)
 static void addrconf_gre_config(struct net_device *dev)
 {
        struct inet6_dev *idev;
-       struct in6_addr addr;
 
        ASSERT_RTNL();
 
@@ -2805,11 +2823,7 @@ static void addrconf_gre_config(struct net_device *dev)
                return;
        }
 
-       ipv6_addr_set(&addr,  htonl(0xFE800000), 0, 0, 0);
-       if (!ipv6_generate_eui64(addr.s6_addr + 8, dev))
-               addrconf_add_linklocal(idev, &addr);
-       else
-               addrconf_prefix_route(&addr, 64, dev, 0, 0);
+       addrconf_addr_gen(idev, true);
 }
 #endif
 
@@ -2825,8 +2839,8 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
        case NETDEV_REGISTER:
                if (!idev && dev->mtu >= IPV6_MIN_MTU) {
                        idev = ipv6_add_dev(dev);
-                       if (!idev)
-                               return notifier_from_errno(-ENOMEM);
+                       if (IS_ERR(idev))
+                               return notifier_from_errno(PTR_ERR(idev));
                }
                break;
 
@@ -2846,7 +2860,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
                        if (!idev && dev->mtu >= IPV6_MIN_MTU)
                                idev = ipv6_add_dev(dev);
 
-                       if (idev) {
+                       if (!IS_ERR_OR_NULL(idev)) {
                                idev->if_flags |= IF_READY;
                                run_pending = 1;
                        }
@@ -2889,7 +2903,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
                        break;
                }
 
-               if (idev) {
+               if (!IS_ERR_OR_NULL(idev)) {
                        if (run_pending)
                                addrconf_dad_run(idev);
 
@@ -2924,7 +2938,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
 
                if (!idev && dev->mtu >= IPV6_MIN_MTU) {
                        idev = ipv6_add_dev(dev);
-                       if (idev)
+                       if (!IS_ERR(idev))
                                break;
                }
 
@@ -2945,10 +2959,14 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
                if (idev) {
                        snmp6_unregister_dev(idev);
                        addrconf_sysctl_unregister(idev);
-                       addrconf_sysctl_register(idev);
-                       err = snmp6_register_dev(idev);
+                       err = addrconf_sysctl_register(idev);
                        if (err)
                                return notifier_from_errno(err);
+                       err = snmp6_register_dev(idev);
+                       if (err) {
+                               addrconf_sysctl_unregister(idev);
+                               return notifier_from_errno(err);
+                       }
                }
                break;
 
@@ -4321,6 +4339,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
        array[DEVCONF_FORCE_TLLAO] = cnf->force_tllao;
        array[DEVCONF_NDISC_NOTIFY] = cnf->ndisc_notify;
        array[DEVCONF_SUPPRESS_FRAG_NDISC] = cnf->suppress_frag_ndisc;
+       array[DEVCONF_ACCEPT_RA_FROM_LOCAL] = cnf->accept_ra_from_local;
 }
 
 static inline size_t inet6_ifla6_size(void)
@@ -4420,6 +4439,10 @@ static int inet6_fill_ifla6_attrs(struct sk_buff *skb, struct inet6_dev *idev)
        nla = nla_reserve(skb, IFLA_INET6_TOKEN, sizeof(struct in6_addr));
        if (nla == NULL)
                goto nla_put_failure;
+
+       if (nla_put_u8(skb, IFLA_INET6_ADDR_GEN_MODE, idev->addr_gen_mode))
+               goto nla_put_failure;
+
        read_lock_bh(&idev->lock);
        memcpy(nla_data(nla), idev->token.s6_addr, nla_len(nla));
        read_unlock_bh(&idev->lock);
@@ -4524,8 +4547,21 @@ static int inet6_set_link_af(struct net_device *dev, const struct nlattr *nla)
        if (nla_parse_nested(tb, IFLA_INET6_MAX, nla, NULL) < 0)
                BUG();
 
-       if (tb[IFLA_INET6_TOKEN])
+       if (tb[IFLA_INET6_TOKEN]) {
                err = inet6_set_iftoken(idev, nla_data(tb[IFLA_INET6_TOKEN]));
+               if (err)
+                       return err;
+       }
+
+       if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
+               u8 mode = nla_get_u8(tb[IFLA_INET6_ADDR_GEN_MODE]);
+
+               if (mode != IN6_ADDR_GEN_MODE_EUI64 &&
+                   mode != IN6_ADDR_GEN_MODE_NONE)
+                       return -EINVAL;
+               idev->addr_gen_mode = mode;
+               err = 0;
+       }
 
        return err;
 }
@@ -5167,6 +5203,13 @@ static struct addrconf_sysctl_table
                        .mode           = 0644,
                        .proc_handler   = proc_dointvec
                },
+               {
+                       .procname       = "accept_ra_from_local",
+                       .data           = &ipv6_devconf.accept_ra_from_local,
+                       .maxlen         = sizeof(int),
+                       .mode           = 0644,
+                       .proc_handler   = proc_dointvec,
+               },
                {
                        /* sentinel */
                }
@@ -5218,12 +5261,23 @@ static void __addrconf_sysctl_unregister(struct ipv6_devconf *p)
        kfree(t);
 }
 
-static void addrconf_sysctl_register(struct inet6_dev *idev)
+static int addrconf_sysctl_register(struct inet6_dev *idev)
 {
-       neigh_sysctl_register(idev->dev, idev->nd_parms,
-                             &ndisc_ifinfo_sysctl_change);
-       __addrconf_sysctl_register(dev_net(idev->dev), idev->dev->name,
-                                       idev, &idev->cnf);
+       int err;
+
+       if (!sysctl_dev_name_is_allowed(idev->dev->name))
+               return -EINVAL;
+
+       err = neigh_sysctl_register(idev->dev, idev->nd_parms,
+                                   &ndisc_ifinfo_sysctl_change);
+       if (err)
+               return err;
+       err = __addrconf_sysctl_register(dev_net(idev->dev), idev->dev->name,
+                                        idev, &idev->cnf);
+       if (err)
+               neigh_sysctl_unregister(idev->nd_parms);
+
+       return err;
 }
 
 static void addrconf_sysctl_unregister(struct inet6_dev *idev)
@@ -5308,6 +5362,7 @@ static struct rtnl_af_ops inet6_ops = {
 
 int __init addrconf_init(void)
 {
+       struct inet6_dev *idev;
        int i, err;
 
        err = ipv6_addr_label_init();
@@ -5346,11 +5401,12 @@ int __init addrconf_init(void)
         * device and it being up should be removed.
         */
        rtnl_lock();
-       if (!ipv6_add_dev(init_net.loopback_dev))
-               err = -ENOMEM;
+       idev = ipv6_add_dev(init_net.loopback_dev);
        rtnl_unlock();
-       if (err)
+       if (IS_ERR(idev)) {
+               err = PTR_ERR(idev);
                goto errlo;
+       }
 
        for (i = 0; i < IN6_ADDR_HSIZE; i++)
                INIT_HLIST_HEAD(&inet6_addr_lst[i]);