]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/iputil.py
Change relative imports to absolute
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / iputil.py
1 # from the Python Standard Library
2 import os, re, socket, sys
3
4 # from Twisted
5 from twisted.internet import defer
6 from twisted.internet import reactor
7 from twisted.internet.protocol import DatagramProtocol
8 from twisted.internet.utils import getProcessOutput
9 from twisted.python.procutils import which
10 from twisted.python import log
11
12 from allmydata.util import observer
13
14 try:
15     import resource
16     def increase_rlimits():
17         # We'd like to raise our soft resource.RLIMIT_NOFILE, since certain
18         # systems (OS-X, probably solaris) start with a relatively low limit
19         # (256), and some unit tests want to open up more sockets than this.
20         # Most linux systems start with both hard and soft limits at 1024,
21         # which is plenty.
22
23         # unfortunately the values to pass to setrlimit() vary widely from
24         # one system to another. OS-X reports (256, HUGE), but the real hard
25         # limit is 10240, and accepts (-1,-1) to mean raise it to the
26         # maximum. Cygwin reports (256, -1), then ignores a request of
27         # (-1,-1): instead you have to guess at the hard limit (it appears to
28         # be 3200), so using (3200,-1) seems to work. Linux reports a
29         # sensible (1024,1024), then rejects (-1,-1) as trying to raise the
30         # maximum limit, so you could set it to (1024,1024) but you might as
31         # well leave it alone.
32
33         try:
34             current = resource.getrlimit(resource.RLIMIT_NOFILE)
35         except AttributeError:
36             # we're probably missing RLIMIT_NOFILE
37             return
38
39         if current[0] >= 1024:
40             # good enough, leave it alone
41             return
42
43         try:
44             if current[1] > 0 and current[1] < 1000000:
45                 # solaris reports (256, 65536)
46                 resource.setrlimit(resource.RLIMIT_NOFILE,
47                                    (current[1], current[1]))
48             else:
49                 # this one works on OS-X (bsd), and gives us 10240, but
50                 # it doesn't work on linux (on which both the hard and
51                 # soft limits are set to 1024 by default).
52                 resource.setrlimit(resource.RLIMIT_NOFILE, (-1,-1))
53                 new = resource.getrlimit(resource.RLIMIT_NOFILE)
54                 if new[0] == current[0]:
55                     # probably cygwin, which ignores -1. Use a real value.
56                     resource.setrlimit(resource.RLIMIT_NOFILE, (3200,-1))
57
58         except ValueError:
59             log.msg("unable to set RLIMIT_NOFILE: current value %s"
60                      % (resource.getrlimit(resource.RLIMIT_NOFILE),))
61         except:
62             # who knows what. It isn't very important, so log it and continue
63             log.err()
64 except ImportError:
65     def _increase_rlimits():
66         # TODO: implement this for Windows.  Although I suspect the
67         # solution might be "be running under the iocp reactor and
68         # make this function be a no-op".
69         pass
70     # pyflakes complains about two 'def FOO' statements in the same time,
71     # since one might be shadowing the other. This hack appeases pyflakes.
72     increase_rlimits = _increase_rlimits
73
74
75 def get_local_addresses_async(target="198.41.0.4"): # A.ROOT-SERVERS.NET
76     """
77     Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
78     strings) that are currently configured on this host, sorted in descending
79     order of how likely we think they are to work.
80
81     @param target: we want to learn an IP address they could try using to
82         connect to us; The default value is fine, but it might help if you
83         pass the address of a host that you are actually trying to be
84         reachable to.
85     """
86     addresses = []
87     local_ip = get_local_ip_for(target)
88     if local_ip:
89         addresses.append(local_ip)
90
91     if sys.platform == "cygwin":
92         d = _cygwin_hack_find_addresses(target)
93     else:
94         d = _find_addresses_via_config()
95
96     def _collect(res):
97         for addr in res:
98             if addr != "0.0.0.0" and not addr in addresses:
99                 addresses.append(addr)
100         return addresses
101     d.addCallback(_collect)
102
103     return d
104
105 def get_local_ip_for(target):
106     """Find out what our IP address is for use by a given target.
107
108     @return: the IP address as a dotted-quad string which could be used by
109               to connect to us. It might work for them, it might not. If
110               there is no suitable address (perhaps we don't currently have an
111               externally-visible interface), this will return None.
112     """
113
114     try:
115         target_ipaddr = socket.gethostbyname(target)
116     except socket.gaierror:
117         # DNS isn't running, or somehow we encountered an error
118
119         # note: if an interface is configured and up, but nothing is
120         # connected to it, gethostbyname("A.ROOT-SERVERS.NET") will take 20
121         # seconds to raise socket.gaierror . This is synchronous and occurs
122         # for each node being started, so users of
123         # test.common.SystemTestMixin (like test_system) will see something
124         # like 120s of delay, which may be enough to hit the default trial
125         # timeouts. For that reason, get_local_addresses_async() was changed
126         # to default to the numerical ip address for A.ROOT-SERVERS.NET, to
127         # avoid this DNS lookup. This also makes node startup fractionally
128         # faster.
129         return None
130     udpprot = DatagramProtocol()
131     port = reactor.listenUDP(0, udpprot)
132     try:
133         udpprot.transport.connect(target_ipaddr, 7)
134         localip = udpprot.transport.getHost().host
135     except socket.error:
136         # no route to that host
137         localip = None
138     port.stopListening() # note, this returns a Deferred
139     return localip
140
141 # k: result of sys.platform, v: which kind of IP configuration reader we use
142 _platform_map = {
143     "linux-i386": "linux", # redhat
144     "linux-ppc": "linux",  # redhat
145     "linux2": "linux",     # debian
146     "win32": "win32",
147     "irix6-n32": "irix",
148     "irix6-n64": "irix",
149     "irix6": "irix",
150     "openbsd2": "bsd",
151     "openbsd3": "bsd",
152     "openbsd4": "bsd",
153     "darwin": "bsd",       # Mac OS X
154     "freebsd4": "bsd",
155     "freebsd5": "bsd",
156     "freebsd6": "bsd",
157     "netbsd1": "bsd",
158     "netbsd2": "bsd",
159     "netbsd3": "bsd",
160     "netbsd4": "bsd",
161     "netbsd5": "bsd",
162     "netbsd6": "bsd",
163     "sunos5": "sunos",
164     "cygwin": "cygwin",
165     }
166
167 class UnsupportedPlatformError(Exception):
168     pass
169
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)
177
178 # These work in Redhat 6.x and Debian 2.2 potato
179 _linux_path = '/sbin/ifconfig'
180 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
181
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 (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
186
187 # Irix 6.5
188 _irix_path = '/usr/etc/ifconfig'
189
190 # Solaris 2.x
191 _sunos_path = '/usr/sbin/ifconfig'
192
193 class SequentialTrier(object):
194     """ I hold a list of executables to try and try each one in turn
195     until one gives me a list of IP addresses."""
196
197     def __init__(self, exebasename, args, regex):
198         assert not os.path.isabs(exebasename)
199         self.exes_left_to_try = which(exebasename)
200         self.exes_left_to_try.reverse()
201         self.args = args
202         self.regex = regex
203         self.o = observer.OneShotObserverList()
204         self._try_next()
205
206     def _try_next(self):
207         if not self.exes_left_to_try:
208             self.o.fire(None)
209         else:
210             exe = self.exes_left_to_try.pop()
211             d2 = _query(exe, self.args, self.regex)
212
213             def cb(res):
214                 if res:
215                     self.o.fire(res)
216                 else:
217                     self._try_next()
218
219             def eb(why):
220                 self._try_next()
221
222             d2.addCallbacks(cb, eb)
223
224     def when_tried(self):
225         return self.o.when_fired()
226
227 # k: platform string as provided in the value of _platform_map
228 # v: tuple of (path_to_tool, args, regex,)
229 _tool_map = {
230     "linux": (_linux_path, (), _linux_re,),
231     "win32": (_win32_path, _win32_args, _win32_re,),
232     "cygwin": (_win32_path, _win32_args, _win32_re,),
233     "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
234     "irix": (_irix_path, _netbsd_args, _netbsd_re,),
235     "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
236     }
237 def _find_addresses_via_config():
238     # originally by Greg Smith, hacked by Zooko to conform to Brian's API
239
240     platform = _platform_map.get(sys.platform)
241     if not platform:
242         raise UnsupportedPlatformError(sys.platform)
243
244     (pathtotool, args, regex,) = _tool_map[platform]
245
246     # If pathtotool is a fully qualified path then we just try that.
247     # If it is merely an executable name then we use Twisted's
248     # "which()" utility and try each executable in turn until one
249     # gives us something that resembles a dotted-quad IPv4 address.
250
251     if os.path.isabs(pathtotool):
252         return _query(pathtotool, args, regex)
253     else:
254         return SequentialTrier(pathtotool, args, regex).when_tried()
255
256 def _query(path, args, regex):
257     d = getProcessOutput(path, args)
258     def _parse(output):
259         addresses = []
260         outputsplit = output.split('\n')
261         for outline in outputsplit:
262             m = regex.match(outline)
263             if m:
264                 addr = m.groupdict()['address']
265                 if addr not in addresses:
266                     addresses.append(addr)
267
268         return addresses
269     d.addCallback(_parse)
270     return d
271
272 def _cygwin_hack_find_addresses(target):
273     addresses = []
274     for h in [target, "localhost", "127.0.0.1",]:
275         try:
276             addr = get_local_ip_for(h)
277             if addr not in addresses:
278                 addresses.append(addr)
279         except socket.gaierror:
280             pass
281
282     return defer.succeed(addresses)