1 # portions extracted from ipaddresslib by Autonomous Zone Industries, LGPL (author: Greg Smith)
2 # portions adapted from nattraverso.ipdiscover
3 # portions authored by Brian Warner, working for Allmydata
4 # most recent version authored by Zooko O'Whielacronx, working for Allmydata
6 # from the Python Standard Library
7 import os, re, socket, sys
10 from twisted.internet import defer
11 from twisted.internet import reactor
12 from twisted.internet.protocol import DatagramProtocol
13 from twisted.internet.utils import getProcessOutput
14 from twisted.python.procutils import which
19 def get_local_addresses_async(target='A.ROOT-SERVERS.NET'):
21 Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
22 strings) that are currently configured on this host, sorted in descending
23 order of how likely we think they are to work.
25 @param target: we want to learn an IP address they could try using to
26 connect to us; The default value is fine, but it might help if you
27 pass the address of a host that you are actually trying to be
31 local_ip = get_local_ip_for(target)
33 addresses.append(local_ip)
35 if sys.platform == "cygwin":
36 d = _cygwin_hack_find_addresses(target)
38 d = _find_addresses_via_config()
42 if not addr in addresses:
43 addresses.append(addr)
45 d.addCallback(_collect)
49 def get_local_ip_for(target):
50 """Find out what our IP address is for use by a given target.
52 @return: the IP address as a dotted-quad string which could be used by
53 to connect to us. It might work for them, it might not. If
54 there is no suitable address (perhaps we don't currently have an
55 externally-visible interface), this will return None.
59 target_ipaddr = socket.gethostbyname(target)
60 except socket.gaierror:
63 udpprot = DatagramProtocol()
64 port = reactor.listenUDP(0, udpprot)
66 udpprot.transport.connect(target_ipaddr, 7)
67 localip = udpprot.transport.getHost().host
69 # no route to that host
71 port.stopListening() # note, this returns a Deferred
74 # k: result of sys.platform, v: which kind of IP configuration reader we use
76 "linux-i386": "linux", # redhat
77 "linux-ppc": "linux", # redhat
78 "linux2": "linux", # debian
84 "darwin": "bsd", # Mac OS X
92 class UnsupportedPlatformError(Exception):
95 # Wow, I'm really amazed at home much mileage we've gotten out of calling
96 # the external route.exe program on windows... It appears to work on all
97 # versions so far. Still, the real system calls would much be preferred...
98 # ... thus wrote Greg Smith in time immemorial...
99 _win32_path = 'route.exe'
100 _win32_args = ('print',)
101 _win32_re = re.compile('^\s*\d+\.\d+\.\d+\.\d+\s.+\s(?P<address>\d+\.\d+\.\d+\.\d+)\s+(?P<metric>\d+)\s*$', flags=re.M|re.I|re.S)
103 # These work in Redhat 6.x and Debian 2.2 potato
104 _linux_path = '/sbin/ifconfig'
105 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
107 # NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
108 _netbsd_path = '/sbin/ifconfig'
109 _netbsd_args = ('-a',)
110 _netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
113 _irix_path = '/usr/etc/ifconfig'
116 _sunos_path = '/usr/sbin/ifconfig'
118 class SequentialTrier(object):
119 """ I hold a list of executables to try and try each one in turn
120 until one gives me a list of IP addresses."""
122 def __init__(self, exebasename, args, regex):
123 assert not os.path.isabs(exebasename)
124 self.exes_left_to_try = which(exebasename)
125 self.exes_left_to_try.reverse()
128 self.o = observer.OneShotObserverList()
132 if not self.exes_left_to_try:
135 exe = self.exes_left_to_try.pop()
136 d2 = _query(exe, self.args, self.regex)
147 d2.addCallbacks(cb, eb)
149 def when_tried(self):
150 return self.o.when_fired()
152 # k: platform string as provided in the value of _platform_map
153 # v: tuple of (path_to_tool, args, regex,)
155 "linux": (_linux_path, (), _linux_re,),
156 "win32": (_win32_path, _win32_args, _win32_re,),
157 "cygwin": (_win32_path, _win32_args, _win32_re,),
158 "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
159 "irix": (_irix_path, _netbsd_args, _netbsd_re,),
160 "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
162 def _find_addresses_via_config():
163 # originally by Greg Smith, hacked by Zooko to conform to Brian's API
165 platform = _platform_map.get(sys.platform)
167 raise UnsupportedPlatformError(sys.platform)
169 (pathtotool, args, regex,) = _tool_map[platform]
171 # If pathtotool is a fully qualified path then we just try that.
172 # If it is merely an executable name then we use Twisted's
173 # "which()" utility and try each executable in turn until one
174 # gives us something that resembles a dotted-quad IPv4 address.
176 if os.path.isabs(pathtotool):
177 return _query(pathtotool, args, regex)
179 return SequentialTrier(pathtotool, args, regex).when_tried()
181 def _query(path, args, regex):
182 d = getProcessOutput(path, args)
185 outputsplit = output.split('\n')
186 for outline in outputsplit:
187 m = regex.match(outline)
189 addr = m.groupdict()['address']
190 if addr not in addresses:
191 addresses.append(addr)
194 d.addCallback(_parse)
197 def _cygwin_hack_find_addresses(target):
199 for h in [target, "localhost", "127.0.0.1",]:
201 addr = get_local_ip_for(h)
202 if addr not in addresses:
203 addresses.append(addr)
204 except socket.gaierror:
207 return defer.succeed(addresses)