内核如何管理 ip 地址
命令行操作 有时候我们需要给一个网口配置的有个 IP 地址,这时候我们有两种配置方法:
使用 ifconfig 配置到网口别名上。
使用 ip addr 命令直接配置到网口上。
两种方法最终在内核实现是一样的,存储位置也一样,并且可以相互读写配置结果。
比如将9.9.9.199/24
配置到 lo 口上,并起个别名lo:9
1 2 3 4 5 6 [root@VM-0-12-centos ~]# ifconfig lo:9 9.9.9.199/24 [root@VM-0-12-centos ~]# ifconfig lo:9 lo:9: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 9.9.9.199 netmask 255.255.255.0 loop txqueuelen 1000 (Local Loopback) [root@VM-0-12-centos ~]#
ifconfig命令的配置结果,也可以通用ip link
命令来查看
1 2 3 4 5 6 7 8 9 [root@VM-0-12-centos ~]# ip a show dev lo 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet 9.9.9.199/24 scope global lo:9 valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
网口的别名在ip命令里被当做label
输出,放在scope字段后面
Q: ifconfig和ip命令在功能上有什么差别呢? A: 从功能上没有什么不一样的地方,只是使用了内核的两套不同的接口,一般推荐使用ip命令来配置网口。
ifconfig:使用ioctl
接口配置/读取网口上的IP地址
ip addr:使用netlink
消息接口配置/读取网口上的IP地址 这里只要写ifconfig
和ioctl
相关的处理逻辑。ip
命令如何实现对网口IP地址的配置,另外一篇文章总结。
内核对网口 IP 地址的管理 概述
每个ip地址,在内核里有一个struct in_ifaddr
的结构体相对应。这个结构体挂在在每个网口设备下,并且通过一个单向链表(ifa_next
)链接在一起。
同时把同 ns 下的所有的in_ifaddr
挂在到一个 hash桶链表(net->ipv4.inet_addr_lst
)里。
每个ifa 地址里除了上面说的两个链的挂载点,还有保存着ip地址,掩码和label。 其中label对应着ifconfig指定的网口别名。
ifconfig命令对网口地址的管理 ifcofig代码实现 源代码: https://github.com/giftnuss/net-tools/blob/master/ifconfig.c
ifconfig 首先创建一个AF_INET
的socket,借助这个socket 通过,通过一个 ioctl 命令SIOCSIFADDR
下发给内核。 配置IP 地址时候,参数被存放到struct ifreq
结构体里,用来存放网口名字和 ip 地址.
1 2 3 4 5 6 7 int main(int argc, char **argv) {... memcpy((char *) &ifr.ifr_addr, (char *) &sa, sizeof(struct sockaddr)); ... fd = get_socket_for_af(AF_INET); ... r = ioctl(fd, SIOCSIFADDR, &ifr);
内核侧代码实现 内核实现是围绕着一个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 2 3 4 5 6 7 143 struct in_ifaddr { 144 struct hlist_node addr_lst; 145 struct in_ifaddr __rcu *ifa_next; 146 struct in_device *ifa_dev; ... 157 char ifa_label[IFNAMSIZ]; ... };
函数调用关系 1 2 3 4 5 6 7 8 9 10 11 SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) => vfs_ioctl(fd_file(f), cmd, arg); => => filp->f_op->unlocked_ioctl //相当于sock_ioctl => => => sock_do_ioctl => => => => ops->ioctl(sock, cmd, arg);// 等效与packet_ioctl => => => => => packet_ioctl => => => => => => inet_ioctl => => => => => => => devinet_ioctl => => => => => => => => __inet_insert_ifa => => => => => => => => => ifa插入到in_dev->ifa_list => => => => => => => => => inet_hash_insert(dev_net(in_dev->dev), ifa);
packet_ops_spkt
1 2 3 4684 static const struct proto_ops packet_ops_spkt = { ... 4694 .ioctl = packet_ioctl,
packet_ioctl
1 2 3 4 5 6 7 8 9 10 11 4285 static int packet_ioctl(struct socket *sock, unsigned int cmd, 4286 unsigned long arg) 4316 case SIOCSIFADDR: 4317 case SIOCGIFBRDADDR: 4318 case SIOCSIFBRDADDR: 4319 case SIOCGIFNETMASK: 4320 case SIOCSIFNETMASK: 4321 case SIOCGIFDSTADDR: 4322 case SIOCSIFDSTADDR: 4323 case SIOCSIFFLAGS: 4324 return inet_dgram_ops.ioctl(sock, cmd, arg);
inet_dgram_ops
1 2 3 4 1087 const struct proto_ops inet_dgram_ops = { ... 1097 .ioctl = inet_ioctl, ...
inet_ioctl
1 2 3 4 5 6 7 8 9 10 11 12 957 int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) 958 { ... 993 case SIOCSIFADDR: 994 case SIOCSIFBRDADDR: 995 case SIOCSIFNETMASK: 996 case SIOCSIFDSTADDR: 997 case SIOCSIFPFLAGS: 998 case SIOCSIFFLAGS: 999 if (get_user_ifreq(&ifr, NULL, p)) 1000 return -EFAULT; 1001 err = devinet_ioctl(net, cmd, &ifr);
devinet_ioctl
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 1071 int devinet_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr) 1072 { 1073 struct sockaddr_in sin_orig; 1074 struct sockaddr_in *sin = (struct sockaddr_in *)&ifr->ifr_addr; ... 1088 colon = strchr(ifr->ifr_name, ':'); 1089 if (colon) 1090 *colon = 0; ... 1132 dev = __dev_get_by_name(net, ifr->ifr_name); 1133 if (!dev) 1134 goto done; 1135 1136 if (colon) 1137 *colon = ':'; ... 1211 case SIOCSIFADDR: /* Set interface address (and family) */ ... 1224 if (colon) 1225 memcpy(ifa->ifa_label, ifr->ifr_name, IFNAMSIZ); 1226 else 1227 memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); ... 1236 1237 ifa->ifa_address = ifa->ifa_local = sin->sin_addr.s_addr; ... 1250 set_ifa_lifetime(ifa, INFINITY_LIFE_TIME, INFINITY_LIFE_TIME); 1251 ret = inet_set_ifa(dev, ifa); 1252 break; 1253
把ifa插入到链表中 ifa
被插入到两个list 里。
ifa_list
单向链表: 同一个网口下的 IP 地址,插入到对应的ifa_list
下的单向链表中。
hash桶链表:同一个ns(netnamespace)下的所有ifa 分散到一个hash链表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 499 static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh, 500 u32 portid, struct netlink_ext_ack *extack) 501 { ... 517 ifap = &in_dev->ifa_list; 518 ifa1 = rtnl_dereference(*ifap); 519 520 while (ifa1) { ... 538 ifap = &ifa1->ifa_next; 539 ifa1 = rtnl_dereference(*ifap); 540 } ... 563 rcu_assign_pointer(ifa->ifa_next, *ifap); 564 rcu_assign_pointer(*ifap, ifa); 565 566 inet_hash_insert(dev_net(in_dev->dev), ifa);
1 2 3 4 5 6 7 129 static void inet_hash_insert(struct net *net, struct in_ifaddr *ifa) 130 { 131 u32 hash = inet_addr_hash(net, ifa->ifa_local); 132 133 ASSERT_RTNL(); 134 hlist_add_head_rcu(&ifa->addr_lst, &net->ipv4.inet_addr_lst[hash]); 135 }
备注:内核代码版本v6.14(v6.14-rc6-22-gb7f94fcf5546)