Wednesday, October 20, 2010

Python: Enumerating IP Addresses on MacOS X

How do you enumerate the host's local IP addresses from python? This turns out to be a surprisingly common question. Unfortunately, there is no pretty answer; it depends on the host operating system. On Windows, you can wrap the IP Helper GetIpAddrTable using ctypes. On modern Linux, *BSD, or MacOS X systems, you can wrap getifaddrs(). Neither is trivial, though, so I'll save those for a future post.

Luckily, MacOS X provides a simpler way to get the local IP addresses: the system configuration dynamic store. Using pyObjC, which comes pre-installed on every Mac, we can write a straight port of Apple's example in Technical Note TN1145 for retrieving a list of all IPv4 addresses assigned to local interfaces:

from SystemConfiguration import * # from pyObjC
import socket

def GetIPv4Addresses():
"""
Get all IPv4 addresses assigned to local interfaces.
Returns a generator object that produces information
about each IPv4 address present at the time that the
function was called.

For each IPv4 address, the returned generator yields
a tuple consisting of the interface name, address
family (always socket.AF_INET), the IP address, and
the netmask. The tuple elements may also be accessed
by the names: "ifname", "family", "address", and
"netmask".
"""
ds = SCDynamicStoreCreate(None, 'GetIPv4Addresses', None, None)
# Get all keys matching pattern State:/Network/Service/[^/]+/IPv4
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(None,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv4)
patterns = CFArrayCreate(None, (pattern, ), 1, kCFTypeArrayCallBacks)
valueDict = SCDynamicStoreCopyMultiple(ds, None, patterns)

ipv4info = namedtuple('ipv4info', 'ifname family address netmask')

for serviceDict in valueDict.values():
ifname = serviceDict[u'InterfaceName']
for address, netmask in zip(serviceDict[u'Addresses'], serviceDict[u'SubnetMasks']):
yield ipv4info(ifname, socket.AF_INET, address, netmask)

One interesting point regarding this code is that it doesn't actually inspect interface information in the system configuration dynamic store. The interface-related keys are stored under State:/Network/Interface/, but this code (and Apple's example on which it is based) inspect keys under State:/Network/Service/ instead. However, if you want to get IPv6 addresses then you do have to inspect the system configuration's interface information:

from SystemConfiguration import * # from pyObjC
import socket
import re
ifnameExtractor = re.compile(r'/Interface/([^/]+)/')

def GetIPv6Addresses():
"""
Get all IPv6 addresses assigned to local interfaces.
Returns a generator object that produces information
about each IPv6 address present at the time that the
function was called.

For each IPv6 address, the returned generator yields
a tuple consisting of the interface name, address
family (always socket.AF_INET6), the IP address, and
the prefix length. The tuple elements may also be
accessed by the names: "ifname", "family", "address",
and "prefixlen".
"""
ds = SCDynamicStoreCreate(None, 'GetIPv6Addresses', None, None)
# Get all keys matching pattern State:/Network/Interface/[^/]+/IPv6
pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(None,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv6)
patterns = CFArrayCreate(None, (pattern, ), 1, kCFTypeArrayCallBacks)
valueDict = SCDynamicStoreCopyMultiple(ds, None, patterns)

ipv6info = namedtuple('ipv6info', 'ifname family address prefixlen')

for key, ifDict in valueDict.items():
ifname = ifnameExtractor.search(key).group(1)
for address, prefixlen in zip(ifDict[u'Addresses'], ifDict[u'PrefixLength']):
yield ipv6info(ifname, socket.AF_INET6, address, prefixlen)

In fact, you could easily adapt the above function to be able to fetch IPv4 addresses from the interface configuration.

1 comment:

Unknown said...

On Unix based systems like Mac OS X, you could execute the following using popen and get a list of IPs:
/sbin/ifconfig -a | awk \'/(cast)/ { print $2 }\' | cut -d\':\' -f2