diff --git a/IPython/utils/localinterfaces.py b/IPython/utils/localinterfaces.py index e67a35f..3e828f4 100644 --- a/IPython/utils/localinterfaces.py +++ b/IPython/utils/localinterfaces.py @@ -5,12 +5,14 @@ LOCALHOST : The loopback interface, or the first interface that points to this machine. It will *almost* always be '127.0.0.1' LOCAL_IPS : A list of IP addresses, loopback first, that point to this machine. + This will include LOCALHOST, PUBLIC_IPS, and aliases for all hosts, + such as '0.0.0.0'. PUBLIC_IPS : A list of public IP addresses that point to this machine. Use these to tell remote clients where to find you. """ #----------------------------------------------------------------------------- -# Copyright (C) 2010-2011 The IPython Development Team +# Copyright (C) 2010 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. @@ -20,9 +22,13 @@ PUBLIC_IPS : A list of public IP addresses that point to this machine. # Imports #----------------------------------------------------------------------------- +import os import socket from .data import uniq_stable +from .process import get_output_error_code +from .py3compat import bytes_to_str +from .warn import warn #----------------------------------------------------------------------------- # Code @@ -31,7 +37,7 @@ from .data import uniq_stable LOCAL_IPS = [] PUBLIC_IPS = [] -LOCALHOST = '127.0.0.1' +LOCALHOST = '' def _only_once(f): """decorator to only run a function once""" @@ -51,17 +57,124 @@ def _requires_ips(f): return f(*args, **kwargs) return ips_loaded -@_only_once -def _load_ips(): - """load the IPs that point to this machine +# subprocess-parsing ip finders +class NoIPAddresses(Exception): + pass + +def _populate_from_list(addrs): + """populate local and public IPs from flat list of all IPs""" + if not addrs: + raise NoIPAddresses - This function will only ever be called once. + global LOCALHOST + public_ips = [] + local_ips = [] + + for ip in addrs: + local_ips.append(ip) + if not ip.startswith('127.'): + public_ips.append(ip) + elif not LOCALHOST: + LOCALHOST = ip + + if not LOCALHOST: + LOCALHOST = '127.0.0.1' + local_ips.insert(0, LOCALHOST) + + local_ips.extend(['0.0.0.0', '']) + + LOCAL_IPS[:] = uniq_stable(local_ips) + PUBLIC_IPS[:] = uniq_stable(public_ips) + +def _load_ips_ifconfig(): + """load ip addresses from `ifconfig` output (posix)""" + + out, err, rc = get_output_error_code('ifconfig') + if rc: + # no ifconfig, it's usually in /sbin and /sbin is not on everyone's PATH + out, err, rc = get_output_error_code('/sbin/ifconfig') + if rc: + raise IOError("no ifconfig: %s" % err) + + lines = bytes_to_str(out).splitlines() + addrs = [] + for line in lines: + blocks = line.lower().split() + if blocks[0] == 'inet': + addrs.append(blocks[1]) + _populate_from_list(addrs) + + +def _load_ips_ip(): + """load ip addresses from `ip addr` output (Linux)""" + out, err, rc = get_output_error_code('ip addr') + if rc: + raise IOError("no ip: %s" % err) + + lines = bytes_to_str(out).splitlines() + addrs = [] + for line in lines: + blocks = line.lower().split() + if blocks[0] == 'inet': + addrs.append(blocks[1].split('/')[0]) + _populate_from_list(addrs) + + +def _load_ips_ipconfig(): + """load ip addresses from `ipconfig` output (Windows)""" + out, err, rc = get_output_error_code('ipconfig') + if rc: + raise IOError("no ipconfig: %s" % err) + + lines = bytes_to_str(out).splitlines() + addrs = ['127.0.0.1'] + for line in lines: + line = line.lower().split() + if line[:2] == ['ipv4', 'address']: + addrs.append(line.split()[-1]) + _populate_from_list(addrs) + + +def _load_ips_netifaces(): + """load ip addresses with netifaces""" + import netifaces + global LOCALHOST + local_ips = [] + public_ips = [] + + # list of iface names, 'lo0', 'eth0', etc. + for iface in netifaces.interfaces(): + # list of ipv4 addrinfo dicts + ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, []) + for entry in ipv4s: + addr = entry.get('addr') + if not addr: + continue + if not (iface.startswith('lo') or addr.startswith('127.')): + public_ips.append(addr) + elif not LOCALHOST: + LOCALHOST = addr + local_ips.append(addr) + if not LOCALHOST: + # we never found a loopback interface (can this ever happen?), assume common default + LOCALHOST = '127.0.0.1' + local_ips.insert(0, LOCALHOST) + local_ips.extend(['0.0.0.0', '']) + LOCAL_IPS[:] = uniq_stable(local_ips) + PUBLIC_IPS[:] = uniq_stable(public_ips) + + +def _load_ips_gethostbyname(): + """load ip addresses with socket.gethostbyname_ex + + This can be slow. """ global LOCALHOST try: LOCAL_IPS[:] = socket.gethostbyname_ex('localhost')[2] except socket.error: - pass + # assume common default + LOCAL_IPS[:] = ['127.0.0.1'] try: hostname = socket.gethostname() @@ -74,7 +187,7 @@ def _load_ips(): finally: PUBLIC_IPS[:] = uniq_stable(PUBLIC_IPS) LOCAL_IPS.extend(PUBLIC_IPS) - + # include all-interface aliases: 0.0.0.0 and '' LOCAL_IPS.extend(['0.0.0.0', '']) @@ -82,6 +195,57 @@ def _load_ips(): LOCALHOST = LOCAL_IPS[0] +def _load_ips_dumb(): + """Fallback in case of unexpected failure""" + global LOCALHOST + LOCALHOST = '127.0.0.1' + LOCAL_IPS[:] = [LOCALHOST, '0.0.0.0', ''] + PUBLIC_IPS[:] = [] + +@_only_once +def _load_ips(): + """load the IPs that point to this machine + + This function will only ever be called once. + + It will use netifaces to do it quickly if available. + Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate. + Finally, it will fallback on socket.gethostbyname_ex, which can be slow. + """ + + try: + # first priority, use netifaces + try: + return _load_ips_netifaces() + except ImportError: + pass + + # second priority, parse subprocess output (how reliable is this?) + + if os.name == 'nt': + try: + return _load_ips_ipconfig() + except (IOError, NoIPAddresses): + pass + else: + try: + return _load_ips_ifconfig() + except (IOError, NoIPAddresses): + pass + try: + return _load_ips_ip() + except (IOError, NoIPAddresses): + pass + + # lowest priority, use gethostbyname + + return _load_ips_gethostbyname() + except Exception as e: + # unexpected error shouldn't crash, load dumb default values instead. + warn("Unexpected error discovering local network interfaces: %s" % e) + _load_ips_dumb() + + @_requires_ips def local_ips(): """return the IP addresses that point to this machine"""