Get local network interface addresses using only proc?

后端 未结 9 579
旧时难觅i
旧时难觅i 2020-12-02 18:21

How can I obtain the (IPv4) addresses for all network interfaces using only proc? After some extensive investigation I\'ve discovered the following:

相关标签:
9条回答
  • 2020-12-02 18:54

    My solution to retrieve IPv4 network config, using /proc only:

    Unfortunately, this is bash (bash only and without any fork), not python. But I hope this will be readable:

    #!/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
    

    There is a sample of output:

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

    Explanation:

    I use integer value of IPV4 in order to check IP & MASK == NETWORK.

    I read first /proc/net/route to find routing configurations, searching for routes reachable without any gateway (gw==000000).

    For such a route, I search in all connections (TCP, than UDP if not found in TCP) for connection using this route, the first end point is my host address.

    Nota: This won't work with PPP connections

    Nota2: This won't work on a totally quiet host without any opened network connection. You could do something like echo -ne '' | nc -q 0 -w 1 8.8.8.8 80 & sleep .2 && ./retrieveIp.sh for ensuring that something where found in /proc/net/tcp.

    Nota3, 2016-09.23: New bash version use >(command) syntax for multiple inline pipe feature. This implie a bug at line 18: a space must be present between > and ( !!

    New version with gateway

    There is a little patch: Once you create a file called getIPv4.sh by copying previous script, you could paste the following to the command: 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
    

    End with Ctrld, this may output:

    patching file getIPv4.sh
    

    And maybe

    Hunk #1 succeeded at 35 with fuzz 2.
    

    Then re-run your script:

    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
    
    0 讨论(0)
  • 2020-12-02 18:57

    cat /proc/net/tcp

    Get the second column, with the heading "local_address", e.g. "CF00A8C0:0203"

    The part after ":" is a port number.

    From the rest use the last two (C0) as a hex number, e.g. C0 is 192, which is the start of the address in this example.

    Took the following into my notes a while ago, from some smart point in the net:

    The IP address is displayed as a little-endian four-byte hexadecimal number; that is, the least significant byte is listed first, so you'll need to reverse the order of the bytes to convert it to an IP address.

    The port number is a simple two-byte hexadecimal number.

    0 讨论(0)
  • 2020-12-02 19:01

    You may find the output of ip addr show easier to parse than output from other tools:

    $ 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
    

    Another option is the file /proc/net/tcp. It shows all currently-open TCP sessions, which is different than what you asked for, but might be Good Enough.

    $ 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]
    

    My IP is 192.168.0.121; note the funny arithmetic to make it come out right. :)

    0 讨论(0)
  • 2020-12-02 19:02

    hers a fancy one i found somewhere in the internet. minorly fixed it up to fit and correctly output tun (vpn) devices.

    #!/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()]
    
    0 讨论(0)
  • 2020-12-02 19:04

    It's bass-ackwards and I probably am forgetting a corner case, but if you look at /proc/1/net/route, that has your routing table. If you select lines for which the gateway is 0.0.0.0, the first column is the interface and the second column is the hex representation of your IP address, in network byte order (and the third column is the gateway ip you want to filter on).

    0 讨论(0)
  • 2020-12-02 19:06

    /proc/net/fib_trie holds the network topography

    To simply print the addresses of all adapters:

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

    To determine the adapter of those addresses (a) consult the adapters' destination networks from /proc/net/route, (b) match those networks with the ones of /proc/net/fib_trie and (c) print the corresponding /32 host addresses listed under those networks.

    Again no python unfortunately, but a quite awky bash approach:

    #!/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
    

    output:

    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
    

    Known limitation:

    This approach does not work reliably for host addresses that share the network with other host addresses. This loss of network uniqueness makes it impossible to determine the correct host address from fib_trie as the order of those addresses does not necessarily match the order of networks of route.

    Having said that, I'm not sure why you would want multiple host addresses belonging to the same network in first place. So in most use cases this approach should work just fine.

    0 讨论(0)
提交回复
热议问题