]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/iputil.py
Avoid Popen() of executables that don't exist
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / iputil.py
1 # from the Python Standard Library
2 import os, re, socket, subprocess, errno
3
4 from sys import platform
5
6 # from Twisted
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
11
12 try:
13     import resource
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,
19         # which is plenty.
20
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.
30
31         try:
32             current = resource.getrlimit(resource.RLIMIT_NOFILE)
33         except AttributeError:
34             # we're probably missing RLIMIT_NOFILE
35             return
36
37         if current[0] >= 1024:
38             # good enough, leave it alone
39             return
40
41         try:
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]))
46             else:
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))
55
56         except ValueError:
57             log.msg("unable to set RLIMIT_NOFILE: current value %s"
58                      % (resource.getrlimit(resource.RLIMIT_NOFILE),))
59         except:
60             # who knows what. It isn't very important, so log it and continue
61             log.err()
62 except ImportError:
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".
67         pass
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
71
72
73 def get_local_addresses_async(target="198.41.0.4"): # A.ROOT-SERVERS.NET
74     """
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.
78
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
82         reachable to.
83     """
84     addresses = []
85     local_ip = get_local_ip_for(target)
86     if local_ip is not None:
87         addresses.append(local_ip)
88
89     if platform == "cygwin":
90         d = _cygwin_hack_find_addresses()
91     else:
92         d = _find_addresses_via_config()
93
94     def _collect(res):
95         for addr in res:
96             if addr != "0.0.0.0" and not addr in addresses:
97                 addresses.append(addr)
98         return addresses
99     d.addCallback(_collect)
100
101     return d
102
103 def get_local_ip_for(target):
104     """Find out what our IP address is for use by a given target.
105
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.
110     """
111
112     try:
113         target_ipaddr = socket.gethostbyname(target)
114     except socket.gaierror:
115         # DNS isn't running, or somehow we encountered an error
116
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
126         # faster.
127         return None
128     udpprot = DatagramProtocol()
129     port = reactor.listenUDP(0, udpprot)
130     try:
131         udpprot.transport.connect(target_ipaddr, 7)
132         localip = udpprot.transport.getHost().host
133     except socket.error:
134         # no route to that host
135         localip = None
136     d = port.stopListening()
137     d.addErrback(log.err)
138     return localip
139
140
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),)
147
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),
156                  )
157
158
159 def _find_addresses_via_config():
160     return threads.deferToThread(_synchronously_find_addresses_via_config)
161
162 def _synchronously_find_addresses_via_config():
163     # originally by Greg Smith, hacked by Zooko and then Daira
164
165     # We don't reach here for cygwin.
166     if platform == 'win32':
167         commands = _win32_commands
168     else:
169         commands = _unix_commands
170
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.
176
177         if os.path.isabs(pathtotool):
178             exes_to_try = [pathtotool]
179         else:
180             exes_to_try = which(pathtotool)
181
182         for exe in exes_to_try:
183             try:
184                 addresses = _query(exe, args, regex)
185             except Exception:
186                 addresses = []
187             if addresses:
188                 return addresses
189
190     return []
191
192 def _query(path, args, regex):
193     if not os.path.isfile(path):
194         return []
195     env = {'LANG': 'en_US.UTF-8'}
196     TRIES = 5
197     for trial in xrange(TRIES):
198         try:
199             p = subprocess.Popen([path] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
200             (output, err) = p.communicate()
201             break
202         except OSError, e:
203             if e.errno == errno.EINTR and trial < TRIES-1:
204                 continue
205             raise
206
207     addresses = []
208     outputsplit = output.split('\n')
209     for outline in outputsplit:
210         m = regex.match(outline)
211         if m:
212             addr = m.group('address')
213             if addr not in addresses:
214                 addresses.append(addr)
215
216     return addresses
217
218 def _cygwin_hack_find_addresses():
219     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)
224
225     return defer.succeed(addresses)