1

Why does OpenSSH_8.4p1 terminate other sessions that share the same connection when ProxyCommand is used? Is there some way to prevent this?

Note: This behavior does not seem to happen if the ProxyCommand argument is omitted.

Steps to reproduce:

  1. Kill any existing shared connections to localhost:
ssh -o ControlPath=/tmp/%C -O exit 127.0.0.1 2>/dev/null
ssh -o ControlPath=/tmp/%C -O exit localhost 2>/dev/null
  1. Run the following command twice in parallel each in a different terminal:
ssh -F none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
  -o ControlMaster=auto -o ControlPath=/tmp/%C -o ControlPersist=1d \
  -o ProxyCommand='ssh -W %h:%p 127.0.0.1' \
  localhost 'sleep 3600'
  1. Interrupt the first ssh process with SIGINT by typing control-C.

Expected behavior

  • Only the SIGINT'ed process terminates.
  • Other process continues to run unaffected.

Actual behavior

  • Both processes are terminated.

1 Answers1

0

Only the SIGINT'ed process terminates.

"Process" is a false premise. Ctrl+C sends SIGINT to the foreground process group of the terminal. A process group may include more than one process.

Then this is relevant:

ProxyCommand
Specifies the command to use to connect to the server. The command string extends to the end of the line, and is executed using the user's shell exec directive to avoid a lingering shell process.
[…]

(source)

The command is executed locally in the same process group as the main ssh. In your example the command is ssh … but in general it may be anything. In any case the command is not aware of ControlMaster and ControlPersist you used for the main ssh.

When you hit Ctrl+C, each process in the foreground process group gets SIGINT. The "main" ssh exits without affecting the socket in the ControlPath because in case of ControlPersist other than no this socket is from the start handled by yet another ssh process which is deliberately spawned in its own process group, this allows it to survive. In this context you may call it the truly main ssh.

The unexpected behavior is because the command specified with ProxyCommand gets spawned in the foreground process group and it does get SIGINT along with the ssh you want to interrupt. The command reacts to the signal as if it would normally do. In your case the command terminates upon SIGINT. And because the command was supposed to relay all data, the master connection (the truly main ssh) is now useless. It terminates along with all dependent ssh processes.

So the original ssh does additional work to ensure ssh handling the master connection (and the socket) survives Ctrl+C, but it doesn't do this for the command specified with ProxyCommand which is equally important. I think you can call it a bug.

It probably would be better if the command specified in ProxyCommand was spawned by the immune ssh in its process group. I haven't analyzed this thoroughly enough. Anyway, for now it's not the case, the command is spawned it the process group that is the foreground process group at the time you hit Ctrl+C, so it gets SIGINT.

A workaround is to make the command immune to SIGINT. Inside ProxyCommand you cannot use trap directly because ProxyCommand uses exec automatically and exec trap … makes no sense and won't work. You need yet another shell:

ssh -F none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
  -o ControlMaster=auto -o ControlPath=/tmp/%C -o ControlPersist=1d \
  -o ProxyCommand='sh -c "trap \"\" INT; exec ssh -W %h:%p 127.0.0.1"' \
  localhost 'sleep 3600'

My tests indicate that being immune to SIGINT will not prevent this inner ssh from exiting when the time comes and the master connection should terminate because of the finite ControlPersist setting.