首先,我要通过编程直接获取,而不是去读诸如ifconfig
等命令的输出。
其实是只想获取IPv6地址的,不过我猜想它们差不多,也确实看到不少相关搜索结果,于是顺带着看了。
首先,使用gethostbyname
查自己通常是不行的,因为可能得到127.0.0.1
,而且我猜,这样不能处理拥有多个IPv4地址的情况。另外一种方式是连上某个主机,然后调用getsockname
。这样需要能够直接连上那个主机,好处是如果有多个网络接口,这样可以知道到底走的是哪个接口,调试网络时不错。我最满意的方案在这里,使用ioctl
来获取。这个方法可以获取指定网络接口的IPv4地址。至于有哪些网络接口嘛,直接读/proc/net/dev
吧。
1 2 3 4 5 6 7 8 | import fcntl import socket import struct ifname = b 'eth0' s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 0x8915 是 SIOCGIFADDR ip = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915 , struct.pack( '256s' , ifname[: 15 ]))[ 20 : 24 ]) print (ip) |
然而,这样只能获取IPv4地址。创建个AF_INET6
的 socket 传过去会报错「Inappropriate ioctl for device」。那怎么办呢?Google 没找到,我去搜了下内核源码。inet_ioctl
里有对SIOCGIFADDR
的处理。但是,inet6_ioctl
里却没有了。
于是,我只好去下载ifconfig
所属的 net-tools 的源码,找到相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 | #if HAVE_AFINET6 /* FIXME: should be integrated into interface.c. */ if ((f = fopen (_PATH_PROCNET_IFINET6, "r" )) != NULL) { while ( fscanf (f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n" , addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4], addr6p[5], addr6p[6], addr6p[7], &if_idx, &plen, &scope, &dad_status, devname) != EOF) { if (! strcmp (devname, ptr->name)) { sprintf (addr6, "%s:%s:%s:%s:%s:%s:%s:%s" , addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4], addr6p[5], addr6p[6], addr6p[7]); |
这里就是ifconfig
输出IPv6部分的代码了。可以看到它打开了一个奇怪的文件。跟过去,发现是
1 | #define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" |
囧,这个文件我早就发现过了的。看来和IPv4的情况不同,IPv6地址只能通过/proc
里的文件获取了。而且输出成人可读格式不容易(ifconfig
是自己实现的)。
PS: 我还发现了件好玩的事,在 Linux 源码的include/linux/sockios.h
中,SIOCGIFINDEX
中的字母 C 写漏了。通过git blame
我发现,这个拼写错误在至少七年前 Linux 内核代码迁移到 git 前就修正了。Linus Torvalds 说之前的代码导入到 git 后有 3.2GB。我不得不承认这是个无比正确的决定,因为现在的.git
已经有600多兆了,git 不支持断点续传,clone 下来已经很不容易了。
另外,我还联想到了 Unix 系统调用中的creat
,以及 HTTP 协议中的referer
:D
1 2 | #define SIOCGIFINDEX 0x8933 /* name -> if_index mapping */ #define SIOGIFINDEX SIOCGIFINDEX /* misprint compatibility :-) |