Skip to content

GUACAMOLE-2301: Add serial console protocol - #695

Merged
necouchman merged 7 commits into
apache:mainfrom
ciroiriarte:feature/serial-protocol
Nov 2, 2026
Merged

necouchman merged 7 commits into
apache:mainfrom
ciroiriarte:feature/serial-protocol

Conversation

@ciroiriarte

@ciroiriarte ciroiriarte commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a new serial console protocol to guacd, implementing GUACAMOLE-2301. It brings serial-line consoles — network switches/routers, PDUs, out-of-band management ports, and VM virtual serial ports — into the browser using the existing guac_terminal, following the structure of the SSH and telnet protocols.

Capabilities

  • Local mode — guacd opens a local device node directly (e.g. /dev/ttyUSB0), guarded by an allowed-devices allowlist.
  • Network mode — reach a remote serial line over TCP (e.g. via ser2net), with the framing selected by network-protocol:
    • raw — transparent TCP byte pipe
    • rfc2217 — Telnet COM-Port-Option; negotiates baud/parity/etc. and can deliver a Send Break
    • telnet — base RFC 854 Telnet framing (e.g. QEMU telnet:, VMware ESXi telnet://)
  • Reverse (listen) mode — with reverse-connect=true, guacd binds a local port and accepts an inbound connection from the device instead of dialing out (QEMU/VMware virtual serial ports, dial-out console servers). Parameters: reverse-connect, bind-address (default 127.0.0.1), listen-timeout.
  • Serial line settings (baud rate, data/stop bits, parity, flow control); Send Break and DTR/RTS control lines from the browser; automatic reconnection; configurable line ending; local echo; paste pacing; and hangup-on-close.

RFC2217/telnet support is gated on libtelnet at build time and degrades gracefully to raw when it is unavailable.

Testing

Validated end-to-end through the web client against a real Juniper switch console and a QEMU/KVM virtual serial console — local, raw, rfc2217, telnet, and reverse mode — including bidirectional input and reconnection. Full QA evidence (screenshots, typescripts, guacd logs) is attached to GUACAMOLE-2301.

Coordinated PRs

This is one of three coordinated PRs:

@necouchman necouchman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciroiriarte I've found a few things that I think could use changing - mainly cosmetic/style/documentation. Other than that it looks pretty good to me.

Comment thread configure.ac Resolved
Comment thread configure.ac Resolved
Comment thread src/protocols/serial/pipe.c Resolved
Comment thread src/protocols/serial/pipe.c Resolved
Comment thread src/protocols/serial/rfc2217.c Resolved
Comment thread src/protocols/serial/settings.h Resolved
Comment thread src/protocols/serial/settings.h Resolved
Comment thread src/protocols/serial/local.c Resolved
Comment thread src/protocols/serial/stream.h Resolved
Comment thread src/protocols/serial/tcp.c Resolved
@ciroiriarte

Copy link
Copy Markdown
Contributor Author

@ciroiriarte I've found a few things that I think could use changing - mainly cosmetic/style/documentation. Other than that it looks pretty good to me.

I appreciate you taking the time to review this. Will look into it.

@ciroiriarte

Copy link
Copy Markdown
Contributor Author

Pushed 8c12b4b addressing the review: style/docs fixes, the libtelnet define cleanup, configure.ac now reuses the telnet check, transport-specific argument parsing, allowed-devices removed (to be revisited as a guacd-level setting), and the unused GUAC_SERIAL_DEFAULT_PORT removed. Rebuilt the plugin from a clean tree with -Werror -Wall, both with libtelnet and with --without-telnet.

@necouchman necouchman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A handful of style changes, and I think it's close.

That said, it's failing build, now, I think because of some recent changes to how process and thread titles are handled (see #723). I think you'll need to rework with those changes to fix that.

Comment thread src/protocols/serial/local.h Resolved
Comment thread src/protocols/serial/rfc2217.h Resolved
Comment thread src/protocols/serial/tcp.h Resolved
Comment thread src/protocols/serial/settings.c Resolved
…w TCP, RFC2217)

