How IPv6 addresses are flushed on link down

IPv6协议栈里, 当一个网口被down之后,网口上对应的IP地址也会一起被flush掉。
面对IPv6跟IPv4不同的行为方式,内核提供了一个规避的开关。
在4.6内核之后提供了一个开关,用来避免IPv6地址别清理掉。
这个开关既有全局的设置,也有每个interface粒度的单独开关。

1
2
3
4
5
6
7
keep_addr_on_down - INTEGER
Keep all IPv6 addresses on an interface down event. If set static
global addresses with no expiration time are not flushed.
>0 : enabled
0 : system default
<0 : disabled
Default: 0 (addresses are removed)

call stack

1
2
-> addrconf_init
-> -> register_netdevice_notifier(ipv6_dev_notf)
1
2
-> addrconf_notify
-> ->

register call back function

IPv6 register a callback function for link down message.

1
7301         register_netdevice_notifier(&ipv6_dev_notf);
1
2
3
4
3713 static struct notifier_block ipv6_dev_notf = {
3714 .notifier_call = addrconf_notify,
3715 .priority = ADDRCONF_NOTIFY_PRIORITY,
3716 };
1
2
3
4
5
6
7
8
9
10
3514 static int addrconf_notify(struct notifier_block *this, unsigned long event,
3515 void *ptr)
3516 {
3668 case NETDEV_DOWN:
3669 case NETDEV_UNREGISTER:
3670 /*
3671 * Remove all addresses from this interface.
3672 */
3673 addrconf_ifdown(dev, event != NETDEV_DOWN);
3674 break;

遍历hash链里配置在本idev下的所有ipv6 地址节点ifa,并把它们从系统里删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
3737 static int addrconf_ifdown(struct net_device *dev, bool unregister)
3738 {
...
3784 /* Step 2: clear hash table */
3785 for (i = 0; i < IN6_ADDR_HSIZE; i++) {
3786 struct hlist_head *h = &net->ipv6.inet6_addr_lst[i];
3787
3788 spin_lock_bh(&net->ipv6.addrconf_hash_lock);
3789 restart:
3790 hlist_for_each_entry_rcu(ifa, h, addr_lst) {
3791 if (ifa->idev == idev) {
3792 addrconf_del_dad_work(ifa);
3793 /* combined flag + permanent flag decide if
3794 * address is retained on a down event
3795 */
3796 if (!keep_addr ||
3797 !(ifa->flags & IFA_F_PERMANENT) ||
3798 addr_is_local(&ifa->addr)) {
3799 hlist_del_init_rcu(&ifa->addr_lst);
3800 goto restart;
3801 }
3802 }
3803 }
3804 spin_unlock_bh(&net->ipv6.addrconf_hash_lock);
3805 }

keep_addr_on_down

面对IPv6跟IPv4不同的行为方式,内核提供了一个规避的开关。
在4.6内核之后提供了一个开关,用来避免IPv6地址别清理掉。
这个开关既有全局的设置,也有每个interface粒度的单独开关。
系统在flush前会看各个开关的并联效果。

比较详细的介绍和讨论见:
https://serverfault.com/questions/842542/why-are-ipv6-addresses-flushed-on-link-down

1
2
3
4
5
6
7
8
9
10
11
12
3771         /* combine the user config with event to determine if permanent
3772 * addresses are to be removed from address hash table
3773 */
3774 if (!unregister && !idev->cnf.disable_ipv6) {
3775 /* aggregate the system setting and interface setting */
3776 int _keep_addr = net->ipv6.devconf_all->keep_addr_on_down;
3777
3778 if (!_keep_addr)
3779 _keep_addr = idev->cnf.keep_addr_on_down;
3780
3781 keep_addr = (_keep_addr > 0);
3782 }