仅使用proc获取本地网络接口地址?

49 投票
11 回答
64748 浏览
提问于 2025-04-16 13:33

我怎么能仅通过 proc 获取所有网络接口的 (IPv4) 地址呢?经过一番深入的调查,我发现了以下几点:

  1. ifconfig 使用了 SIOCGIFADDR,这个方法需要打开的套接字和提前知道所有接口的名称。而且在Linux的手册中没有相关的文档。
  2. proc 里面有 /proc/net/dev,但这只是一个接口统计信息的列表。
  3. proc 里还有 /proc/net/if_inet6,这个正是我需要的,但它是针对IPv6的。
  4. 一般来说,在 proc 中找到接口是比较简单的,但实际的地址很少被使用,除非它是某个连接的一部分。
  5. 有一个系统调用叫做 getifaddrs,这个函数就像是“魔法”一样,通常在Windows中能见到。它在BSD上也有实现。不过它的文本输出不太友好,这让非C语言使用起来比较困难。

11 个回答

17

你可能会发现使用 ip addr show 命令的输出比其他工具的输出更容易理解:

$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:24:1d:ce:47:05 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.121/24 brd 192.168.0.255 scope global eth0
    inet6 fe80::224:1dff:fece:4705/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000
    link/ether 00:24:1d:ce:35:d5 brd ff:ff:ff:ff:ff:ff
4: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 92:e3:6c:08:1f:af brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
    inet6 fe80::90e3:6cff:fe08:1faf/64 scope link
       valid_lft forever preferred_lft forever

另一个选择是查看文件 /proc/net/tcp。这个文件显示了所有当前打开的TCP连接,虽然这和你问的有点不同,但可能也足够用了。

$ cat tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 13536 1 ffff88019f0a1380 300 0 0 2 -1
   1: 00000000:1355 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 19877854 1 ffff880016e69380 300 0 0 2 -1
   2: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 13633 1 ffff88019f0a1a00 300 0 0 2 -1
   3: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 8971 1 ffff88019f0a0000 300 0 0 2 -1
   4: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 12952880 1 ffff880030e30680 300 0 0 2 -1
   5: 00000000:0539 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 14332 1 ffff88019f0a2080 300 0 0 2 -1
   6: 00000000:C000 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 14334 1 ffff88019f0a2700 300 0 0 2 -1
   7: 0100007F:0A44 00000000:0000 0A 00000000:00000000 00:00000000 00000000   119        0 51794804 1 ffff880016e6a700 300 0 0 2 -1
   8: 7900A8C0:B094 53D50E48:01BB 01 00000000:00000000 00:00000000 00000000  1000        0 64877487 1 ffff880100502080 23 4 16 4 -1
   9: 7900A8C0:9576 537F7D4A:01BB 06 00000000:00000000 03:00000E5D 00000000     0        0 0 3 ffff880100c84600
  10: 7900A8C0:CC84 0CC181AE:01BB 01 00000000:00000000 00:00000000 00000000  1000        0 61775908 1 ffff880198715480 35 4 11 4 -1
$ irb
irb(main):001:0> [0x79, 0x00, 0xa8, 0xc0]
=> [121, 0, 168, 192]

我的IP地址是 192.168.0.121;注意这里有点搞笑的计算方式,让它显示得正确。:)

32

/proc/net/fib_trie 这个文件里保存了网络的结构信息。

如果你想简单地打印出所有适配器的地址,可以使用以下代码:

$ awk '/32 host/ { print f } {f=$2}' <<< "$(</proc/net/fib_trie)"
127.0.0.1
192.168.0.5
192.168.1.14

要确定这些地址对应的适配器,步骤是这样的:首先,查看适配器的目标网络,这些信息在 /proc/net/route 里;然后,把这些网络和 /proc/net/fib_trie 里的网络进行匹配;最后,打印出在这些网络下列出的对应的 /32 主机地址。

虽然没有使用 python,但这里有一个比较奇怪的 bash 方法:

#!/bin/bash

ft_local=$(awk '$1=="Local:" {flag=1} flag' <<< "$(</proc/net/fib_trie)")

for IF in $(ls /sys/class/net/); do
    networks=$(awk '$1=="'$IF'" && $3=="00000000" && $8!="FFFFFFFF" {printf $2 $8 "\n"}' <<< "$(</proc/net/route)" )
    for net_hex in $networks; do
            net_dec=$(awk '{gsub(/../, "0x& "); printf "%d.%d.%d.%d\n", $4, $3, $2, $1}' <<< $net_hex)
            mask_dec=$(awk '{gsub(/../, "0x& "); printf "%d.%d.%d.%d\n", $8, $7, $6, $5}' <<< $net_hex)
            awk '/'$net_dec'/{flag=1} /32 host/{flag=0} flag {a=$2} END {print "'$IF':\t" a "\n\t'$mask_dec'\n"}' <<< "$ft_local"
    done
done

exit 0

输出结果:

eth0:     192.168.0.5
          255.255.255.0

lo:       127.0.0.1
          255.0.0.0

wlan0:    192.168.1.14
          255.255.255.0

已知的限制:

这种方法对于那些和其他主机地址共享同一网络的主机地址并不总是有效。因为网络不唯一,所以无法从 fib_trie 中准确确定正确的主机地址,因为这些地址的顺序不一定和 route 中网络的顺序一致。

不过,我不太明白为什么你会想要多个属于同一网络的主机地址。大多数情况下,这种方法应该是可以正常工作的。

7

没有类似于 /proc/net/if_inet6 的 IPv4 版本。

ifconfig 命令可以做到:

fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)
ioctl(fd, SIOCGIFCONF, ...)

你会看到类似这样的输出:

ioctl(4, SIOCGIFCONF, {120, {{"lo", {AF_INET, inet_addr("127.0.0.1")}}, {"eth0", {AF_INET, inet_addr("10.6.23.69")}}, {"tun0", {AF_INET, inet_addr("10.253.10.151")}}}})

撰写回答