Skip to content

Commit 6b03b81

Browse files
Migrate SSH tunneling from pyzmq
As this part of code is mainly used in IPython we agreed to move it from pyzmq. It will be easier to maintain here and some planned changes in this code will be easier to apply and release.
1 parent 78951c3 commit 6b03b81

File tree

5 files changed

+488
-12
lines changed

5 files changed

+488
-12
lines changed

jupyter_client/connect.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535

3636

3737
def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
38-
control_port=0, ip='', key=b'', transport='tcp',
39-
signature_scheme='hmac-sha256', kernel_name=''
40-
):
38+
control_port=0, ip='', key=b'', transport='tcp',
39+
signature_scheme='hmac-sha256', kernel_name=''
40+
):
4141
"""Generates a JSON config file, including the selection of random ports.
4242
4343
Parameters
@@ -193,7 +193,7 @@ def find_connection_file(filename='kernel-*.json', path=None, profile=None):
193193
path = ['.', jupyter_runtime_dir()]
194194
if isinstance(path, string_types):
195195
path = [path]
196-
196+
197197
try:
198198
# first, try explicit name
199199
return filefind(filename, path)
@@ -208,11 +208,11 @@ def find_connection_file(filename='kernel-*.json', path=None, profile=None):
208208
else:
209209
# accept any substring match
210210
pat = '*%s*' % filename
211-
211+
212212
matches = []
213213
for p in path:
214214
matches.extend(glob.glob(os.path.join(p, pat)))
215-
215+
216216
matches = [ os.path.abspath(m) for m in matches ]
217217
if not matches:
218218
raise IOError("Could not find %r in %r" % (filename, path))
@@ -249,7 +249,7 @@ def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
249249
(shell, iopub, stdin, hb) : ints
250250
The four ports on localhost that have been forwarded to the kernel.
251251
"""
252-
from zmq.ssh import tunnel
252+
from jupyter_core.ssh import tunnel
253253
if isinstance(connection_info, string_types):
254254
# it's a path, unpack it
255255
with open(connection_info) as f:
@@ -289,11 +289,11 @@ def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
289289

290290
class ConnectionFileMixin(LoggingConfigurable):
291291
"""Mixin for configurable classes that work with connection files"""
292-
292+
293293
data_dir = Unicode()
294294
def _data_dir_default(self):
295295
return jupyter_data_dir()
296-
296+
297297
# The addresses for the communication channels
298298
connection_file = Unicode('', config=True,
299299
help="""JSON file in which to store connection info [default: kernel-<pid>.json]
@@ -480,7 +480,7 @@ def write_connection_file(self):
480480

481481
def load_connection_file(self, connection_file=None):
482482
"""Load connection info from JSON dict in self.connection_file.
483-
483+
484484
Parameters
485485
----------
486486
connection_file: unicode, optional
@@ -496,10 +496,10 @@ def load_connection_file(self, connection_file=None):
496496

497497
def load_connection_info(self, info):
498498
"""Load connection info from a dict containing connection info.
499-
499+
500500
Typically this data comes from a connection file
501501
and is called by load_connection_file.
502-
502+
503503
Parameters
504504
----------
505505
info: dict

jupyter_client/ssh/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from jupyter_client.ssh.tunnel import *

jupyter_client/ssh/forward.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#
2+
# This file is adapted from a paramiko demo, and thus licensed under LGPL 2.1.
3+
# Original Copyright (C) 2003-2007 Robey Pointer <[email protected]>
4+
# Edits Copyright (C) 2010 The IPython Team
5+
#
6+
# Paramiko is free software; you can redistribute it and/or modify it under the
7+
# terms of the GNU Lesser General Public License as published by the Free
8+
# Software Foundation; either version 2.1 of the License, or (at your option)
9+
# any later version.
10+
#
11+
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
12+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13+
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14+
# details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
18+
# 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA.
19+
20+
"""
21+
Sample script showing how to do local port forwarding over paramiko.
22+
23+
This script connects to the requested SSH server and sets up local port
24+
forwarding (the openssh -L option) from a local port through a tunneled
25+
connection to a destination reachable from the SSH server machine.
26+
"""
27+
28+
from __future__ import print_function
29+
30+
import logging
31+
import select
32+
try: # Python 3
33+
import socketserver
34+
except ImportError: # Python 2
35+
import SocketServer as socketserver
36+
37+
logger = logging.getLogger('ssh')
38+
39+
40+
class ForwardServer (socketserver.ThreadingTCPServer):
41+
daemon_threads = True
42+
allow_reuse_address = True
43+
44+
45+
class Handler (socketserver.BaseRequestHandler):
46+
47+
def handle(self):
48+
try:
49+
chan = self.ssh_transport.open_channel('direct-tcpip',
50+
(self.chain_host, self.chain_port),
51+
self.request.getpeername())
52+
except Exception as e:
53+
logger.debug('Incoming request to %s:%d failed: %s' % (self.chain_host,
54+
self.chain_port,
55+
repr(e)))
56+
return
57+
if chan is None:
58+
logger.debug('Incoming request to %s:%d was rejected by the SSH server.' %
59+
(self.chain_host, self.chain_port))
60+
return
61+
62+
logger.debug('Connected! Tunnel open %r -> %r -> %r' % (self.request.getpeername(),
63+
chan.getpeername(), (self.chain_host, self.chain_port)))
64+
while True:
65+
r, w, x = select.select([self.request, chan], [], [])
66+
if self.request in r:
67+
data = self.request.recv(1024)
68+
if len(data) == 0:
69+
break
70+
chan.send(data)
71+
if chan in r:
72+
data = chan.recv(1024)
73+
if len(data) == 0:
74+
break
75+
self.request.send(data)
76+
chan.close()
77+
self.request.close()
78+
logger.debug('Tunnel closed ')
79+
80+
81+
def forward_tunnel(local_port, remote_host, remote_port, transport):
82+
# this is a little convoluted, but lets me configure things for the Handler
83+
# object. (SocketServer doesn't give Handlers any way to access the outer
84+
# server normally.)
85+
class SubHander (Handler):
86+
chain_host = remote_host
87+
chain_port = remote_port
88+
ssh_transport = transport
89+
ForwardServer(('127.0.0.1', local_port), SubHander).serve_forever()
90+
91+
92+
__all__ = ['forward_tunnel']

0 commit comments

Comments
 (0)