Want to speed up your svn/cvs operations? Want fast path completions for scp commands? Than you should enable the connection sharing feature from OpenSSH.
The essence is, that your first connection to a remote host acts as a master connection. All subsequent connection attempts will use the master connection to re-use the connection of the master.
What you save is the repeated authentication procedure, especially if you use passwd authentication.
First you need to create a directory where all master connections create a socket so that other clients can find already established connections and communicate with the master.
$ mkdir -p ~/.ssh/masters
$ chmod 0700 ~/.ssh/masters
Next we need to enable the feature in your ~/.ssh/config:
ControlMaster auto
ControlPath "~/.ssh/masters/%r@%h:%p"
The ControlPath is a format string where %r is the remote user name, %h the remote host, and %p the port. If your ~/.ssh directory is on a mounted share, you should also include a %l for the local host. So that you can connect to a remote from multiple hosts.
ControlPath "~/.ssh/masters/%l -> %r@%h:%p"
It would be nice to put these into separate directories, but OpenSSH does not create directories.
If you try this out you will probably encounter a problem. If your master is a interactive shell and you close it, ssh will not return to the console if other clients still use this shared connection.
To circumvent this you can start a ssh process into the back ground:
You can also use sshfs to act as the master:
I still haven’t found a nice way to make this somehow automatic. My wish would be a hook into the NetworkManager to get notified when a network connection is established. With this one can automatically connect to predefined hosts.
A second problem, which most of you will only notice if your computer hangs-up periodically and you try to reconnect after the restart, is that ssh complains that it can not use the still existing socket in the masters directories. The problem was more on my side and the solution is obvious:
Set the ControlMaster to no in the ~/.ssh/config file.
In this mode (which is the default) the client tries to connect to a master and fallbacks to a normal connection if it fails.
Now you need to start your master connection explicitly with the ControlMaster=yes option:
$ ssh -nNf -o ControlMaster=yes host
or
$ sshfs -o ControlMaster=yes host: mntpoint
respectively.
Supplement 1:
To close a master connection initiated with ssh -nNf ... run this:
Supplement 2:
Now to the fun part:
I just hacked a formidable solution to the problem, when to start the master control. And by hacked I mean it.
Get the latest OpenSSH portable package and extract it:
$ wget ftp://mirror.roothell.org/pub/OpenBSD/OpenSSH/portable/openssh-5.2p1.tar.gz
$ tar xf openssh-5.2p1.tar.gz
$ cd openssh-5.2p1
Now safe the following patch as controlcommand.patch and apply it to the source:
diff --git a/mux.c b/mux.c
index 79f8376..1b07bab 100644
--- a/mux.c
+++ b/mux.c
@@ -518,6 +518,9 @@ muxclient(const char *path)
/* FALLTHROUGH */
case SSHCTL_MASTER_NO:
break;
+ case SSHCTL_MASTER_COMMAND:
+ debug("command-mux: Start control command");
+ break;
default:
return;
}
@@ -534,13 +537,22 @@ muxclient(const char *path)
if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
fatal("%s socket(): %s", __func__, strerror(errno));
+retry:
if (connect(sock, (struct sockaddr *)&addr, addr_len) == -1) {
if (muxclient_command != SSHMUX_COMMAND_OPEN) {
fatal("Control socket connect(%.100s): %s", path,
strerror(errno));
}
if (errno == ENOENT)
- debug("Control socket \"%.100s\" does not exist", path);
+ if (options.control_master != SSHCTL_MASTER_COMMAND ||
+ options.control_command == NULL) {
+ debug("Control socket \"%.100s\" does not exist", path);
+ } else {
+ int rc = ssh_local_cmd(options.control_command);
+ debug("Executing control command: %.500s: %d", options.control_command, rc);
+ if (!rc)
+ goto retry;
+ }
else {
error("Control socket connect(%.100s): %s", path,
strerror(errno));
diff --git a/readconf.c b/readconf.c
index 53fc6c7..f2be9c5 100644
--- a/readconf.c
+++ b/readconf.c
@@ -128,8 +128,9 @@ typedef enum {
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
oAddressFamily, oGssAuthentication, oGssDelegateCreds,
oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
- oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
- oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
+ oSendEnv, oControlPath, oControlMaster, oControlCommand,
+ oHashKnownHosts, oTunnel, oTunnelDevice,
+ oLocalCommand, oPermitLocalCommand,
oVisualHostKey, oZeroKnowledgePasswordAuthentication,
oDeprecated, oUnsupported
} OpCodes;
@@ -222,6 +223,7 @@ static struct {
{ "sendenv", oSendEnv },
{ "controlpath", oControlPath },
{ "controlmaster", oControlMaster },
+ { "controlcommand", oControlCommand },
{ "hashknownhosts", oHashKnownHosts },
{ "tunnel", oTunnel },
{ "tunneldevice", oTunnelDevice },
@@ -856,6 +858,8 @@ parse_int:
value = SSHCTL_MASTER_ASK;
else if (strcmp(arg, "autoask") == 0)
value = SSHCTL_MASTER_AUTO_ASK;
+ else if (strcmp(arg, "command") == 0)
+ value = SSHCTL_MASTER_COMMAND;
else
fatal("%.200s line %d: Bad ControlMaster argument.",
filename, linenum);
@@ -863,6 +867,10 @@ parse_int:
*intptr = value;
break;
+ case oControlCommand:
+ charptr = &options->control_command;
+ goto parse_string;
+
case oHashKnownHosts:
intptr = &options->hash_known_hosts;
goto parse_flag;
@@ -1057,6 +1065,7 @@ initialize_options(Options * options)
options->num_send_env = 0;
options->control_path = NULL;
options->control_master = -1;
+ options->control_command = NULL;
options->hash_known_hosts = -1;
options->tun_open = -1;
options->tun_local = -1;
diff --git a/readconf.h b/readconf.h
index 8fb3a85..93255a0 100644
--- a/readconf.h
+++ b/readconf.h
@@ -112,6 +112,7 @@ typedef struct {
char *control_path;
int control_master;
+ char *control_command;
int hash_known_hosts;
@@ -130,6 +131,7 @@ typedef struct {
#define SSHCTL_MASTER_AUTO 2
#define SSHCTL_MASTER_ASK 3
#define SSHCTL_MASTER_AUTO_ASK 4
+#define SSHCTL_MASTER_COMMAND 5
void initialize_options(Options *);
void fill_default_options(Options *);
diff --git a/ssh.c b/ssh.c
index 9d43bb7..02f1960 100644
--- a/ssh.c
+++ b/ssh.c
@@ -212,6 +212,7 @@ main(int ac, char **av)
extern char *optarg;
struct servent *sp;
Forward fwd;
+ char *host_arg;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -547,6 +548,9 @@ main(int ac, char **av)
if (!host)
usage();
+ /* safe the hostname given on the command-line */
+ host_arg = host;
+
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
@@ -622,6 +626,9 @@ main(int ac, char **av)
&options, 0);
}
+ if (options.hostname != NULL)
+ host = options.hostname;
+
/* Fill configuration defaults. */
fill_default_options(&options);
@@ -651,15 +658,12 @@ main(int ac, char **av)
cp = options.local_command;
options.local_command = percent_expand(cp, "d", pw->pw_dir,
"h", options.hostname? options.hostname : host,
- "l", thishost, "n", host, "r", options.user, "p", buf,
+ "l", thishost, "n", host_arg, "r", options.user, "p", buf,
"u", pw->pw_name, (char *)NULL);
debug3("expanded LocalCommand: %s", options.local_command);
xfree(cp);
}
- if (options.hostname != NULL)
- host = options.hostname;
-
/* force lowercase for hostkey matching */
if (options.host_key_alias != NULL) {
for (p = options.host_key_alias; *p; p++)
@@ -672,12 +676,12 @@ main(int ac, char **av)
xfree(options.proxy_command);
options.proxy_command = NULL;
}
+
if (options.control_path != NULL &&
strcmp(options.control_path, "none") == 0) {
xfree(options.control_path);
options.control_path = NULL;
}
-
if (options.control_path != NULL) {
char thishost[NI_MAXHOST];
@@ -691,6 +695,32 @@ main(int ac, char **av)
"r", options.user, "l", thishost, (char *)NULL);
xfree(cp);
}
+
+ if (options.control_command != NULL &&
+ strcmp(options.control_command, "none") == 0) {
+ xfree(options.control_command);
+ options.control_command = NULL;
+ }
+ if (options.control_command != NULL && options.control_path != NULL) {
+ char thishost[NI_MAXHOST];
+
+ if (gethostname(thishost, sizeof(thishost)) == -1)
+ fatal("gethostname: %s", strerror(errno));
+ snprintf(buf, sizeof(buf), "%d", options.port);
+ cp = options.control_command;
+ options.control_command = percent_expand(cp,
+ "l", thishost,
+ "h", options.hostname ?: host,
+ "p", buf,
+ "r", options.user,
+ "n", host_arg,
+ "u", pw->pw_name,
+ "d", pw->pw_dir,
+ "s", options.control_path,
+ (char *)NULL);
+ xfree(cp);
+ }
+
if (muxclient_command != 0 && options.control_path == NULL)
fatal("No ControlPath specified for \"-O\" command");
if (options.control_path != NULL)
diff --git a/sshconnect.c b/sshconnect.c
index c04aa10..9d6e6c2 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1155,8 +1155,7 @@ ssh_local_cmd(const char *args)
pid_t pid;
int status;
- if (!options.permit_local_command ||
- args == NULL || !*args)
+ if (args == NULL || !*args)
return (1);
if ((shell = getenv("SHELL")) == NULL)
$ patch -p1 < controlcommand.patch
configure and build openssh with --bindir=$HOME/bin and use $DESTDIR to install the complete package:
$ make DESTDIR=$PWD/root install-nosysconf
Copy the binaries into your PATH:
$ cp $PWD/root$HOME/bin/{ssh,scp,sftp} $HOME/bin
Change your ~/.ssh/config:
ControlMaster command
ControlCommand "ssh-cc.sh %h"
Finally put this script as ssh-cc.sh somewhere into your PATH:
#!/bin/bash
ssh -nNf -o ControlMaster=yes "$1"
I used a script to put some sshfs mounts into this.
To be sure your tools use the new ssh command add something like this into a proper file:
export GIT_SSH=$HOME/bin/ssh
export CVS_RSH=$HOME/bin/ssh
export RSYNC_RSH=$HOME/bin/ssh
export SVN_SSH=$HOME/bin/ssh
Recent Comments