There is a race in the setup of the ControlMaster socket in auto mode, as
illustrated by the following command line:
ssh -oControlMaster=auto -oControlPath=sock localhost 'sleep 1; echo 1'
&
ssh -oControlMaster=auto -oControlPath=sock localhost 'sleep 2; echo 2'
&
Both of the commands will try to start up as a control client, find that
sock does not exist, and switch into control master mode. One will succeed
in creating the control master socket and the other will fail and bomb.
The attached patch eliminates this race by trying to create a control
master socket first, and falling back to control client mode if master
mode fails.
Tony.
--
f.a.n.finch <dot at dotat.at> http://dotat.at/
IRISH SEA: SOUTHERLY, BACKING NORTHEASTERLY FOR A TIME, 3 OR 4. SLIGHT OR
MODERATE. SHOWERS. MODERATE OR GOOD, OCCASIONALLY POOR.
-------------- next part --------------
--- ssh.c~ Fri Jan 5 05:30:17 2007
+++ ssh.c Fri Aug 3 19:21:18 2007
@@ -1045,18 +1045,19 @@
}
}
-static void
-ssh_control_listener(void)
+static int
+ssh_control_listener(int test)
{
struct sockaddr_un addr;
mode_t old_umask;
int addr_len;
if (options.control_path == NULL ||
- options.control_master == SSHCTL_MASTER_NO)
- return;
+ options.control_master == SSHCTL_MASTER_NO ||
+ control_fd != -1)
+ return 1;
- debug("setting up multiplex master socket");
+ debug("trying to set up multiplex master socket");
memset(&addr, '\0', sizeof(addr));
addr.sun_family = AF_UNIX;
@@ -1073,11 +1074,9 @@
old_umask = umask(0177);
if (bind(control_fd, (struct sockaddr *)&addr, addr_len) == -1) {
control_fd = -1;
- if (errno == EINVAL || errno == EADDRINUSE)
- fatal("ControlSocket %s already exists",
- options.control_path);
- else
+ if (errno != EINVAL && errno != EADDRINUSE)
fatal("%s bind(): %s", __func__, strerror(errno));
+ return 0;
}
umask(old_umask);
@@ -1085,6 +1084,9 @@
fatal("%s listen(): %s", __func__, strerror(errno));
set_nonblock(control_fd);
+
+ debug("control master listening on %s", options.control_path);
+ return 1;
}
/* request pty/x11/agent/tcpfwd/shell for channel */
@@ -1201,7 +1203,9 @@
/* XXX should be pre-session */
ssh_init_forwarding();
- ssh_control_listener();
+ if (!ssh_control_listener(0))
+ fatal("control master socket %s already exists",
+ options.control_path);
if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
id = ssh_session2_open();
@@ -1319,7 +1323,13 @@
switch (options.control_master) {
case SSHCTL_MASTER_AUTO:
case SSHCTL_MASTER_AUTO_ASK:
- debug("auto-mux: Trying existing master");
+ /* see if we can create a control master socket
+ to avoid a race between two auto clients */
+ if (mux_command == SSHMUX_COMMAND_OPEN &&
+ ssh_control_listener(1))
+ return;
+ debug("trying to connect to control master socket %s",
+ options.control_path);
/* FALLTHROUGH */
case SSHCTL_MASTER_NO:
break;
@@ -1452,6 +1462,8 @@
signal(SIGINT, control_client_sighandler);
signal(SIGTERM, control_client_sighandler);
signal(SIGWINCH, control_client_sigrelay);
+
+ debug("connected to control master; waiting for exit");
if (tty_flag)
enter_raw_mode();