python - getifaddrs

For whatever reason python lacks a binding for getifaddrs. For dionaea I created the binding myself, but the code can not be used without dionaea, and I don't like having to install additional bindings to get some basic functionality.
Therefore, I decided to create the functionality provided by the dionaea getifaddrs binding using python ctypes, easy to install, no compiling, copy&paste does the trick.

The code works with python2/3, Linux works, Darwin/OSX may work, Windows? good question.
For python3, decoding the name to unicode is a good idea.

from ctypes import *
from socket import AF_INET, AF_INET6, AF_PACKET, inet_ntop
from sys import platform
 
def getifaddrs():
	# getifaddr structs
	class ifa_ifu_u(Union):
		_fields_ = [ 
			( "ifu_broadaddr", c_void_p ),
			( "ifu_dstaddr",   c_void_p )  
		]
 
 
	class ifaddrs(Structure):
		_fields_ = [
			( "ifa_next",    c_void_p  ),
			( "ifa_name",    c_char_p  ),
			( "ifa_flags",   c_uint    ),
			( "ifa_addr",    c_void_p  ),
			( "ifa_netmask", c_void_p  ),
			( "ifa_ifu",     ifa_ifu_u ),
			( "ifa_data",    c_void_p  ) 
		]
 
	# AF_UNKNOWN / generic
	if platform.startswith( "darwin" ) or platform.startswith( "freebsd" ):
		class sockaddr ( Structure ):
			_fields_ = [ 
				("sa_len",     c_uint8 ),
				("sa_family",  c_uint8 ),
				("sa_data",   (c_uint8 * 14) ) ]
	else:
		class sockaddr(Structure):
			_fields_ = [
				( "sa_family", c_uint16 ),
				( "sa_data",   (c_uint8 * 14) ) 
			]
 
	# AF_INET / IPv4
	class in_addr(Union):
		_fields_ = [
			("s_addr", c_uint32),
		]
 
	class sockaddr_in(Structure):
		_fields_ = [
		    ("sin_family", c_short),
		    ("sin_port",   c_ushort),
		    ("sin_addr",   in_addr),
		    ("sin_zero",   (c_char * 8) ), # padding
		]
 
	# AF_INET6 / IPv6
	class in6_u(Union):
		_fields_ = [
			("u6_addr8",  (c_uint8 * 16) ),
			("u6_addr16", (c_uint16 * 8) ),
			("u6_addr32", (c_uint32 * 4) )
		]
 
	class in6_addr(Union):
		_fields_ = [
			("in6_u", in6_u),
		]
 
	class sockaddr_in6(Structure):
		_fields_ = [
			("sin6_family",	  c_short),
			("sin6_port",     c_ushort),
			("sin6_flowinfo", c_uint32),
			("sin6_addr",     in6_addr),
			("sin6_scope_id", c_uint32),
		]
 
	# AF_PACKET / Linux
	class sockaddr_ll( Structure ):
		_fields_ = [
			("sll_family",   c_uint16 ),
			("sll_protocol", c_uint16 ),
			("sll_ifindex",  c_uint32 ),
			("sll_hatype",   c_uint16 ),
			("sll_pktype",   c_uint8  ),
			("sll_halen",    c_uint8  ),
			("sll_addr",     (c_uint8 * 8) ) 
		]
 
	# AF_LINK / BSD|OSX
	class sockaddr_dl( Structure ):
		_fields_ = [ 
			("sdl_len",    c_uint8  ),
			("sdl_family", c_uint8  ),
			("sdl_index",  c_uint16 ),
			("sdl_type",   c_uint8  ),
			("sdl_nlen",   c_uint8  ),
			("sdl_alen",   c_uint8  ),
			("sdl_slen",   c_uint8  ),
			("sdl_data",   (c_uint8 * 46) ) 
		]
 
	libc = CDLL("libc.so.6")
	ptr = c_void_p(None)
	result = libc.getifaddrs(pointer(ptr))
	if result:
	    return None
	ifa = ifaddrs.from_address(ptr.value)
	result = {}
 
	while ifa:
		name = ifa.ifa_name
