Получить адреса локального сетевого интерфейса, используя только proc?

Как я могу получить адреса (IPv4) для всех сетевых интерфейсов, используя только proc ? После некоторого обширного расследования я обнаружил следующее:

  1. ifconfig использует SIOCGIFADDR , для которого требуются открытые сокеты и знание всех имен интерфейсов. Он также не документирован на каких-либо страницах руководства Linux.
  2. proc содержит /proc/net/dev , но это список статистики интерфейса.
  3. proc содержит /proc/net/if_inet6 , что именно то, что мне нужно, но для IPv6.
  4. Обычно интерфейсы легко найти в proc , но фактические адреса используются очень редко, за исключением случаев, когда они явно являются частью некоторого соединения.
  5. Существует системный вызов, называемый getifaddrs , который является очень «волшебной» функцией, которую вы ожидаете увидеть в Windows. Он также реализован на BSD. Однако он не очень ориентирован на текст, что затрудняет использование с не-C языков.

Нет аналога IPv4 / proc / net / if_inet6

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")}}}}) 

Вы можете найти, что вывод 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 ; обратите внимание на смешную арифметику, чтобы сделать ее правдой. 🙂

Мое решение для восстановления сетевой конфигурации IPv4, используя только /proc :

К сожалению, это bash ( только bash и без какой-либо вилки), а не python . Но я надеюсь, что это будет читаемо:

 #!/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 & MASK == NETWORK .

Я читаю first /proc/net/route для поиска конфигураций маршрутизации, поиска маршрутов, доступных без каких-либо шлюзов ( gw==000000 ).

Для такого маршрута я ищу во всех соединениях (TCP, чем UDP, если не найден в TCP) для соединения с использованием этого маршрута, первой конечной точкой является адрес моего хоста.

Примечание: это не будет работать с соединениями PPP

Nota2: Это не будет работать на полностью спокойном хосте без какого-либо открытого сетевого подключения. Вы могли бы сделать что-то вроде echo -ne '' | nc -q 0 -w 1 8.8.8.8 80 & sleep .2 && ./retrieveIp.sh echo -ne '' | nc -q 0 -w 1 8.8.8.8 80 & sleep .2 && ./retrieveIp.sh для обеспечения того, что что-то найдено в /proc/net/tcp .

Nota3, 2016-09.23: Новая версия bash использует >(command) синтаксис для multiple inline pipe функций multiple inline pipe . Это подразумевает ошибку в строке 18: пространство должно находиться между > и ( !!

Новая версия с шлюзом

Существует небольшой патч: после создания файла с именем 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 

Завершите с помощью Ctrl d , это может выводить:

 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 

/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 к сожалению, но довольно awky 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, поскольку порядок этих адресов не обязательно соответствует порядку сетей маршрута.

Сказав это, я не уверен, почему вы хотели бы, чтобы на одном месте находилось несколько адресов хостов, принадлежащих одной и той же сети. Поэтому в большинстве случаев этот подход должен работать нормально.

 ip addr show dev eth0 | grep "inet " | cut -d ' ' -f 6 | cut -f 1 -d '/' 

cat / proc / net / tcp

Получите второй столбец с заголовком «local_address», например «CF00A8C0: 0203»,

Часть после «:» – номер порта.

Из остальных используйте последние два (C0) в качестве шестнадцатеричного числа, например C0 – 192, что является началом адреса в этом примере.

Некоторое время назад в мои заметки входили некоторые из соображений:

IP-адрес отображается в виде шестизначного четырехбайтового шестнадцатеричного числа; то есть младший значащий байт указан первым, поэтому вам нужно будет отменить порядок байтов, чтобы преобразовать его в IP-адрес.

Номер порта – это простое двухбайтное шестнадцатеричное число.

ее причудливый, который я нашел где-то в Интернете. незначительно исправил его до соответствия и правильно выводит устройства 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()] 

Это бас-аккорд, и я, вероятно, забываю о углу, но если вы посмотрите на / proc / 1 / net / route, у вас есть таблица маршрутизации. Если вы выбираете строки, для которых шлюз 0.0.0.0, первый столбец является интерфейсом, а второй столбец представляет собой шестнадцатеричное представление вашего IP-адреса в сетевом порядке байтов (а третий столбец – это шлюз ip, который вы хотите фильтровать на ).