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