1 # from the Python Standard Library
2 import os, re, socket, sys, subprocess
5 from twisted.internet import defer, threads, reactor
6 from twisted.internet.protocol import DatagramProtocol
7 from twisted.python.procutils import which
8 from twisted.python import log
12 def increase_rlimits():
13 # We'd like to raise our soft resource.RLIMIT_NOFILE, since certain
14 # systems (OS-X, probably solaris) start with a relatively low limit
15 # (256), and some unit tests want to open up more sockets than this.
16 # Most linux systems start with both hard and soft limits at 1024,
19 # unfortunately the values to pass to setrlimit() vary widely from
20 # one system to another. OS-X reports (256, HUGE), but the real hard
21 # limit is 10240, and accepts (-1,-1) to mean raise it to the
22 # maximum. Cygwin reports (256, -1), then ignores a request of
23 # (-1,-1): instead you have to guess at the hard limit (it appears to
24 # be 3200), so using (3200,-1) seems to work. Linux reports a
25 # sensible (1024,1024), then rejects (-1,-1) as trying to raise the
26 # maximum limit, so you could set it to (1024,1024) but you might as
27 # well leave it alone.
30 current = resource.getrlimit(resource.RLIMIT_NOFILE)
31 except AttributeError:
32 # we're probably missing RLIMIT_NOFILE
35 if current[0] >= 1024:
36 # good enough, leave it alone
40 if current[1] > 0 and current[1] < 1000000:
41 # solaris reports (256, 65536)
42 resource.setrlimit(resource.RLIMIT_NOFILE,
43 (current[1], current[1]))
45 # this one works on OS-X (bsd), and gives us 10240, but
46 # it doesn't work on linux (on which both the hard and
47 # soft limits are set to 1024 by default).
48 resource.setrlimit(resource.RLIMIT_NOFILE, (-1,-1))
49 new = resource.getrlimit(resource.RLIMIT_NOFILE)
50 if new[0] == current[0]:
51 # probably cygwin, which ignores -1. Use a real value.
52 resource.setrlimit(resource.RLIMIT_NOFILE, (3200,-1))
55 log.msg("unable to set RLIMIT_NOFILE: current value %s"
56 % (resource.getrlimit(resource.RLIMIT_NOFILE),))
58 # who knows what. It isn't very important, so log it and continue
61 def _increase_rlimits():
62 # TODO: implement this for Windows. Although I suspect the
63 # solution might be "be running under the iocp reactor and
64 # make this function be a no-op".
66 # pyflakes complains about two 'def FOO' statements in the same time,
67 # since one might be shadowing the other. This hack appeases pyflakes.
68 increase_rlimits = _increase_rlimits
71 def get_local_addresses_async(target="198.41.0.4"): # A.ROOT-SERVERS.NET
73 Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
74 strings) that are currently configured on this host, sorted in descending
75 order of how likely we think they are to work.
77 @param target: we want to learn an IP address they could try using to
78 connect to us; The default value is fine, but it might help if you
79 pass the address of a host that you are actually trying to be
83 local_ip = get_local_ip_for(target)
85 addresses.append(local_ip)
87 if sys.platform == "cygwin":
88 d = _cygwin_hack_find_addresses(target)
90 d = _find_addresses_via_config()
94 if addr != "0.0.0.0" and not addr in addresses:
95 addresses.append(addr)
97 d.addCallback(_collect)
101 def get_local_ip_for(target):
102 """Find out what our IP address is for use by a given target.
104 @return: the IP address as a dotted-quad string which could be used by
105 to connect to us. It might work for them, it might not. If
106 there is no suitable address (perhaps we don't currently have an
107 externally-visible interface), this will return None.
111 target_ipaddr = socket.gethostbyname(target)
112 except socket.gaierror:
113 # DNS isn't running, or somehow we encountered an error
115 # note: if an interface is configured and up, but nothing is
116 # connected to it, gethostbyname("A.ROOT-SERVERS.NET") will take 20
117 # seconds to raise socket.gaierror . This is synchronous and occurs
118 # for each node being started, so users of
119 # test.common.SystemTestMixin (like test_system) will see something
120 # like 120s of delay, which may be enough to hit the default trial
121 # timeouts. For that reason, get_local_addresses_async() was changed
122 # to default to the numerical ip address for A.ROOT-SERVERS.NET, to
123 # avoid this DNS lookup. This also makes node startup fractionally
126 udpprot = DatagramProtocol()
127 port = reactor.listenUDP(0, udpprot)
129 udpprot.transport.connect(target_ipaddr, 7)
130 localip = udpprot.transport.getHost().host
132 # no route to that host
134 port.stopListening() # note, this returns a Deferred
137 # k: result of sys.platform, v: which kind of IP configuration reader we use
139 "linux-i386": "linux", # redhat
140 "linux-ppc": "linux", # redhat
141 "linux2": "linux", # debian
142 "linux3": "linux", # debian
150 "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 [a-zA-Z]*:?(?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 [a-zA-Z]*:?(?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'
194 # k: platform string as provided in the value of _platform_map
195 # v: tuple of (path_to_tool, args, regex,)
197 "linux": (_linux_path, (), _linux_re,),
198 "win32": (_win32_path, _win32_args, _win32_re,),
199 "cygwin": (_win32_path, _win32_args, _win32_re,),
200 "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
201 "irix": (_irix_path, _netbsd_args, _netbsd_re,),
202 "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
205 def _find_addresses_via_config():
206 return threads.deferToThread(_synchronously_find_addresses_via_config)
208 def _synchronously_find_addresses_via_config():
209 # originally by Greg Smith, hacked by Zooko to conform to Brian's API
211 platform = _platform_map.get(sys.platform)
213 raise UnsupportedPlatformError(sys.platform)
215 (pathtotool, args, regex,) = _tool_map[platform]
217 # If pathtotool is a fully qualified path then we just try that.
218 # If it is merely an executable name then we use Twisted's
219 # "which()" utility and try each executable in turn until one
220 # gives us something that resembles a dotted-quad IPv4 address.
222 if os.path.isabs(pathtotool):
223 return _query(pathtotool, args, regex)
225 exes_to_try = which(pathtotool)
226 for exe in exes_to_try:
228 addresses = _query(exe, args, regex)
235 def _query(path, args, regex):
236 env = {'LANG': 'en_US.UTF-8'}
237 p = subprocess.Popen([path] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
238 (output, err) = p.communicate()
241 outputsplit = output.split('\n')
242 for outline in outputsplit:
243 m = regex.match(outline)
245 addr = m.groupdict()['address']
246 if addr not in addresses:
247 addresses.append(addr)
251 def _cygwin_hack_find_addresses(target):
253 for h in [target, "localhost", "127.0.0.1",]:
255 addr = get_local_ip_for(h)
256 if addr not in addresses:
257 addresses.append(addr)
258 except socket.gaierror:
261 return defer.succeed(addresses)