|
@@ -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 */
|
|
@@ -3168,6 +3170,55 @@ static void addrconf_gre_config(struct net_device *dev)
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
+static int fixup_permanent_addr(struct inet6_dev *idev,
|
|
|
+ struct inet6_ifaddr *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 +3304,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 +3410,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 +3440,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 +3458,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 +3500,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);
|
|
@@ -4716,6 +4811,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
|
|
|
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)
|
|
@@ -5197,10 +5293,12 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
|
|
|
if (rt)
|
|
|
ip6_del_rt(rt);
|
|
|
}
|
|
|
- dst_hold(&ifp->rt->dst);
|
|
|
-
|
|
|
- ip6_del_rt(ifp->rt);
|
|
|
+ if (ifp->rt) {
|
|
|
+ dst_hold(&ifp->rt->dst);
|
|
|
|
|
|
+ ip6_del_rt(ifp->rt);
|
|
|
+ ifp->rt = NULL;
|
|
|
+ }
|
|
|
rt_genid_bump_ipv6(net);
|
|
|
break;
|
|
|
}
|
|
@@ -5803,6 +5901,14 @@ static struct addrconf_sysctl_table
|
|
|
.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 */
|
|
|
}
|