Merge tag 'ofs-pull-tag-1' of git://git.kernel.org/pub/scm/linux/kernel/git/hubcap...
[cascardo/linux.git] / net / ipv6 / addrconf.c
index bdd7eac..27aed1a 100644 (file)
@@ -216,6 +216,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
        },
        .use_oif_addrs_only     = 0,
        .ignore_routes_with_linkdown = 0,
+       .keep_addr_on_down      = 0,
 };
 
 static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -260,6 +261,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
        },
        .use_oif_addrs_only     = 0,
        .ignore_routes_with_linkdown = 0,
+       .keep_addr_on_down      = 0,
 };
 
 /* Check if a valid qdisc is available */
@@ -471,18 +473,21 @@ static int inet6_netconf_msgsize_devconf(int type)
 {
        int size =  NLMSG_ALIGN(sizeof(struct netconfmsg))
                    + nla_total_size(4);        /* NETCONFA_IFINDEX */
+       bool all = false;
 
-       /* type -1 is used for ALL */
-       if (type == -1 || type == NETCONFA_FORWARDING)
+       if (type == NETCONFA_ALL)
+               all = true;
+
+       if (all || type == NETCONFA_FORWARDING)
                size += nla_total_size(4);
 #ifdef CONFIG_IPV6_MROUTE
-       if (type == -1 || type == NETCONFA_MC_FORWARDING)
+       if (all || type == NETCONFA_MC_FORWARDING)
                size += nla_total_size(4);
 #endif
-       if (type == -1 || type == NETCONFA_PROXY_NEIGH)
+       if (all || type == NETCONFA_PROXY_NEIGH)
                size += nla_total_size(4);
 
-       if (type == -1 || type == NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN)
+       if (all || type == NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN)
                size += nla_total_size(4);
 
        return size;
@@ -495,33 +500,36 @@ static int inet6_netconf_fill_devconf(struct sk_buff *skb, int ifindex,
 {
        struct nlmsghdr  *nlh;
        struct netconfmsg *ncm;
+       bool all = false;
 
        nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct netconfmsg),
                        flags);
        if (!nlh)
                return -EMSGSIZE;
 
+       if (type == NETCONFA_ALL)
+               all = true;
+
        ncm = nlmsg_data(nlh);
        ncm->ncm_family = AF_INET6;
 
        if (nla_put_s32(skb, NETCONFA_IFINDEX, ifindex) < 0)
                goto nla_put_failure;
 
-       /* type -1 is used for ALL */
-       if ((type == -1 || type == NETCONFA_FORWARDING) &&
+       if ((all || type == NETCONFA_FORWARDING) &&
            nla_put_s32(skb, NETCONFA_FORWARDING, devconf->forwarding) < 0)
                goto nla_put_failure;
 #ifdef CONFIG_IPV6_MROUTE
-       if ((type == -1 || type == NETCONFA_MC_FORWARDING) &&
+       if ((all || type == NETCONFA_MC_FORWARDING) &&
            nla_put_s32(skb, NETCONFA_MC_FORWARDING,
                        devconf->mc_forwarding) < 0)
                goto nla_put_failure;
 #endif
-       if ((type == -1 || type == NETCONFA_PROXY_NEIGH) &&
+       if ((all || type == NETCONFA_PROXY_NEIGH) &&
            nla_put_s32(skb, NETCONFA_PROXY_NEIGH, devconf->proxy_ndp) < 0)
                goto nla_put_failure;
 
-       if ((type == -1 || type == NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN) &&
+       if ((all || type == NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN) &&
            nla_put_s32(skb, NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN,
                        devconf->ignore_routes_with_linkdown) < 0)
                goto nla_put_failure;
@@ -607,14 +615,14 @@ static int inet6_netconf_get_devconf(struct sk_buff *in_skb,
        }
 
        err = -ENOBUFS;
-       skb = nlmsg_new(inet6_netconf_msgsize_devconf(-1), GFP_ATOMIC);
+       skb = nlmsg_new(inet6_netconf_msgsize_devconf(NETCONFA_ALL), GFP_ATOMIC);
        if (!skb)
                goto errout;
 
        err = inet6_netconf_fill_devconf(skb, ifindex, devconf,
                                         NETLINK_CB(in_skb).portid,
                                         nlh->nlmsg_seq, RTM_NEWNETCONF, 0,
-                                        -1);
+                                        NETCONFA_ALL);
        if (err < 0) {
                /* -EMSGSIZE implies BUG in inet6_netconf_msgsize_devconf() */
                WARN_ON(err == -EMSGSIZE);
@@ -658,7 +666,7 @@ static int inet6_netconf_dump_devconf(struct sk_buff *skb,
                                                       cb->nlh->nlmsg_seq,
                                                       RTM_NEWNETCONF,
                                                       NLM_F_MULTI,
-                                                      -1) < 0) {
+                                                      NETCONFA_ALL) < 0) {
                                rcu_read_unlock();
                                goto done;
                        }
@@ -674,7 +682,7 @@ cont:
                                               NETLINK_CB(cb->skb).portid,
                                               cb->nlh->nlmsg_seq,
                                               RTM_NEWNETCONF, NLM_F_MULTI,
-                                              -1) < 0)
+                                              NETCONFA_ALL) < 0)
                        goto done;
                else
                        h++;
