diff --git a/IPython/frontend/qt/console/qtconsoleapp.py b/IPython/frontend/qt/console/qtconsoleapp.py
index e4c4d91..0a4e93f 100644
--- a/IPython/frontend/qt/console/qtconsoleapp.py
+++ b/IPython/frontend/qt/console/qtconsoleapp.py
@@ -21,26 +21,22 @@ import glob
 import os
 import signal
 import sys
-from getpass import getpass
 
 # System library imports
 from IPython.external.qt import QtGui
 from pygments.styles import get_all_styles
 from zmq.utils import jsonapi as json
 
-# external imports
-from IPython.external.ssh import tunnel
-
 # Local imports
 from IPython.config.application import boolean_flag
 from IPython.core.application import BaseIPythonApplication
 from IPython.core.profiledir import ProfileDir
+from IPython.lib.kernel import tunnel_to_kernel
 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
 from IPython.frontend.qt.console import styles
 from IPython.frontend.qt.kernelmanager import QtKernelManager
-from IPython.parallel.util import select_random_ports
 from IPython.utils.path import filefind
 from IPython.utils.py3compat import str_to_bytes
 from IPython.utils.traitlets import (
@@ -424,24 +420,30 @@ class IPythonQtConsoleApp(BaseIPythonApplication):
             return
         
         if self.sshkey and not self.sshserver:
+            # specifying just the key implies that we are connecting directly
             self.sshserver = self.ip
-            self.ip=LOCALHOST
+            self.ip = LOCALHOST
         
-        lports = select_random_ports(4)
-        rports = self.shell_port, self.iopub_port, self.stdin_port, self.hb_port
-        self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = lports
+        # build connection dict for tunnels:
+        info = dict(ip=self.ip,
+                    shell_port=self.shell_port,
+                    iopub_port=self.iopub_port,
+                    stdin_port=self.stdin_port,
+                    hb_port=self.hb_port
+        )
         
-        remote_ip = self.ip
-        self.ip = LOCALHOST
-        self.log.info("Forwarding connections to %s via %s"%(remote_ip, self.sshserver))
+        self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
         
-        if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey):
-            password=False
-        else:
-            password = getpass("SSH Password for %s: "%self.sshserver)
+        # tunnels return a new set of ports, which will be on localhost:
+        self.ip = LOCALHOST
+        try:
+            newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
+        except:
+            # even catch KeyboardInterrupt
+            self.log.error("Could not setup tunnels", exc_info=True)
+            self.exit(1)
         
-        for lp,rp in zip(lports, rports):
-            tunnel.ssh_tunnel(lp, rp, self.sshserver, remote_ip, self.sshkey, password)
+        self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
         
         cf = self.connection_file
         base,ext = os.path.splitext(cf)
diff --git a/IPython/lib/kernel.py b/IPython/lib/kernel.py
index 1293344..4abfeba 100644
--- a/IPython/lib/kernel.py
+++ b/IPython/lib/kernel.py
@@ -19,8 +19,13 @@ Authors:
 
 import json
 import sys
+from getpass import getpass
 from subprocess import Popen, PIPE
 
+# external imports
+from IPython.external.ssh import tunnel
+
+# IPython imports
 from IPython.utils.path import filefind
 from IPython.utils.py3compat import str_to_bytes
 
@@ -96,4 +101,52 @@ def connect_qtconsole(argv=None):
     
     return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
 
+def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
+    """tunnel connections to a kernel via ssh
+    
+    This will open four SSH tunnels from localhost on this machine to the
+    ports associated with the kernel.  They can be either direct
+    localhost-localhost tunnels, or if an intermediate server is necessary,
+    the kernel must be listening on a public IP.
+    
+    Parameters
+    ----------
+    connection_info : dict or str (path)
+        Either a connection dict, or the path to a JSON connection file
+    sshserver : str
+        The ssh sever to use to tunnel to the kernel. Can be a full
+        `user@server:port` string. ssh config aliases are respected.
+    sshkey : str [optional]
+        Path to file containing ssh key to use for authentication.
+        Only necessary if your ssh config does not already associate
+        a keyfile with the host.
+    
+    Returns
+    -------
+    
+    (shell, iopub, stdin, hb) : ints
+        The four ports on localhost that have been forwarded to the kernel.
+    """
+    if isinstance(connection_info, basestring):
+        # it's a path, unpack it
+        with open(connection_info) as f:
+            connection_info = json.loads(f.read())
+    
+    cf = connection_info
+    
+    lports = tunnel.select_random_ports(4)
+    rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
+    
+    remote_ip = cf['ip']
+    
+    if tunnel.try_passwordless_ssh(sshserver, sshkey):
+        password=False
+    else:
+        password = getpass("SSH Password for %s: "%sshserver)
+    
+    for lp,rp in zip(lports, rports):
+        tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
+    
+    return tuple(lports)
+