Stream Control Transfer Protocol

For something different than TCP and UDP, try SCTP. This allows a selection of services, ultimately SCTP is interesting to do SIGTRAN such as for ISDN and SS7, but start with something familiar:

I decide to let it transport SSH at first. systemd and opensshonly have partial support for SCTP but it is enough to augment with some helpers.

SCTP has the ability to use multiple IP addresses, such as to support ipv6 and ipv4, and do failover between multiple links. Listening on :: or 0.0.0.0 results in connecting peers being able to enumerate all ip addresses on the system, this is not desirable when there is a combination of public and private addresses, so have it listen on a single primary address, this needs to be ipv6 to support both families. using ipv4 results in an ipv4 only socket. This is not what multiple ListenStream= entries in a socket unit does however, that creates independent fd, much like a convenience over having multiple socket units, if not needing to use BindToDevice= for example, then use bindx to add additional addresses to each file descriptor.

systemd as of 2026 did not have inbuilt support for sctp_bindx, or to correctly set the payload protocol for SSH to 45, so we use a helper app. This is passed the newly created sctp fd's and scans them for the first bound address and then do bindx and payload setting on them. Then systemd waits for activity on any of them before launching openssh server with all listening descriptors.

my helper implementation takes arguments of a comma separated first address to look for, because systemd by design does not guarantee ListenStream= to file descriptor ordering, then payload, then supplimentary addresses to bindx, elements separated with _ consisting of family, port, address text and optional interface name used especially for vrf names.

Firstly, after changing to use socket activation for ssh on tcp/ip, enable service of SSH over SCTP via a new systemd.socket unit:

Typically placed as /etc/systemd/system/ssh-sctp-example.socket; when activated or restarted after edits, ssh.service has to be stopped temporarily. This was found not to cut open ssh sessions but always prudent to have alternate means of access af least initially. In this example we have two independent SCTP sockets, one external in 2001:db8 to bundle with addresses in 192.0.2.0 and an internal in fec0:0:0 to bundle with 192.168.40.40 addresses.

[Unit]
Description=SSH on sctp

[Socket]
SocketProtocol=SCTP
ListenStream=[2001:db8:d0be:f00d:aede:48ff:fe23:4567]:22
ListenStream=[fec0:0:0:d0be:f00d:aede:48ff:fe23:4567]:22
FreeBind=true
PassFileDescriptorsToExec=true
ExecStartPost=/usr/local/sbin/bindx \
2001:db8:d0be:f00d:aede:48ff:fe23:4567,45,6_22_2002:c000:0201:f00d:aede:48ff:fe23:4567,4_22_192.0.2.1 \
fec0:0:0:d0be:f00d:aede:48ff:fe23:4567,45,4_22_192.168.40.40 \
Service=ssh.service

[Install]
WantedBy=sockets.target

If the systemd is too old for PassFileDescriptorsToExec=true then a bindx.service can be defined and also add After=bindx.service and Requires=bindx.service to the ssh.service customisation drop-in as a workaround, although this does delay config until there is socket activity unless it is set to start:

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/bindx fec0:0:0:d0be:f00d:aede:48ff:fe23:4567,45,4_22_192.168.40.40
Sockets=ssh.socket ssh-sctp.socket

Connection from OpenSSH

OpenSSH can use SCTP but coul not create them, socat can make SCTP sockets and apply setsockopts to enable features. For maximum efficiency, pass the file descripter from an EXEC: back to openssh. This needs a helper compiling, using the sendfd function from seccomp_unotify

echo "$(zcat /usr/share/man/man2/seccomp_unotify.2.gz | \
tail -n +1396 | head -n 83 | \
sed 's_\\&__g;s_\\__g;'); int main(void) { sendfd(3,0); return 0; }" | \
gcc -x c -o /tmp/sendfd /dev/stdin

Then make ssh_config definitions to make sctp calls. the setsockopt=132:34: sets the payload to 45, where 2d is that in hexadecimal.

Host sse
   ProxyCommand /bin/sh -c 'exec 3>&1; socat SCTP:example:22,setsockopt=132:34:x000000000000002d0000000000000002 EXEC:"/tmp/sendfd",nofork'
   ProxyUseFdpass yes
   HostName example
   GSSAPIServerIdentity example
   ForwardAgent yes
   ForwardX11 yes