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
167 class UnsupportedPlatformError(Exception):
170 # Wow, I'm really amazed at home much mileage we've gotten out of calling
171 # the external route.exe program on windows... It appears to work on all
172 # versions so far. Still, the real system calls would much be preferred...
173 # ... thus wrote Greg Smith in time immemorial...
174 _win32_path = 'route.exe'
175 _win32_args = ('print',)
176 _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)
178 # These work in Redhat 6.x and Debian 2.2 potato
179 _linux_path = '/sbin/ifconfig'
180 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
182 # NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
183 _netbsd_path = '/sbin/ifconfig'
184 _netbsd_args = ('-a',)
185 _netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
188 _irix_path = '/usr/etc/ifconfig'
191 _sunos_path = '/usr/sbin/ifconfig'
193 class SequentialTrier(object):
194 """ I hold a list of executables to try and try each one in turn
195 until one gives me a list of IP addresses."""
197 def __init__(self, exebasename, args, regex):
198 assert not os.path.isabs(exebasename)
199 self.exes_left_to_try = which(exebasename)
200 self.exes_left_to_try.reverse()
203 self.o = observer.OneShotObserverList()
207 if not self.exes_left_to_try:
210 exe = self.exes_left_to_try.pop()
211 d2 = _query(exe, self.args, self.regex)
222 d2.addCallbacks(cb, eb)
224 def when_tried(self):
225 return self.o.when_fired()
227 # k: platform string as provided in the value of _platform_map
228 # v: tuple of (path_to_tool, args, regex,)
230 "linux": (_linux_path, (), _linux_re,),
231 "win32": (_win32_path, _win32_args, _win32_re,),
232 "cygwin": (_win32_path, _win32_args, _win32_re,),
233 "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
234 "irix": (_irix_path, _netbsd_args, _netbsd_re,),
235 "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
237 def _find_addresses_via_config():
238 # originally by Greg Smith, hacked by Zooko to conform to Brian's API
240 platform = _platform_map.get(sys.platform)
242 raise UnsupportedPlatformError(sys.platform)
244 (pathtotool, args, regex,) = _tool_map[platform]
246 # If pathtotool is a fully qualified path then we just try that.
247 # If it is merely an executable name then we use Twisted's
248 # "which()" utility and try each executable in turn until one
249 # gives us something that resembles a dotted-quad IPv4 address.
251 if os.path.isabs(pathtotool):
252 return _query(pathtotool, args, regex)
254 return SequentialTrier(pathtotool, args, regex).when_tried()
256 def _query(path, args, regex):
257 d = getProcessOutput(path, args)
260 outputsplit = output.split('\n')
261 for outline in outputsplit:
262 m = regex.match(outline)
264 addr = m.groupdict()['address']
265 if addr not in addresses:
266 addresses.append(addr)
269 d.addCallback(_parse)
272 def _cygwin_hack_find_addresses(target):
274 for h in [target, "localhost", "127.0.0.1",]:
276 addr = get_local_ip_for(h)
277 if addr not in addresses:
278 addresses.append(addr)
279 except socket.gaierror:
282 return defer.succeed(addresses)