【问题标题】:Get local network interface addresses using only proc?仅使用 proc 获取本地网络接口地址?
【发布时间】:2011-07-13 23:20:47
【问题描述】:

如何仅使用 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 语言中使用。

【问题讨论】:

  • @forest:因为它意味着可解析的文本,对现代 Linux 的依赖,并且不需要生成进程。
  • 我明白了。遗憾的是,我不知道您需要的 /proc 数据结构是否能保证在操作系统甚至内核版本之间保持一致。 (有人请证明我错了。)同时,ifconfig 和 ip 程序产生稳定的输出,这就是为什么我最终选择解析它而不是转向 /proc。这是一个看起来很有希望的替代方案:pypi.python.org/pypi/dnet
  • 你试过iproute2套件吗?

标签: python linux networking ipv4 procfs


【解决方案1】:

/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

要确定这些地址的适配器 (a) 从 /proc/net/route 查询适配器的目标网络,(b) 将这些网络与 /proc/net/fib_trie 的网络匹配,并且 (c) 打印下面列出的相应 /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 确定正确的主机地址,因为这些地址的顺序不一定与路由网络的顺序相匹配。

话虽如此,但我不确定您为什么首先需要属于同一网络的多个主机地址。因此,在大多数用例中,这种方法应该可以正常工作。

