python linux networking ipv4 procfs

python - Obtener direcciones de interfaz de red local utilizando solo proc?



linux networking (8)

¿Cómo puedo obtener las direcciones (IPv4) para todas las interfaces de red usando solo proc ? Después de una extensa investigación, descubrí lo siguiente:

  1. ifconfig hace uso de SIOCGIFADDR , que requiere sockets abiertos y conocimiento avanzado de todos los nombres de interfaz. Tampoco está documentado en ninguna página de manual en Linux.
  2. proc contiene /proc/net/dev , pero esta es una lista de estadísticas de interfaz.
  3. proc contiene /proc/net/if_inet6 , que es exactamente lo que necesito, pero para IPv6.
  4. En general, las interfaces son fáciles de encontrar en el proc , pero las direcciones reales se usan muy raramente, excepto cuando forman parte explícita de alguna conexión.
  5. Hay una llamada al sistema llamada getifaddrs , que es en gran getifaddrs una función "mágica" que esperaría ver en Windows. También se implementa en BSD. Sin embargo, no está muy orientado al texto, lo que dificulta el uso de idiomas que no sean C.

Mi solución para recuperar la configuración de red IPv4, usando /proc solamente:

Desafortunadamente, esto es bash ( solo bash y sin ningún tenedor), no python . Pero espero que esto sea legible:

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

Hay una muestra de salida:

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

Explicación:

Utilizo el valor entero de IPV4 para verificar IP & MASK == NETWORK .

Leí first /proc/net/route para encontrar configuraciones de enrutamiento, buscando rutas accesibles sin ninguna puerta de enlace ( gw==000000 ).

Para tal ruta, busco en todas las conexiones (TCP, que UDP si no se encuentra en TCP) para la conexión usando esta ruta, el primer punto final es mi dirección de host.

Nota: Esto no funcionará con las conexiones PPP

Nota2: Esto no funcionará en un host totalmente silencioso sin ninguna conexión de red abierta. Podrías hacer algo como 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 para garantizar que algo se encuentre en /proc/net/tcp .

Nota3, 2016-09.23: nueva versión de bash usa la sintaxis >(command) para multiple inline pipe características de multiple inline pipe . Esto implica un error en la línea 18: un espacio debe estar presente entre > y ( !!

Nueva versión con puerta de enlace

Hay un pequeño parche: una vez que crea un archivo llamado getIPv4.sh copiando el script anterior, puede pegar lo siguiente al comando: 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

Termine con Ctrl d , esto puede dar como resultado:

patching file getIPv4.sh

Y tal vez

Hunk #1 succeeded at 35 with fuzz 2.

A continuación, vuelva a ejecutar su secuencia de comandos:

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


Es bajo-ackwards y probablemente estoy olvidando un caso de esquina, pero si miras a / proc / 1 / net / route, eso tiene tu tabla de enrutamiento. Si selecciona líneas para las cuales la puerta de enlace es 0.0.0.0, la primera columna es la interfaz y la segunda columna es la representación hexadecimal de su dirección IP, en orden de bytes de red (y la tercera columna es la puerta de enlace IP que desea filtrar )


Es posible que la salida de ip addr show sea ​​más fácil de analizar que la salida de otras herramientas:

$ 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

Otra opción es el archivo /proc/net/tcp . Muestra todas las sesiones TCP abiertas actualmente, que es diferente de lo que solicitó, pero podría ser suficiente.

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

Mi IP es 192.168.0.121 ; tenga en cuenta la aritmética divertida para que salga bien. :)


No hay un análogo IPv4 de / proc / net / if_inet6

ifconfig hace:

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

Obtendrás algo como esto:

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


cat / proc / net / tcp

Obtenga la segunda columna, con el encabezado "local_address", por ejemplo, "CF00A8C0: 0203"

La parte después de ":" es un número de puerto.

Del resto, use los dos últimos (C0) como un número hexadecimal, por ejemplo, C0 es 192, que es el comienzo de la dirección en este ejemplo.

Tomó lo siguiente en mis notas hace un tiempo, desde algún punto inteligente en la red:

La dirección IP se muestra como un número hexadecimal de cuatro bytes little-endian; es decir, el byte menos significativo aparece primero, por lo que deberá invertir el orden de los bytes para convertirlo a una dirección IP.

El número de puerto es un número hexadecimal simple de dos bytes.


el de ella uno de lujo que encontré en algún lugar de Internet. lo arregló de manera mínima para que se ajustara y emitiera correctamente los dispositivos 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/net/fib_trie tiene la topografía de la red

Para simplemente imprimir las direcciones de todos los adaptadores:

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

Para determinar el adaptador de esas direcciones (a) consulte las redes de destino de los adaptadores desde /proc/net/route , (b) /proc/net/fib_trie coincidir esas redes con las de /proc/net/fib_trie y (c) imprima el host / 32 correspondiente direcciones listadas bajo esas redes.

De nuevo, no hay python desafortunadamente, pero un enfoque de bash bastante extraño:

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

salida:

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

Limitación conocida:

Este enfoque no funciona de manera confiable para las direcciones de host que comparten la red con otras direcciones de host. Esta pérdida de la exclusividad de la red hace que sea imposible determinar la dirección de host correcta de fib_trie ya que el orden de esas direcciones no coincide necesariamente con el orden de las redes de ruta.

Habiendo dicho eso, no estoy seguro de por qué querría que varias direcciones de host que pertenecen a la misma red en primer lugar. Entonces, en la mayoría de los casos de uso, este enfoque debería funcionar bien.


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