@@ -685,7 +693,7 @@ cont:
                                               NETLINK_CB(cb->skb).portid,
                                               cb->nlh->nlmsg_seq,
                                               RTM_NEWNETCONF, NLM_F_MULTI,
-                                              -1) < 0)
+                                              NETCONFA_ALL) < 0)
                        goto done;
                else
                        h++;
@@ -3168,6 +3176,81 @@ static void addrconf_gre_config(struct net_device *dev)
 }
 #endif
 
+#if IS_ENABLED(CONFIG_NET_L3_MASTER_DEV)
+/* If the host route is cached on the addr struct make sure it is associated
+ * with the proper table. e.g., enslavement can change and if so the cached
+ * host route needs to move to the new table.
+ */
+static void l3mdev_check_host_rt(struct inet6_dev *idev,
+                                 struct inet6_ifaddr *ifp)
+{
+       if (ifp->rt) {
+               u32 tb_id = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL;
+
+               if (tb_id != ifp->rt->rt6i_table->tb6_id) {
+                       ip6_del_rt(ifp->rt);
+                       ifp->rt = NULL;
+               }
+       }
+}
+#else
+static void l3mdev_check_host_rt(struct inet6_dev *idev,
+                                 struct inet6_ifaddr *ifp)
+{
+}
+#endif
+
+static int fixup_permanent_addr(struct inet6_dev *idev,
+                               struct inet6_ifaddr *ifp)
+{
+       l3mdev_check_host_rt(idev, ifp);
+
+       if (!ifp->rt) {
+               struct rt6_info *rt;
+
+               rt = addrconf_dst_alloc(idev, &ifp->addr, false);
+               if (unlikely(IS_ERR(rt)))
+                       return PTR_ERR(rt);
+
+               ifp->rt = rt;
+       }
+
+       if (!(ifp->flags & IFA_F_NOPREFIXROUTE)) {
+               addrconf_prefix_route(&ifp->addr, ifp->prefix_len,
+                                     idev->dev, 0, 0);
+       }
+
+       addrconf_dad_start(ifp);
+
+       return 0;
+}
+
+static void addrconf_permanent_addr(struct net_device *dev)
+{
+       struct inet6_ifaddr *ifp, *tmp;
+       struct inet6_dev *idev;
+
+       idev = __in6_dev_get(dev);
+       if (!idev)
+               return;
+
+       write_lock_bh(&idev->lock);
+
+       list_for_each_entry_safe(ifp, tmp, &idev->addr_list, if_list) {
+               if ((ifp->flags & IFA_F_PERMANENT) &&
+                   fixup_permanent_addr(idev, ifp) < 0) {
+                       write_unlock_bh(&idev->lock);
+                       ipv6_del_addr(ifp);
+                       write_lock_bh(&idev->lock);
+
+                       net_info_ratelimited("%s: Failed to add prefix route for address %pI6c; dropping\n",
+                                            idev->dev->name, &ifp->addr);
+               }
+       }
+
+       write_unlock_bh(&idev->lock);
+}
+
 static int addrconf_notify(struct notifier_block *this, unsigned long event,
                           void *ptr)
 {
@@ -3253,6 +3336,9 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
                        run_pending = 1;
                }
 
