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
15 from twisted.python import log
22 def increase_rlimits():
23 # We'd like to raise our soft resource.RLIMIT_NOFILE, since certain
24 # systems (OS-X, probably solaris) start with a relatively low limit
25 # (256), and some unit tests want to open up more sockets than this.
26 # Most linux systems start with both hard and soft limits at 1024,
29 # unfortunately the values to pass to setrlimit() vary widely from
30 # one system to another. OS-X reports (256, HUGE), but the real hard
31 # limit is 10240, and accepts (-1,-1) to mean raise it to the
32 # maximum. Cygwin reports (256, -1), then ignores a request of
33 # (-1,-1): instead you have to guess at the hard limit (it appears to
34 # be 3200), so using (3200,-1) seems to work. Linux reports a
35 # sensible (1024,1024), then rejects (-1,-1) as trying to raise the
36 # maximum limit, so you could set it to (1024,1024) but you might as
37 # well leave it alone.
40 current = resource.getrlimit(resource.RLIMIT_NOFILE)
41 except AttributeError:
42 # we're probably missing RLIMIT_NOFILE
45 if current[0] >= 1024:
46 # good enough, leave it alone
50 if current[1] > 0 and current[1] < 1000000:
51 # solaris reports (256, 65536)
52 resource.setrlimit(resource.RLIMIT_NOFILE,
53 (current[1], current[1]))
55 # this one works on OS-X (bsd), and gives us 10240, but
56 # it doesn't work on linux (on which both the hard and
57 # soft limits are set to 1024 by default).
58 resource.setrlimit(resource.RLIMIT_NOFILE, (-1,-1))
59 new = resource.getrlimit(resource.RLIMIT_NOFILE)
60 if new[0] == current[0]:
61 # probably cygwin, which ignores -1. Use a real value.
62 resource.setrlimit(resource.RLIMIT_NOFILE, (3200,-1))
65 log.msg("unable to set RLIMIT_NOFILE: current value %s"
66 % (resource.getrlimit(resource.RLIMIT_NOFILE),))
68 # who knows what. It isn't very important, so log it and continue
71 def increase_rlimits():
72 # TODO: implement this for Windows. Although I suspect the
73 # solution might be "be running under the iocp reactor and
74 # make this function be a no-op".
77 def get_local_addresses_async(target='A.ROOT-SERVERS.NET'):
79 Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
80 strings) that are currently configured on this host, sorted in descending
81 order of how likely we think they are to work.
83 @param target: we want to learn an IP address they could try using to
84 connect to us; The default value is fine, but it might help if you
85 pass the address of a host that you are actually trying to be
89 local_ip = get_local_ip_for(target)
91 addresses.append(local_ip)
93 if sys.platform == "cygwin":
94 d = _cygwin_hack_find_addresses(target)
96 d = _find_addresses_via_config()
100 if addr != "0.0.0.0" and not addr in addresses:
101 addresses.append(addr)
103 d.addCallback(_collect)
107 def get_local_ip_for(target):
108 """Find out what our IP address is for use by a given target.
110 @return: the IP address as a dotted-quad string which could be used by
111 to connect to us. It might work for them, it might not. If
112 there is no suitable address (perhaps we don't currently have an
113 externally-visible interface), this will return None.
117 target_ipaddr = socket.gethostbyname(target)
118 except socket.gaierror:
121 udpprot = DatagramProtocol()
122 port = reactor.listenUDP(0, udpprot)
124 udpprot.transport.connect(target_ipaddr, 7)
125 localip = udpprot.transport.getHost().host
127 # no route to that host
129 port.stopListening() # note, this returns a Deferred
132 # k: result of sys.platform, v: which kind of IP configuration reader we use
134 "linux-i386": "linux", # redhat
135 "linux-ppc": "linux", # redhat
136 "linux2": "linux", # debian
142 "darwin": "bsd", # Mac OS X
150 class UnsupportedPlatformError(Exception):
153 # Wow, I'm really amazed at home much mileage we've gotten out of calling
154 # the external route.exe program on windows... It appears to work on all
155 # versions so far. Still, the real system calls would much be preferred...
156 # ... thus wrote Greg Smith in time immemorial...
157 _win32_path = 'route.exe'
158 _win32_args = ('print',)
159 _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)
161 # These work in Redhat 6.x and Debian 2.2 potato
162 _linux_path = '/sbin/ifconfig'
163 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
165 # NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
166 _netbsd_path = '/sbin/ifconfig'
167 _netbsd_args = ('-a',)
168 _netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
171 _irix_path = '/usr/etc/ifconfig'
174 _sunos_path = '/usr/sbin/ifconfig'
176 class SequentialTrier(object):
177 """ I hold a list of executables to try and try each one in turn
178 until one gives me a list of IP addresses."""
180 def __init__(self, exebasename, args, regex):
181 assert not os.path.isabs(exebasename)
182 self.exes_left_to_try = which(exebasename)
183 self.exes_left_to_try.reverse()
186 self.o = observer.OneShotObserverList()
190 if not self.exes_left_to_try:
193 exe = self.exes_left_to_try.pop()
194 d2 = _query(exe, self.args, self.regex)
205 d2.addCallbacks(cb, eb)
207 def when_tried(self):
208 return self.o.when_fired()
210 # k: platform string as provided in the value of _platform_map
211 # v: tuple of (path_to_tool, args, regex,)
213 "linux": (_linux_path, (), _linux_re,),
214 "win32": (_win32_path, _win32_args, _win32_re,),
215 "cygwin": (_win32_path, _win32_args, _win32_re,),
216 "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
217 "irix": (_irix_path, _netbsd_args, _netbsd_re,),
218 "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
220 def _find_addresses_via_config():
221 # originally by Greg Smith, hacked by Zooko to conform to Brian's API
223 platform = _platform_map.get(sys.platform)
225 raise UnsupportedPlatformError(sys.platform)
227 (pathtotool, args, regex,) = _tool_map[platform]
229 # If pathtotool is a fully qualified path then we just try that.
230 # If it is merely an executable name then we use Twisted's
231 # "which()" utility and try each executable in turn until one
232 # gives us something that resembles a dotted-quad IPv4 address.
234 if os.path.isabs(pathtotool):
235 return _query(pathtotool, args, regex)
237 return SequentialTrier(pathtotool, args, regex).when_tried()
239 def _query(path, args, regex):
240 d = getProcessOutput(path, args)
243 outputsplit = output.split('\n')
244 for outline in outputsplit:
245 m = regex.match(outline)
247 addr = m.groupdict()['address']
248 if addr not in addresses:
249 addresses.append(addr)
252 d.addCallback(_parse)
255 def _cygwin_hack_find_addresses(target):
257 for h in [target, "localhost", "127.0.0.1",]:
259 addr = get_local_ip_for(h)
260 if addr not in addresses:
261 addresses.append(addr)
262 except socket.gaierror:
265 return defer.succeed(addresses)