A minimal getty for embedded Linux that auto-detects the client and dispatches incoming serial connections to a login prompt, a PPP session, or the local SSH server as appropriate.
Description
ugetty replaces a conventional getty on a serial TTY. Rather than
immediately prompting for a username, it watches the first bytes from the
remote end and routes accordingly:
- PPP (RFC 1662 LCP frame) -> exec
pppd - SSH (
SSH-2.0-identification string) -> proxy the connection to the localsshd - Anything else -> display
/etc/issue, prompt for username, execlogin
While waiting for a connection, ugetty actively probes for an attached
terminal by periodically sending cursor position requests. A real terminal
will respond with a cursor position report, which ugetty silently discards
before proceeding. This means the port starts responding promptly rather than
waiting for the remote end to send the first byte.
This allows a single serial port on a headless device to serve as a universal out-of-band access channel. Plug in a USB-serial adapter or connect via a hardware UART, and whatever you send just works.
proxycommand is a companion binary intended to be used as an SSH
ProxyCommand on the connecting host. It handles the serial connection and
the banner detection handshake with ugetty.
Getting Started
Dependencies
ugetty (target device):
- Any Linux system with a vaguely modern
gccandmake. Building withclangshould also work, but has not been tested. - For the default ARM cross-compiled build: a musl-cross toolchain in
/dev/shm/armv6-linux-musleabihf-cross/. Pre-built tool chains are available from various sources, including musl.cc.
proxycommand (connecting host):
- Any Linux system with
gccandmake, as withugetty
Building
git clone https://github.com/ryancdotorg/ugetty.git
cd ugetty
Native build (both binaries, useful for testing proxycommand locally):
make bin/ugetty bin/proxycommand
Cross-compiled, statically linked ARM build of ugetty plus a native
proxycommand:
Or with an explicit triple:
CROSS_TRIPLE=armv6-linux-musleabihf make cross/$CROSS_TRIPLE/bin/ugetty-stripped bin/proxycommand
Usage
ugetty
Usage: ugetty [OPTIONS] BAUD_RATE TTY [TERMTYPE]
Open TTY, look for terminal, PPP, or SSH, then prompt for username and
invoke `login`, `pppd`, or an SSH connection as appropriate.
-h Enable hardware flow control
-L Ignore carrier detect state
-n Do not prompt for username
-w Wait for CR or LF before prompting for username
-i Don't display issue file
-t SEC Timeout for username prompt
-f ISSUE_FILE Alternate issue file
-l LOGIN Alternate `login` binary
-P [PPPD] Enable PPP support. Optionally specify `pppd` binary.
-S [PORT] Enable SSH proxy support. Optionally specify port.
BAUD_RATE of "0" leaves it unchanged
The TTY argument can be a device name (e.g. ttyUSB0) or the special value
auto, which determines the tty name based on the file descriptor passed by
init or systemd. Note that ugetty will not open a TTY device itself,
it must inherit one on STDIO. If the TTY argument does not match STDIO,
ugetty will fail to start.
Arguments after -- are passed directly to pppd as its argument vector,
bypassing the peer file lookup and overriding the built-in defaults. Note
that -P must be specified for -- to be valid.
The command line arguments for ugetty have been chosen to match (and not
conflict with) those of common getty software to the extent feasible.
Explicit non-features (do not ask me to add them):
- Autobaud (janky)
- Autologin (footgun)
- Data/parity/stop bit detection (y u no use 8n1‽)
- Modem support (where did you even get a pots line?)
- Uppercase terminal detection/support (ykinmkbykiok)
proxycommand
proxycommand /dev/ttyXXX BAUD_RATE # connect via serial port
proxycommand HOST PORT # connect via TCP
Intended to be called by ssh as a ProxyCommand, not run directly. It will
auto-detect whether ProxyUseFdpass is enabled and operate accordingly.
You can set up ssh to use it automatically by adding something like this to
~/.ssh/config:
Match host tty* exec "test -c /dev/%n -a -w /dev/%n"
ControlMaster auto
ControlPath ${XDG_RUNTIME_DIR}/ssh/%n-%C.sock
ProxyUseFdpass yes
ProxyCommand /path/to/proxycommand /dev/%n 115200
You need to use %n rather than %h because the %h is normalized to
lowercase, whereas %n is not. The ControlMaster and ControlPath settings
are needed for multiplexing if you want to be able to have multiple connections
to the device.
Examples
Interactive login over a USB serial adapter
On the device with systemd, create /etc/systemd/system/serial-getty@ttyUSB0.service.d/override.conf:
[Service] ExecStart= ExecStart=-/sbin/ugetty 115200 %I
On the connecting host:
To also support PPP (with explicit IPv4 addresses), use /etc/inittab with busybox init:
ttyUSB0::respawn:/sbin/ugetty -L -P 115200 ttyUSB0 -- local noauth nodetach 192.168.100.1:192.168.100.2
SSH via serial
On the device, ensure ugetty is started with -S:
ttyUSB0::respawn:/sbin/ugetty -S 115200 ttyUSB0
On the connecting host:
ssh -o ProxyCommand='path/to/proxycommand /dev/%n 115200' user@ttyUSB0
SSH via a TCP serial console server
If ugetty is started with -w -S, it stays silent until the client sends the
first byte, which means an unmodified ssh client can connect directly to the
TCP serial server. ugetty will detect the SSH identification string and proxy
it automatically. Without -w, the active terminal probes would interfere with
the SSH handshake.
PPP networking over serial
When -P is given and a PPP LCP frame is detected, ugetty selects pppd
arguments in the following order:
- Explicit arguments following
--(bypasses peer file lookup entirely) /etc/ppp/peers/ugetty.TTYNAME(e.g.ugetty.ttyUSB0) — port-specific config/etc/ppp/peers/ugetty— shared config for all ports- Built-in defaults:
local noauth nodetach noip noipdefault +ipv6 ipv6cp-use-persistent
The built-in defaults bring up an IPv6 link-local PPP session without negotiating IPv4.
For the default IPv6 link-local-only setup, start ugetty with -P and run
something like this on the connecting host:
sudo pppd /dev/ttyUSB0 115200 local noauth nodetach
ugetty will detect the LCP frame and hand the port directly to pppd on the
device. If you want IPv4 on the device side, set up a peer file or pass an
explicit pppd argument list after -- when starting ugetty, as in the
earlier /etc/inittab example (which uses -P ... -- ...).
Restricting SSH access per serial port in sshd_config
When ugetty proxies an SSH connection it binds the source address to
127.<major & 0xFF>.<minor & 0xFF>.<hash(name) % 253 + 2>, which is stable
for a given device node. This lets sshd apply per-port policy:
# /etc/ssh/sshd_config
# Must listen on 0.0.0.0 for ugetty's proxy connections to reach it.
ListenAddress 0.0.0.0
# Allow only key-based auth for connections via USB ACM serial (major 166).
Match Address 127.166.*.*
PasswordAuthentication no
# Completely block SSH-over-serial on a specific port (ttyUSB0, major 188, minor 0).
Match Address 127.188.0.*
DenyUsers *
To find the source address for a given tty:
ls -la /dev/ttyUSB0 # shows major, minor
# or check /var/log/auth.log after connecting
Help
You can file an issue on GitHub, however I may not respond. This software is being provided without warranty in the hopes that it may be useful.
Author
Ryan Castellucci (@ryancdotorg)
Donations
I am currently involved in a protracted civil rights case against the British government. If you find my work useful, please donate to my crowdfunding effort.
License
Your choice of 0BSD, MIT-0, or CC0-1.0+. Do what you want.