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