Adds a new "serial" terminal protocol plugin for browser access to serial
consoles (switch/router/PDU administration). Three transports select via the
serial-type/network-protocol parameters: a local tty device configured with
termios, raw TCP to a ser2net endpoint, and RFC2217 (telnet COM-PORT-OPTION,
compiled only when libtelnet is available via ENABLE_SERIAL_RFC2217).

Supports configurable send-break (break-duration) through a client-agnostic
serial-control pipe, paste pacing (paste-delay), and a device-path allowlist,
reusing the shared terminal for recording/typescript/clipboard/color-scheme,
mirroring the telnet/ipmi plugin structure.
…, and diagnostics support

Implements the reviewed serial console feature gaps on top of the initial
serial protocol plugin:

- Auto-reconnect on drop (new "auto-reconnect", default true): the terminal and
  scrollback are kept alive across reconnects; a single stream object persists
  for the session and its fd/telnet context is swapped under write_lock by
  guac_serial_stream_reopen(), so the input thread never touches a stale fd.
- Line-ending translation (new "line-ending": cr|lf|crlf, default cr) applied to
  typed Enter and pasted text via a small cross-buffer state machine.
- Distinct contention/error logging: EBUSY/EACCES/EPERM and TIOCEXCL-busy on
  local devices, ECONNREFUSED/ECONNRESET on network transports.
- Local echo (new "local-echo", default false).
- Structured connect/disconnect reason logging (EOF, EIO, reset, hangup).
- Hangup-on-close (new "hangup-on-close" -> HUPCL) and DTR/RTS control-line
  toggles via the serial-control pipe (dtr-on/off, rts-on/off), using
  TIOCMBIS/TIOCMBIC locally and RFC2217 SET-CONTROL (DTR 8/9, RTS 11/12).
- Connect status banner with an "8N1"-style summary and press-Enter hint.
- Interruptible paste-delay pacing so a long paste cannot stall teardown.

The device allowlist is retained in settings and re-validated (re-realpath) on
every local (re)open.
Add a reverse/listen mode for network serial connections: instead of
dialing out to a gateway, guacd binds a local TCP port and accepts a
single inbound connection from the device (e.g. a QEMU/KVM or VMware
virtual serial port, or a console server configured to dial out).

Network fd acquisition is factored into guac_serial_net_open_fd(), used
by both the raw and RFC2217 backends, which either dials out or, when
reverse-connect is set, binds/listens/accepts. The accepted socket feeds
the existing raw/RFC2217 framing unchanged, and the auto-reconnect path
re-listens for the next inbound connection on drop.

New parameters: reverse-connect, bind-address (default 127.0.0.1),
listen-timeout (default 60s). In reverse mode hostname is optional.
Add network-protocol=telnet: base RFC854 Telnet framing (IAC escaping,
BINARY/SGA/ECHO negotiation) via libtelnet, without the RFC2217 COM-PORT
option. Serial line settings are informational, as with raw. Intended for
endpoints that speak plain Telnet rather than RFC2217 -- QEMU/KVM telnet:
serial ports, VMware ESXi telnet:// virtual serial ports, and telnet-mode
console servers.

Reuses the existing libtelnet event handler and send/recv/free, and
acquires its socket through guac_serial_net_open_fd(), so it works in both
forward and reverse mode. A Send Break is delivered as the Telnet BRK
command. Gated on libtelnet (ENABLE_SERIAL_RFC2217) with no-op stubs when
libtelnet is absent.
Style and documentation:
- Put each switch case on its own lines and expand one-line conditionals.
- Complete function documentation, document every parity and flow-control
  enum member, and note the default of each connection argument.
- Correct docs that omitted the Telnet transport or claimed that open
  failures abort the client; document the dtr/rts control-pipe commands.
- Convert the open() and connect() errno chains to switch statements.

libtelnet / build:
- Drop the TELNET_BREAK fallback (libtelnet defines it) and rename the
  COM-PORT-OPTION number to GUAC_SERIAL_TELOPT_COMPORT, since libtelnet
  does not define option 44.