【讨论】:

  • 如何将fib_trie中的ip地址与/proc/net/route中的网络接口名称匹配?检查我的 /proc/net/route,我有多个相同网络接口名称的条目。此外,来自 fib_trie 的 ip 地址似乎与路由表中 ANY 条目的“目标”列不匹配,该列是从十六进制和反向顺序转换而来的 IP。
  • 这个文件在 CentOS 5 中不存在。
  • @Maytas Monsereenusorn fib_trie 和 route 的共同点是它们提供 NETWORK IP 地址。通常,此网络对于 HOST IP 地址是唯一的。因此,您可以将路由中的相应接口与 fib_trie 中的相应主机 IP 地址链接起来。同一个接口可以有多个主机 IP,但它们不应属于同一个网络(有关原因,请参见上面的限制)。在 /proc/net/route 中谈到“同一网络接口名称的多个条目”时,您是否可能包含主机路由?脚本不考虑这些条目。
  • 为什么 ` awk '..' </proc/...
【解决方案2】:

您可能会发现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;请注意有趣的算术以使其正确。 :)

【讨论】:

  • 嗯,ip(1) 很好。回退到 ifconfig 会好得多,谢谢。
  • 为什么不简单:printf "%d.%d.%d.%d\n" 0xc0 0xa8 0x00 0x79(我已经测试过,这可以在 bash、dash、zsh、csh 和 ksh 下工作。)
  • @F.Hauri,十六进制->十进制的实际打印方法并不重要。重要的是0x79 0x00.. 在我滥用irb 时给出的顺序与/proc/net/tcp 中的最后一个条目的顺序相同。正如您所做的那样,反转字节,我认为更难发现您正在寻找的数据以及如何修复它。当然,如果你正在编写一个可移植的脚本,printf(1) 是要走的路。 :) 但是这里irb 仅用于说明。谢谢:)
【解决方案3】:

我检索 IPv4 网络配置的解决方案,仅使用 /proc

不幸的是,这是仅限 bash 并且没有任何分叉),而不是 。但我希望这将是可读的:

#!/bin/bash

# ip functions that set variables instead of returning to STDOUT

hexToInt() {
    printf -v $1 "%d\n" 0x${2:6:2}${2:4:2}${2:2:2}${2:0:2}
}
intToIp() {
    local var=$1 iIp
    shift
    for iIp ;do 
        printf -v $var "%s %s.%s.%s.%s" "${!var}" $(($iIp>>24)) \
            $(($iIp>>16&255)) $(($iIp>>8&255)) $(($iIp&255))
    done
}
maskLen() {
    local i
    for ((i=0; i<32 && ( 1 & $2 >> (31-i) ) ;i++));do :;done
    printf -v $1 "%d" $i
}

# The main loop.

while read -a rtLine ;do
    if [ ${rtLine[2]} == "00000000" ] && [ ${rtLine[7]} != "00000000" ] ;then
        hexToInt netInt  ${rtLine[1]}
        hexToInt maskInt ${rtLine[7]}
        if [ $((netInt&maskInt)) == $netInt ] ;then
            for procConnList in /proc/net/{tcp,udp} ;do
                while IFS=': \t\n' read -a conLine ;do
                    if [[ ${conLine[1]} =~ ^[0-9a-fA-F]*$ ]] ;then
                        hexToInt ipInt ${conLine[1]}
                        [ $((ipInt&maskInt)) == $netInt ] && break 3
                    fi
                done < $procConnList
            done
        fi
    fi
done < /proc/net/route 

# And finaly the printout of what's found

maskLen maskBits $maskInt
intToIp addrLine $ipInt $netInt $maskInt
printf -v outForm '%-12s: %%s\\n' Interface Address Network Netmask Masklen
printf "$outForm" $rtLine $addrLine $maskBits\ bits

有一个输出样本:

Interface   : eth0
Address     : 192.168.1.32
Network     : 192.168.1.0
Netmask     : 255.255.255.0
Masklen     : 24 bits

解释:

我使用 IPV4 的整数值来检查IP &amp; MASK == NETWORK

我首先阅读/proc/net/route 以查找路由配置,搜索无需任何网关即可到达的路由 (gw==000000)。

对于这样的路由,我使用 this 路由在所有连接(TCP,如果在 TCP 中找不到,则不是 UDP)中搜索连接,第一个端点是我的主机地址。

注意:这不适用于 PPP 连接

Nota2:这在没有任何打开的网络连接的完全安静的主机上不起作用。 您可以执行echo -ne '' | nc -q 0 -w 1 8.8.8.8 80 &amp; sleep .2 &amp;&amp; ./retrieveIp.sh 之类的操作,以确保在/proc/net/tcp 中找到某些内容。

Nota3,2016-09.23:新的 版本使用&gt;(command) 语法来表示multiple inline pipe 功能。这意味着第 18 行有一个错误:&gt;( 之间必须有一个空格 必须 !!

带有网关的新版本

有一个小补丁:通过复制以前的脚本创建一个名为 getIPv4.sh 的文件后,您可以将以下内容粘贴到命令中:patch -p0

--- getIPv4.sh
+++ getIPv4.sh
@@ -35,13 +35,16 @@
                 done < $procConnList
             done
         fi
+    elif [ ${rtLine[1]} == "00000000" ] && [ ${rtLine[7]} == "00000000" ] ;then
+       hexToInt netGw ${rtLine[2]}
     fi
 done < /proc/net/route 

 # And finaly the printout of what's found

 maskLen maskBits $maskInt
-intToIp addrLine $ipInt $netInt $maskInt
-printf -v outForm '%-12s: %%s\\n' Interface Address Network Netmask Masklen
+intToIp addrLine $ipInt $netInt $netGw $maskInt
+printf -v outForm '%-12s: %%s\\n' \
+       Interface Address Network Gateway Netmask Masklen
 printf "$outForm" $rtLine $addrLine $maskBits\ bits

Ctrld结尾,可能会输出:

patching file getIPv4.sh

也许

Hunk #1 succeeded at 35 with fuzz 2.

然后重新运行您的脚本:

getIPv4.sh
Interface   : eth0
Address     : 192.168.1.32
Network     : 192.168.1.0
Gateway     : 192.168.1.1
Netmask     : 255.255.255.0
Masklen     : 24 bits

【讨论】:

  • 不需要在脚本中执行任何export 语句。对于整数比较,您应该使用if (( expr == num )) 形式而不是if [ $((expr)) == $num ]。对于其他if 语句,您应该使用双方括号而不是单方括号。
  • export 也不需要 source 这个脚本。只有在该脚本的子进程中使用变量和函数时才需要它。使用正确的 Bash 形式进行比较可以简化语句,使它们的含义更加清晰,并提供额外的功能和稳健性。
  • @DennisWilliamson 你有没有注意到新的&gt;(...) bashism 让这个不再起作用了……&gt; 后面要加一个空格! ... :-/
【解决方案4】:

没有 /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")}}}})

【讨论】:

  • 多么奇怪。为什么SIOCGIFCONFgetifaddrs 同时存在?
【解决方案5】:
ip addr show dev eth0 | grep "inet " | cut -d ' ' -f 6  | cut -f 1 -d '/'

【讨论】:

  • 通过向 ip 添加更多开关保存了一个“grep”:ip -o -4 addr show dev eth0 | cut -d ' ' -f 7 | cut -f 1 -d '/' -o 表示单行,-4 表示仅显示 ipv4
  • 这没有帮助,因为 OP 要求提供一种仅使用 /proc 文件系统的方法!
【解决方案6】:

她是我在互联网某处找到的一个花哨的。对其进行了轻微修复以适合并正确输出 tun (vpn) 设备。

#!/usr/bin/python
from socket import AF_INET, AF_INET6, inet_ntop
from ctypes import (
    Structure, Union, POINTER, 
    pointer, get_errno, cast,
    c_ushort, c_byte, c_void_p, c_char_p, c_uint, c_int, c_uint16, c_uint32
)
import ctypes.util
import ctypes

class struct_sockaddr(Structure):
     _fields_ = [
        ('sa_family', c_ushort),
        ('sa_data', c_byte * 14),]

class struct_sockaddr_in(Structure):
    _fields_ = [
        ('sin_family', c_ushort),
        ('sin_port', c_uint16),
        ('sin_addr', c_byte * 4)]

class struct_sockaddr_in6(Structure):
    _fields_ = [
        ('sin6_family', c_ushort),
        ('sin6_port', c_uint16),
        ('sin6_flowinfo', c_uint32),
        ('sin6_addr', c_byte * 16),
        ('sin6_scope_id', c_uint32)]

class union_ifa_ifu(Union):
    _fields_ = [
        ('ifu_broadaddr', POINTER(struct_sockaddr)),
        ('ifu_dstaddr', POINTER(struct_sockaddr)),]

class struct_ifaddrs(Structure):
    pass

struct_ifaddrs._fields_ = [
    ('ifa_next', POINTER(struct_ifaddrs)),
    ('ifa_name', c_char_p),
    ('ifa_flags', c_uint),
    ('ifa_addr', POINTER(struct_sockaddr)),
    ('ifa_netmask', POINTER(struct_sockaddr)),
    ('ifa_ifu', union_ifa_ifu),
    ('ifa_data', c_void_p),]

libc = ctypes.CDLL(ctypes.util.find_library('c'))

def ifap_iter(ifap):
    ifa = ifap.contents
    while True:
        yield ifa
        if not ifa.ifa_next:
            break
        ifa = ifa.ifa_next.contents

def getfamaddr(sa):
    family = sa.sa_family
    addr = None
    if family == AF_INET:
        sa = cast(pointer(sa), POINTER(struct_sockaddr_in)).contents
        addr = inet_ntop(family, sa.sin_addr)
    elif family == AF_INET6:
        sa = cast(pointer(sa), POINTER(struct_sockaddr_in6)).contents
        addr = inet_ntop(family, sa.sin6_addr)
    return family, addr

class NetworkInterface(object):
    def __init__(self, name):
        self.name = name
        self.index = libc.if_nametoindex(name)
        self.addresses = {}
    def __str__(self):
        return "%s [index=%d, IPv4=%s, IPv6=%s]" % (
            self.name, self.index,
            self.addresses.get(AF_INET),
            self.addresses.get(AF_INET6))

def get_network_interfaces():
    ifap = POINTER(struct_ifaddrs)()
    result = libc.getifaddrs(pointer(ifap))
    if result != 0:
        raise OSError(get_errno())
    del result
    try:
        retval = {}
        for ifa in ifap_iter(ifap):
            name = ifa.ifa_name
            i = retval.get(name)
            if not i:
                i = retval[name] = NetworkInterface(name)
            try:
                family, addr = getfamaddr(ifa.ifa_addr.contents)
            except ValueError:
                family, addr = None, None
            if addr:
                i.addresses[family] = addr
        return retval.values()
    finally:
        libc.freeifaddrs(ifap)

if __name__ == '__main__':
    print [str(ni) for ni in get_network_interfaces()]

【讨论】:

【解决方案7】:

cat /proc/net/tcp

获取第二列,标题为“local_address”,例如"CF00A8C0:0203"

“:”后面的部分是端口号。

从其余部分使用最后两个 (C0) 作为十六进制数字,例如C0 是 192,在这个例子中是地址的开始。

不久前,我从网上的某个聪明点将以下内容记入了我的笔记中:

IP 地址显示为 little-endian 的四字节十六进制数; 也就是说,首先列出最低有效字节, 因此您需要颠倒字节顺序以将其转换为 IP 地址。

端口号是一个简单的两字节十六进制数。

【讨论】:

  • 喜欢:tail -n1 /proc/net/tcp 7: 1002000A:65F5 0202000A:DBEA 01 00000000:00000000 00:00000000 00000000 0 0 13951 3 de559e00 21 4 4 .%d.%d.%d\n" 0x10 0x02 0x00 0x0A 16.2.0.10
【解决方案8】:
ip -j -o -4 addr show dev eth0 | jq .[1].addr_info[0].local

【讨论】:

  • “所有网络接口”,我的系统上什至没有 eth0
  • OP 想要一种使用 /proc 而非实际 cmds 来收集此信息的方法。
【解决方案9】:

当心 /proc/net/tcp ,因为在我这里的系统上,我使用 keepalived 在首选主机上调出一个额外的 IP 地址,但是当我检查时,该地址没有在 /proc/net 中表示/tcp,但它存在于 /proc/net/fib_trie

【讨论】:

    【解决方案10】:

    这是 bass-ackwards,我可能忘记了一个角落案例,但如果你查看 /proc/1/net/route,它有你的路由表。如果选择网关为 0.0.0.0 的行,则第一列是接口,第二列是 IP 地址的十六进制表示,按网络字节顺序(第三列是要过滤的网关 ip )。

    【讨论】:

    • 第二列不是IP地址,而是网络地址,所以这行不通,很遗憾。
    猜你喜欢
    • 2013-06-21
    • 2011-02-05
    • 2015-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多