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