内核如何管理同网口下多个ip地址(网口别名)

总结

内核如何管理 网口上的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

[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命令来查看

[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地址
    这里只要写ifconfigioctl相关的处理逻辑。ip命令如何实现对网口IP地址的配置,另外一篇文章总结。

ifcofig代码实现

源代码: https://github.com/giftnuss/net-tools/blob/master/ifconfig.c
ifconfig 首先创建一个AF_INET的socket,借助这个socket 通过,通过一个 ioctl 命令SIOCSIFADDR下发给内核。
配置IP 地址时候,参数被存放到struct ifreq结构体里,用来存放网口名字和 ip 地址.

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
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];
... };

函数调用栈

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

内核基于文件的 ioctl的实现,最终会调用到packet_ioctl

4684 static const struct proto_ops packet_ops_spkt = {
...
4694         .ioctl =        packet_ioctl,

函数packet_ioctl

4285 static int packet_ioctl(struct socket *sock, unsigned int cmd,
4286                         unsigned long arg)
4316         case SIOCSIFADDR:
...
4324                 return inet_dgram_ops.ioctl(sock, cmd, arg);
1087 const struct proto_ops inet_dgram_ops = {
...
1097         .ioctl             = inet_ioctl,
...

函数inet_ioctl

 957 int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 958 {
...
 993         case SIOCSIFADDR:
...
 998         case SIOCSIFFLAGS:
 999                 if (get_user_ifreq(&ifr, NULL, p))
1000                         return -EFAULT;
1001                 err = devinet_ioctl(net, cmd, &ifr);

函数 devinet_ioctl

别名(label)有`:前后两部分, 前半部分是网口名字,后半部分是用户自定义的字符串。

  • 根据前半部分网口名字找到对应的网口 netdev。
  • 调用`inet_set_ifa·,把 ifa 加入到对应的网口 netdev 下。
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

inet_set_ifa:主要函数功能是调用inet_insert_ifa,把 ifa 插入到两个链表里。

 590 static int inet_set_ifa(struct net_device *dev, struct in_ifaddr *ifa)
 591 {
...
 599         return inet_insert_ifa(ifa);
 600 }

函数 inet_insert_ifa

ifa节点插入到两个list 里。

  • ifa_list单向链表: 同一个网口下的 IP 地址,插入到对应的ifa_list下的单向链表中。
  • hash桶链表:同一个ns(netnamespace)下的所有ifa 分散到一个hash链表中。根据计算出的 hash,挂载到net->ipv4.inet_addr_lst[hash]这个桶下面。
 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);
 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 }
  • 备注:2025更新到内核代码版本v6.14(v6.14-rc6-22-gb7f94fcf5546)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值