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

Python 仅使用proc获取本地网络接口地址?,python,linux,networking,ipv4,procfs,bash,Python,Linux,Networking,Ipv4,Procfs,Bash,我如何才能只使用一次就获得所有网络接口的(IPv4)地址?经过深入调查,我发现了以下几点: ifconfig利用了SIOCGIFADDR,这需要打开套接字并预先了解所有接口名称。Linux上的任何手册页中也没有记录它 proc包含/proc/net/dev,但这是接口统计信息的列表 proc包含/proc/net/if\u inet6,这正是除了IPv6之外我所需要的 通常,在proc中很容易找到接口,但实际地址很少使用,除非是某些连接的一部分 有一个名为的系统调用,这是一个非常“神奇”的功能,

我如何才能只使用一次就获得所有网络接口的(IPv4)地址?经过深入调查,我发现了以下几点:

  • ifconfig
    利用了
    SIOCGIFADDR
    ,这需要打开套接字并预先了解所有接口名称。Linux上的任何手册页中也没有记录它
  • proc
    包含
    /proc/net/dev
    ,但这是接口统计信息的列表
  • proc
    包含
    /proc/net/if\u inet6
    ,这正是除了IPv6之外我所需要的
  • 通常,在
    proc
    中很容易找到接口,但实际地址很少使用,除非是某些连接的一部分
  • 有一个名为的系统调用,这是一个非常“神奇”的功能,你可以在Windows中看到。它也在BSD上实现。然而,它不是非常面向文本的,这使得它很难从非C语言中使用

  • 您可能会发现
    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
    

    我的IP是
    192.168.0.121
    ;注意有趣的算术,使其正确无误。:)

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

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

    cat/proc/net/tcp

    获取标题为“本地地址”的第二列,例如“CF00A8C0:0203”

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

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

    不久前,我从网上某个聪明的地方记下了以下内容:

    IP地址显示为一个小的四字节十六进制数; 也就是说,最低有效字节列在第一位, 因此,您需要颠倒字节的顺序才能将其转换为IP地址

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

    仅使用
    /proc
    检索IPv4网络配置的我的解决方案: 不幸的是,这是(只有bash,没有任何fork),而不是。但我希望这将是可读的:

    #!/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
    
    说明:

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

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

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

    注:这不适用于PPP连接

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

    NOTA32016-09.23:新版本使用
    >(命令)
    语法,用于
    多内联管道
    功能。这意味着第18行有一个bug:
    之间必须存在一个空格

    带网关的新版本 有一个小补丁:通过复制以前的脚本创建名为
    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
    
    也许

    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
    

    她是我在互联网上找到的一个漂亮的。minorly把它修好了,可以安装并正确输出tun(vpn)设备


    /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
    
    输出:

    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中确定正确的主机地址,因为这些地址的顺序不一定与路由网络的顺序匹配


    话虽如此,我不知道为什么您首先希望多个主机地址属于同一个网络。因此在大多数使用情况下,这种方法应该可以正常工作。

    @forest:因为它意味着可解析文本,依赖于现代Linux,并且不需要生成进程。我明白了。遗憾的是,我不知道/proc数据结构您所需要的res保证在操作系统甚至内核版本之间保持一致。(请有人证明我错了。)同时,ifconfig和ip程序产生稳定的输出,这就是为什么我最终选择解析它而不是使用/proc。这里有一个看起来很有希望的替代方案:您是否尝试了
    iproute2
    suite?嗯,ip(1)很好。使用ifconfig会比使用ifconfig好得多,谢谢。为什么不简单一点:
    printf”%d.%d.%d.%d\n0xc0 0xa8 0x00 0x79
    (我已经在bash、dash、zsh、csh和ksh下测试了这项工作。)@F.Hauri,十六进制->十进制的实际打印方法并不重要。重要的是,
    0x79 0x00..
    是在我滥用
    irb
    时给出的,其顺序与
    /proc/net/tcp
    中最后一个条目的顺序相同。正如你所做的那样,反转字节会使你更难发现你正在寻找的数据以及如何查找当然,如果你在写一个可移植的脚本,
    printf(1)
    是一个好办法。:)但是这里的
    irb
    只是用来说明。谢谢:)真奇怪。为什么
    SIOCGIFCONF
    getifaddrs
    都存在?第二列不是IP地址,而是rat
    ip -j -o -4 addr show dev eth0 | jq .[1].addr_info[0].local
    
    #!/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()]
    
    ip addr show dev eth0 | grep "inet " | cut -d ' ' -f 6  | cut -f 1 -d '/'
    
    $ awk '/32 host/ { print f } {f=$2}' <<< "$(</proc/net/fib_trie)"
    127.0.0.1
    192.168.0.5
    192.168.1.14
    
    #!/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
    
    ip -j -o -4 addr show dev eth0 | jq .[1].addr_info[0].local