Skip to content
This repository has been archived by the owner on Apr 19, 2024. It is now read-only.

Commit

Permalink
Implement connection sharing between instances of PuTTY.
Browse files Browse the repository at this point in the history
The basic strategy is described at the top of the new source file
sshshare.c. In very brief: an 'upstream' PuTTY opens a Unix-domain
socket or Windows named pipe, and listens for connections from other
PuTTYs wanting to run sessions on the same server. The protocol spoken
down that socket/pipe is essentially the bare ssh-connection protocol,
using a trivial binary packet protocol with no encryption, and the
upstream has to do some fiddly transformations that I've been
referring to as 'channel-number NAT' to avoid resource clashes between
the sessions it's managing.

This is quite different from OpenSSH's approach of using the Unix-
domain socket as a means of passing file descriptors around; the main
reason for that is that fd-passing is Unix-specific but this system
has to work on Windows too. However, there are additional advantages,
such as making it easy for each downstream PuTTY to run its own
independent set of port and X11 forwardings (though the method for
making the latter work is quite painful).

Sharing is off by default, but configuration is intended to be very
easy in the normal case - just tick one box in the SSH config panel
and everything else happens automatically.

[originally from svn r10083]
  • Loading branch information
sgtatham committed Nov 17, 2013
1 parent f6f78f8 commit bb78583
Show file tree
Hide file tree
Showing 30 changed files with 3,915 additions and 216 deletions.
7 changes: 4 additions & 3 deletions Recipe
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,10 @@ NONSSH = telnet raw rlogin ldisc pinger
SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
+ sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
+ sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf
+ sshgssc pgssapi
WINSSH = SSH winnoise winsecur winpgntc wingss winhsock errsock
UXSSH = SSH uxnoise uxagentc uxgss
+ sshgssc pgssapi sshshare
WINSSH = SSH winnoise winsecur winpgntc wingss winshare winnps winnpc
+ winhsock errsock
UXSSH = SSH uxnoise uxagentc uxgss uxshare

# SFTP implementation (pscp, psftp).
SFTP = sftp int64 logging
Expand Down
20 changes: 20 additions & 0 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,26 @@ void setup_config_box(struct controlbox *b, int midsession,
I(CONF_compression));
}

if (!midsession || protcfginfo != 1) {
s = ctrl_getset(b, "Connection/SSH", "sharing", "Sharing an SSH connection between PuTTY tools");

ctrl_checkbox(s, "Share SSH connections if possible", 's',
HELPCTX(ssh_share),
conf_checkbox_handler,
I(CONF_ssh_connection_sharing));

ctrl_text(s, "Permitted roles in a shared connection:",
HELPCTX(ssh_share));
ctrl_checkbox(s, "Upstream (connecting to the real server)", 'u',
HELPCTX(ssh_share),
conf_checkbox_handler,
I(CONF_ssh_connection_sharing_upstream));
ctrl_checkbox(s, "Downstream (connecting to the upstream PuTTY)", 'd',
HELPCTX(ssh_share),
conf_checkbox_handler,
I(CONF_ssh_connection_sharing_downstream));
}

if (!midsession) {
s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");

Expand Down
58 changes: 58 additions & 0 deletions doc/config.but
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,64 @@ If you select \q{1 only} or \q{2 only} here, PuTTY will only connect
if the server you connect to offers the SSH protocol version you
have specified.

\S{config-ssh-sharing} Sharing an SSH connection between PuTTY tools

The controls in this box allow you to configure PuTTY to reuse an
existing SSH connection, where possible.

The SSH-2 protocol permits you to run multiple data channels over the
same SSH connection, so that you can log in just once (and do the
expensive encryption setup just once) and then have more than one
terminal window open.

Each instance of PuTTY can still run at most one terminal session, but
using the controls in this box, you can configure PuTTY to check if
another instance of itself has already connected to the target host,
and if so, share that instance's SSH connection instead of starting a
separate new one.

To enable this feature, just tick the box \q{Share SSH connections if
possible}. Then, whenever you start up a PuTTY session connecting to a
particular host, it will try to reuse an existing SSH connection if
one is available. For example, selecting \q{Duplicate Session} from
the system menu will launch another session on the same host, and if
sharing is enabled then it will reuse the existing SSH connection.

When this mode is in use, the first PuTTY that connected to a given
server becomes the \q{upstream}, which means that it is the one
managing the real SSH connection. All subsequent PuTTYs which reuse
the connection are referred to as \q{downstreams}: they do not connect
to the real server at all, but instead connect to the upstream PuTTY
via local inter-process communication methods.

For this system to be activated, \e{both} the upstream and downstream
instances of PuTTY must have the sharing option enabled.

The upstream PuTTY can therefore not terminate until all its
downstreams have closed. This is similar to the effect you get with
port forwarding or X11 forwarding, in which a PuTTY whose terminal
session has already finished will still remain open so as to keep
serving forwarded connections.

In case you need to configure this system in more detail, there are
two additional checkboxes which allow you to specify whether a
particular PuTTY can act as an upstream or a downstream or both.
(These boxes only take effect if the main \q{Share SSH connections if
possible} box is also ticked.) By default both of these boxes are
ticked, so that multiple PuTTYs started from the same configuration
will designate one of themselves as the upstream and share a single
connection; but if for some reason you need a particular PuTTY
configuration \e{not} to be an upstream (e.g. because you definitely
need it to close promptly) or not to be a downstream (e.g. because it
needs to do its own authentication using a special private key) then
you can untick one or the other of these boxes.

I have referred to \q{PuTTY} throughout the above discussion, but all
the other PuTTY tools which make SSH connections can use this
mechanism too. For example, if PSCP or PSFTP loads a configuration
with sharing enabled, then it can act as a downstream and use an
existing SSH connection set up by an instance of GUI PuTTY. The one
special case is that PSCP and PSFTP will \e{never} act as upstreams.

\H{config-ssh-kex} The Kex panel

Expand Down
27 changes: 17 additions & 10 deletions logging.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ void log_eventlog(void *handle, const char *event)
void log_packet(void *handle, int direction, int type,
char *texttype, const void *data, int len,
int n_blanks, const struct logblank_t *blanks,
const unsigned long *seq)
const unsigned long *seq,
unsigned downstream_id, const char *additional_log_text)
{
struct LogContext *ctx = (struct LogContext *)handle;
char dumpdata[80], smalldata[5];
Expand All @@ -248,15 +249,21 @@ void log_packet(void *handle, int direction, int type,

/* Packet header. */
if (texttype) {
if (seq) {
logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",
direction == PKT_INCOMING ? "Incoming" : "Outgoing",
*seq, type, type, texttype);
} else {
logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",
direction == PKT_INCOMING ? "Incoming" : "Outgoing",
type, type, texttype);
}
logprintf(ctx, "%s packet ",
direction == PKT_INCOMING ? "Incoming" : "Outgoing");

if (seq)
logprintf(ctx, "#0x%lx, ", *seq);

logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype);

