网口状态标志位解析part2: 内核如何维护网卡carrier的状态

####operstate的小插曲
2006年内核引入operstate特性,在当时协议栈的维护者中也是颇有争议的!!!
引入operstat特性的patch
图4: 2006年协议栈加入operstate 特性

标志位IFF_UP

设置/清除标志位IFF_UP

ip link set eth0 up:

  • 每个eth口在内核里有对应的struct net_device
  • 每个netdev设备里有一个上的flags用来存放标志位
  • ip link set eth0 up 设置 eth0口对应的IFF_UP标志。
  • ip link set eth0 down清除对应的IFF_UP标记。

ip link源码实现

ip link set eth0 up命令实现:

  • 通过ioctol获取eth0口对应的flags,
  • 然后将IFF_UP标志位设置到 flags 上,
  • 再通过ioctol 命令SIOCSIFFLAGS下发会内核。

具体实现在函数do_setdo_chflags中。

  1. do_set: 解析命令,转换为需要设置的标志位。
1
2
3
4
5
6
7
8
9
10
11
12
13
1370 static int do_set(int argc, char **argv)
1371 {
...
1383 while (argc > 0) {
1384 if (strcmp(*argv, "up") == 0) {
1385 mask |= IFF_UP;
1386 flags |= IFF_UP;
1387 } else if (strcmp(*argv, "down") == 0) {
1388 mask |= IFF_UP;
1389 flags &= ~IFF_UP;
...
1536 if (mask)
1537 return do_chflags(dev, flags, mask);
  1. do_chflags: 借助ioctl函数接口,与内核交互。
    • 首先,读取内核的网口标志位netdev->flags
    • 把期望更新的标志位更新成期望值。
    • 最后,把期望的标志位状态下发回内核。
1
2
3
4
5
6
7
8
9
static int do_chflags(const char *dev, __u32 flags, __u32 mask)
...
err = ioctl(fd, SIOCGIFFLAGS, &ifr);
...
if ((ifr.ifr_flags^flags)&mask) {
ifr.ifr_flags &= ~mask;
ifr.ifr_flags |= mask&flags;
err = ioctl(fd, SIOCSIFFLAGS, &ifr);
...

*** 注意: *** ioctol命令参数里,获取和设置的命令名字, 只有一个字母GS的差别。

内核代码实现

ioctl在内核的对应实现比较复杂, 避免歪楼,单拉一篇去介绍实现吧。

内核如何维护网卡设备的RUNNING 状态

流程概述

主要几个部分:

  1. 网卡驱动有个看门狗:不同网卡驱动实现可能不太一样 ,功能负责监控网卡硬件上网线的状态, 当网线状态变换的时候,会激发内核的 carrier 处理函数。
  2. 内核两个通用的处理函数:netif_carrier_onnetif_carrier_off。这个函数会
    • 设置或者清除netdev->state上的__LINK_STATE_NOCARRIER标志位。
    • 发送事件消息给linkwatch,做后续处理.
    • 如果是网线插好了状态, 会启动一个通用的看门狗,这个看门狗是负责检测tx队列是否’HUNG’了, 如果’HUNG’了就调用网卡对应的处理函数ndo_tx_timeout, 做一些应急补救,比如对网卡队列复位等操作。这里的看门狗跟网卡驱动里的看门狗还不是同一个看门狗。具体差别待研究。
  3. linkwath模块:linkwatch本身是个workqueue 队列,对接受到的消息按照,分为紧急和非紧急两类。紧急的决定立即处理,非紧急的则挂到一个链表里,等定时器超时后,再集中处理。所有事件处理,最终都交给linkwatch_do_dev(struct net_device *dev)函数进行处理。 该函数更新netdev->operate标志位。同时调用通用的dev_activate或者dev_deactivate对网卡做网卡队列进行处理。 我们这里重点关注跟网卡状态位有管的部分,忽略跟网卡队列的处理。
    这里有两个重要函数rfc2863_policydefault_operstate 后面我们重点介绍。

carrier on 和 off 函数

netif_carrier_onnetif_carrier_off: 内核里的两个通用的处理函数,功能基本对称

carrier标志位

总结:
dev->state下的__LINK_STATE_NOCARRIER是 carrier是否OK 的唯一判断标准。

1
2
3
4
4390 static inline bool netif_carrier_ok(const struct net_device *dev)
4391 {
4392 return !test_bit(__LINK_STATE_NOCARRIER, &dev->state);
4393 }
carrier on函数解析

总结:netif_carrier_on主要依次做下面三个操作:

  • __LINK_STATE_NOCARRIER: 设置netdev->state上的__LINK_STATE_NOCARRIER标志位。这个标志位很关键,后续判断carrier 是否 OK,就看这个标志位。
    如果是从无到有这个标志位,则执行下面两步,否则就可以 return 结束了。
  • 计数: 增加网卡设备下的dev->carrier_up_count的计数次数。
  • 发送事件消息给linkwatch:让他做后续处理。激发动作有两部分
  • 如果是网线插好了状态, 会启动一个网卡的看门狗,这个看门狗是负责检测tx队列是否’HUNG’了, 如果’HUNG’了就调用网卡对应的处理函数ndo_tx_timeout, 做一些应急补救,比如对网卡队列复位等操作。
1
2
3
4
5
6
7
8
9
10
11
12
581 void netif_carrier_on(struct net_device *dev)
582 {
583 if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
584 if (dev->reg_state == NETREG_UNINITIALIZED)
585 return;
586 atomic_inc(&dev->carrier_up_count);
587 linkwatch_fire_event(dev);
588 if (netif_running(dev))
589 netdev_watchdog_up(dev);
590 }
591 }
592 EXPORT_SYMBOL(netif_carrier_on);
carrier off函数解析

netif_carrier_on函数对称,唯一差别是不会处理看门狗的部分。

1
2
3
4
5
6
7
8
9
10
600 void netif_carrier_off(struct net_device *dev)
601 {
602 if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
603 if (dev->reg_state == NETREG_UNINITIALIZED)
604 return;
605 atomic_inc(&dev->carrier_down_count);
606 linkwatch_fire_event(dev);
607 }
608 }
609 EXPORT_SYMBOL(netif_carrier_off);

link watch事件在被激发后,会设置一个标志位__LINK_STATE_LINKWATCH_PENDING
然后通过linkwatch_add_event, 把 link watch事件相关的 netdev 设备挂载到一个全局链表上。

1
2
3
4
5
6
7
8
9
10
11
291 void linkwatch_fire_event(struct net_device *dev)
292 {
293 bool urgent = linkwatch_urgent_event(dev);
294
295 if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {
296 linkwatch_add_event(dev);
297 } else if (!urgent)
298 return;
299
300 linkwatch_schedule_work(urgent);
301 }
linkwatch_add_event

将 netdev 增加到链表中

1
2
3
4
5
6
7
8
9
10
11
124 static void linkwatch_add_event(struct net_device *dev)
125 {
126 unsigned long flags;
127
128 spin_lock_irqsave(&lweventlist_lock, flags);
129 if (list_empty(&dev->link_watch_list)) {
130 list_add_tail(&dev->link_watch_list, &lweventlist);
131 netdev_hold(dev, &dev->linkwatch_dev_tracker, GFP_ATOMIC);
132 }
133 spin_unlock_irqrestore(&lweventlist_lock, flags);
134 }

link watch 的workqueue 处理函数,把dev从链表上摘下来,逐个调用linkwatch_do_dev处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
194 static void __linkwatch_run_queue(int urgent_only)
195 {
...
224 spin_lock_irq(&lweventlist_lock);
225 list_splice_init(&lweventlist, &wrk);
226
227 while (!list_empty(&wrk) && do_dev > 0) {
229
230 dev = list_first_entry(&wrk, struct net_device, link_watch_list);
...
243 linkwatch_do_dev(dev);
...
246 }
...
254 }

linkwatch_do_dev里, 才执行真正的网口设备状态处理。

  • 清除__LINK_STATE_LINKWATCH_PENDING标志位,在这个标准位在事件被激发时候,被设置到netdevice上。
  • 更新 netdev设备状态dev->operstate: 核心函数rfc2863_policy,后续详解
  • 网卡状态变换通知:如果网卡是 UP 状态下, 出现 carrier 状态变换,网卡active/deactive,,并通知响应的回调模块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
166 static void linkwatch_do_dev(struct net_device *dev)
167 {
...
177 clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);
178
179 rfc2863_policy(dev);
180 if (dev->flags & IFF_UP) {
181 if (netif_carrier_ok(dev))
182 dev_activate(dev);
183 else
184 dev_deactivate(dev);
185
186 netdev_state_change(dev);
187 }
...
192 }
网卡的状态维护核心函数

两个核心函数rfc2863_policydefault_operstate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
74 static void rfc2863_policy(struct net_device *dev)
75 {
76 unsigned int operstate = default_operstate(dev);
77
78 if (operstate == READ_ONCE(dev->operstate))
79 return;
80
81 switch(dev->link_mode) {
82 case IF_LINK_MODE_TESTING:
83 if (operstate == IF_OPER_UP)
84 operstate = IF_OPER_TESTING;
85 break;
86
87 case IF_LINK_MODE_DORMANT:
88 if (operstate == IF_OPER_UP)
89 operstate = IF_OPER_DORMANT;
90 break;
91 case IF_LINK_MODE_DEFAULT:
92 default:
93 break;
94 }
95
96 WRITE_ONCE(dev->operstate, operstate);
97 }
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
26
27
28
29
30
31
32
33
34
35
36
37
36 static unsigned int default_operstate(const struct net_device *dev)
37 {
38 if (netif_testing(dev))
39 return IF_OPER_TESTING;
40
41 /* Some uppers (DSA) have additional sources for being down, so
42 * first check whether lower is indeed the source of its down state.
43 */
44 if (!netif_carrier_ok(dev)) {
45 struct net_device *peer;
46 int iflink;
47
48 /* If called from netdev_run_todo()/linkwatch_sync_dev(),
49 * dev_net(dev) can be already freed, and RTNL is not held.
50 */
51 if (dev->reg_state <= NETREG_REGISTERED)
52 iflink = dev_get_iflink(dev);
53 else
54 iflink = dev->ifindex;
55
56 if (iflink == dev->ifindex)
57 return IF_OPER_DOWN;
58
59 ASSERT_RTNL();
60 peer = __dev_get_by_index(dev_net(dev), iflink);
61 if (!peer)
62 return IF_OPER_DOWN;
63
64 return netif_carrier_ok(peer) ? IF_OPER_DOWN :
65 IF_OPER_LOWERLAYERDOWN;
66 }
67
68 if (netif_dormant(dev))
69 return IF_OPER_DORMANT;
70
71 return IF_OPER_UP;
72 }

依照上面的逻辑,当 veth1 up 后, veth0 口的 operate 状态应该是LOWERLAYERDOWN。但是在命令显示里, 是DOWN。。。

如果网口没有启动(RUNNING),返回的operate状态是IF_OPER_DOWN

1
2
3
4
5
6
7
2017 static int rtnl_fill_ifinfo(struct sk_buff *skb,
2018 struct net_device *dev, struct net *src_net,
...
2050 nla_put_u8(skb, IFLA_OPERSTATE,
2051 netif_running(dev) ? READ_ONCE(dev->operstate) :
2052 IF_OPER_DOWN) ||
2053 nla_put_u8(skb, IFLA_LINKMODE, READ_ONCE(dev->link_mode)) ||

回调函数 (待细看)

这部分代码要涉及到 netdev的callback链和 rtnetlin 消息通知。
是网卡设备状态变换后的消息通知和处理工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
1530 void netdev_state_change(struct net_device *dev)
1531 {
1532 if (dev->flags & IFF_UP) {
1533 struct netdev_notifier_change_info change_info = {
1534 .info.dev = dev,
1535 };
1536
1537 call_netdevice_notifiers_info(NETDEV_CHANGE,
1538 &change_info.info);
1539 rtmsg_ifinfo(RTM_NEWLINK, dev, 0, GFP_KERNEL, 0, NULL);
1540 }
1541 }
1542 EXPORT_SYMBOL(netdev_state_change);

另:
kernel: operate的帮助文档