本文来自依云's Blog,转载请注明。
首先,我要通过编程直接获取,而不是去读诸如ifconfig
等命令的输出。
其实是只想获取IPv6地址的,不过我猜想它们差不多,也确实看到不少相关搜索结果,于是顺带着看了。
首先,使用gethostbyname
查自己通常是不行的,因为可能得到127.0.0.1
,而且我猜,这样不能处理拥有多个IPv4地址的情况。另外一种方式是连上某个主机,然后调用getsockname
。这样需要能够直接连上那个主机,好处是如果有多个网络接口,这样可以知道到底走的是哪个接口,调试网络时不错。我最满意的方案在这里,使用ioctl
来获取。这个方法可以获取指定网络接口的IPv4地址。至于有哪些网络接口嘛,直接读/proc/net/dev
吧。
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 的源码,找到相关代码:
#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部分的代码了。可以看到它打开了一个奇怪的文件。跟过去,发现是
#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
#define SIOCGIFINDEX 0x8933 /* name -> if_index mapping */ #define SIOGIFINDEX SIOCGIFINDEX /* misprint compatibility :-) */
Jun 06, 2012 08:41:00 PM
如果百合能解释一下那个宏 SIOCGIFADDR 就好了。
Jun 06, 2012 08:59:22 PM
我猜是 socket I/O configuration get interface address.
Jun 07, 2012 10:06:01 AM
或许 iproute2 的代码也可以参考一下。这货代码太多了,我简单 grep 了一下 if_inet6,似乎不是直接 parse 文件做的。
ps.为啥这么看重『编程直接获取』呢?/proc 实际上就是 os 的通用接口啊。
Jun 07, 2012 10:40:25 AM
windows 上没有这个,所以算不上跨平台。bsd 好像也很诡异。
Jun 07, 2012 12:44:40 PM
很好,我眼下正要用到。本来是想读 ifconfig/ipconfig 输出的,感谢。
Jun 07, 2012 01:26:35 PM
我指的是不调用其它的命令。/proc 当然可以呀,只是先看到 ioctl 获取IPv4的,所以以为它也可以获得IPv6的。另外,我没有在 /proc 里看到IPv4地址。
iproute2 的代码我看了,它使用了 netlink 这个东西,「Netlink is a socket-like mechanism for IPC between the kernel and user space processes...」,貌似比进程间传文件描述符还神秘……
Jun 07, 2012 01:28:08 PM
FreeBSD 可以弄个 procfs 出来,OpenBSD 貌似不行。
Jun 07, 2012 03:59:03 PM
如果一台机器同时连了两个局域网,那么无论是通过 ifconfig 还是本文的方法都能检测到两个 ip 地址,现在如果其中一个局域网是通过路由外接 internet 的,另一个是不接的,那请问怎么在程序里检测区分呢? Win 下面的 ipconfig 会显示 default gateway 这一项,有值的就是外接的。Linux 下面的 ifconfig 却没有。我希望程序有点自适应能力,以后采取不同网络配置的时候不必重写。谢谢。
Jun 07, 2012 05:07:05 PM
查网关的话用 route -n 获取。要获取到 Internet 的网络接口的地址,我文中已经提到了,连到某个服务器上,然后 getsockname。
Jun 07, 2012 06:18:17 PM
然:)多谢。我的程序是分布式的,网络里既有 Windows 也有 Linux。本来想写的牛B点,让它能自己检测网上其它机器的 ip 的,今天搜一下,好像这个叫 host discovery,用 nmap 可以做,但是代价有点大。慢不说,nmap 在 Win 和 Linux 上还都得另装。现在觉得还是手动给每个机器设 ip 算了,设好就不动了。
Jun 07, 2012 07:21:46 PM
用 ARP 扫描,很快的。
Jun 07, 2012 07:52:18 PM
哦,看见了。arp-scan 是专用于局域网的,nmap 可以检测任何 ip,怪不得这么大。arp-scan 才是我想要的。但是 arp-scan 需要 root 权限。除了 sudo 运行 python 程序外,没有办法能开始运行以后再获得 root 权限的吧?这听起来有点像黑客的干活。