6
6
2012
12

编程获取本机IPv4及IPv6地址

本文来自依云'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 :-)   */
Category: Linux | Tags: C代码 linux python 网络 | Read Count: 17148
vx13 说:
Jun 06, 2012 08:41:00 PM

如果百合能解释一下那个宏 SIOCGIFADDR 就好了。

Avatar_small
依云 说:
Jun 06, 2012 08:59:22 PM

我猜是 socket I/O configuration get interface address.

lyman 说:
Jun 07, 2012 10:06:01 AM

或许 iproute2 的代码也可以参考一下。这货代码太多了,我简单 grep 了一下 if_inet6,似乎不是直接 parse 文件做的。

ps.为啥这么看重『编程直接获取』呢?/proc 实际上就是 os 的通用接口啊。

vx13 说:
Jun 07, 2012 10:40:25 AM

windows 上没有这个,所以算不上跨平台。bsd 好像也很诡异。

Avatar_small
Jacky Liu 说:
Jun 07, 2012 12:44:40 PM

很好,我眼下正要用到。本来是想读 ifconfig/ipconfig 输出的,感谢。

Avatar_small
依云 说:
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...」,貌似比进程间传文件描述符还神秘……

Avatar_small
依云 说:
Jun 07, 2012 01:28:08 PM

FreeBSD 可以弄个 procfs 出来,OpenBSD 貌似不行。

Avatar_small
Jacky Liu 说:
Jun 07, 2012 03:59:03 PM

如果一台机器同时连了两个局域网,那么无论是通过 ifconfig 还是本文的方法都能检测到两个 ip 地址,现在如果其中一个局域网是通过路由外接 internet 的,另一个是不接的,那请问怎么在程序里检测区分呢? Win 下面的 ipconfig 会显示 default gateway 这一项,有值的就是外接的。Linux 下面的 ifconfig 却没有。我希望程序有点自适应能力,以后采取不同网络配置的时候不必重写。谢谢。

Avatar_small
依云 说:
Jun 07, 2012 05:07:05 PM

查网关的话用 route -n 获取。要获取到 Internet 的网络接口的地址,我文中已经提到了,连到某个服务器上,然后 getsockname。

Avatar_small
Jacky Liu 说:
Jun 07, 2012 06:18:17 PM

然:)多谢。我的程序是分布式的,网络里既有 Windows 也有 Linux。本来想写的牛B点,让它能自己检测网上其它机器的 ip 的,今天搜一下,好像这个叫 host discovery,用 nmap 可以做,但是代价有点大。慢不说,nmap 在 Win 和 Linux 上还都得另装。现在觉得还是手动给每个机器设 ip 算了,设好就不动了。

Avatar_small
依云 说:
Jun 07, 2012 07:21:46 PM

用 ARP 扫描,很快的。

Avatar_small
Jacky Liu 说:
Jun 07, 2012 07:52:18 PM

哦,看见了。arp-scan 是专用于局域网的,nmap 可以检测任何 ip,怪不得这么大。arp-scan 才是我想要的。但是 arp-scan 需要 root 权限。除了 sudo 运行 python 程序外,没有办法能开始运行以后再获得 root 权限的吧?这听起来有点像黑客的干活。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com