总结
内核如何管理 网口上的IPv4 地址:
- 每个网口下面有多个IPv4地址,每个IP地址存放到一个
struct in_ifaddr的结构体里。这个结构体里存放别名、ip地址和掩码信息。 - 每个网口下面有一个单链表,每个ifa 节点通过
ifa_next指向下一个 ifa 节点。通过这个链表把多个 ifa 地址链接在一起。 - 每个 net_namespace下有多个网口,通过一个 hash 数据链表(
net->ipv4.inet_addr_lst),把各个网口下的全部ifa节点,放到这个hash链里面。hash值相同的 ifa 节点,通过addr_list挂在到一个hash 桶下。 - 别名并不会创建一个独立的网口,只是在同一个网口下增加一个对应的 ifa结构体并挂载到上面的两个链表中。跟通过ip link 命令给一个网口增加一个地址相同,唯一不同在于这个地址起了一个别名或者叫label。

命令行操作
有时候我们需要给一个网口配置的多个 IP 地址,这时候我们有两种配置方法:
- 使用 ifconfig 配置到网口别名上。
- 使用 ip addr 命令直接配置到网口上。
两种方法最终在内核实现是一样的,存储位置也一样,并且可以相互读写配置结果。
比如将9.9.9.199/24配置到 lo 口上,并起个别名lo:9
1 | [root@VM-0-12-centos ~]# ifconfig lo:9 9.9.9.199/24 |
ifconfig命令的配置结果,也可以通用ip link命令来查看
1 | [root@VM-0-12-centos ~]# ip a show dev lo |
网口的别名在ip命令里被当做label输出,放在scope字段后面
Q: ifconfig和ip命令在功能上有什么差别呢?
A: 从功能上没有什么不一样的地方,只是使用了内核的两套不同的接口,一般推荐使用ip命令来配置网口。
- ifconfig:使用
ioctl接口配置/读取网口上的IP地址 - ip addr:使用
netlink消息接口配置/读取网口上的IP地址
这里只要写ifconfig和ioctl相关的处理逻辑。ip命令如何实现对网口IP地址的配置,另外一篇文章总结。
ifcofig代码实现
源代码: https://github.com/giftnuss/net-tools/blob/master/ifconfig.c
ifconfig 首先创建一个AF_INET的socket,借助这个socket 通过,通过一个 ioctl 命令SIOCSIFADDR下发给内核。
配置IP 地址时候,参数被存放到struct ifreq结构体里,用来存放网口名字和 ip 地址.
1 | int main(int argc, char **argv) |
内核侧代码实现
内核实现是围绕着一个ifa结构体struct in_ifaddr展开的。
- 函数调用:内核的 ioctl系统调用函数,接收用户空间传回来的命令
SIOCSIFADDR和对应的struct ifreq变量, 里面存放着别名及 IP 地址。 - ifa结构体
struct in_ifaddr:ioctl通过几层抽象及 ops 的调用,最终在devinet_ioctl中, 创建一个struct in_ifaddr的结构体,存放别名(ifa_label)及IP 地址(ifa_address)。 - 同一个网口下多个IP地址:只要地址不冲突,可以配置多个 IP 地址。 并通过 ifa 结构体下面的
ifa_next组成一个单向链表。 - 同一个 ns(netnamespace)下,ifa 按照 hash 分散到一个 hash 桶链表里。
数据结构
ifa结构体in_ifaddr
1 | 143 struct in_ifaddr { |
函数调用栈
1 | SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) |
变量 packet_ops_spkt
内核基于文件的 ioctl的实现,最终会调用到packet_ioctl
1 | 4684 static const struct proto_ops packet_ops_spkt = { |
函数packet_ioctl
1 | 4285 static int packet_ioctl(struct socket *sock, unsigned int cmd, |
1 | 1087 const struct proto_ops inet_dgram_ops = { |
函数inet_ioctl
1 | 957 int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
函数 devinet_ioctl
别名(label)有:前后两部分, 前半部分是网口名字,后半部分是用户自定义的字符串。
- 根据前半部分网口名字找到对应的网口 netdev。
- 调用
inet_set_ifa,把 ifa 加入到对应的网口 netdev 下。
1 | 1071 int devinet_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr) |
inet_set_ifa:主要函数功能是调用inet_insert_ifa,把 ifa 插入到两个链表里。
1 | 590 static int inet_set_ifa(struct net_device *dev, struct in_ifaddr *ifa) |
函数 inet_insert_ifa
把ifa节点插入到两个list 里。
ifa_list单向链表: 同一个网口下的 IP 地址,插入到对应的ifa_list下的单向链表中。- hash桶链表:同一个ns(netnamespace)下的所有ifa 分散到一个hash链表中。根据计算出的 hash,挂载到
net->ipv4.inet_addr_lst[hash]这个桶下面。
1 | 499 static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh, |
1 | 129 static void inet_hash_insert(struct net *net, struct in_ifaddr *ifa) |
- 备注:2025更新到内核代码版本v6.14(v6.14-rc6-22-gb7f94fcf5546)