IP link
命令结果里的网口状态标志位
我们先来看几个场景里,不同状态的网口,输出的标志位有哪些不一样。
正常状态下物理网口eth0 的输出
命令行ip link show dev eth0
会输出网口eth0的一些状态标志位。
这里注意一个现象, ‘ifconfig’命令显示的结果里有’RUNNING’标志位, 但是’ip link’命令的结果里没有这个标志位。
场景2:拔掉物理口的网线后, 网口的状态变化。
网口状态标志位里有个明显变化,多了一个’NO-CARRIER’
场景3:veth口 UP/DOWN的状态变化
veth口在对端veth0口down状态下,veth1 down/up状态下的对比
通过结果,我们注意到,’ip link’命令输出的结果里,跟状态有关系的有两个地方,在图中用红色标记出来。
一部分是刚开始尖括弧里的值,一部分是 ‘state XX’,
这里有几个疑问?
NO_CARRIER
标志位:为什么拔掉网线时候,ip link会显示这个标志位,正常状态下不会显示CARRIER——OK之类消息?RUNNING
标志位:为什么ifconfig命令显示有这个标志位,而ip link 命令没有显示出来。UP,LOWER_UP
: 标志位里这两个标记分别代表的意义是什么?有啥区别?state UP
: 这个’UP’标记跟前面的UP标是不是重复的?
结论:
这两组状态都是从内核里读取过来的,分别对应内核里的两个状态标记,dev->flags
和dev->operate
在具体实现实现时候,还要涉及另外一个状态标记dev->state
。
网口状态 | 网线 | ip 命令 | flag标记 | state(内核 operate 标记) |
---|---|---|---|---|
普通以太网口 | 正常 | link up | UP,LOWER_UP,有 RUNNING不展示 | UP(内核 IF_OPER_UP) |
普通以太网口 | 拔线 | link up | NO-CARRIER,UP | DOWN(IP_OPER_DOWN) |
普通以太网口 | 正常/拔线 | link down | DOWN | DOWN(IP_OPER_DOWN) |
veth1 | veth0 up | veth1 up | UP | UP(内核 IF_OPER_UP) |
veth1 | veth0 down | veth1 up | NO-CARRIER, UP, M-DOWN | LOWERLAYERDOWN |
veth1 | veth0 down | veth1 down | M-DOWN | DOWN(内核 IF_OPER_DOWN) |
下面我们详细讲述下, ip link
命令如何把展示这些标志位从内核里读出来,以及这些标志位在内核里的相关关系.
dev->flags标志位的定义
我们这里关注几个标志位
NO-CARRIER
IFF_UP
IFF_RUNING
IFF_LOWER_UP
IFF_DORMANT
根据这些标志位的来源和产生方式,又可以把这些标志位分为3类
- ‘IP link’类:根据内核返回的设备状态,’ip link’命里自己打印的,内核里并没有这一类标志。 如’NO-CARRIER’.。
- ‘netdev->flags’类: 直接读取的内核里,对应网口的netdev结构体里的flags标志位。如
IFF_UP
, ``IFF_PROMISC’等。` - 内核合成类:根据内核网口对应的netdev下的,operate和state 状态合成的。
IPF_XXX
flags定义
1 | 80 enum net_device_flags { |
ip link
命里如何打印这些标记的?
ip link命令通过netlink 消息跟内核交互,以获取内核里 netdevice 的状态,然后跟进内核返回状态,打印输出网口标志位和状态值。
可以分为以下几部:
- 构建一个netlink scoekt 并发送命令,发送给内核,
- 内核根据命令,读取网口状态并发送相应消息,返回给
ip link
命令。 ip link
根据内核返回结果,输出网口flags和状态
具体函数调用关系如下,最终函数print_link_flags
输出网口的 flags,print_operstate
输出网卡operate状态state UP
1 | => main / iplink.c |
标志位NO_CARRIER
这个标志位是 ip link 特殊处理的,跟IFF_RUNNING
一起讲。
标志位IFF_UP
标志位IFF_UP
: ip link命令 up/down 网口
ip link set eth0 up
会分别设置内核里 eth0 口对应的 netdevice 设备上的 flags 标志位IFF_UP
代码实现
在 ip link 命令实现里, 是通过ioctol获取eth0 口对应的 flags,然后将IFF_UP
标志位设置到 flags 上,再通过ioctol 命令SIOCSIFFLAGS
下发会内核。1
2
3
4
5
6
7
8
9static 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命令参数里,获取和设置的命令名字, 只有一个字母
G
和S
的差别。1
2#define SIOCGIFFLAGS 0x8913 /* get flags */
#define SIOCSIFFLAGS 0x8914 /* set flags */
标志位IFF_RUNNING
和 NO-CARRIER
总结: IFF_UP
+ !IFF_RUNNING
<==> NO-CARRIER
两者是等价的。
ip link 命令根据内核返回的网口属性里的ifi_flags
标志进行打印。
NO-CARRIER
:如果网口处于IFF_UP
状态, 但是没有IFF_RUNNING
标志,ip link命令首先就会打印一个NO-CARRIER
标志。IFF_RUNNING
:ip link
后续的处理中,不再考虑IFF_RUNNING
标志位。因此即使内核携带IFF_RUNNING
标记位,在 iproute2 工具中不再显示的输出它。这与ifconfig的处理是不一样的。
反向推导一下ip link
输出了IFF_UP
,但没有NO-CARRIER
,则网口 eth0 其实处于IFF_RUNNING
的
1 | 1003 |
1 | 83 static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown) |
标志位 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
标记
1 | 9191 unsigned int dev_get_flags(const struct net_device *dev) |
+注意:内核的’netif_running’跟用户态’ip link’命令里用到的’RUNNING’标志位含义是完全不一样的。
标志位 IFF_DORMANT
DORMANT
在我工作中没有接触到,看文档是 wifi 类的网口会使用,这里没有具体验证,只描述理解。
应用场景:wifi 网口, 当 wifi 网口连接到路由器后,记为 T1(carrier OK),跟以太网网口不一样的是,在物理载质检测通过后, wifi 口有时候还需要做一些安全认证, 也就是咱们平时配置 wifi 密码的哪些上层身份验证。DORMANT
感觉翻译为匿名
比较合适,因为没有还没有通过身份校验。
具体代码有机会在展开分析。对以太网网口没有意义。
operater 标志位 IF_OPER_LOWERLAYERDOWN
veth1口的对端网口是veth0。 veth0没有UP时候,veth1口的operater就会显示为更IF_OPER_LOWERLAYERDOWN
.
这样可以更精确的知道 vet1 口是因为底层网口(对端网口,没有carrier_ok导致,veth 口只要UP就carrier_ok)
1 | 36 static unsigned int default_operstate(const struct net_device *dev) |
TODO M-DOWN
==备注:源代码引用的内核版本:v6.14
==