1 # from the Python Standard Library
2 import os, re, socket, sys
5 from twisted.internet import defer
6 from twisted.internet import reactor
7 from twisted.internet.protocol import DatagramProtocol
8 from twisted.internet.utils import getProcessOutput
9 from twisted.python.procutils import which
10 from twisted.python import log
12 from allmydata.util import observer
16 def increase_rlimits():
17 # We'd like to raise our soft resource.RLIMIT_NOFILE, since certain
18 # systems (OS-X, probably solaris) start with a relatively low limit
19 # (256), and some unit tests want to open up more sockets than this.
20 # Most linux systems start with both hard and soft limits at 1024,
23 # unfortunately the values to pass to setrlimit() vary widely from
24 # one system to another. OS-X reports (256, HUGE), but the real hard
25 # limit is 10240, and accepts (-1,-1) to mean raise it to the
26 # maximum. Cygwin reports (256, -1), then ignores a request of
27 # (-1,-1): instead you have to guess at the hard limit (it appears to
28 # be 3200), so using (3200,-1) seems to work. Linux reports a
29 # sensible (1024,1024), then rejects (-1,-1) as trying to raise the
30 # maximum limit, so you could set it to (1024,1024) but you might as
31 # well leave it alone.
34 current = resource.getrlimit(resource.RLIMIT_NOFILE)
35 except AttributeError:
36 # we're probably missing RLIMIT_NOFILE
39 if current[0] >= 1024:
40 # good enough, leave it alone
44 if current[1] > 0 and current[1] < 1000000:
45 # solaris reports (256, 65536)
46 resource.setrlimit(resource.RLIMIT_NOFILE,
47 (current[1], current[1]))
49 # this one works on OS-X (bsd), and gives us 10240, but
50 # it doesn't work on linux (on which both the hard and
51 # soft limits are set to 1024 by default).
52 resource.setrlimit(resource.RLIMIT_NOFILE, (-1,-1))
53 new = resource.getrlimit(resource.RLIMIT_NOFILE)
54 if new[0] == current[0]:
55 # probably cygwin, which ignores -1. Use a real value.
56 resource.setrlimit(resource.RLIMIT_NOFILE, (3200,-1))
59 log.msg("unable to set RLIMIT_NOFILE: current value %s"
60 % (resource.getrlimit(resource.RLIMIT_NOFILE),))
62 # who knows what. It isn't very important, so log it and continue
65 def _increase_rlimits():
66 # TODO: implement this for Windows. Although I suspect the
67 # solution might be "be running under the iocp reactor and
68 # make this function be a no-op".
70 # pyflakes complains about two 'def FOO' statements in the same time,
71 # since one might be shadowing the other. This hack appeases pyflakes.
72 increase_rlimits = _increase_rlimits
75 def get_local_addresses_async(target="198.41.0.4"): # A.ROOT-SERVERS.NET
77 Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
78 strings) that are currently configured on this host, sorted in descending
79 order of how likely we think they are to work.
81 @param target: we want to learn an IP address they could try using to
82 connect to us; The default value is fine, but it might help if you
83 pass the address of a host that you are actually trying to be
87 local_ip = get_local_ip_for(target)
89 addresses.append(local_ip)
91 if sys.platform == "cygwin":
92 d = _cygwin_hack_find_addresses(target)
94 d = _find_addresses_via_config()
98 if addr != "0.0.0.0" and not addr in addresses:
99 addresses.append(addr)
101 d.addCallback(_collect)
105 def get_local_ip_for(target):
106 """Find out what our IP address is for use by a given target.
108 @return: the IP address as a dotted-quad string which could be used by
109 to connect to us. It might work for them, it might not. If
110 there is no suitable address (perhaps we don't currently have an
111 externally-visible interface), this will return None.
115 target_ipaddr = socket.gethostbyname(target)
116 except socket.gaierror:
117 # DNS isn't running, or somehow we encountered an error
119 # note: if an interface is configured and up, but nothing is
120 # connected to it, gethostbyname("A.ROOT-SERVERS.NET") will take 20
121 # seconds to raise socket.gaierror . This is synchronous and occurs
122 # for each node being started, so users of
123 # test.common.SystemTestMixin (like test_system) will see something
124 # like 120s of delay, which may be enough to hit the default trial
125 # timeouts. For that reason, get_local_addresses_async() was changed
126 # to default to the numerical ip address for A.ROOT-SERVERS.NET, to
127 # avoid this DNS lookup. This also makes node startup fractionally
130 udpprot = DatagramProtocol()
131 port = reactor.listenUDP(0, udpprot)
133 udpprot.transport.connect(target_ipaddr, 7)
134 localip = udpprot.transport.getHost().host
136 # no route to that host
138 port.stopListening() # note, this returns a Deferred
141 # k: result of sys.platform, v: which kind of IP configuration reader we use
143 "linux-i386": "linux", # redhat
144 "linux-ppc": "linux", # redhat
145 "linux2": "linux", # debian
153 "darwin": "bsd", # Mac OS X
170 class UnsupportedPlatformError(Exception):
173 # Wow, I'm really amazed at home much mileage we've gotten out of calling
174 # the external route.exe program on windows... It appears to work on all
175 # versions so far. Still, the real system calls would much be preferred...
176 # ... thus wrote Greg Smith in time immemorial...
177 _win32_path = 'route.exe'
178 _win32_args = ('print',)
179 _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)
181 # These work in Redhat 6.x and Debian 2.2 potato
182 _linux_path = '/sbin/ifconfig'
183 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
185 # NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
186 _netbsd_path = '/sbin/ifconfig'
187 _netbsd_args = ('-a',)
188 _netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
191 _irix_path = '/usr/etc/ifconfig'
194 _sunos_path = '/usr/sbin/ifconfig'
196 class SequentialTrier(object):
197 """ I hold a list of executables to try and try each one in turn
198 until one gives me a list of IP addresses."""
200 def __init__(self, exebasename, args, regex):
201 assert not os.path.isabs(exebasename)
202 self.exes_left_to_try = which(exebasename)
203 self.exes_left_to_try.reverse()
206 self.o = observer.OneShotObserverList()
210 if not self.exes_left_to_try:
213 exe = self.exes_left_to_try.pop()
214 d2 = _query(exe, self.args, self.regex)
225 d2.addCallbacks(cb, eb)
227 def when_tried(self):
228 return self.o.when_fired()
230 # k: platform string as provided in the value of _platform_map
231 # v: tuple of (path_to_tool, args, regex,)
233 "linux": (_linux_path, (), _linux_re,),
234 "win32": (_win32_path, _win32_args, _win32_re,),
235 "cygwin": (_win32_path, _win32_args, _win32_re,),
236 "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
237 "irix": (_irix_path, _netbsd_args, _netbsd_re,),
238 "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
240 def _find_addresses_via_config():
241 # originally by Greg Smith, hacked by Zooko to conform to Brian's API
243 platform = _platform_map.get(sys.platform)
245 raise UnsupportedPlatformError(sys.platform)
247 (pathtotool, args, regex,) = _tool_map[platform]
249 # If pathtotool is a fully qualified path then we just try that.
250 # If it is merely an executable name then we use Twisted's
251 # "which()" utility and try each executable in turn until one
252 # gives us something that resembles a dotted-quad IPv4 address.
254 if os.path.isabs(pathtotool):
255 return _query(pathtotool, args, regex)
257 return SequentialTrier(pathtotool, args, regex).when_tried()
259 def _query(path, args, regex):
260 d = getProcessOutput(path, args)
263 outputsplit = output.split('\n')
264 for outline in outputsplit:
265 m = regex.match(outline)
267 addr = m.groupdict()['address']
268 if addr not in addresses:
269 addresses.append(addr)
272 d.addCallback(_parse)
275 def _cygwin_hack_find_addresses(target):
277 for h in [target, "localhost", "127.0.0.1",]:
279 addr = get_local_ip_for(h)
280 if addr not in addresses:
281 addresses.append(addr)
282 except socket.gaierror:
285 return defer.succeed(addresses)