Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiplexing for NativeSsh #918

Merged
merged 2 commits into from
Jan 19, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 106 additions & 1 deletion src/Server/Remote/NativeSsh.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

class NativeSsh implements ServerInterface
{
const UNIX_SOCKET_MAX_LENGTH = 104;

/**
* @var array
*/
Expand Down Expand Up @@ -48,9 +50,15 @@ public function run($command)
$sshOptions = [
'-A',
'-q',
'-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
'-o UserKnownHostsFile=/dev/null',
'-o StrictHostKeyChecking=no'
];

if (\Deployer\get('ssh_type_native_mux')) {
$this->initMultiplexing();
$sshOptions = array_merge($sshOptions, $this->getMultiplexingSshOptions());
}

$username = $serverConfig->getUser() ? $serverConfig->getUser() : null;
if (!empty($username)) {
$username = $username . '@';
Expand Down Expand Up @@ -134,6 +142,11 @@ public function scpCopy($target, $target2)
$scpOptions[] = '-i ' . escapeshellarg($serverConfig->getPrivateKey());
}

if (\Deployer\get('ssh_type_native_mux')) {
$this->initMultiplexing();
$scpOptions = array_merge($scpOptions, $this->getMultiplexingSshOptions());
}

$scpCommand = 'scp ' . implode(' ', $scpOptions) . ' ' . escapeshellarg($target) . ' ' . escapeshellarg($target2);

$process = new Process($scpCommand);
Expand All @@ -152,4 +165,96 @@ public function getConfiguration()
{
return $this->configuration;
}

/**
* Return ssh multiplexing socket name
*
* When $connectionHash is longer than 104 chars we can get "SSH Error: unix_listener: too long for Unix domain socket".
* https://github.com/ansible/ansible/issues/11536
* So try to get as descriptive hash as possible.
* %C is creating hash out of connection attributes.
*
* @return string Ssh multiplexing socket name
*/
protected function getConnectionHash()
{
$serverConfig = $this->getConfiguration();
$connectionData = "{$serverConfig->getUser()}@{$serverConfig->getHost()}:{$serverConfig->getPort()}";
$tryLongestPossibleSocketName = 0;

$connectionHash = '';
do {
switch ($tryLongestPossibleSocketName) {
case 1:
$connectionHash = "~/.ssh/deployer_mux_" . $connectionData;
break;
case 2:
$connectionHash = "~/.ssh/deployer_mux_%C";
break;
case 3:
$connectionHash = "~/deployer_mux_$connectionData";
break;
case 4:
$connectionHash = "~/deployer_mux_%C";
break;
case 5:
$connectionHash = "~/mux_%C";
break;
case 6:
throw new \RuntimeException("The multiplexing socket name is too long. Socket name is:" . $connectionHash);
default:
$connectionHash = "~/.ssh/deployer_mux_$connectionData";
}
$tryLongestPossibleSocketName++;
} while (strlen($connectionHash) > self::UNIX_SOCKET_MAX_LENGTH);

return $connectionHash;
}


/**
* Return ssh options for multiplexing
*
* @return string[]
*/
protected function getMultiplexingSshOptions()
{
return [
'-o ControlMaster=auto',
'-o ControlPersist=5',
'-o ControlPath=\'' . $this->getConnectionHash() . '\'',
];
}


/**
* Init multiplexing with exec() command
*
* Background: Symfony Process hangs on creating multiplex connection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why symfony process does not working here?

Copy link
Contributor Author

@kszymukowicz kszymukowicz Dec 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sry but I could not debug the problem.
I can only describe you the behaviour. When ssh command was run with multiplexing options then using the Symfony Process whole task just hungs but multiplexing socket was created in background. If I canceled such hanged process with "ctrl+c" and run again then Symfony Process was using multiplexing socket and all was working well.

* but after mux is created with exec() then Symfony Process
* can work with it.
*/
public function initMultiplexing()
{
$serverConfig = $this->getConfiguration();
$username = $serverConfig->getUser() ? $serverConfig->getUser() . '@' : null;
$hostname = $serverConfig->getHost();

$sshOptions = [];
if ($serverConfig->getPort()) {
$sshOptions[] = '-p ' . escapeshellarg($serverConfig->getPort());
}
if ($serverConfig->getPrivateKey()) {
$sshOptions[] = '-i ' . escapeshellarg($serverConfig->getPrivateKey());
}
$sshOptions = array_merge($sshOptions, $this->getMultiplexingSshOptions());

exec('ssh ' . implode(' ', $sshOptions) . ' -O check -S ' . $this->getConnectionHash() . ' ' . escapeshellarg($username . $hostname) . ' 2>&1', $checkifMuxActive);
if (!preg_match('/Master running/', $checkifMuxActive[0])) {
if (\Deployer\isVerbose()) {
\Deployer\writeln(' SSH multiplexing initialization');
}
exec('ssh ' . implode(' ', $sshOptions) . ' ' . escapeshellarg($username . $hostname) . " 'echo \"SSH multiplexing initialization\"' ");
}
}
}