]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/iputil.py
refactor iputil and make it return addresses in descending order of goodness instead...
[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 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 def get_local_addresses_async(target='A.ROOT-SERVERS.NET'):
17     """
18     Return a Deferred that fires with a list of IPv4 addresses (as dotted-quad
19     strings) that are currently configured on this host, sorted in descending
20     order of how likely we think they are to work.
21
22     @param target: we want to learn an IP address they could try using to
23         connect to us; The default value is fine, but it might help if you
24         pass the address of a host that you are actually trying to be
25         reachable to.
26     """
27     addresses = []
28     addresses.append(get_local_ip_for(target))
29
30     if sys.platform == "cygwin":
31         d = _cygwin_hack_find_addresses(target)
32     else:
33         d = _find_addresses_via_config()
34
35     def _collect(res):
36         for addr in res:
37             if not addr in addresses:
38                 addresses.append(addr)
39         return addresses
40     d.addCallback(_collect)
41
42     return d
43
44 def get_local_ip_for(target):
45     """Find out what our IP address is for use by a given target.
46
47     @returns: the IP address as a dotted-quad string which could be used by
48         'target' to connect to us. It might work for them, it might not
49     """
50     target_ipaddr = socket.gethostbyname(target)
51     udpprot = DatagramProtocol()
52     port = reactor.listenUDP(0, udpprot)
53     udpprot.transport.connect(target_ipaddr, 7)
54     localip = udpprot.transport.getHost().host
55     port.stopListening() # note, this returns a Deferred
56     return localip
57
58 # k: result of sys.platform, v: which kind of IP configuration reader we use
59 _platform_map = {
60     "linux-i386": "linux", # redhat
61     "linux-ppc": "linux",  # redhat
62     "linux2": "linux",     # debian
63     "win32": "win32",
64     "irix6-n32": "irix",
65     "irix6-n64": "irix",
66     "irix6": "irix",
67     "openbsd2": "bsd",
68     "darwin": "bsd",       # Mac OS X
69     "freebsd4": "bsd",
70     "freebsd5": "bsd",
71     "netbsd1": "bsd",
72     "sunos5": "sunos",
73     "cygwin": "cygwin",
74     }
75
76 class UnsupportedPlatformError(Exception):
77     pass
78
79 # Wow, I'm really amazed at home much mileage we've gotten out of calling
80 # the external route.exe program on windows...  It appears to work on all
81 # versions so far.  Still, the real system calls would much be preferred...
82 # ... thus wrote Greg Smith in time immemorial...
83 _win32_path = 'route.exe'
84 _win32_args = ('print',)
85 _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)
86
87 # These work in Redhat 6.x and Debian 2.2 potato
88 _linux_path = '/sbin/ifconfig'
89 _linux_re = re.compile('^\s*inet addr:(?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
90
91 # NetBSD 1.4 (submitted by Rhialto), Darwin, Mac OS X
92 _netbsd_path = '/sbin/ifconfig'
93 _netbsd_args = ('-a',)
94 _netbsd_re = re.compile('^\s+inet (?P<address>\d+\.\d+\.\d+\.\d+)\s.+$', flags=re.M|re.I|re.S)
95
96 # Irix 6.5
97 _irix_path = '/usr/etc/ifconfig'
98
99 # Solaris 2.x
100 _sunos_path = '/usr/sbin/ifconfig'
101
102 # k: platform string as provided in the value of _platform_map
103 # v: tuple of (path_to_tool, args, regex,)
104 _tool_map = {
105     "linux": (_linux_path, (), _linux_re,),
106     "win32": (_win32_path, _win32_args, _win32_re,),
107     "cygwin": (_win32_path, _win32_args, _win32_re,),
108     "bsd": (_netbsd_path, _netbsd_args, _netbsd_re,),
109     "irix": (_irix_path, _netbsd_args, _netbsd_re,),
110     "sunos": (_sunos_path, _netbsd_args, _netbsd_re,),
111     }
112 def _find_addresses_via_config():
113     # originally by Greg Smith, hacked by Zooko to conform to Brian's API
114     
115     platform = _platform_map.get(sys.platform)
116     if not platform:
117         raise UnsupportedPlatformError(sys.platform)
118
119     (pathtotool, args, regex,) = _tool_map[platform]
120     
121     l = []
122     for executable in which(pathtotool):
123         l.append(_query(executable, args, regex))
124     dl = defer.DeferredList(l)
125     def _gather_results(res):
126         addresses = []
127         for (succ, addrs,) in res:
128             if succ:
129                 for addr in addrs:
130                     if addr not in addresses:
131                         addresses.append(addr)
132         return addresses
133     dl.addCallback(_gather_results)
134     return dl
135
136 def _query(path, args, regex):
137     d = getProcessOutput(path, args)
138     def _parse(output):
139         addresses = []
140         outputsplit = output.split('\n')
141         for outline in outputsplit:
142             m = regex.match(outline)
143             if m:
144                 addr = m.groupdict()['address']
145                 if addr not in addresses:
146                     addresses.append(addr)
147     
148         return addresses
149     d.addCallback(_parse)
150     return d
151
152 def _cygwin_hack_find_addresses(target):
153     addresses = []
154     for h in [target, "localhost", "127.0.0.1",]:
155         try:
156             addr = get_local_ip_for(h)
157             if addr not in addresses:
158                 addresses.append(addr)
159         except socket.gaierror:
160             pass
161
162     return defer.succeed(addresses)