if (downstream_id) {
logprintf(ctx, " on behalf of downstream #%u", downstream_id);
if (additional_log_text)
logprintf(ctx, " (%s)", additional_log_text);
}

logprintf(ctx, "\r\n");
} else {
/*
* Raw data is logged with a timestamp, so that it's possible
Expand Down
3 changes: 3 additions & 0 deletions network.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only,
Conf *conf, int addressfamily);
SockAddr name_lookup(char *host, int port, char **canonicalname,
Conf *conf, int addressfamily);
int proxy_for_destination (SockAddr addr, const char *hostname, int port,
Conf *conf);

/* platform-dependent callback from new_connection() */
/* (same caveat about addr as new_connection()) */
Expand All @@ -116,6 +118,7 @@ void sk_cleanup(void); /* called just before program exit */
SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family);
SockAddr sk_nonamelookup(const char *host);
void sk_getaddr(SockAddr addr, char *buf, int buflen);
int sk_addr_needs_port(SockAddr addr);
int sk_hostname_is_local(const char *name);
int sk_address_is_local(SockAddr addr);
int sk_address_is_special_local(SockAddr addr);
Expand Down
24 changes: 24 additions & 0 deletions noshare.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Stub implementation of SSH connection-sharing IPC, for any
* platform which can't support it at all.
*/

#include <stdio.h>
#include <assert.h>
#include <errno.h>

#include "tree234.h"
#include "putty.h"
#include "ssh.h"
#include "network.h"

int platform_ssh_share(const char *name, Conf *conf,
Plug downplug, Plug upplug, Socket *sock,
char **logtext)
{
return SHARE_NONE;
}

void platform_ssh_share_cleanup(const char *name)
{
}
4 changes: 2 additions & 2 deletions proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ static int plug_proxy_accepting(Plug p,
* This function can accept a NULL pointer as `addr', in which case
* it will only check the host name.
*/
static int proxy_for_destination (SockAddr addr, const char *hostname,
int port, Conf *conf)
int proxy_for_destination (SockAddr addr, const char *hostname,
int port, Conf *conf)
{
int s = 0, e = 0;
char hostip[64];
Expand Down
3 changes: 3 additions & 0 deletions pscp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2307,6 +2307,9 @@ void cmdline_error(char *p, ...)
exit(1);
}

const int share_can_be_downstream = TRUE;
const int share_can_be_upstream = FALSE;

/*
* Main program. (Called `psftp_main' because it gets called from
* *sftp.c; bit silly, I know, but it had to be called _something_.)
Expand Down
3 changes: 3 additions & 0 deletions psftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,9 @@ void cmdline_error(char *p, ...)
exit(1);
}

const int share_can_be_downstream = TRUE;
const int share_can_be_upstream = FALSE;

/*
* Main program. Parse arguments etc.
*/
Expand Down
6 changes: 5 additions & 1 deletion putty.h
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,9 @@ void cleanup_exit(int);
* large window in SSH-2. \
*/ \
X(INT, NONE, ssh_simple) \
X(INT, NONE, ssh_connection_sharing) \
X(INT, NONE, ssh_connection_sharing_upstream) \
X(INT, NONE, ssh_connection_sharing_downstream) \
/* Options for pterm. Should split out into platform-dependent part. */ \
X(INT, NONE, stamp_utmp) \
X(INT, NONE, login_shell) \
Expand Down Expand Up @@ -1016,7 +1019,8 @@ struct logblank_t {
void log_packet(void *logctx, int direction, int type,
char *texttype, const void *data, int len,
int n_blanks, const struct logblank_t *blanks,
const unsigned long *sequence);
const unsigned long *sequence,
unsigned downstream_id, const char *additional_log_text);

/*
* Exports from testback.c
Expand Down
6 changes: 6 additions & 0 deletions settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,9 @@ void save_open_settings(void *sesskey, Conf *conf)
write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing));
write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
}

void load_settings(char *section, Conf *conf)
Expand Down Expand Up @@ -983,6 +986,9 @@ void load_open_settings(void *sesskey, Conf *conf)
gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing);
gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
}

void do_defaults(char *session, Conf *conf)
Expand Down
Loading

0 comments on commit bb78583

Please sign in to comment.