diff --git a/IPython/consoleapp.py b/IPython/consoleapp.py index 26554c4..dddcb8a 100644 --- a/IPython/consoleapp.py +++ b/IPython/consoleapp.py @@ -53,7 +53,7 @@ from IPython.kernel.connect import ConnectionFileMixin # Network Constants #----------------------------------------------------------------------------- -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost #----------------------------------------------------------------------------- # Globals @@ -254,7 +254,7 @@ class IPythonConsoleApp(ConnectionFileMixin): with open(fname) as f: cfg = json.load(f) self.transport = cfg.get('transport', 'tcp') - self.ip = cfg.get('ip', LOCALHOST) + self.ip = cfg.get('ip', localhost()) for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'): name = channel + '_port' @@ -282,7 +282,7 @@ class IPythonConsoleApp(ConnectionFileMixin): if self.sshkey and not self.sshserver: # specifying just the key implies that we are connecting directly self.sshserver = ip - ip = LOCALHOST + ip = localhost() # build connection dict for tunnels: info = dict(ip=ip, @@ -295,7 +295,7 @@ class IPythonConsoleApp(ConnectionFileMixin): self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver)) # tunnels return a new set of ports, which will be on localhost: - self.ip = LOCALHOST + self.ip = localhost() try: newports = tunnel_to_kernel(info, self.sshserver, self.sshkey) except: diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index efe5313..955f6df 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -78,7 +78,7 @@ from IPython.kernel.zmq.kernelapp import ( kernel_aliases, ) from IPython.utils.importstring import import_item -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils import submodule from IPython.utils.traitlets import ( Dict, Unicode, Integer, List, Bool, Bytes, @@ -293,9 +293,11 @@ class NotebookApp(BaseIPythonApplication): # Network related information. - ip = Unicode(LOCALHOST, config=True, + ip = Unicode(config=True, help="The IP address the notebook server will listen on." ) + def _ip_default(self): + return localhost() def _ip_changed(self, name, old, new): if new == u'*': self.ip = u'' @@ -694,7 +696,7 @@ class NotebookApp(BaseIPythonApplication): info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).") if self.open_browser or self.file_to_run: - ip = self.ip or LOCALHOST + ip = self.ip or localhost() try: browser = webbrowser.get(self.browser or None) except webbrowser.Error as e: diff --git a/IPython/kernel/connect.py b/IPython/kernel/connect.py index 83167ad..8ce939d 100644 --- a/IPython/kernel/connect.py +++ b/IPython/kernel/connect.py @@ -36,7 +36,7 @@ from IPython.external.ssh import tunnel # IPython imports from IPython.config import Configurable from IPython.core.profiledir import ProfileDir -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils.path import filefind, get_ipython_dir from IPython.utils.py3compat import str_to_bytes, bytes_to_str from IPython.utils.traitlets import ( @@ -49,7 +49,7 @@ from IPython.utils.traitlets import ( #----------------------------------------------------------------------------- def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0, - control_port=0, ip=LOCALHOST, key=b'', transport='tcp', + control_port=0, ip='', key=b'', transport='tcp', signature_scheme='hmac-sha256', ): """Generates a JSON config file, including the selection of random ports. @@ -90,6 +90,8 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, and 'sha256' is the default hash function. """ + if not ip: + ip = localhost() # default to temporary connector file if not fname: fname = tempfile.mktemp('.json') @@ -391,7 +393,7 @@ class ConnectionFileMixin(Configurable): transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True) - ip = Unicode(LOCALHOST, config=True, + ip = Unicode(config=True, help="""Set the kernel\'s IP address [default localhost]. If the IP address is something other than localhost, then Consoles on other machines will be able to connect @@ -405,7 +407,7 @@ class ConnectionFileMixin(Configurable): else: return 'kernel-ipc' else: - return LOCALHOST + return localhost() def _ip_changed(self, name, old, new): if new == '*': diff --git a/IPython/kernel/manager.py b/IPython/kernel/manager.py index cf5efe1..a26423e 100644 --- a/IPython/kernel/manager.py +++ b/IPython/kernel/manager.py @@ -1,5 +1,4 @@ -"""Base class to manage a running kernel -""" +"""Base class to manage a running kernel""" #----------------------------------------------------------------------------- # Copyright (C) 2013 The IPython Development Team @@ -24,7 +23,7 @@ import zmq # Local imports from IPython.config.configurable import LoggingConfigurable from IPython.utils.importstring import import_item -from IPython.utils.localinterfaces import LOCAL_IPS +from IPython.utils.localinterfaces import is_local_ip, local_ips from IPython.utils.traitlets import ( Any, Instance, Unicode, List, Bool, Type, DottedObjectName ) @@ -185,11 +184,11 @@ class KernelManager(LoggingConfigurable, ConnectionFileMixin): keyword arguments that are passed down to build the kernel_cmd and launching the kernel (e.g. Popen kwargs). """ - if self.transport == 'tcp' and self.ip not in LOCAL_IPS: + if self.transport == 'tcp' and not is_local_ip(self.ip): raise RuntimeError("Can only launch a kernel on a local interface. " "Make sure that the '*_address' attributes are " "configured properly. " - "Currently valid addresses are: %s"%LOCAL_IPS + "Currently valid addresses are: %s" % local_ips() ) # write connection file / get default ports diff --git a/IPython/kernel/tests/test_multikernelmanager.py b/IPython/kernel/tests/test_multikernelmanager.py index 1136af2..3838b72 100644 --- a/IPython/kernel/tests/test_multikernelmanager.py +++ b/IPython/kernel/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 IPython.utils.localinterfaces import localhost from IPython.kernel import KernelManager from IPython.kernel.multikernelmanager import MultiKernelManager @@ -64,7 +64,7 @@ class TestKernelManager(TestCase): def test_tcp_cinfo(self): km = self._get_tcp_km() - self._run_cinfo(km, 'tcp', LOCALHOST) + self._run_cinfo(km, 'tcp', localhost()) @dec.skip_win32 def test_ipc_lifecycle(self): diff --git a/IPython/kernel/zmq/heartbeat.py b/IPython/kernel/zmq/heartbeat.py index a018abb..ccfd5dc 100644 --- a/IPython/kernel/zmq/heartbeat.py +++ b/IPython/kernel/zmq/heartbeat.py @@ -19,7 +19,7 @@ from threading import Thread import zmq -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost #----------------------------------------------------------------------------- # Code @@ -29,7 +29,9 @@ from IPython.utils.localinterfaces import LOCALHOST class Heartbeat(Thread): "A simple ping-pong style heartbeat that runs in a thread." - def __init__(self, context, addr=('tcp', LOCALHOST, 0)): + def __init__(self, context, addr=None): + if addr is None: + addr = ('tcp', localhost(), 0) Thread.__init__(self) self.context = context self.transport, self.ip, self.port = addr diff --git a/IPython/kernel/zmq/kernelapp.py b/IPython/kernel/zmq/kernelapp.py index 3b100fd..7461bbf 100644 --- a/IPython/kernel/zmq/kernelapp.py +++ b/IPython/kernel/zmq/kernelapp.py @@ -39,7 +39,7 @@ from IPython.core.shellapp import ( InteractiveShellApp, shell_flags, shell_aliases ) from IPython.utils import io -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils.path import filefind from IPython.utils.py3compat import str_to_bytes from IPython.utils.traitlets import ( @@ -156,7 +156,8 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp): else: return 'kernel-ipc' else: - return LOCALHOST + return localhost() + hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]") shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]") iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]") diff --git a/IPython/parallel/apps/ipcontrollerapp.py b/IPython/parallel/apps/ipcontrollerapp.py index 28c651c..a4b879d 100755 --- a/IPython/parallel/apps/ipcontrollerapp.py +++ b/IPython/parallel/apps/ipcontrollerapp.py @@ -11,7 +11,7 @@ Authors: """ #----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team +# Copyright (C) 2008 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. @@ -44,7 +44,7 @@ from IPython.parallel.apps.baseapp import ( catch_config_error, ) from IPython.utils.importstring import import_item -from IPython.utils.localinterfaces import LOCALHOST, PUBLIC_IPS +from IPython.utils.localinterfaces import localhost, public_ips from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict, TraitError from IPython.kernel.zmq.session import ( @@ -224,13 +224,13 @@ class IPControllerApp(BaseParallelApplication): location = cdict['location'] if not location: - if PUBLIC_IPS: - location = PUBLIC_IPS[-1] + if public_ips(): + location = public_ips()[-1] else: self.log.warn("Could not identify this machine's IP, assuming %s." " You may need to specify '--location=' to help" - " IPython decide when to connect via loopback." % LOCALHOST) - location = LOCALHOST + " IPython decide when to connect via loopback." % localhost() ) + location = localhost() cdict['location'] = location fname = os.path.join(self.profile_dir.security_dir, fname) self.log.info("writing connection info to %s", fname) diff --git a/IPython/parallel/apps/logwatcher.py b/IPython/parallel/apps/logwatcher.py index 80be410..c6ed6a3 100644 --- a/IPython/parallel/apps/logwatcher.py +++ b/IPython/parallel/apps/logwatcher.py @@ -26,7 +26,7 @@ import zmq from zmq.eventloop import ioloop, zmqstream from IPython.config.configurable import LoggingConfigurable -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils.traitlets import Int, Unicode, Instance, List #----------------------------------------------------------------------------- @@ -44,8 +44,10 @@ class LogWatcher(LoggingConfigurable): # configurables topics = List([''], config=True, help="The ZMQ topics to subscribe to. Default is to subscribe to all messages") - url = Unicode('tcp://%s:20202' % LOCALHOST, config=True, + url = Unicode(config=True, help="ZMQ url on which to listen for log messages") + def _url_default(self): + return 'tcp://%s:20202' % localhost() # internals stream = Instance('zmq.eventloop.zmqstream.ZMQStream') diff --git a/IPython/parallel/client/client.py b/IPython/parallel/client/client.py index e6dc974..0b0c722 100644 --- a/IPython/parallel/client/client.py +++ b/IPython/parallel/client/client.py @@ -37,7 +37,7 @@ from IPython.core.profiledir import ProfileDir, ProfileDirError from IPython.utils.capture import RichOutput from IPython.utils.coloransi import TermColors from IPython.utils.jsonutil import rekey -from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS +from IPython.utils.localinterfaces import localhost, is_local_ip from IPython.utils.path import get_ipython_dir from IPython.utils.py3compat import cast_bytes from IPython.utils.traitlets import (HasTraits, Integer, Instance, Unicode, @@ -433,13 +433,13 @@ class Client(HasTraits): url = cfg['registration'] - if location is not None and addr == LOCALHOST: + if location is not None and addr == localhost(): # location specified, and connection is expected to be local - if location not in LOCAL_IPS and not sshserver: + if not is_local_ip(location) and not sshserver: # load ssh from JSON *only* if the controller is not on # this machine sshserver=cfg['ssh'] - if location not in LOCAL_IPS and not sshserver: + if not is_local_ip(location) and not sshserver: # warn if no ssh specified, but SSH is probably needed # This is only a warning, because the most likely cause # is a local Controller on a laptop whose IP is dynamic diff --git a/IPython/parallel/controller/hub.py b/IPython/parallel/controller/hub.py index ae0b3a8..e049fc9 100644 --- a/IPython/parallel/controller/hub.py +++ b/IPython/parallel/controller/hub.py @@ -30,7 +30,7 @@ from zmq.eventloop.zmqstream import ZMQStream # internal: from IPython.utils.importstring import import_item -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils.py3compat import cast_bytes from IPython.utils.traitlets import ( HasTraits, Instance, Integer, Unicode, Dict, Set, Tuple, CBytes, DottedObjectName @@ -177,20 +177,25 @@ class HubFactory(RegistrationFactory): def _notifier_port_default(self): return util.select_random_ports(1)[0] - engine_ip = Unicode(LOCALHOST, config=True, + engine_ip = Unicode(config=True, help="IP on which to listen for engine connections. [default: loopback]") + def _engine_ip_default(self): + return localhost() engine_transport = Unicode('tcp', config=True, help="0MQ transport for engine connections. [default: tcp]") - client_ip = Unicode(LOCALHOST, config=True, + client_ip = Unicode(config=True, help="IP on which to listen for client connections. [default: loopback]") client_transport = Unicode('tcp', config=True, help="0MQ transport for client connections. [default : tcp]") - monitor_ip = Unicode(LOCALHOST, config=True, + monitor_ip = Unicode(config=True, help="IP on which to listen for monitor messages. [default: loopback]") monitor_transport = Unicode('tcp', config=True, help="0MQ transport for monitor messages. [default : tcp]") + + _client_ip_default = _monitor_ip_default = _engine_ip_default + monitor_url = Unicode('') diff --git a/IPython/parallel/engine/engine.py b/IPython/parallel/engine/engine.py index 208212f..e60f2bf 100644 --- a/IPython/parallel/engine/engine.py +++ b/IPython/parallel/engine/engine.py @@ -24,7 +24,7 @@ from zmq.eventloop import ioloop, zmqstream from IPython.external.ssh import tunnel # internal -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils.traitlets import ( Instance, Dict, Integer, Type, Float, Integer, Unicode, CBytes, Bool ) @@ -184,13 +184,13 @@ class EngineFactory(RegistrationFactory): if self.max_heartbeat_misses > 0: # Add a monitor socket which will record the last time a ping was seen mon = self.context.socket(zmq.SUB) - mport = mon.bind_to_random_port('tcp://%s' % LOCALHOST) + mport = mon.bind_to_random_port('tcp://%s' % localhost()) mon.setsockopt(zmq.SUBSCRIBE, b"") self._hb_listener = zmqstream.ZMQStream(mon, self.loop) self._hb_listener.on_recv(self._report_ping) - hb_monitor = "tcp://%s:%i" % (LOCALHOST, mport) + hb_monitor = "tcp://%s:%i" % (localhost(), mport) heart = Heart(hb_ping, hb_pong, hb_monitor , heart_id=identity) heart.start() diff --git a/IPython/parallel/factory.py b/IPython/parallel/factory.py index 3b2a770..4636136 100644 --- a/IPython/parallel/factory.py +++ b/IPython/parallel/factory.py @@ -24,7 +24,7 @@ import zmq from zmq.eventloop.ioloop import IOLoop from IPython.config.configurable import Configurable -from IPython.utils.localinterfaces import LOCALHOST +from IPython.utils.localinterfaces import localhost from IPython.utils.traitlets import Integer, Instance, Unicode from IPython.parallel.util import select_random_ports @@ -40,16 +40,18 @@ class RegistrationFactory(SessionFactory): url = Unicode('', config=True, help="""The 0MQ url used for registration. This sets transport, ip, and port - in one variable. For example: url='tcp://%s:12345' or + in one variable. For example: url='tcp://127.0.0.1:12345' or url='epgm://*:90210'""" - % LOCALHOST) # url takes precedence over ip,regport,transport + ) # url takes precedence over ip,regport,transport transport = Unicode('tcp', config=True, help="""The 0MQ transport for communications. This will likely be the default of 'tcp', but other values include 'ipc', 'epgm', 'inproc'.""") - ip = Unicode(LOCALHOST, config=True, + ip = Unicode(config=True, help="""The IP address for registration. This is generally either '127.0.0.1' for loopback only or '*' for all interfaces. - [default: '%s']""" % LOCALHOST) + """) + def _ip_default(self): + return localhost() regport = Integer(config=True, help="""The port on which the Hub listens for registration.""") def _regport_default(self): diff --git a/IPython/parallel/util.py b/IPython/parallel/util.py index a725d5a..cf0824a 100644 --- a/IPython/parallel/util.py +++ b/IPython/parallel/util.py @@ -43,7 +43,7 @@ from IPython.external.decorator import decorator # IPython imports from IPython.config.application import Application -from IPython.utils.localinterfaces import LOCALHOST, PUBLIC_IPS +from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips from IPython.kernel.zmq.log import EnginePUBHandler from IPython.kernel.zmq.serialize import ( unserialize_object, serialize_object, pack_apply_message, unpack_apply_message @@ -187,9 +187,9 @@ def disambiguate_ip_address(ip, location=None): """turn multi-ip interfaces '0.0.0.0' and '*' into connectable ones, based on the location (default interpretation of location is localhost).""" if ip in ('0.0.0.0', '*'): - if location is None or location in PUBLIC_IPS or not PUBLIC_IPS: + if location is None or is_public_ip(location) or not public_ips(): # If location is unspecified or cannot be determined, assume local - ip = LOCALHOST + ip = localhost() elif location: return location return ip diff --git a/IPython/qt/console/qtconsoleapp.py b/IPython/qt/console/qtconsoleapp.py index e8ddbf4..3ef7fa1 100644 --- a/IPython/qt/console/qtconsoleapp.py +++ b/IPython/qt/console/qtconsoleapp.py @@ -74,7 +74,7 @@ from IPython.consoleapp import ( # Network Constants #----------------------------------------------------------------------------- -from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS +from IPython.utils.localinterfaces import is_local_ip #----------------------------------------------------------------------------- # Globals @@ -250,7 +250,7 @@ class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): QtGui.QApplication.setWindowIcon(self.app.icon) ip = self.ip - local_kernel = (not self.existing) or ip in LOCAL_IPS + local_kernel = (not self.existing) or is_local_ip(ip) self.widget = self.widget_factory(config=self.config, local_kernel=local_kernel) self.init_colors(self.widget) diff --git a/IPython/utils/localinterfaces.py b/IPython/utils/localinterfaces.py index 6418459..e67a35f 100644 --- a/IPython/utils/localinterfaces.py +++ b/IPython/utils/localinterfaces.py @@ -29,27 +29,80 @@ from .data import uniq_stable #----------------------------------------------------------------------------- LOCAL_IPS = [] -try: - LOCAL_IPS = socket.gethostbyname_ex('localhost')[2] -except socket.error: - pass - PUBLIC_IPS = [] -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] + +LOCALHOST = '127.0.0.1' + +def _only_once(f): + """decorator to only run a function once""" + f.called = False + def wrapped(): + if f.called: + return + ret = f() + 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 + +@_only_once +def _load_ips(): + """load the IPs that point to this machine + + This function will only ever be called once. + """ + global LOCALHOST + try: + LOCAL_IPS[:] = socket.gethostbyname_ex('localhost')[2] + except socket.error: + pass + + 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] + +@_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