- Define ENABLE_SERIAL_RFC2217 from the existing ENABLE_TELNET conditional
  rather than a separate one, report build_telnet in the configure
  summary, and align the summary columns.

Settings:
- Parse transport-specific arguments only for the transport in use.
- Remove the per-connection allowed-devices parameter: whoever can set
  "device" can also set the allowlist, so it provides no protection. A
  guacd-level allowlist can be proposed separately.
- Remove the unused GUAC_SERIAL_DEFAULT_PORT.
GUACAMOLE-2283 removed guacamole/proctitle.h from libguac. Threads are now
created with guac_thread_create(), which names them, and the endpoint shown
in guacd's process title is reported through guac_client_set_info(). Local
devices and reverse-mode listeners are reported as the resource name, and
outbound network connections as hostname and port.

guac_thread tracks whether it was started, so the separate
client_thread_valid flag is no longer needed.

Also fixes early line breaks in the open() documentation of local.h,
rfc2217.h and tcp.h.
@ciroiriarte
ciroiriarte force-pushed the feature/serial-protocol branch from 8c12b4b to 5e0e378 Compare October 31, 2026 22:49
@ciroiriarte

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and pushed 5e0e378, which adapts serial to the GUACAMOLE-2283 changes from #723:

  • Threads are started with guac_thread_create() (serial-client, serial-stdin) and joined with guac_thread_join(). The separate client_thread_valid flag is gone because guac_thread already tracks whether it was started.
  • The process title is set through guac_client_set_info(). A local device or reverse-mode listener goes in resource_name (guacd(serial) /dev/ttyUSB0, guacd(serial) listen:2323). An outbound network connection uses hostname and port.
  • GUAC_SERIAL_PROCESS_TITLE_NAME is removed.

The three early line breaks are fixed in the same commit. The plugin builds with -Werror -Wall against the rebased libguac, both with libtelnet and with --without-telnet.

@necouchman

Copy link
Copy Markdown
Contributor

Rebased onto current main and pushed 5e0e378, which adapts serial to the GUACAMOLE-2283 changes from #723:

* Threads are started with `guac_thread_create()` (`serial-client`, `serial-stdin`) and joined with `guac_thread_join()`. The separate `client_thread_valid` flag is gone because `guac_thread` already tracks whether it was started.

* The process title is set through `guac_client_set_info()`. A local device or reverse-mode listener goes in `resource_name` (`guacd(serial) /dev/ttyUSB0`, `guacd(serial) listen:2323`). An outbound network connection uses `hostname` and `port`.

* `GUAC_SERIAL_PROCESS_TITLE_NAME` is removed.

The three early line breaks are fixed in the same commit. The plugin builds with -Werror -Wall against the rebased libguac, both with libtelnet and with --without-telnet.

Great. I'll take one more pass through it, hopefully tomorrow, and see if there's anything else to be fixed.

@necouchman necouchman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more things that might need to be updated. After these items I think it'll be ready to go.

Comment thread src/protocols/serial/Makefile.am Resolved
Comment thread src/protocols/serial/input.c Resolved
Comment thread src/protocols/serial/pipe.c Resolved
Comment thread src/protocols/serial/rfc2217.c Resolved
- Include config.h automatically via AM_CPPFLAGS, as the other protocol
  plugins do, and drop the explicit includes.
- Define the Pause, Break and Ctrl+0 keysyms that send a serial break as
  named constants.
- Replace the RFC2217 COM-PORT-OPTION command #defines with an enum, and
  name the SET-PARITY and SET-CONTROL values instead of using bare numbers.
- Document why control pipe tokens are limited to 64 bytes, and discard
  oversized tokens entirely. Previously the buffer was reset but the rest
  of the token kept accumulating, so the tail of an oversized token could
  be dispatched as a command.
@ciroiriarte

Copy link
Copy Markdown
Contributor Author

Pushed 1b7d730, which addresses the four items from the last review. It builds with -Wall -Werror with libtelnet and with --without-telnet.

@necouchman
necouchman merged commit 9feca19 into apache:main Nov 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants