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