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
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
Host gateway.example.com ProxyCommand none Host *.example.com ProxyCommand ssh gateway.example.com -W %h:%p
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:
- setup a file descriptor;
- send this file descriptor to the OpenSSH client process through its (own) standard output (
SCM_RIGHTSusing a one-byte message;
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, int(sys.argv))) # 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:
- use a custom way to resolve the server address;
- bind the socket to a network device;
- set some socket options (
- 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.;
- choose different connection strategies depending on the current network environment;
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 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, 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
It is often suggested to use this configuration instead:
ProxyCommand ssh gateway.example.com nc %h %p
It is not usable for a SSH jump server but can be used in simpler cases. ↩