Skip to content

Commit d9832e0

Browse files
authored
Merge pull request #335 from reef-technologies/migrate_ssh_tunneling_from_pyzmq
Migrate SSH tunneling from pyzmq
2 parents a6ffbd0 + f05abf7 commit d9832e0

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 .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)