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