+# portions extracted from ipaddresslib by Autonomous Zone Industries, LGPL (author: Greg Smith)
+# portions adapted from nattraverso.ipdiscover
+# portions authored by Brian Warner, working for Allmydata
+# most recent version authored by Zooko O'Whielacronx, working for Allmydata
-# adapted from nattraverso.ipdiscover
+# from the Python Standard Library
+import re, socket, sys
-import subprocess
-import re
-import socket
-from cStringIO import StringIO
+# from Twisted
+from twisted.internet import defer
+from twisted.python import log
from twisted.internet import reactor
from twisted.internet.protocol import DatagramProtocol
from twisted.internet.utils import getProcessOutput
+from twisted.python.procutils import which
-from fcntl import ioctl
-import struct
-SIOCGIFADDR = 0x8915 # linux-specific
-
-# inspired by scapy
-def get_if_list():
- f = open("/proc/net/dev","r")
- f.readline(); f.readline()
- names = []
- for l in f.readlines():
- names.append(l[:l.index(":")].strip())
- return names
+def get_local_addresses_async(target='A.ROOT-SERVERS.NET'):
+ """
+ Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
+ strings) that are currently configured on this host.
-def get_if_addr(ifname):
- try:
- s=socket.socket()
- ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", ifname))
- s.close()
- naddr = ifreq[20:24]
- return socket.inet_ntoa(naddr)
- except IOError:
- return None
-
-def get_local_addresses():
- """Return a list of IPv4 addresses (as dotted-quad strings) that are
- currently configured on this host.
-
- This will only work under linux, because it uses both a linux-specific
- /proc/net/dev devices (to get the interface names) and a SIOCGIFADDR
- ioctl (to get their addresses). If the listing-the-interfaces were done
- with an ioctl too (and if if you're lucky enough to be using the same
- value for the ioctls), then it might work on other forms of unix too.
- Windows is right out."""
-
- ifnames = []
- for ifname in get_if_list():
- addr = get_if_addr(ifname)
- if addr:
- ifnames.append(addr)
- return ifnames
-
-def get_local_addresses_sync():
- """Return a list of IPv4 addresses (as dotted-quad strings) that are
- currently configured on this host.
-
- Unfortunately this is not compatible with Twisted: it catches SIGCHLD and
- this usually results in errors about 'Interrupted system call'.
-
- This will probably work on both linux and OS-X, but probably not windows.
+ @param target: we want to learn an IP address they could try using to
+ connect to us; The default value is fine, but it might help if you
+ pass the address of a host that you are actually trying to be
+ reachable to.
"""
- # eventually I want to use somebody else's cross-platform library for
- # this. For right now, I'm running ifconfig and grepping for the 'inet '
- # lines.
-
- cmd = "/sbin/ifconfig"
- #p = os.popen(cmd)
- c = subprocess.Popen(["ifconfig"], stdout=subprocess.PIPE)
- output = c.communicate()[0]
- p = StringIO(output)
addresses = []
- for line in p.readlines():
- # linux shows: " inet addr:1.2.3.4 Bcast:1.2.3.255..."
- # OS-X shows: " inet 1.2.3.4 ..."
- m = re.match("^\s+inet\s+[a-z:]*([\d\.]+)\s", line)
- if m:
- addresses.append(m.group(1))
- return addresses
-
-def get_local_addresses_async():
- """Return a Deferred that fires with a list of IPv4 addresses (as
- dotted-quad strings) that are currently configured on this host.
-
- This will probably work on both linux and OS-X, but probably not windows.
- """
- # eventually I want to use somebody else's cross-platform library for
- # this. For right now, I'm running ifconfig and grepping for the 'inet '
- # lines.
+ addresses.append(get_local_ip_for(target))
- # I'd love to do this synchronously.
- cmd = "/sbin/ifconfig"
- d = getProcessOutput(cmd)
- def _parse(output):
- addresses = []
- for line in StringIO(output).readlines():
- # linux shows: " inet addr:1.2.3.4 Bcast:1.2.3.255..."
- # OS-X shows: " inet 1.2.3.4 ..."
- m = re.match("^\s+inet\s+[a-z:]*([\d\.]+)\s", line)
- if m:
- addresses.append(m.group(1))
+ d = _find_addresses_via_config()
+ def _collect(res):
+ addresses.extend(res)
return addresses
- def _fallback(f):
- return ["127.0.0.1", get_local_ip_for()]
- d.addCallbacks(_parse, _fallback)
- return d
+ d.addCallback(_collect)
+ return d
-def get_local_ip_for(target='A.ROOT-SERVERS.NET'):
+def get_local_ip_for(target):
"""Find out what our IP address is for use by a given target.
Returns a string that holds the IP address which could be used by
port.stopListening() # note, this returns a Deferred
return localip
+# k: result of sys.platform, v: which kind of IP configuration reader we use
+_platform_map = {
+ "linux-i386": "linux", # redhat
+ "linux-ppc": "linux", # redhat
+ "linux2": "linux", # debian
+ "win32": "win32",
+ "irix6-n32": "irix",
+ "irix6-n64": "irix",
+ "irix6": "irix",
+ "openbsd2": "bsd",
+ "darwin": "bsd", # Mac OS X
+ "freebsd4": "bsd",
+ "freebsd5": "bsd",
+ "netbsd1": "bsd",
+ "sunos5": "sunos",
+ "cygwin": "cygwin",
+ }
+
+class UnsupportedPlatformError(Exception):
+ pass
+
+# Wow, I'm really amazed at home much mileage we've gotten out of calling
+# the external route.exe program on windows... It appears to work on all
+# versions so far. Still, the real system calls would much be preferred...
+# ... thus wrote Greg Smith in time immemorial...
+_win32_path = 'route.exe'
+_win32_args = ('print',)
+_win32_re = re.compile('^\s*0\.0\.0\.0\s.+\s(?P<address>\d+\.\d+\.\d+\.\d+)\s+(?P<metric>\d+)\s*$', flags=re.M|re.I|re.S)
+
+# These work in Redhat 6.x and Debian 2.2 potato
+_linux_path = '/sbin/ifconfig'
+_linux_re = re.compile('^(?P<name>\w+)\s.+\sinet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
+
+# NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
+_netbsd_path = '/sbin/ifconfig -a'
+_netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
+
+# Irix 6.5
+_irix_path = '/usr/etc/ifconfig -a'
+
+# Solaris 2.x
+_sunos_path = '/usr/sbin/ifconfig -a'
+
+def _find_addresses_via_config():
+ # originally by Greg Smith, hacked by Zooko to conform to Brian's API
+
+ platform = _platform_map.get(sys.platform)
+ if not platform:
+ raise UnsupportedPlatformError(sys.platform)
+
+ if platform in ('win32', 'cygwin',):
+ l = []
+ for executable in which(_win32_path):
+ l.append(_query(executable, _win32_re, _win32_args))
+ dl = defer.DeferredList(l)
+ def _gather_results(res):
+ addresses = []
+ for r in res:
+ if r[0]:
+ addresses.extend(r[1])
+ return addresses
+ dl.addCallback(_gather_results)
+ return dl
+ elif platform == 'linux':
+ return _query(_linux_path, _linux_re)
+ elif platform == 'bsd':
+ return _query(_netbsd_path, _netbsd_re)
+ elif platform == 'irix' :
+ return _query(_irix_path, _netbsd_re)
+ elif platform == 'sunos':
+ return _query(_sunos_path, _netbsd_re)
+
+def _query(path, regex, args=()):
+ d = getProcessOutput(path, args)
+ def _parse(output):
+ addresses = []
+ outputsplit = output.split('\n')
+ for output in outputsplit:
+ m = regex.match(output)
+ if m:
+ d = m.groupdict()
+ addresses.append(d['address'])
+
+ return addresses
+ d.addCallback(_parse)
+ return d