OpenSSH ProxyUseFdPass

While looking at the OpenSSH ssh_config manpage, I found the ProxyUseFdpass configuration I did not know about. It's apparently not widely known or used.

Update 2017-08-02: netcat (nc) as an option to pass the created descriptor using fdpass. In additional to the straightforward connect(), it can pass a file descriptor having initiated a connection through a SOCKS proxy (with -x proxy.example.com) or with HTTP connect (-x proxy.example.com -X connect).

ProxyCommand

OpenSSH client has a ProxyCommand configuration which can be used to use a command as a transport to the server:

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.

Instead of opening a socket to the server itself, the OpenSSH client spawns the specified command and use its standard input and output to communicate with the server.

The man page suggests to use (the OpenBSD variant of) netcat to connect through a HTTP (or SOCKS) proxy:

ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p

A typical usage is to use a relay/bastion/jump/gateway1 SSH server with ssh -W2:

Host gateway.example.com
ProxyCommand none

Host *.example.com
ProxyCommand ssh gateway.example.com -W %h:%p

ProxyUseFdPass

While looking at the new ProxyJump configuration1, I found a ProxyUseFdpass option which:

Specifies that ProxyCommand will pass a connected file descriptor back to ssh(1) instead of continuing to execute and pass data. The default is “no”.

When enabled, instead of communicating with the server through the ProxyCommand standard input and output, the SSH client expects the command to give it a file descriptor to use. The idea is to avoid having a uncessary lingering process and extra write/reads when it is not necessary3.

The documentation does not explay how it's supposed to work exactly and I did not find any working example or any suggestion of a program which would be able to pass the file descriptor.

The spawned command is expected to:

  1. setup a file descriptor;

  2. send it to the client over its standard output (sendmsg with SCM_RIGHTS) with a on-byte message;

  3. exit(0).

A minimal program which does the job is:

#!/usr/bin/env python3

import sys
import socket
import array

# Create the file descriptor:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
s.connect((sys.argv[1], int(sys.argv[2])))

# Pass the file descriptor:
fds = array.array("i", [s.fileno()])
ancdata = [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)]
socket.socket(fileno = 1).sendmsg([b'\0'], ancdata)

Which can be used with:

ProxyCommand /path/to/passfd %h %p
ProxyUseFdpass yes

It does not much. It creates a socket the same way the OpenSSH client would have and pass it to the OpenSSH client. However, it can extended in order to do things such as:

  • use a custom way to resolve the server address;

  • bind the socket to a device;

  • set some socket options (SO_DONTROUTE, etc.);

  • open up firewalling rules before connecting to the server;

  • connect to the server over UNIX socket, SCTP, etc.;

  • connect to the server over HTTP proxy, SOCKS proxy, TCPMUX, etc.;

  • choosing different connection strategies depending on the current network environment;

  • etc.

For testing purpose this receiving program can be used:

#!/usr/bin/env python3

import os
import sys
import socket
import array

(a, b) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
pid = os.fork()

def recv_fd(sock):
    fds = array.array("i")
    cmsg_len = socket.CMSG_LEN(fds.itemsize)
    msg, ancdata, flags, addr = sock.recvmsg(1, cmsg_len)
    for cmsg_level, cmsg_type, cmsg_data in ancdata:
        if (cmsg_level, cmsg_type) == (socket.SOL_SOCKET, socket.SCM_RIGHTS):
            fds.fromstring(cmsg_data)
            return fds[0]
    sys.exit(1)

if pid == 0:
    # Exec specified command in the child:
    a.close();
    os.dup2(b.fileno(), 0)
    os.dup2(b.fileno(), 1)
    b.close()
    os.execvp(sys.argv[1], sys.argv[1:])
else:
    # Receive file descriptor and wait in the parent:
    b.close();
    s = recv_fd(a)
    os.waitpid(pid, 0)
    print(s)

Which can be used as:

fdrecv fdpass localhost 80

  1. OpenSSH 7.3 includes special support for SSH jump servers with the ProxyJump configuration and -J flag. 

  2. It's often suggested to use this configuration instead:

    ProxyCommand ssh gateway.example.com nc %h %p
    

    This requires netcat to be available on the server. ssh -W only needs client-side support which is available in OpenSSH since 5.4 (released in 2010) and the SSH server to accept TCP forwarding. 

  3. It is not usable for an SSH jump server but can be used in simpler cases.