localinterfaces.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. """Utilities for identifying local IP addresses."""
  2. # Copyright (c) Jupyter Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. import os
  5. import re
  6. import socket
  7. import subprocess
  8. from subprocess import Popen, PIPE
  9. from warnings import warn
  10. LOCAL_IPS = []
  11. PUBLIC_IPS = []
  12. LOCALHOST = ''
  13. def _uniq_stable(elems):
  14. """uniq_stable(elems) -> list
  15. Return from an iterable, a list of all the unique elements in the input,
  16. maintaining the order in which they first appear.
  17. From ipython_genutils.data
  18. """
  19. seen = set()
  20. return [x for x in elems if x not in seen and not seen.add(x)]
  21. def _get_output(cmd):
  22. """Get output of a command, raising IOError if it fails"""
  23. startupinfo = None
  24. if os.name == 'nt':
  25. startupinfo = subprocess.STARTUPINFO()
  26. startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  27. p = Popen(cmd, stdout=PIPE, stderr=PIPE, startupinfo=startupinfo)
  28. stdout, stderr = p.communicate()
  29. if p.returncode:
  30. raise IOError("Failed to run %s: %s" % (cmd, stderr.decode('utf8', 'replace')))
  31. return stdout.decode('utf8', 'replace')
  32. def _only_once(f):
  33. """decorator to only run a function once"""
  34. f.called = False
  35. def wrapped(**kwargs):
  36. if f.called:
  37. return
  38. ret = f(**kwargs)
  39. f.called = True
  40. return ret
  41. return wrapped
  42. def _requires_ips(f):
  43. """decorator to ensure load_ips has been run before f"""
  44. def ips_loaded(*args, **kwargs):
  45. _load_ips()
  46. return f(*args, **kwargs)
  47. return ips_loaded
  48. # subprocess-parsing ip finders
  49. class NoIPAddresses(Exception):
  50. pass
  51. def _populate_from_list(addrs):
  52. """populate local and public IPs from flat list of all IPs"""
  53. if not addrs:
  54. raise NoIPAddresses
  55. global LOCALHOST
  56. public_ips = []
  57. local_ips = []
  58. for ip in addrs:
  59. local_ips.append(ip)
  60. if not ip.startswith('127.'):
  61. public_ips.append(ip)
  62. elif not LOCALHOST:
  63. LOCALHOST = ip
  64. if not LOCALHOST:
  65. LOCALHOST = '127.0.0.1'
  66. local_ips.insert(0, LOCALHOST)
  67. local_ips.extend(['0.0.0.0', ''])
  68. LOCAL_IPS[:] = _uniq_stable(local_ips)
  69. PUBLIC_IPS[:] = _uniq_stable(public_ips)
  70. _ifconfig_ipv4_pat = re.compile(r'inet\b.*?(\d+\.\d+\.\d+\.\d+)', re.IGNORECASE)
  71. def _load_ips_ifconfig():
  72. """load ip addresses from `ifconfig` output (posix)"""
  73. try:
  74. out = _get_output('ifconfig')
  75. except (IOError, OSError):
  76. # no ifconfig, it's usually in /sbin and /sbin is not on everyone's PATH
  77. out = _get_output('/sbin/ifconfig')
  78. lines = out.splitlines()
  79. addrs = []
  80. for line in lines:
  81. m = _ifconfig_ipv4_pat.match(line.strip())
  82. if m:
  83. addrs.append(m.group(1))
  84. _populate_from_list(addrs)
  85. def _load_ips_ip():
  86. """load ip addresses from `ip addr` output (Linux)"""
  87. out = _get_output(['ip', '-f', 'inet', 'addr'])
  88. lines = out.splitlines()
  89. addrs = []
  90. for line in lines:
  91. blocks = line.lower().split()
  92. if (len(blocks) >= 2) and (blocks[0] == 'inet'):
  93. addrs.append(blocks[1].split('/')[0])
  94. _populate_from_list(addrs)
  95. _ipconfig_ipv4_pat = re.compile(r'ipv4.*?(\d+\.\d+\.\d+\.\d+)$', re.IGNORECASE)
  96. def _load_ips_ipconfig():
  97. """load ip addresses from `ipconfig` output (Windows)"""
  98. out = _get_output('ipconfig')
  99. lines = out.splitlines()
  100. addrs = []
  101. for line in lines:
  102. m = _ipconfig_ipv4_pat.match(line.strip())
  103. if m:
  104. addrs.append(m.group(1))
  105. _populate_from_list(addrs)
  106. def _load_ips_netifaces():
  107. """load ip addresses with netifaces"""
  108. import netifaces
  109. global LOCALHOST
  110. local_ips = []
  111. public_ips = []
  112. # list of iface names, 'lo0', 'eth0', etc.
  113. for iface in netifaces.interfaces():
  114. # list of ipv4 addrinfo dicts
  115. ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
  116. for entry in ipv4s:
  117. addr = entry.get('addr')
  118. if not addr:
  119. continue
  120. if not (iface.startswith('lo') or addr.startswith('127.')):
  121. public_ips.append(addr)
  122. elif not LOCALHOST:
  123. LOCALHOST = addr
  124. local_ips.append(addr)
  125. if not LOCALHOST:
  126. # we never found a loopback interface (can this ever happen?), assume common default
  127. LOCALHOST = '127.0.0.1'
  128. local_ips.insert(0, LOCALHOST)
  129. local_ips.extend(['0.0.0.0', ''])
  130. LOCAL_IPS[:] = _uniq_stable(local_ips)
  131. PUBLIC_IPS[:] = _uniq_stable(public_ips)
  132. def _load_ips_gethostbyname():
  133. """load ip addresses with socket.gethostbyname_ex
  134. This can be slow.
  135. """
  136. global LOCALHOST
  137. try:
  138. LOCAL_IPS[:] = socket.gethostbyname_ex('localhost')[2]
  139. except socket.error:
  140. # assume common default
  141. LOCAL_IPS[:] = ['127.0.0.1']
  142. try:
  143. hostname = socket.gethostname()
  144. PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2]
  145. # try hostname.local, in case hostname has been short-circuited to loopback
  146. if not hostname.endswith('.local') and all(ip.startswith('127') for ip in PUBLIC_IPS):
  147. PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + '.local')[2]
  148. except socket.error:
  149. pass
  150. finally:
  151. PUBLIC_IPS[:] = _uniq_stable(PUBLIC_IPS)
  152. LOCAL_IPS.extend(PUBLIC_IPS)
  153. # include all-interface aliases: 0.0.0.0 and ''
  154. LOCAL_IPS.extend(['0.0.0.0', ''])
  155. LOCAL_IPS[:] = _uniq_stable(LOCAL_IPS)
  156. LOCALHOST = LOCAL_IPS[0]
  157. def _load_ips_dumb():
  158. """Fallback in case of unexpected failure"""
  159. global LOCALHOST
  160. LOCALHOST = '127.0.0.1'
  161. LOCAL_IPS[:] = [LOCALHOST, '0.0.0.0', '']
  162. PUBLIC_IPS[:] = []
  163. @_only_once
  164. def _load_ips(suppress_exceptions=True):
  165. """load the IPs that point to this machine
  166. This function will only ever be called once.
  167. It will use netifaces to do it quickly if available.
  168. Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate.
  169. Finally, it will fallback on socket.gethostbyname_ex, which can be slow.
  170. """
  171. try:
  172. # first priority, use netifaces
  173. try:
  174. return _load_ips_netifaces()
  175. except ImportError:
  176. pass
  177. # second priority, parse subprocess output (how reliable is this?)
  178. if os.name == 'nt':
  179. try:
  180. return _load_ips_ipconfig()
  181. except (IOError, NoIPAddresses):
  182. pass
  183. else:
  184. try:
  185. return _load_ips_ip()
  186. except (IOError, OSError, NoIPAddresses):
  187. pass
  188. try:
  189. return _load_ips_ifconfig()
  190. except (IOError, OSError, NoIPAddresses):
  191. pass
  192. # lowest priority, use gethostbyname
  193. return _load_ips_gethostbyname()
  194. except Exception as e:
  195. if not suppress_exceptions:
  196. raise
  197. # unexpected error shouldn't crash, load dumb default values instead.
  198. warn("Unexpected error discovering local network interfaces: %s" % e)
  199. _load_ips_dumb()
  200. @_requires_ips
  201. def local_ips():
  202. """return the IP addresses that point to this machine"""
  203. return LOCAL_IPS
  204. @_requires_ips
  205. def public_ips():
  206. """return the IP addresses for this machine that are visible to other machines"""
  207. return PUBLIC_IPS
  208. @_requires_ips
  209. def localhost():
  210. """return ip for localhost (almost always 127.0.0.1)"""
  211. return LOCALHOST
  212. @_requires_ips
  213. def is_local_ip(ip):
  214. """does `ip` point to this machine?"""
  215. return ip in LOCAL_IPS
  216. @_requires_ips
  217. def is_public_ip(ip):
  218. """is `ip` a publicly visible address?"""
  219. return ip in PUBLIC_IPS