Using gensio and ser2net

gensio and the related ser2net are generic tools for communication.

With gensio, you can set up a sort of pipeline of communication, using sockets, files, stdin/stdout, serial lines, external programs, and so forth. It is akin to a more powerful version of socat, netcat, redir, or stunnel.

Generally, with gensio and ser2net, you can:

  • Provide services that are started from inetd, init, or systemd
  • Convert an unpacketized link (such as a serial port) into a packetized interface
  • Convert an unreliable packetized link (such as UDP or a gensio-packetized serial interface) into a reliable link
  • Add encryption and authentication to any link

A communication pipeline is called a “gensio stack” and is given in a string format, separated by commas. A gensio stack is composed of one or more “gensios”, which will be of two different types:

  • A terminal gensio terminates the communication in some way: serial, stdio, TCP, whatever. It always is the rightmost gensio in the string. There is always exactly one terminal gensio in a stack.
  • Filter gensios are optional, and there can be 0 or more of them in a gensio stack. They adapt the communication in some way.

The word “gensio” can refer to both a component of the stack, or the multi-language gensio library itself. Built atop the gensio library are the ser2net daemon and gensiot command-line program.

Here’s an example of connecting to example.com with telnet:

telnet,tcp,example.com,23

Here, telnet is a filter gensio that adds RFC2217 telnet to the connection. tcp,example.com,23 is a terminal gensio that gives the connector (TCP) and the destination (example.com port 23).

Generally, you will connect gensio stacks to each other. The gensiot program (in the gensio-bin package on Debian) will, by default, use stdin/stdout (the stdio gensio) for one end, and let you specify the other. But, you can specify both.

ser2net is a daemon that is based on gensio. Its original purpose was to bridge serial ports to the network (particularly letting you telnet to a certain port and have it bridged to a serial device). However, it is really a multi-purpose gensio daemon and can do anything from terminating TLS to TCP port forwarding. The ser2net configuration file simply defines pairs of gensios; the accepter that listens, and the connector that gives the destination. You can install the ser2net Debian package to get it.

I will mostly demonstrate the gensiot program here, but these examples can be easily daemonized by using ser2net.

The gensio(5) manpage gives details on all the gensios. Because gensio supports TLS-based security and authentication, a gtlssh program can act as a replacement for the standard ssh, running atop gensio.

Let’s dive in!

Network basics

netcat replacement

These are some of the simplest examples. You can make a listening gensio (similar to netcat -l) like this:

gensiot -a tcp,12345

The -a says that we are running an accepter (listener). Now, we can open up another terminal and connect to it:

gensiot tcp,localhost,12345

You might be surprised that as you type on either end, you don’t see your characters echoed back to the screen as you would with netcat, but you do see them showing up in the other terminal. Also, unlike netcat, characters are sent immediately rather than after you hit enter. You also may note that Ctrl-C doesn’t terminate. That’s because, when gensiot detects stdio is a terminal, it puts the terminal in raw mode (using the stdio(raw,self) gensio). This is, after all, what you’d want if you are using it as a telnet client. You can exit by pressing Ctrl-\ and then hit q.

To get a more netcat-like behavior, use:

gensiot -i 'stdio(self)' tcp,localhost,12345

The -i gives the other (“input”) gensio, and in this case stdio(self) will leave it in a more standard mode. gensiot also uses stdio(self) when stdio is not a terminal.

So how about using it as a HTTP client? Here’s an example:

gensiot -i 'stdio(self)' tcp,example.com,http
GET / HTTP/1.1
Host: example.com
Connection: close

HTTP/1.1 200 OK
...

Running a TCP port forwarder using inetd.conf

Sometimes you want to forward a TCP port. For instance, I had a need to receive a connection on an IPv4 port and forward it out across IPv6 using Yggdrasil. The redir program in Debian is designed to do this, but sadly redir isn’t IPv6-aware. This could be accomplished using ser2net, but I happened to do it with gensio using inetd.conf.

Let’s say you want to receive a connection on port 12345 on your local machine, and want to forward it to port 23456 on example.com. Assuming you have installed tcpd and openbsd-inetd (or similar packages for other distributions), you can add this to /etc/inetd.conf:

12345  stream tcp nowait nobody  /usr/sbin/tcpd /usr/bin/gensiot tcp,example.com,23456

