/dev/posts/

OpenSSH ProxyUseFdPass

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

Update 2017-08-02: netcat (nc) has an option (nc.openbsd -F www.example.com 80) to pass the created file descriptor using the "fdpass" mechanism. In addition 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).

Update (2021-02-09): replaced fds.fromstring() with fds.frombytes().

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 an 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 an uncessary lingering process and extra write/reads when it is not necessary3.

The documentation does not explain how it is 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 this file descriptor to the OpenSSH client process through its (own) standard output (sendmsg with SCM_RIGHTS using a one-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

In its current form, it does not do 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:

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.frombytes(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 is 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 a SSH jump server but can be used in simpler cases.