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
145 # versions so far. Still, the real system calls would much be preferred...
146 # ... thus wrote Greg Smith in time immemorial...
147 _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)
148 _win32_commands = (('route.exe', ('print',), _win32_re),)
150 # These work in most Unices.
151 _addr_re = re.compile(r'^\s*inet [a-zA-Z]*:?(?P<address>\d+\.\d+\.\d+\.\d+)[\s/].+$', flags=re.M|re.I|re.S)
152 _unix_commands = (('/bin/ip', ('addr',), _addr_re),
153 ('/sbin/ifconfig', ('-a',), _addr_re),
154 ('/usr/sbin/ifconfig', ('-a',), _addr_re),
155 ('/usr/etc/ifconfig', ('-a',), _addr_re),
156 ('ifconfig', ('-a',), _addr_re),
157 ('/sbin/ifconfig', (), _addr_re),
161 def _find_addresses_via_config():
162 return threads.deferToThread(_synchronously_find_addresses_via_config)
164 def _synchronously_find_addresses_via_config():
165 # originally by Greg Smith, hacked by Zooko and then Daira
167 # We don't reach here for cygwin.
168 if platform == 'win32':
169 commands = _win32_commands
171 commands = _unix_commands
173 for (pathtotool, args, regex) in commands:
174 # If pathtotool is a fully qualified path then we just try that.
175 # If it is merely an executable name then we use Twisted's
176 # "which()" utility and try each executable in turn until one
177 # gives us something that resembles a dotted-quad IPv4 address.
179 if os.path.isabs(pathtotool):
180 exes_to_try = [pathtotool]
182 exes_to_try = which(pathtotool)
184 for exe in exes_to_try:
186 addresses = _query(exe, args, regex)
194 def _query(path, args, regex):
195 if not os.path.isfile(path):
197 env = {'LANG': 'en_US.UTF-8'}
199 for trial in xrange(TRIES):
201 p = subprocess.Popen([path] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
202 (output, err) = p.communicate()
205 if e.errno == errno.EINTR and trial < TRIES-1:
210 outputsplit = output.split('\n')
211 for outline in outputsplit:
212 m = regex.match(outline)
214 addr = m.group('address')
215 if addr not in addresses:
216 addresses.append(addr)
220 def _cygwin_hack_find_addresses():
222 for h in ["localhost", "127.0.0.1",]:
223 addr = get_local_ip_for(h)
224 if addr is not None and addr not in addresses:
225 addresses.append(addr)
227 return defer.succeed(addresses)