]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/iputil.py
fix iputil so that it doesn't launch dozens of processes when you give it a full...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / iputil.py
1 # portions extracted from ipaddresslib by Autonomous Zone Industries, LGPL (author: Greg Smith)
2 # portions adapted from nattraverso.ipdiscover
3 # portions authored by Brian Warner, working for Allmydata
4 # most recent version authored by Zooko O'Whielacronx, working for Allmydata
5
6 # from the Python Standard Library
7 import os, re, socket, sys
8
9 # from Twisted
10 from twisted.internet import defer
11 from twisted.internet import reactor
12 from twisted.internet.protocol import DatagramProtocol
13 from twisted.internet.utils import getProcessOutput
14 from twisted.python.procutils import which
15
16 # from allmydata.util
17 import observer
18
19 def get_local_addresses_async(target='A.ROOT-SERVERS.NET'):
20     """
21     Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
22     strings) that are currently configured on this host, sorted in descending
23     order of how likely we think they are to work.
24
25     @param target: we want to learn an IP address they could try using to
26         connect to us; The default value is fine, but it might help if you
27         pass the address of a host that you are actually trying to be
28         reachable to.
29     """
30     addresses = []
31     local_ip = get_local_ip_for(target)
32     if local_ip:
33         addresses.append(local_ip)
34
35     if sys.platform == "cygwin":
36         d = _cygwin_hack_find_addresses(target)
37     else:
38         d = _find_addresses_via_config()
39
40     def _collect(res):
41         for addr in res:
42             if not addr in addresses:
43                 addresses.append(addr)
44         return addresses
45     d.addCallback(_collect)
46
47     return d
48
49 def get_local_ip_for(target):
50     """Find out what our IP address is for use by a given target.
51
52     @return: the IP address as a dotted-quad string which could be used by
53               to connect to us. It might work for them, it might not. If
54               there is no suitable address (perhaps we don't currently have an
55               externally-visible interface), this will return None.
56     """
57
58     try:
59         target_ipaddr = socket.gethostbyname(target)
60     except socket.gaierror:
61         # DNS isn't running
62         return None
63     udpprot = DatagramProtocol()
64     port = reactor.listenUDP(0, udpprot)
65     try:
66         udpprot.transport.connect(target_ipaddr, 7)
67         localip = udpprot.transport.getHost().host
68     except socket.error:
69         # no route to that host
70         localip = None
71     port.stopListening() # note, this returns a Deferred
72     return localip
73
74 # k: result of sys.platform, v: which kind of IP configuration reader we use
75 _platform_map = {
76     "linux-i386": "linux", # redhat
77     "linux-ppc": "linux",  # redhat
78     "linux2": "linux",     # debian
79     "win32": "win32",
80     "irix6-n32": "irix",
81     "irix6-n64": "irix",
82     "irix6": "irix",
83     "openbsd2": "bsd",
84     "darwin": "bsd",       # Mac OS X
85     "freebsd4": "bsd",
86     "freebsd5": "bsd",
87     "netbsd1": "bsd",
88     "sunos5": "sunos",
89     "cygwin": "cygwin",
90     }
91
92 class UnsupportedPlatformError(Exception):
93     pass
94
95 # Wow, I'm really amazed at home much mileage we've gotten out of calling
96 # the external route.exe program on windows...  It appears to work on all
97 # versions so far.  Still, the real system calls would much be preferred...
98 # ... thus wrote Greg Smith in time immemorial...
99 _win32_path = 'route.exe'
100 _win32_args = ('print',)
101 _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)
102
103 # These work in Redhat 6.x and Debian 2.2 potato
104 _linux_path = '/sbin/ifconfig'
105 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
106
107 # NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
108 _netbsd_path = '/sbin/ifconfig'
109 _netbsd_args = ('-a',)
110 _netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
111
112 # Irix 6.5
113 _irix_path = '/usr/etc/ifconfig'
114
115 # Solaris 2.x
116 _sunos_path = '/usr/sbin/ifconfig'
117
118 class SequentialTrier(object):
119     """ I hold a list of executables to try and try each one in turn
120     until one gives me a list of IP addresses."""
121
122     def __init__(self, exebasename, args, regex):
123         assert not os.path.isabs(exebasename)
124         self.exes_left_to_try = which(exebasename)
125         self.exes_left_to_try.reverse()
126         self.args = args
127         self.regex = regex
128         self.o = observer.OneShotObserverList()
129         self._try_next()
130
131     def _try_next(self):
132         if not self.exes_left_to_try:
133             self.o.fire(None)
134         else:
135             exe = self.exes_left_to_try.pop()
136             d2 = _query(exe, self.args, self.regex)
137
138             def cb(res):
139                 if res:
140                     self.o.fire(res)
141                 else:
142                     self._try_next()
143
144             def eb(why):
145                 self._try_next()
146
147             d2.addCallbacks(cb, eb)
148
149     def when_tried(self):
150         return self.o.when_fired()
151     
152 # k: platform string as provided in the value of _platform_map
153 # v: tuple of (path_to_tool, args, regex,)
154 _tool_map = {
155     "linux": (_linux_path, (), _linux_re,),
156     "win32": (_win32_path, _win32_args, _win32_re,),
157     "cygwin": (_win32_path, _win32_args, _win32_re,),
158     "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
159     "irix": (_irix_path, _netbsd_args, _netbsd_re,),
160     "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
161     }
162 def _find_addresses_via_config():
163     # originally by Greg Smith, hacked by Zooko to conform to Brian's API
164     
165     platform = _platform_map.get(sys.platform)
166     if not platform:
167         raise UnsupportedPlatformError(sys.platform)
168
169     (pathtotool, args, regex,) = _tool_map[platform]
170
171     # If pathtotool is a fully qualified path then we just try that.
172     # If it is merely an executable name then we use Twisted's
173     # "which()" utility and try each executable in turn until one
174     # gives us something that resembles a dotted-quad IPv4 address.
175
176     if os.path.isabs(pathtotool):
177         return _query(pathtotool, args, regex)
178     else:
179         return SequentialTrier(pathtotool, args, regex).when_tried()
180         
181 def _query(path, args, regex):
182     d = getProcessOutput(path, args)
183     def _parse(output):
184         addresses = []
185         outputsplit = output.split('\n')
186         for outline in outputsplit:
187             m = regex.match(outline)
188             if m:
189                 addr = m.groupdict()['address']
190                 if addr not in addresses:
191                     addresses.append(addr)
192     
193         return addresses
194     d.addCallback(_parse)
195     return d
196
197 def _cygwin_hack_find_addresses(target):
198     addresses = []
199     for h in [target, "localhost", "127.0.0.1",]:
200         try:
201             addr = get_local_ip_for(h)
202             if addr not in addresses:
203                 addresses.append(addr)
204         except socket.gaierror:
205             pass
206
207     return defer.succeed(addresses)