常见问题
我们在平常定位问题,经常会通过ip link
或ifconfig
查看网络环境和配置。其中网口的标志位是其中很重要的一个检查环节。在输出结果里,会有两组标志位信息
- 一组是尖括弧里包含的值
<BROADCAST,MULTICAST,UP,LOWER_UP\>
,有很多标志位的组合。 - 另一组是
state UP
。还有其他状态如DOWN
,LOWERLAYERDOWN
等。
这些信息有时候看起来是一致的, 有时候是重复的,甚至矛盾的。难免让人产生一些疑惑,这两组标志位分别代表什么?他们是什么关系?下面我们就这两组标志,逐个展开分析下。
我们先来看几个场景里,网口在不同状态下,输出的标志位有哪些差异。
场景1:网线没插,显示NO-CARRIER
- 【Q1】 网口状态标志位里有个明显特征,有个
NO-CARRIER
标志,但是状态里一个UP
,一个DOWN
,why? :(
场景2: 正常状态下物理口eth0 的输出
ip link show dev eth0
命令输出网口eth0的一些状态标志位。
这里有几个疑问:
- 【Q2】
RUNNING
标志位:为什么ifconfig命令显示有这个标志位,而ip link 命令没有显示出来。
或者说,我们通过ip link
命令能确认网口是RUNNING
吗?还是只能通过ifconfig
命令查看。 - 【Q3】
LOWER_UP
标志位:UP
紧跟个LOWER_UP
. 标志位里这两个标记分别代表的意义是什么?有啥区别?是不是重复了?
场景3:veth口 UP/DOWN的状态变化
veth: 在对端veth0
口down状态下,veth1
down/up状态下的对比
- 通过ip link命令创建一对veth口。默认网口状态down。
- 【Q4】
M-DOWN
:veth0口上第一组状态标志位里是M-DOWN
(不是DOWN
), 而第二组是state DOWN
- 设置
veth1
网口UP
veth1被UP后,有两个变化:
M-DOWN
:veth0口上的M-DOWN
消失了, veth1口UP后反而没有消失LOWERLAYERDOWN
: veth1口UP后, state没有变成UP而是LOWERLAYERDOWN
这里就带来个问题:
- 【Q5】
M-DOWN
state LOWERLAYERDOWN
跟UP/DOWN
的关联关系是什么,有哪些区别?
标记汇总:
<flags>
UP
: 可以通过ip link set dev xx up
设置,系统管理员启用某个网口,不代表网口可以正常使用。DOWN
:与UP
类似。通过ip命里停用某个网口。 停用后,网口无法收发包,设置可以因为节能,将设备断电。NO-CARRIER
: 如果是物理网口,网口上网线没有插好。如果是虚拟口,关联网口的状态不正常。如veth情况下,对端网口没有被UPRUNNING
: 通过ifconfig命令才能看到。在ip link命令不显示这个标志位。在 iplink 命令里如果网口有UP
标志,没有NO-CARRIER
,那么ifconfig一定能看到这个标志。反之也成立, 如果网口有UP
和NO-CARRIER
标志两个标志, ifconfig肯定看不到RUNNING
。LOWER_UP
: 本意是指当网口被UP
后,网口底层底层载体是正常的,如以太网口的网线是插好的(wifi情况是指信道是好的?待学习)。 对eth口来说,有``LOWER_UP, 等价与有
RUNNING`DORMANT
:M-DOWN
: 当网口有配对端口时,对端网口如果没有UP
标记,则本端网口会显示这个标记。 若 veth0/1配对, veth0如果是DOWN
, 则 veth1 口的状态里会有这个标记。
operate
state UP
: 代表网口可以正常使用。 对eth口,等于flag里的UP
和RUNNING
同时存在。state DOWN
: 代表网口不可用。有可能是flags的标志位DOWN
了, 也有可能是网口被UP
但是状态不正常(如NO-CARRIER
或者 wifi 网口的认证不通过)。state LOWERLAYERDOWN
:当网口配对使用时候,本端网口被UP
, 但是对端网口没有UP
时候,会显示这个状态。对veth口来说等同于UP
和M-DOWN
同时存在。state DORMANT
: 跟移动网络和节能相关。 工作中没有遇不到这种场景,现在只能贴英文, 待学习。state DORMANT
: A state in which the mobile restricts its ability to receive normal IP traffic by reducing monitoring of radio
channels. This allows the mobile to save power and reduces signaling load on the network.`
以上提到的UP
和DOWN
,都是指 flags 标志位(<>
)里的,不要跟下面的 state 标志混了。
标志位分类
IP link标志位分类
根据这些标志位的来源, 我们可以把他们分为三大类:
前两类都是在<XX, ...>
里打印的, 第三类是在 state XX
里打印的。
ip link
类:ip link
命令自己打印的,不是内核里携带过来的。这一类比较特殊,比如NO_CARRIER
和M-DOWMN
,flags
类:根据内核里网口的dev->flags
对应的标志位转换而来的。 如IFF_UP
,IFF_PROMISC
等。operstate
类:与内核里对应网口下的dev->operstate
一致。在第二组状态里,以state XXX
形式显示。内核里网口的operstate是根据dev->state
状态和网口类型等条件转换而来。
内核网口的3个状态变量
这两组状态都是从内核里读取过来的,分别对应内核里的两个状态标记,dev->flags
和dev->operstate
在具体实现实现时候,还要涉及另外一个状态标记dev->state
dev->flags
:每个位代表一个标志。dev->operstate
:dev->state
:
ip link命令通过netlink消息跟内核交互,通过sendmsg下发req请求(命令), 通过recvmsg获取内核返回结果(命令结果)。解析结果,以获取内核里网口(netdevice)的状态。根据内核返回网口状态信息,打印输出网口的标志位(falgs)和状态值(operstate)。
可以分为以下几步:
- 构建一个
netlink socket
,并通过它往内核发送一个获取内核网口状态的命令, - 内核根据命令,读取网口状态并发送一个响应消息,通过
netlink socket
返回给ip link
命令。 ip link
根据内核返回结果,输出网口flags
和状态
netlink命令的实现机制涉及的内容很多,我们这里不展开讲解netlink在内核如何实现。只关注调用链两端的实现。即:
- ip命令如何解析并打印网口状态
- 内核如何搜集并发回网口状态
后续我们按照标志位分类顺序,结合ip link show eth0
命令,逐个解析各个标志位在两端(内核和 ip link)的实现。
ip link
命令的实现
函数调用栈
当执行命令ip link show eth0
时候,具体函数调用关系如下:
1 | main / iplink.c |
ip link
命令, 因为没有指定具体网口,参数不同, 它的调用栈跟这个略有不同。
1 | main / iplink.c |
但最终都会调用print_linkinfo
去处理netlink返回消息,逐个打印返回消息里的网口信息。这里不展开讨论。
主要函数
其中处理状态标志位的两个主要函数:函数print_link_flags
和print_operstate
- 函数
print_link_flags
输出网口的flags。如<BROADCAST,MULTICAST,UP,LOWER_UP\>
。 - 函数
print_operstate
网卡operstate状态。 如state UP
。
print_link_flags
1 | 83 static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown) |
这个函数会打印<>
中全部的标志位,因此我们后续拆分成几部分解释:
NO_CARRIER
标志位:RUNNING
标志位:UP
、LOWER_UP
、DORMANT
标志位M-DOWN
标志位:
print_operstate
1 | 124 static void print_operstate(FILE *f, __u8 state) |
这个函数会打印state XX
这个状态信息,具体数值是从内核传递回来的, 使用oper_states
做了数值到状态字符串的转换。
1 | 119 static const char *oper_states[] = { |
NO_CARRIER
标志位
在函数print_link_flags
里,ip link命令根据内核返回的网口属性里的ifi_flags
标志进行打印,实现了NO_CARRIER
标志位的输出。
这个标志位比较特殊:不是内核里自带的,而是ip link
根据返回结果自己追加的。
只有在网口UP
并且没有RUNNING
时,ip link
就会打印NO_CARRIER
,这个标志。
或者说:
+ 如果网口被UP
了,没有NO_CARRIER
,那说明网口是RUNNING
状态。此时是CARRIER_OK
(现实中没有这个标志位)
+ 如网口被DOWN
了(NOT UP
),RUNNING
标志位肯定就没有了。
1 | 86 if (flags & IFF_UP && !(flags & IFF_RUNNING)) |
【Q】 这里有个问题解释下:
CARRIER
本意是表示网口上网线状态。现实中,网线是否插好,跟网口是否被UP
或者DOWN
没直接关系。
为什么必须网口被UP
后,才能显示?网口被DOWN
的时候,为什么不考虑显示?
A:【待完善】有些类型的物理网卡,当网口DOWN
了,会做节能断电处理。 此时设备上没有光电信号,不可能去做网口的信号检测,因此不能判断光纤或者网线是否插好。
RUNNING
标志位
【Q】为什么ip link命令,输出的网口状态标志位的列表里,看不到标志位RUNNING
?
接前面NO_CARRIER
的处理,print_link_flags
处理完NO_CARRIER
标志位的后,
就会把抹掉标志位IFF_RUNNING
,不再处理。(见代码第89行)
因此即使内核携带IFF_RUNNING
标记位返回给ip命令,在 iplink命令中也不会显示RUNNING
标志位。这与ifconfig的处理是不一样的。
1 | 89 flags &= ~IFF_RUNNING; |
UP``LOWER_UP ``DORMANT
标志位
这三个标志位处理比较简单, 都是根据内核里返回的标志位,打印对应的标记字符串。_PF
的解释:
每次打印一个标志位前,先清除掉这个标志位,再打印。
打印时候,如果是最后一个标志(flags
为0), 则只打印状态字符串(格式”%s”),否选择要多个,
(格式”%s”,)。用,
分割多个标志位。
1 | 90 #define _PF(f) if (flags&IFF_##f) { \ |
M-DOWN
标志位:
结论:当前网口的peer端口,没有UP时候,会在当前接口的<>
里打印这个标志。
print_name_and_link
:根据内核上传的网口的iflink的序号(IFLA_LINK属性),查找到iflink对应的关联网口peer, 并返回peer网口的flags
(m_flag)。print_link_flags
: 如果peer网口没有IFF_UP
,打印M-DOWN
标记。
1 | 962 int print_linkinfo(struct nlmsghdr *n, void *arg) |
print_name_and_link
1 | 1269 unsigned int print_name_and_link(const char *fmt, |
1 | 83 static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown) |
内核的网口状态标志位
struct net_device
的相关定义
- 每个网口在内核里有对应的
struct net_device
结构体。1
2
3
4
5
62080 struct net_device {
...
2127 unsigned long state;
2128 unsigned int flags;
...
2232 unsigned int operstate;
标志位netdev->flags
每个netdev
设备里有一个上的flags用来存放标志位,我们这里只关注up/down相关的几个标志位
IFF_UP
IFF_RUNING
IFF_LOWER_UP
IFF_DORMANT
定义
1 | 80 enum net_device_flags { |
状态获取函数
1 | 9191 unsigned int dev_get_flags(const struct net_device *dev) |
标志位IFF_RUNNING
放到后面跟netif_oper_up
一起解释。
标志位IFF_LOWER_UP
在内核里,IFF_LOWER_UP
标志位被置位的两个前提条件:
- 网口处于
UP
状态: 即前面解释的IFF_UP
标志位被设置 - 网口的载质(网线) OK: 内核判断函数netif_carrier_ok为
TRUE
,即dev->state
上没有__LINK_STATE_NOCARRIER
标志位
对应场景:以太网网口eth0,被管理员通过 ip link 命令设置UP
后,并且网线正常接入对端交换机(交换机状态正常加电,且交换机上对应网口也被 UP)
当内核网口flags上有这个标志位时候,ip link
打印网口标志位时候就会打印LOWER_UP
标记。
这个等同于ifconfig命令里的RUNNING
标记
注意: 内核的netif_running
跟用户态ip link
命令里用到的RUNNING
标志位含义是完全不一样的。netif_running
在网口up时候,被设置标志位__LINK_STATE_START
标志位 IFF_DORMANT
DORMANT
在我工作中没有接触到,看文档是wifi类的网口会使用,这里没有具体验证,只描述理解。
应用场景:wifi 网口, 当 wifi 网口连接到路由器后,记为 T1(carrier OK),跟以太网网口不一样的是,在物理载质检测通过后, wifi 口有时候还需要做一些安全认证, 也就是咱们平时配置 wifi 密码的哪些上层身份验证。DORMANT
感觉翻译为匿名
比较合适,因为没有还没有通过身份校验。
具体代码有机会在展开分析。对以太网网口没有意义。
netdev->state
和netdev->operstate
这部分我们放到后面part2里详细讲述。
operstate的小插曲
2006年内核引入operstate
特性,在当时协议栈的维护者中也是颇有争议的!!!
引入operstat特性的patch
==注:更新源代码的内核版本:v6.14
==