You can further add userspace access control with /etc/hosts.allow and hosts.deny as usual. Or, of course, firewall it.

The same could be done with ser2net with an acceptor of tcp,12345 and a connector of tcp,example.com,23456.

gensio supports TLS/SSL termination and authentication as well; see the manpages for more details. With that support, it can act as a replacement for TLS terminators such as stunnel4.

Serial port support

gensio has full support for using serial ports and other stream-like devices. By default, gensio monitors the serial port control likes such as DCD and therefore knows when the remote end connects and disconnects.

gensio can add a lightweight support for making serial communications reliable. This includes error detection, retransmission, and sliding window support. The basic gensio for this is:

relpkt,msgdelim,serialdev,/dev/ttyS0

replacing /dev/ttyS0 with whatever device you want to use, of course.

msgdelim provides packetization across a stream with a CRC16, but doesn’t provide support for retransmitting missing or corrupted packets. relpkt adds that. It is much lower overhead than PPP while still providing a reliable channel for something that needs it.

For very fast serial links, you may wish to raise the default packet size from 128 bytes with something like this:

relpkt(max_pktsize=4000),msgdelim(writebuf=4005,readbuf=4005),serialdev,/dev/ttyS0,115200n81

The relpkt packet size needs to be at least 5 bytes smaller than the msgdelim packet size.

Running real ssh over a serial port

Ordinarily, running ssh over a serial port can be problematic unless the serial line is exceptionally clean. With gensio, we can make it clean!

On the system to receive the ssh connection, let’s set up a proxy from serial to the ssh server running on port 22:

gensiot -i tcp,localhost,22 -a \
'conacc,relpkt(mode=server,max_pktsize=4000),msgdelim(writebuf=4005,readbuf=4005),serialdev,/dev/ttyS0,115200n81'

Here the -a flag says to listen on the serial port. conacc and mode=server are necessary to go along with that. OK, so how do we connect to it? Add this to ~/.ssh/config:

Host gensio
  ProxyCommand gensiot
  'relpkt(max_pktsize=4000),msgdelim(writebuf=4005,readbuf=4005),serialdev,/dev/ttyS1,115200n81'

Now you can ssh gensio and connect to your remote machine via ssh! This only supports one ssh session. However, if you use ssh’s handy connection muxing, you can run a bunch of ssh sessions across this one link. Just add this to the Host gensio part:

  ControlMaster auto
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlPersist 5m

Reliable program over a serial port

It is easy enough to run a serial getty on a serial port, but that’s not a reliable link; it is susceptible to errors and corruption.

But you can do it easily enough. NOTE: there are security implications for what I’m about to show; I don’t recommend running an unauthenticated shell in this way!

On one end, run:

gensiot -i pty,bash -a 'conacc,relpkt(mode=server),msgdelim,/dev/ttyUSB2,115200n81'

And to connect to it, run:

gensiot relpkt,msgdelim,/dev/ttyUSB3,115200n81

Instant shell. Again, don’t just use this without adding some security. It would be better to run ssh or gtlssh over the port.

You could easily enough combine this with a telnet to localhost if you wanted a login.

NNCP over serial

While normally running NNCP over a serial port isn’t great because nncp-call(er) and nncp-daemon require a reliable and clean connection, we can easily run them over gensio.

On one machine, you can run nncp-daemon like this:

gensiot -i 'stdio,nncp-daemon -ucspi' -a \
'conacc,relpkt(mode=server,max_pktsize=4000),msgdelim(writebuf=4005,readbuf=4005),serialdev,/dev/ttyUSB2,115200n81'

On another, find the addrs section for the target in nncp.hjson, and add:

gensio: "|gensiot 'relpkt(max_pktsize=4000),msgdelim(writebuf=4005,readbuf=4005),serialdev,/dev/ttyS1,115200n81'"

Additional information on these kinds of uses of NNCP can be found at Tunneling NNCP over other transports.

Further Reading


Sometimes we want better-than-firewall security for things. For instance:

NNCP has built-in support for running over TCP, with nncp-daemon and nncp-call/caller. NNCP’s own use cases page talks about various use cases for NNCP. Some of them, such as the no link page, cover use of nncp-xfer; others, such as the one-way broadcasting page go over nncp-bundle.

Old technology is any tech that’s, well… old.