Welcome, guest | Sign In | My Account | Store | Cart

Uses the SIOCGIFCONF ioctl to obtain a list of interfaces and extracts those names, returning them in a list of strings.

Python, 17 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import socket
import fcntl
import struct
import array

def all_interfaces():
    max_possible = 128  # arbitrary. raise if needed.
    bytes = max_possible * 32
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    names = array.array('B', '\0' * bytes)
    outbytes = struct.unpack('iL', fcntl.ioctl(
        s.fileno(),
        0x8912,  # SIOCGIFCONF
        struct.pack('iL', bytes, names.buffer_info()[0])
    ))[0]
    namestr = names.tostring()
    return [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]

This solution should be faster than running ifconfig and parsing its output, and simpler than reading and parsing /proc/net/dev, since that will list all existing interfaces, whether active (up) or not.

This solution probably only works on Linux-- and possibly not all versions of even that-- since it depends on some C structures having a particular size and layout, as well as having the SIOCGIFCONF ioctl number (0x8912) hardcoded. I believe it could be adjusted, though, to work on other Unix systems.

8 comments

Duane Voth 14 years, 6 months ago  # | flag

struct ifconf has changed... I wish we didn't have to use constants for the structure offsets... For a 2.6.17 kernel the above offsets are incorrect. I've replaced the last line with:

    ...
    lst = []
    for i in range(0, outbytes, 40):
        name = namestr[i:i+16].split('\0', 1)[0]
        ip   = namestr[i+20:i+24]
        lst.append((name, ip))
    return lst

def format_ip(addr):
    return str(ord(addr[0])) + '.' + \
           str(ord(addr[1])) + '.' + \
           str(ord(addr[2])) + '.' + \
           str(ord(addr[3]))


ifs = all_interfaces()
for i in ifs:
    print "%12s   %s" % (i[0], format_ip(i[1]))
Duane Voth 14 years, 6 months ago  # | flag

The above is for a 64 bit kernel... Ah, structure offsets have not changed over the years for 32 bit kernels, my patch is for a 64 bit 2.6.17 kernel.

Guilherme Polo 13 years, 10 months ago  # | flag

no need for format_ip. You could have done: socket.inet_ntoa(ip)

Samuel Nelson 11 years, 5 months ago  # | flag

To go along with Duane Voth's comment, here's a little extra to detect whether the code is running on 64bit or 32bit and then run the platform-specific code. It also returns a list of tuples of (iface, iface_ip), in case anyone needs the IP addresses associated with those interfaces.


import fcntl
import array
import struct
import socket
import platform

# global constants.  If you don't like 'em here,
# move 'em inside the function definition.
SIOCGIFCONF = 0x8912
MAXBYTES = 8096

def localifs():
    """
    Used to get a list of the up interfaces and associated IP addresses
    on this machine (linux only).

    Returns:
        List of interface tuples.  Each tuple consists of
        (interface name, interface IP)
    """
    global SIOCGIFCONF
    global MAXBYTES

    arch = platform.architecture()[0]

    # I really don't know what to call these right now
    var1 = -1
    var2 = -1
    if arch == '32bit':
        var1 = 32
        var2 = 32
    elif arch == '64bit':
        var1 = 16
        var2 = 40
    else:
        raise OSError("Unknown architecture: %s" % arch)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    names = array.array('B', '\0' * MAXBYTES)
    outbytes = struct.unpack('iL', fcntl.ioctl(
        sock.fileno(),
        SIOCGIFCONF,
        struct.pack('iL', MAXBYTES, names.buffer_info()[0])
        ))[0]

    namestr = names.tostring()
    return [(namestr[i:i+var1].split('\0', 1)[0], socket.inet_ntoa(namestr[i+20:i+24])) \
            for i in xrange(0, outbytes, var2)]

I know, my code's not particularly pretty, but it seems to work on both 32bit and 64bit systems.

I most specifically apologize for the naming of var1 and var2 - I honestly don't know what they represent, or I would probably have come up with a different naming convention for them. If anyone can clue me in, I am actually curious (but have had a hard time googling for the types of things done in this code).

Apostolos Vlahopoulos 11 years, 1 month ago  # | flag

i can't understand how the data are passed in the array...could you help me understand it a bit more...and second question how do u know where to check for ip?i mean why is it in [20:24] where did you find those info?

Marco 10 years, 9 months ago  # | flag

I tried this script on a ubuntu machine - all packages are up to date. However, the localifs() function returns all setup interfaces. Meaning, if the interface has an invalid static ip address it will be returned as an 'up' interface, although it is not. I wrote a script in /etc/network/if-up.d/mystartupscript, which writes a log that a new interface has come up. This script does not run for an invalid interface configuration.

Is there a way, that invalid configured interfaces, which are not 'up', are not included in the result set?

Noam Yorav-Raphael 9 years, 2 months ago  # | flag

Thanks to all previous comments combined, here's a version that works on both 32- and 64-bits, returns both the interface name and its address, and as a bonus isn't limited to 128 interfaces (although I don't believe it's really going to be a problem for anyone)

import sys
import socket
import fcntl
import struct
import array

def all_interfaces():
    is_64bits = sys.maxsize > 2**32
    struct_size = 40 if is_64bits else 32
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    max_possible = 8 # initial value
    while True:
        bytes = max_possible * struct_size
        names = array.array('B', '\0' * bytes)
        outbytes = struct.unpack('iL', fcntl.ioctl(
            s.fileno(),
            0x8912,  # SIOCGIFCONF
            struct.pack('iL', bytes, names.buffer_info()[0])
        ))[0]
        if outbytes == bytes:
            max_possible *= 2
        else:
            break
    namestr = names.tostring()
    return [(namestr[i:i+16].split('\0', 1)[0],
             socket.inet_ntoa(namestr[i+20:i+24]))
            for i in range(0, outbytes, struct_size)]
Luc 6 years, 5 months ago  # | flag

Many thanks to Noam Yorav-Raphael. I've adapted his code to work in python 3.4:

import socket
import fcntl
import struct
import array
import sys

def all_interfaces():
    is_64bits = sys.maxsize > 2**32
    struct_size = 40 if is_64bits else 32
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    max_possible = 8 # initial value
    while True:
        _bytes = max_possible * struct_size
        names = array.array('B')
        for i in range(0, _bytes):
            names.append(0)
        outbytes = struct.unpack('iL', fcntl.ioctl(
            s.fileno(),
            0x8912,  # SIOCGIFCONF
            struct.pack('iL', _bytes, names.buffer_info()[0])
        ))[0]
        if outbytes == _bytes:
            max_possible *= 2
        else:
            break
    namestr = names.tostring()
    ifaces = []
    for i in range(0, outbytes, struct_size):
        iface_name = bytes.decode(namestr[i:i+16]).split('\0', 1)[0]
        iface_addr = socket.inet_ntoa(namestr[i+20:i+24])
        ifaces.append((iface_name, iface_addr))

    return ifaces