ip addr命令实现源码分析
执行命令ip addr show dev eth2
执行strace 查看相应的系统调用如下
root@TopSecSystem:~/iproute2-5.6.0/tc\>strace ip addr show dev eth2
execve("/sbin/ip", ["ip", "addr", "show", "dev", "eth2"], [/* 22 vars */]) = 0
brk(0) = 0x848000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d3c000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=32941, ...}) = 0
mmap(NULL, 32941, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f3c85d33000
close(3) = 0
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(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) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=17384, ...}) = 0
mmap(0x3055600000, 2109712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3055600000
mprotect(0x3055603000, 2093056, PROT_NONE) = 0
mmap(0x3055802000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x3055802000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(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) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1723928, ...}) = 0
mmap(0x3055200000, 3832352, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3055200000
mprotect(0x305539f000, 2093056, PROT_NONE) = 0
mmap(0x305559e000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19e000) = 0x305559e000
mmap(0x30555a4000, 14880, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x30555a4000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d32000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d31000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d30000
arch_prctl(ARCH_SET_FS, 0x7f3c85d31700) = 0
mprotect(0x3055802000, 4096, PROT_READ) = 0
mprotect(0x305559e000, 16384, PROT_READ) = 0
mprotect(0x305381f000, 4096, PROT_READ) = 0
munmap(0x7f3c85d33000, 32941) = 0
socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, 0) = 3
setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0
setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0
bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
getsockname(3, {sa_family=AF_NETLINK, pid=27974, groups=00000000}, [12]) = 0
access("/proc/net", R_OK) = 0
access("/proc/net/unix", R_OK) = 0
socket(PF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 4
ioctl(4, SIOCGIFINDEX, {ifr_name="eth2", ifr_index=4}) = 0
close(4) = 0
sendto(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) = 40
recvmsg(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) = 3240
brk(0) = 0x848000
brk(0x869000) = 0x869000
recvmsg(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) = 1088
recvmsg(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) = 20
sendto(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) = 40
recvmsg(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) = 148
recvmsg(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) = 128
recvmsg(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) = 20
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(3, 1), ...}) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d3b000
write(1, "4: eth2: <BROADCAST,MULTICAST,UP"..., 884: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
) = 88
write(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
) = 55
write(1, " inet 192.168.74.55/24 brd 19"..., 63 inet 192.168.74.55/24 brd 192.168.74.255 scope global eth2
) = 63
write(1, " valid_lft forever preferr"..., 47 valid_lft forever preferred_lft forever
) = 47
open("/etc/iproute2/rt_scopes", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=92, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c85d3a000
read(4, "#\n# reserved values\n#\n0\tglobal\n2"..., 4096) = 92
read(4, "", 4096) = 0
close(4) = 0
munmap(0x7f3c85d3a000, 4096) = 0
write(1, " inet6 fe80::20c:29ff:fe56:77"..., 70 inet6 fe80::20c:29ff:fe56:7771/64 scope link tentative dadfailed
) = 70
write(1, " valid_lft forever preferr"..., 47 valid_lft forever preferred_lft forever
) = 47
exit_group(0) = ?
+++ exited with 0 +++
源码分析
ip 对应的命令及主要实现接口定义在struct cmd结构中,如下
今天我们主要分析ip addr show 命令, 所以对应的接口实现为do_ipaddr。
进入main函数,在执行具体的流程分支前,代码会先初始化netlink套接字信息,流程如下:
rtnl_open的接口实现为
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 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接口,忽略掉不相关代码, 程序执行
分析代码,最终获取eth2网口信息的实现在 ipaddr_link_get接口中,实现如下:
最后遍历linfo链表打印到终端,代码如下:
应用
正所谓它山之石可以攻玉,学习的目的在于应用,如果我们在实际的项目开发中,需要通过netlink接口获取内核信息, 完全可以参考iproute2的代码实现, 但是我们完全没有必要自己全部分装netlink的各个与内核交互的接口和信息, 通过分析iproute2源码的makefile文件和目录结构如下:
我们完全只需要将include和lib拷贝到自己的工程中,如果需要获取ip相关信息,那只需要调用lib提供的相关接口,再参考ip目录提供的实现例子对获取的信息进行解析即可。
lib目录中提供了两个静态库,名字为libnetlink.a 和 libutil.a。Makefile实现如下,如果读者习惯使用动态库,可以稍加修改Makefile脚本,编译为动态库即可。
include目录提供了相关接口的声明。