1 # from the Python Standard Library
2 import os, re, socket, subprocess, errno
4 from sys import platform
7 from twisted.internet import defer, threads, reactor
8 from twisted.internet.protocol import DatagramProtocol
9 from twisted.internet.error import CannotListenError
10 from twisted.python.procutils import which
11 from twisted.python import log
15 def increase_rlimits():
16 # We'd like to raise our soft resource.RLIMIT_NOFILE, since certain
17 # systems (OS-X, probably solaris) start with a relatively low limit
18 # (256), and some unit tests want to open up more sockets than this.
19 # Most linux systems start with both hard and soft limits at 1024,
22 # unfortunately the values to pass to setrlimit() vary widely from
23 # one system to another. OS-X reports (256, HUGE), but the real hard
24 # limit is 10240, and accepts (-1,-1) to mean raise it to the
25 # maximum. Cygwin reports (256, -1), then ignores a request of
26 # (-1,-1): instead you have to guess at the hard limit (it appears to
27 # be 3200), so using (3200,-1) seems to work. Linux reports a
28 # sensible (1024,1024), then rejects (-1,-1) as trying to raise the
29 # maximum limit, so you could set it to (1024,1024) but you might as
30 # well leave it alone.
33 current = resource.getrlimit(resource.RLIMIT_NOFILE)
34 except AttributeError:
35 # we're probably missing RLIMIT_NOFILE
38 if current[0] >= 1024:
39 # good enough, leave it alone
43 if current[1] > 0 and current[1] < 1000000:
44 # solaris reports (256, 65536)
45 resource.setrlimit(resource.RLIMIT_NOFILE,
46 (current[1], current[1]))
48 # this one works on OS-X (bsd), and gives us 10240, but
49 # it doesn't work on linux (on which both the hard and
50 # soft limits are set to 1024 by default).
51 resource.setrlimit(resource.RLIMIT_NOFILE, (-1,-1))
52 new = resource.getrlimit(resource.RLIMIT_NOFILE)
53 if new[0] == current[0]:
54 # probably cygwin, which ignores -1. Use a real value.
55 resource.setrlimit(resource.RLIMIT_NOFILE, (3200,-1))
58 log.msg("unable to set RLIMIT_NOFILE: current value %s"
59 % (resource.getrlimit(resource.RLIMIT_NOFILE),))
61 # who knows what. It isn't very important, so log it and continue
64 def _increase_rlimits():
65 # TODO: implement this for Windows. Although I suspect the
66 # solution might be "be running under the iocp reactor and
67 # make this function be a no-op".
69 # pyflakes complains about two 'def FOO' statements in the same time,
70 # since one might be shadowing the other. This hack appeases pyflakes.
71 increase_rlimits = _increase_rlimits
74 def get_local_addresses_async(target="198.41.0.4"): # A.ROOT-SERVERS.NET
76 Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
77 strings) that are currently configured on this host, sorted in descending
78 order of how likely we think they are to work.
80 @param target: we want to learn an IP address they could try using to
81 connect to us; The default value is fine, but it might help if you
82 pass the address of a host that you are actually trying to be
86 local_ip = get_local_ip_for(target)
87 if local_ip is not None:
88 addresses.append(local_ip)
90 if platform == "cygwin":
91 d = _cygwin_hack_find_addresses()
93 d = _find_addresses_via_config()
97 if addr != "0.0.0.0" and not addr in addresses:
98 addresses.append(addr)
100 d.addCallback(_collect)
104 def get_local_ip_for(target):
105 """Find out what our IP address is for use by a given target.
107 @return: the IP address as a dotted-quad string which could be used by
108 to connect to us. It might work for them, it might not. If
109 there is no suitable address (perhaps we don't currently have an
110 externally-visible interface), this will return None.
114 target_ipaddr = socket.gethostbyname(target)
115 except socket.gaierror:
116 # DNS isn't running, or somehow we encountered an error
118 # note: if an interface is configured and up, but nothing is
119 # connected to it, gethostbyname("A.ROOT-SERVERS.NET") will take 20
120 # seconds to raise socket.gaierror . This is synchronous and occurs
121 # for each node being started, so users of
122 # test.common.SystemTestMixin (like test_system) will see something
123 # like 120s of delay, which may be enough to hit the default trial
124 # timeouts. For that reason, get_local_addresses_async() was changed
125 # to default to the numerical ip address for A.ROOT-SERVERS.NET, to
126 # avoid this DNS lookup. This also makes node startup fractionally
131 udpprot = DatagramProtocol()
132 port = reactor.listenUDP(0, udpprot)
133 udpprot.transport.connect(target_ipaddr, 7)
134 localip = udpprot.transport.getHost().host
135 d = port.stopListening()
136 d.addErrback(log.err)
137 except (socket.error, CannotListenError):
138 # no route to that host
143 # Wow, I'm really amazed at home much mileage we've gotten out of calling
144 # the external route.exe program on windows... It appears to work on all
146 # ... thus wrote Greg Smith in time immemorial...
147 # Also, the Win32 APIs for this are really klunky and error-prone. --Daira
149 _win32_re = re.compile(r'^\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)
150 _win32_commands = (('route.exe', ('print',), _win32_re),)
152 # These work in most Unices.
153 _addr_re = re.compile(r'^\s*inet [a-zA-Z]*:?(?P<address>\d+\.\d+\.\d+\.\d+)[\s/].+$', flags=re.M|re.I|re.S)
154 _unix_commands = (('/bin/ip', ('addr',), _addr_re),
155 ('/sbin/ifconfig', ('-a',), _addr_re),
156 ('/usr/sbin/ifconfig', ('-a',), _addr_re),
157 ('/usr/etc/ifconfig', ('-a',), _addr_re),
158 ('ifconfig', ('-a',), _addr_re),
159 ('/sbin/ifconfig', (), _addr_re),
163 def _find_addresses_via_config():
164 return threads.deferToThread(_synchronously_find_addresses_via_config)
166 def _synchronously_find_addresses_via_config():
167 # originally by Greg Smith, hacked by Zooko and then Daira
169 # We don't reach here for cygwin.
170 if platform == 'win32':
171 commands = _win32_commands
173 commands = _unix_commands
175 for (pathtotool, args, regex) in commands:
176 # If pathtotool is a fully qualified path then we just try that.
177 # If it is merely an executable name then we use Twisted's
178 # "which()" utility and try each executable in turn until one
179 # gives us something that resembles a dotted-quad IPv4 address.
181 if os.path.isabs(pathtotool):
182 exes_to_try = [pathtotool]
184 exes_to_try = which(pathtotool)
186 for exe in exes_to_try:
188 addresses = _query(exe, args, regex)
196 def _query(path, args, regex):
197 if not os.path.isfile(path):
199 env = {'LANG': 'en_US.UTF-8'}
201 for trial in xrange(TRIES):
203 p = subprocess.Popen([path] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
204 (output, err) = p.communicate()
207 if e.errno == errno.EINTR and trial < TRIES-1:
212 outputsplit = output.split('\n')
213 for outline in outputsplit:
214 m = regex.match(outline)
216 addr = m.group('address')
217 if addr not in addresses:
218 addresses.append(addr)
222 def _cygwin_hack_find_addresses():
224 for h in ["localhost", "127.0.0.1",]:
225 addr = get_local_ip_for(h)
226 if addr is not None and addr not in addresses:
227 addresses.append(addr)
229 return defer.succeed(addresses)