+               /* restore routes for permanent addresses */
+               addrconf_permanent_addr(dev);
+
                switch (dev->type) {
 #if IS_ENABLED(CONFIG_IPV6_SIT)
                case ARPHRD_SIT:
@@ -3356,7 +3442,10 @@ static int addrconf_ifdown(struct net_device *dev, int how)
 {
        struct net *net = dev_net(dev);
        struct inet6_dev *idev;
-       struct inet6_ifaddr *ifa;
+       struct inet6_ifaddr *ifa, *tmp;
+       struct list_head del_list;
+       int _keep_addr;
+       bool keep_addr;
        int state, i;
 
        ASSERT_RTNL();
@@ -3383,6 +3472,16 @@ static int addrconf_ifdown(struct net_device *dev, int how)
 
        }
 
+       /* aggregate the system setting and interface setting */
+       _keep_addr = net->ipv6.devconf_all->keep_addr_on_down;
+       if (!_keep_addr)
+               _keep_addr = idev->cnf.keep_addr_on_down;
+
+       /* combine the user config with event to determine if permanent
+        * addresses are to be removed from address hash table
+        */
+       keep_addr = !(how || _keep_addr <= 0);
+
        /* Step 2: clear hash table */
        for (i = 0; i < IN6_ADDR_HSIZE; i++) {
                struct hlist_head *h = &inet6_addr_lst[i];
@@ -3391,9 +3490,15 @@ static int addrconf_ifdown(struct net_device *dev, int how)
 restart:
                hlist_for_each_entry_rcu(ifa, h, addr_lst) {
                        if (ifa->idev == idev) {
-                               hlist_del_init_rcu(&ifa->addr_lst);
                                addrconf_del_dad_work(ifa);
-                               goto restart;
+                               /* combined flag + permanent flag decide if
+                                * address is retained on a down event
+                                */
+                               if (!keep_addr ||
+                                   !(ifa->flags & IFA_F_PERMANENT)) {
+                                       hlist_del_init_rcu(&ifa->addr_lst);
+                                       goto restart;
+                               }
                        }
                }
                spin_unlock_bh(&addrconf_hash_lock);
@@ -3427,31 +3532,53 @@ restart:
                write_lock_bh(&idev->lock);
        }
 
-       while (!list_empty(&idev->addr_list)) {
-               ifa = list_first_entry(&idev->addr_list,
-                                      struct inet6_ifaddr, if_list);
-               addrconf_del_dad_work(ifa);
+       /* re-combine the user config with event to determine if permanent
+        * addresses are to be removed from the interface list
+        */
+       keep_addr = (!how && _keep_addr > 0);
 
-               list_del(&ifa->if_list);
+       INIT_LIST_HEAD(&del_list);
+       list_for_each_entry_safe(ifa, tmp, &idev->addr_list, if_list) {
+               addrconf_del_dad_work(ifa);
 
                write_unlock_bh(&idev->lock);
-
                spin_lock_bh(&ifa->lock);
-               state = ifa->state;
-               ifa->state = INET6_IFADDR_STATE_DEAD;
+
+               if (keep_addr && (ifa->flags & IFA_F_PERMANENT)) {
+                       /* set state to skip the notifier below */
+                       state = INET6_IFADDR_STATE_DEAD;
+                       ifa->state = 0;
+                       if (!(ifa->flags & IFA_F_NODAD))
+                               ifa->flags |= IFA_F_TENTATIVE;
+               } else {
+                       state = ifa->state;
+                       ifa->state = INET6_IFADDR_STATE_DEAD;
+
+                       list_del(&ifa->if_list);
+                       list_add(&ifa->if_list, &del_list);
+               }
+
                spin_unlock_bh(&ifa->lock);
 
                if (state != INET6_IFADDR_STATE_DEAD) {
                        __ipv6_ifa_notify(RTM_DELADDR, ifa);
                        inet6addr_notifier_call_chain(NETDEV_DOWN, ifa);
                }
-               in6_ifa_put(ifa);
 
                write_lock_bh(&idev->lock);
        }
 
        write_unlock_bh(&idev->lock);
 
+       /* now clean up addresses to be removed */
+       while (!list_empty(&del_list)) {
+               ifa = list_first_entry(&del_list,
+                                      struct inet6_ifaddr, if_list);
+               list_del(&ifa->if_list);
+
+               in6_ifa_put(ifa);
+       }
+
        /* Step 5: Discard anycast and multicast list */
        if (how) {
                ipv6_ac_destroy_dev(idev);
@@ -4714,6 +4841,9 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
        array[DEVCONF_IGNORE_ROUTES_WITH_LINKDOWN] = cnf->ignore_routes_with_linkdown;
        /* we omit DEVCONF_STABLE_SECRET for now */
        array[DEVCONF_USE_OIF_ADDRS_ONLY] = cnf->use_oif_addrs_only;
+       array[DEVCONF_DROP_UNICAST_IN_L2_MULTICAST] = cnf->drop_unicast_in_l2_multicast;
+       array[DEVCONF_DROP_UNSOLICITED_NA] = cnf->drop_unsolicited_na;
+       array[DEVCONF_KEEP_ADDR_ON_DOWN] = cnf->keep_addr_on_down;
 }
 
 static inline size_t inet6_ifla6_size(void)
@@ -5787,6 +5917,28 @@ static struct addrconf_sysctl_table
                        .mode           = 0644,
                        .proc_handler   = addrconf_sysctl_ignore_routes_with_linkdown,
                },
+               {
+                       .procname       = "drop_unicast_in_l2_multicast",
+                       .data           = &ipv6_devconf.drop_unicast_in_l2_multicast,
+                       .maxlen         = sizeof(int),
+                       .mode           = 0644,
+                       .proc_handler   = proc_dointvec,
+               },
+               {
+                       .procname       = "drop_unsolicited_na",
+                       .data           = &ipv6_devconf.drop_unsolicited_na,
+                       .maxlen         = sizeof(int),
+                       .mode           = 0644,
+                       .proc_handler   = proc_dointvec,
+               },
+               {
+                       .procname       = "keep_addr_on_down",
+                       .data           = &ipv6_devconf.keep_addr_on_down,
+                       .maxlen         = sizeof(int),
+                       .mode           = 0644,
+                       .proc_handler   = proc_dointvec,
+
+               },
                {
                        /* sentinel */
                }