diff --git a/IPython/utils/localinterfaces.py b/IPython/utils/localinterfaces.py index aee852b..89b8fde 100644 --- a/IPython/utils/localinterfaces.py +++ b/IPython/utils/localinterfaces.py @@ -1,278 +1,5 @@ -"""Simple utility for building a list of local IPs using the socket module. -This module defines two constants: +from warnings import warn -LOCALHOST : The loopback interface, or the first interface that points to this - machine. It will *almost* always be '127.0.0.1' +warn("IPython.utils.localinterfaces has moved to jupyter_client.localinterfaces") -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 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. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import re -import socket - -from .data import uniq_stable -from .process import get_output_error_code -from .warn import warn - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -LOCAL_IPS = [] -PUBLIC_IPS = [] - -LOCALHOST = '' - -def _only_once(f): - """decorator to only run a function once""" - f.called = False - def wrapped(**kwargs): - if f.called: - return - ret = f(**kwargs) - f.called = True - return ret - return wrapped - -def _requires_ips(f): - """decorator to ensure load_ips has been run before f""" - def ips_loaded(*args, **kwargs): - _load_ips() - return f(*args, **kwargs) - return ips_loaded - -# 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 - - 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 = out.splitlines() - addrs = [] - for line in lines: - blocks = line.lower().split() - if (len(blocks) >= 2) and (blocks[0] == 'inet'): - if blocks[1].startswith("addr:"): - addrs.append(blocks[1].split(":")[1]) - else: - 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 = out.splitlines() - addrs = [] - for line in lines: - blocks = line.lower().split() - if (len(blocks) >= 2) and (blocks[0] == 'inet'): - addrs.append(blocks[1].split('/')[0]) - _populate_from_list(addrs) - -_ipconfig_ipv4_pat = re.compile(r'ipv4.*?(\d+\.\d+\.\d+\.\d+)$', re.IGNORECASE) - -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 = out.splitlines() - addrs = [] - for line in lines: - m = _ipconfig_ipv4_pat.match(line.strip()) - if m: - addrs.append(m.group(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: - # assume common default - LOCAL_IPS[:] = ['127.0.0.1'] - - try: - hostname = socket.gethostname() - PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2] - # try hostname.local, in case hostname has been short-circuited to loopback - if not hostname.endswith('.local') and all(ip.startswith('127') for ip in PUBLIC_IPS): - PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + '.local')[2] - except socket.error: - pass - 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', '']) - - LOCAL_IPS[:] = uniq_stable(LOCAL_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(suppress_exceptions=True): - """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: - if not suppress_exceptions: - raise - # 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""" - return LOCAL_IPS - -@_requires_ips -def public_ips(): - """return the IP addresses for this machine that are visible to other machines""" - return PUBLIC_IPS - -@_requires_ips -def localhost(): - """return ip for localhost (almost always 127.0.0.1)""" - return LOCALHOST - -@_requires_ips -def is_local_ip(ip): - """does `ip` point to this machine?""" - return ip in LOCAL_IPS - -@_requires_ips -def is_public_ip(ip): - """is `ip` a publicly visible address?""" - return ip in PUBLIC_IPS +from jupyter_client.localinterfaces import * diff --git a/jupyter_client/connect.py b/jupyter_client/connect.py index ac3927f..23a00fb 100644 --- a/jupyter_client/connect.py +++ b/jupyter_client/connect.py @@ -20,7 +20,7 @@ import tempfile import zmq from IPython.config import LoggingConfigurable -from IPython.utils.localinterfaces import localhost +from .localinterfaces import localhost from IPython.utils.path import filefind from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2, string_types) diff --git a/jupyter_client/consoleapp.py b/jupyter_client/consoleapp.py index 8755a87..6bb09bb 100644 --- a/jupyter_client/consoleapp.py +++ b/jupyter_client/consoleapp.py @@ -28,7 +28,7 @@ from .session import Session ConnectionFileMixin = connect.ConnectionFileMixin -from IPython.utils.localinterfaces import localhost +from .localinterfaces import localhost #----------------------------------------------------------------------------- # Aliases and Flags diff --git a/jupyter_client/localinterfaces.py b/jupyter_client/localinterfaces.py new file mode 100644 index 0000000..27a70b4 --- /dev/null +++ b/jupyter_client/localinterfaces.py @@ -0,0 +1,268 @@ +"""Simple utility for building a list of local IPs using the socket module. +This module defines two constants: + +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) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import re +import socket + +from IPython.utils.data import uniq_stable +from IPython.utils.process import get_output_error_code +from warnings import warn + + +LOCAL_IPS = [] +PUBLIC_IPS = [] + +LOCALHOST = '' + +def _only_once(f): + """decorator to only run a function once""" + f.called = False + def wrapped(**kwargs): + if f.called: + return + ret = f(**kwargs) + f.called = True + return ret + return wrapped + +def _requires_ips(f): + """decorator to ensure load_ips has been run before f""" + def ips_loaded(*args, **kwargs): + _load_ips() + return f(*args, **kwargs) + return ips_loaded + +# 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 + + 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 = out.splitlines() + addrs = [] + for line in lines: + blocks = line.lower().split() + if (len(blocks) >= 2) and (blocks[0] == 'inet'): + if blocks[1].startswith("addr:"): + addrs.append(blocks[1].split(":")[1]) + else: + 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 = out.splitlines() + addrs = [] + for line in lines: + blocks = line.lower().split() + if (len(blocks) >= 2) and (blocks[0] == 'inet'): + addrs.append(blocks[1].split('/')[0]) + _populate_from_list(addrs) + +_ipconfig_ipv4_pat = re.compile(r'ipv4.*?(\d+\.\d+\.\d+\.\d+)$', re.IGNORECASE) + +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 = out.splitlines() + addrs = [] + for line in lines: + m = _ipconfig_ipv4_pat.match(line.strip()) + if m: + addrs.append(m.group(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: + # assume common default + LOCAL_IPS[:] = ['127.0.0.1'] + + try: + hostname = socket.gethostname() + PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2] + # try hostname.local, in case hostname has been short-circuited to loopback + if not hostname.endswith('.local') and all(ip.startswith('127') for ip in PUBLIC_IPS): + PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + '.local')[2] + except socket.error: + pass + 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', '']) + + LOCAL_IPS[:] = uniq_stable(LOCAL_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(suppress_exceptions=True): + """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: + if not suppress_exceptions: + raise + # 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""" + return LOCAL_IPS + +@_requires_ips +def public_ips(): + """return the IP addresses for this machine that are visible to other machines""" + return PUBLIC_IPS + +@_requires_ips +def localhost(): + """return ip for localhost (almost always 127.0.0.1)""" + return LOCALHOST + +@_requires_ips +def is_local_ip(ip): + """does `ip` point to this machine?""" + return ip in LOCAL_IPS + +@_requires_ips +def is_public_ip(ip): + """is `ip` a publicly visible address?""" + return ip in PUBLIC_IPS diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py index 124b6b8..10b79cc 100644 --- a/jupyter_client/manager.py +++ b/jupyter_client/manager.py @@ -20,7 +20,7 @@ except ImportError: import zmq from IPython.utils.importstring import import_item -from IPython.utils.localinterfaces import is_local_ip, local_ips +from .localinterfaces import is_local_ip, local_ips from IPython.utils.path import get_ipython_dir from IPython.utils.traitlets import ( Any, Instance, Unicode, List, Bool, Type, DottedObjectName diff --git a/IPython/utils/tests/test_localinterfaces.py b/jupyter_client/tests/test_localinterfaces.py similarity index 100% rename from IPython/utils/tests/test_localinterfaces.py rename to jupyter_client/tests/test_localinterfaces.py diff --git a/jupyter_client/tests/test_multikernelmanager.py b/jupyter_client/tests/test_multikernelmanager.py index 4e99416..925dca2 100644 --- a/jupyter_client/tests/test_multikernelmanager.py +++ b/jupyter_client/tests/test_multikernelmanager.py @@ -7,7 +7,7 @@ from unittest import TestCase from IPython.testing import decorators as dec from IPython.config.loader import Config -from IPython.utils.localinterfaces import localhost +from ..localinterfaces import localhost from jupyter_client import KernelManager from jupyter_client.multikernelmanager import MultiKernelManager