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