首先,我要通过编程直接获取,而不是去读诸如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 :-)
|