#		name = ifa.ifa_name.decode('UTF-8') # use this for python3
 
		if name not in result:
			result[name] = {}
 
		sa = sockaddr.from_address(ifa.ifa_addr)
 
		if sa.sa_family not in result[name]:
			result[name][sa.sa_family] = []
 
		data = {}
 
		if sa.sa_family == AF_INET:
			if ifa.ifa_addr is not None:
				si = sockaddr_in.from_address(ifa.ifa_addr)
				data['addr'] = inet_ntop(si.sin_family,si.sin_addr)
			if ifa.ifa_netmask is not None:
				si = sockaddr_in.from_address(ifa.ifa_netmask)
				data['netmask'] = inet_ntop(si.sin_family,si.sin_addr)
 
		if sa.sa_family == AF_INET6:
			if ifa.ifa_addr is not None:
				si = sockaddr_in6.from_address(ifa.ifa_addr)
				data['addr'] = inet_ntop(si.sin6_family,si.sin6_addr)
				if data['addr'].startswith('fe80:'):
					data['scope'] = si.sin6_scope_id
			if ifa.ifa_netmask is not None:
				si = sockaddr_in6.from_address(ifa.ifa_netmask)
				data['netmask'] = inet_ntop(si.sin6_family,si.sin6_addr)
 
		if sa.sa_family == AF_PACKET:
			if ifa.ifa_addr is not None:
				si = sockaddr_ll.from_address(ifa.ifa_addr)
				addr = ""
				for i in range(si.sll_halen):
					addr += "%02x:" % si.sll_addr[i]
				addr = addr[:-1]
				data['addr'] = addr
 
		if len(data) > 0:
			result[name][sa.sa_family].append(data)
 
		if ifa.ifa_next:
			ifa = ifaddrs.from_address(ifa.ifa_next)
		else:
			break
 
	libc.freeifaddrs(ptr)
	return result

getifaddrs.py

useage

readable print

ifaces=getifaddrs()
for iface in ifaces:
	print(iface)
	for family in ifaces[iface]:
		print("\t%s" % { AF_INET: 'IPv4', AF_INET6 : 'IPv6', AF_PACKET: 'HW' }[family])
		for addr in ifaces[iface][family]:
			for i in ['addr','netmask','scope']:
				if i in addr:
					print("\t\t%s %s" % (i, str(addr[i])))
			print("")

get all IPv4 addresses

v4 = {}			
for iface in ifaces:
	if AF_INET in ifaces[iface]:
		for i in ifaces[iface][AF_INET]:
			v4[i['addr']] = iface
print(v4)

Comments

1

There is an error in here when you run this on a system that has a vpn tunnel on it where the tunnel interface doesn't have a hardware address. There should be a check at the beginning of the while loop that checks to see if the ifa_addr is None and if it is continue to the next entry.

rpmoore
2012/11/30 20:08
2

Hi,

ifa_addr is checke for every family - which is why I do not understand the problem.

I changed the while condition to while ifa, but I think that does not fit to the problem you describe.

I'll update again - if you post me a diff.

Markus
2012/12/01 16:30
3

It is checked for every type but on the line:

sa = sockaddr.from_address(ifa.ifa_addr)

You access it when it could be None. So prior to that line there needs to be a None check on ifa.ifa_addr and if it is None move to the next entry.

rpmoore
2012/12/03 17:39
4

I've sent a patch to the Robot Operating System (ROS) project, containing your getifaddrs Python implementation.

The ROS project is licensed under a BSD license. Would you be willing to license the code under a BSD license? It's used to implement IPv6 support for ROS.

Thanks,

Tobias

Tobias Schneider
2013/01/16 22:30
5

sure - just use it.

Markus
2013/01/24 15:24
6

This wasn't actually working under FreeBSD (and presumably OS X). I put a version that works under FreeBSD and Linux up here:

http://pastebin.com/wxjai3Mw

You can see the diff here:

http://pastebin.com/CKPxYsQ2

The problems:

* AF_PACKET is not defined in FreeBSD, so I put that in a try/except wrapper

* AF_LINK is used to provide MAC address information, although that is not defined in the socket module (at least on the system I checked on); I hacked around that by defining the appropriate constants

* The sockaddr_in and sockaddr_in6 structures have a “len” field, so I use different versions of these structures

* The name of the library file is different (just “libc.so”)

* sin6_family was set to 0 for some reason on IPv6 addresses, so I just hard-coded it to AF_INET6 (which it has to be) when calling inet_ntop(); I changed all calls to inet_ntop() for consistency

I made a few other changes as well:

* The code works under either Python 2 or Python 3 now

* If no information is included for a type (happens for AF_LINK in FreeBSD), then that type is not added at all to the particular interface

* If the HW address is all 0 (happens for AF_PACKET in Linux with loopback and other virtual interfaces), then the HW address is not added to that interface inforation

There is still a slight problem because hardware information is in the AF_PACKET family in Linux and AF_LINK family in FreeBSD, but I'm not sure the best way to resolve that.

Thanks for the excellent code! :)

Shane Kerr
2013/01/28 20:30
7
  • sin6_family was set to 0 for some reason on IPv6 addresses

Does not make sense, you use the very same data to read the sockaddr and get the sa_family, as when you read a sockaddr_in6 - so it should be set. sa.sa_family always has to match si.sin(6)?_family.

You missed that ifa_addr can be empty from comment #3.

yxz
2013/01/30 12:22


2010/07/22/python_-_getifaddrs.txt · Last modified: 2012/12/01 16:25 by common
chimeric.de = chi`s home Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0