vlambda博客
学习文章列表

ip addr命令实现源码分析

执行命令ip addr  show  dev eth2

执行strace 查看相应的系统调用如下

root@TopSecSystem:~/iproute2-5.6.0/tc\>strace ip addr show dev eth2execve("/sbin/ip", ["ip", "addr", "show", "dev", "eth2"], [/* 22 vars */]) = 0brk(0) = 0x848000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d3c000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=32941, ...}) = 0mmap(NULL, 32941, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f3c85d33000close(3) = 0open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\16`U0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=17384, ...}) = 0mmap(0x3055600000, 2109712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3055600000mprotect(0x3055603000, 2093056, PROT_NONE) = 0mmap(0x3055802000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x3055802000close(3) = 0open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\35\"U0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1723928, ...}) = 0mmap(0x3055200000, 3832352, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3055200000mprotect(0x305539f000, 2093056, PROT_NONE) = 0mmap(0x305559e000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19e000) = 0x305559e000mmap(0x30555a4000, 14880, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x30555a4000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d32000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d31000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d30000arch_prctl(ARCH_SET_FS, 0x7f3c85d31700) = 0mprotect(0x3055802000, 4096, PROT_READ) = 0mprotect(0x305559e000, 16384, PROT_READ) = 0mprotect(0x305381f000, 4096, PROT_READ) = 0munmap(0x7f3c85d33000, 32941) = 0socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, 0) = 3setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0getsockname(3, {sa_family=AF_NETLINK, pid=27974, groups=00000000}, [12]) = 0access("/proc/net", R_OK) = 0access("/proc/net/unix", R_OK) = 0socket(PF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 4ioctl(4, SIOCGIFINDEX, {ifr_name="eth2", ifr_index=4}) = 0close(4) = 0sendto(3, "(\0\0\0\22\0\1\3@\2505`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 40, 0, NULL, 0) = 40recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"0\4\0\0\20\0\2\0@\2505`Fm\0\0\0\0\4\3\1\0\0\0I\0\1\0\0\0\0\0"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 3240brk(0) = 0x848000brk(0x869000) = 0x869000recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"@\4\0\0\20\0\2\0@\2505`Fm\0\0\0\0\1\0\4\0\0\0C\20\1\0\0\0\0\0"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 1088recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\24\0\0\0\3\0\2\0@\2505`Fm\0\0\0\0\0\0", 16384}], msg_controllen=0, msg_flags=0}, 0) = 20sendto(3, "(\0\0\0\26\0\1\3A\2505`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 40, 0, NULL, 0) = 40recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"D\0\0\0\24\0\2\0A\2505`Fm\0\0\2\10\200\376\1\0\0\0\10\0\1\0\177\0\0\1"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 148recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"@\0\0\0\24\0\2\0A\2505`Fm\0\0\n\200\200\376\1\0\0\0\24\0\1\0\0\0\0\0"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 128recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\24\0\0\0\3\0\2\0A\2505`Fm\0\0\0\0\0\0", 16384}], msg_controllen=0, msg_flags=0}, 0) = 20fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(3, 1), ...}) = 0ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d3b000write(1, "4: eth2: <BROADCAST,MULTICAST,UP"..., 884: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000) = 88write(1, " link/ether 00:0c:29:43:96:78"..., 55 link/ether 00:0c:29:43:96:78 brd ff:ff:ff:ff:ff:ff) = 55write(1, " inet 192.168.74.55/24 brd 19"..., 63 inet 192.168.74.55/24 brd 192.168.74.255 scope global eth2) = 63write(1, " valid_lft forever preferr"..., 47 valid_lft forever preferred_lft forever) = 47open("/etc/iproute2/rt_scopes", O_RDONLY) = 4fstat(4, {st_mode=S_IFREG|0644, st_size=92, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d3a000read(4, "#\n# reserved values\n#\n0\tglobal\n2"..., 4096) = 92read(4, "", 4096) = 0close(4) = 0munmap(0x7f3c85d3a000, 4096) = 0write(1, " inet6 fe80::20c:29ff:fe56:77"..., 70 inet6 fe80::20c:29ff:fe56:7771/64 scope link tentative dadfailed ) = 70write(1, " valid_lft forever preferr"..., 47 valid_lft forever preferred_lft forever) = 47exit_group(0) = ?+++ exited with 0 +++

源码分析

ip 对应的命令及主要实现接口定义在struct cmd结构中,如下

ip addr命令实现源码分析

今天我们主要分析ip addr  show 命令, 所以对应的接口实现为do_ipaddr。

进入main函数,在执行具体的流程分支前,代码会先初始化netlink套接字信息,流程如下:

ip addr命令实现源码分析

rtnl_open的接口实现为

ip addr命令实现源码分析

rtnl_open_byproto接口的实现为

int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, int protocol){ socklen_t addr_len; int sndbuf = 32768; int one = 1; memset(rth, 0, sizeof(*rth)); rth->proto = protocol; rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); if (rth->fd < 0) { perror("Cannot open netlink socket"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) { perror("SO_SNDBUF"); return -1; }
if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) { perror("SO_RCVBUF"); return -1; }
/* Older kernels may no support extended ACK reporting */ setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one));
memset(&rth->local, 0, sizeof(rth->local)); rth->local.nl_family = AF_NETLINK; rth->local.nl_groups = subscriptions;
if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) < 0) { perror("Cannot bind netlink socket"); return -1; } addr_len = sizeof(rth->local); if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { perror("Cannot getsockname"); return -1; } if (addr_len != sizeof(rth->local)) { fprintf(stderr, "Wrong address length %d\n", addr_len); return -1; } if (rth->local.nl_family != AF_NETLINK) { fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family); return -1; } rth->seq = time(NULL); return 0;}

通过cmd定义,ip addr命令对应的实现为 do_ipaddr接口中。继续分析do_ipaddr接口实现

ip addr命令实现源码分析

根据ip addr 命令后携带参数类型及数量,执行不同的代码分支。根据代码可以知道,在终端执行 ip addr 、ip addr show、 ip addr list或者 ip addr lst效果一致。

我们刚开始执行的命令为ip  addr  show  dev  eth2,就会进入ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_LIST);分支。继续分析ipaddr_list_flush_or_save接口,忽略掉不相关代码,  程序执行

ip addr命令实现源码分析

ip addr命令实现源码分析

ip addr命令实现源码分析

分析代码,最终获取eth2网口信息的实现在 ipaddr_link_get接口中,实现如下:

ip addr命令实现源码分析

ip addr命令实现源码分析

最后遍历linfo链表打印到终端,代码如下:

ip addr命令实现源码分析

应用

正所谓它山之石可以攻玉,学习的目的在于应用,如果我们在实际的项目开发中,需要通过netlink接口获取内核信息, 完全可以参考iproute2的代码实现, 但是我们完全没有必要自己全部分装netlink的各个与内核交互的接口和信息, 通过分析iproute2源码的makefile文件和目录结构如下:

我们完全只需要将include和lib拷贝到自己的工程中,如果需要获取ip相关信息,那只需要调用lib提供的相关接口,再参考ip目录提供的实现例子对获取的信息进行解析即可。

lib目录中提供了两个静态库,名字为libnetlink.a 和 libutil.a。Makefile实现如下,如果读者习惯使用动态库,可以稍加修改Makefile脚本,编译为动态库即可。

include目录提供了相关接口的声明。