]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/iputil.py
d58c578aaf646a09bde8efa5916f717606c53c77
[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.internet.error import CannotListenError
10 from twisted.python.procutils import which
11 from twisted.python import log
12
13 try:
14     import resource
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,
20         # which is plenty.
21
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.
31
32         try:
33             current = resource.getrlimit(resource.RLIMIT_NOFILE)
34         except AttributeError:
35             # we're probably missing RLIMIT_NOFILE
36             return
37
38         if current[0] >= 1024:
39             # good enough, leave it alone
40             return
41
42         try:
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]))
47             else:
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))
56
57         except ValueError:
58             log.msg("unable to set RLIMIT_NOFILE: current value %s"
59                      % (resource.getrlimit(resource.RLIMIT_NOFILE),))
60         except:
61             # who knows what. It isn't very important, so log it and continue
62             log.err()
63 except ImportError:
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".
68         pass
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
72
73
74 def get_local_addresses_async(target="198.41.0.4"): # A.ROOT-SERVERS.NET
75     """
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.
79
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
83         reachable to.
84     """
85     addresses = []
86     local_ip = get_local_ip_for(target)
87     if local_ip is not None:
88         addresses.append(local_ip)
89
90     if platform == "cygwin":
91         d = _cygwin_hack_find_addresses()
92     else:
93         d = _find_addresses_via_config()
94
95     def _collect(res):
96         for addr in res:
97             if addr != "0.0.0.0" and not addr in addresses:
98                 addresses.append(addr)
99         return addresses
100     d.addCallback(_collect)
101
102     return d
103
104 def get_local_ip_for(target):
105     """Find out what our IP address is for use by a given target.
106
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.
111     """
112
113     try:
114         target_ipaddr = socket.gethostbyname(target)
115     except socket.gaierror:
116         # DNS isn't running, or somehow we encountered an error
117
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
127         # faster.
128         return None
129
130     try:
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
139         localip = None
140     return localip
141
142
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),)
149
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),
158                  )
159
160
161 def _find_addresses_via_config():
162     return threads.deferToThread(_synchronously_find_addresses_via_config)
163
164 def _synchronously_find_addresses_via_config():
165     # originally by Greg Smith, hacked by Zooko and then Daira
166
167     # We don't reach here for cygwin.
168     if platform == 'win32':
169         commands = _win32_commands
170     else:
171         commands = _unix_commands
172
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.
178
179         if os.path.isabs(pathtotool):
180             exes_to_try = [pathtotool]
181         else:
182             exes_to_try = which(pathtotool)
183
184         for exe in exes_to_try:
185             try:
186                 addresses = _query(exe, args, regex)
187             except Exception:
188                 addresses = []
189             if addresses:
190                 return addresses
191
192     return []
193
194 def _query(path, args, regex):
195     if not os.path.isfile(path):
196         return []
197     env = {'LANG': 'en_US.UTF-8'}
198     TRIES = 5
199     for trial in xrange(TRIES):
200         try:
201             p = subprocess.Popen([path] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
202             (output, err) = p.communicate()
203             break
204         except OSError, e:
205             if e.errno == errno.EINTR and trial < TRIES-1:
206                 continue
207             raise
208
209     addresses = []
210     outputsplit = output.split('\n')
211     for outline in outputsplit:
212         m = regex.match(outline)
213         if m:
214             addr = m.group('address')
215             if addr not in addresses:
216                 addresses.append(addr)
217
218     return addresses
219
220 def _cygwin_hack_find_addresses():
221     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)
226
227     return defer.succeed(addresses)