How to make SSH's `ControlPath` distinguish between IPv4 and IPv6?

I’m using

ControlMaster   auto
ControlPath     ~/.ssh/tmp/%l_%r@%h:%p

in my ~/.ssh/config to open multiple sessions using the same connection when accessing a host via SSH.

That works fine for most use cases, but doesn’t allow me to connect to the same hostname via IPv4 and IPv6 at the same time; all additional connections use the control socket created by the first connection because the ControlPath doesn’t allow to distinguish between IPv4 and IPv6 connections.

Is there a way to have separate control sockets for IPv4 and IPv6 connections (perhaps by finding a way to use the remote IP rather then the remote hostname in the socket path)?


EDIT #1

I just remembered the Match option available in ssh_config and sshd_config files and tried:

Match AddressFamily inet
    ControlPath     ~/.ssh/tmp/%l_%r@%h:%p.inet

Match AddressFamily inet6
    ControlPath     ~/.ssh/tmp/%l_%r@%h:%p.inet6

Unfortunately, that fails with “Unsupported Match attribute AddressFamily” when I try to connect to any host, so I’m back to square one…

Asked By: n.st

||

This is not typical use case so there is no direct way of doing so. But, you can workaround this using user configuration file as such, if you don’t require too many remote hosts:

Host hostname-4
    Hostname hostname
    AddressFamily inet
    ControlPath ~/.ssh/master4-%l%h%p%r
Host hostname-6
    Hostname hostname
    AddressFamily inet6
    ControlPath ~/.ssh/master6-%l%h%p%r
Answered By: Jakuje

I’ve split my answer into two separate answers so you can up/downvote the “wrapper” and “patch” approaches separately.

Solution 1: Write a wrapper function

ssh ()
{
    controlpath=""
    for argument in $@
    do
        if [[ "$argument" = "-"*"4"* ]]
        then
            controlpath="~/.ssh/tmp/%l_%r@%h:%p.inet"
        fi
        if [[ "$argument" = "-"*"6"* ]]
        then
            controlpath="~/.ssh/tmp/%l_%r@%h:%p.inet6"
        fi
    done

    if [ -n "$controlpath" ]
    then
        /usr/bin/ssh -o "ControlPath=$controlpath" $@
    else
        /usr/bin/ssh $@
    fi
}

This wrapper function will instruct ssh to create separate control sockets for ssh host, ssh -4 host, and ssh -6 host.
It might not parse something like ssh -464466 host correctly (even though that’s technically allowed), but it should be the easiest workaround for simple scenarios.

Answered By: n.st

I’ve split my answer into two separate answers so you can up/downvote the “wrapper” and “patch” approaches separately.

Solution 2: Use the source, Luke!

Like I mentioned in the update to my answer, OpenSSH doesn’t support Match AddressFamilyyet.
It’s fairly easy to add a corresponding clause to the Match parsing in readconf.c. My proof-of-concept patch worked, but involved a bunch of hardcoded strings, so it certainly needs some more work before I can submit it to the OpenSSH maintainers for consideration.

It might also be worth a look if we can’t just add another placeholder (like %a) for ControlPath definitions that would evaluate to any, inet or inet6, depending on the address family used.
That will however cause issues depending on the order in which the AddressFamily and ControlPath options are evaluated and stored, so it might require some fairly extensive changes to work.
The same problem applies to my original idea of expanding the placeholder to the actual address family used for the connection: The address family depends on a gethostbyname() call that isn’t even made when a suitable control socket exists.

Answered By: n.st
Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.