[
https://issues.apache.org/jira/browse/GUACAMOLE-2307?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Nick Couchman updated GUACAMOLE-2307:
-------------------------------------
Priority: Minor (was: Major)
> Add opt-in raw text-output mode for terminal protocols (SSH/telnet/Kubernetes)
> ------------------------------------------------------------------------------
>
> Key: GUACAMOLE-2307
> URL: https://issues.apache.org/jira/browse/GUACAMOLE-2307
> Project: Guacamole
> Issue Type: New Feature
> Components: guacamole-server
> Reporter: Ciro Iriarte
> Priority: Minor
>
> h2. Summary
> Add an opt-in per-connection parameter, {{text-output}}, that makes guacd tee
> the
> raw remote PTY byte stream (including ANSI/CSI/OSC sequences) to an outbound
> Guacamole {{pipe}} stream named {{STDOUT}}, for the SSH, telnet, and
> Kubernetes
> protocols. This lets a non-graphical / native CLI client present a terminal
> session
> as true in-terminal text instead of decoding rasterized glyphs. Default off;
> no
> change to existing connections.
> h2. Motivation
> The browser terminal renders output graphically: guacd runs the emulator and
> emits
> only drawing instructions, so no character codepoints reach the client. A
> native or
> CLI client therefore cannot reconstruct the text of a session. This adds a
> faithful
> raw byte channel in the output direction, complementing the inbound STDIN pipe
> (GUACAMOLE-574). It deliberately does *not* reuse the {{guacctl}} pipe
> redirect
> (GUACAMOLE-17): that path runs through {{guac_terminal_echo()}}, which strips
> ESC/CSI/OSC sequences and so cannot carry a faithful raw stream.
> h2. Design
> * New connection parameter {{text-output}} (string enum):
> ** {{true}} — tee mode: raw bytes are teed to STDOUT *and* the graphical
> display is
> still rendered, so browser users are unaffected.
> ** {{raw}} — headless mode: the graphical terminal is not rendered at all;
> bytes are
> delivered only via STDOUT, eliminating the graphical instruction stream
> and its
> render cost.
> ** anything else, including {{false}} or omitted — disabled.
> * guacd opens an outbound {{pipe}} stream named {{STDOUT}}, mimetype
> {{application/octet-stream}} (advisory; payload is raw bytes carried in
> base64
> {{blob}} instructions, so it is binary-safe and charset-agnostic — the
> client
> decodes the bytes itself).
> * The tee is taken *at the protocol source* — in each protocol's PTY read
> path,
> upstream of the terminal emulator — via a dedicated {{text_output_stream}}
> on
> {{guac_terminal}}, independent of the {{guacctl}} pipe machinery.
> * The stream is allocated on the connection *owner's* user socket
> ({{guac_user_alloc_stream}}), not broadcast to every user, and uses an even
> (ack-routable) stream index so client {{ack}} instructions return to the
> stream.
> * New additive public API in {{terminal.h}}:
> {{guac_terminal_text_output_open/write/flush/close}} (+ a {{should_open}}
> helper).
> No existing symbol is changed.
> * In {{raw}} mode the read loop skips {{guac_terminal_write()}} (the terminal
> is kept
> for input/size/stdin) and flushes per-read, since there is no graphical
> frame cycle.
> h2. Flow control / backpressure
> This is the subtle part and is worth reviewer attention:
> * Clients must {{ack}} every {{blob}}, on receipt rather than after rendering.
> * guacd bounds the unacknowledged backlog by *bytes*: 256 KB
> ({{GUAC_TERMINAL_TEXT_OUTPUT_MAX_INFLIGHT_BYTES}}), plus a 256-blob cap
> purely to
> bound a small size-tracking FIFO. The byte bound is the operative one,
> because
> {{raw}} mode emits one blob per PTY read and those blobs are often only a
> few bytes.
> * On a stalled consumer the response differs by mode, deliberately:
> ** {{tee}}: buffered output is *dropped* and the session continues. The tee
> shares the
> protocol read loop with the graphical display, so blocking on a stalled
> text
> consumer would also stall any co-attached browser user.
> ** {{raw}}: the read loop is *throttled* — the writer waits (on a condition
> variable)
> for the window to drain, which propagates backpressure to the remote
> program
> through the PTY exactly as a slow local terminal would. Raw renders nothing
> graphically, so pausing starves no one and the byte stream stays intact.
> * A consumer that stops acking entirely for 15 s
> ({{GUAC_TERMINAL_TEXT_OUTPUT_STALL_TIMEOUT}}) is disconnected with
> {{SERVER_ERROR}}.
> The wait also short-circuits immediately once the connection is going away,
> so an
> abrupt disconnect does not delay teardown.
> h2. Security
> * {{text-output}} is effectively a copy/export channel, so it is gated behind
> {{disable-copy}}: guacd refuses to open the pipe when copying from the
> terminal is
> disabled (and logs a warning).
> * Pipe contents are not logged or recorded by default (output may echo
> secrets).
> * Intended trust model: the parameter is admin-provisioned on the connection
> (not
> client-requestable); guacd is reached only over the authenticated
> tunnel/gateway.
> No new port, no new protocol instruction (reuses
> {{pipe}}/{{blob}}/{{end}}/{{ack}}),
> no authentication change.
> h2. Backwards compatibility / ABI
> Additive and opt-in, default off. No new protocol instruction, no DB schema
> change,
> and no web-app change is required — an unmodified guacamole-client already
> passes the
> parameter through, since {{ConfiguredGuacamoleSocket}} builds the {{connect}}
> instruction from the argument list guacd announces in its handshake. No ABI
> break:
> only additive public symbols are introduced.
> h2. Validation
> * CUnit unit tests for terminal text-output: disable-copy gating,
> owner-stream + ack
> routing, tee drop-on-stall, raw throttle-vs-abort, the byte bound, ACK
> retiring the
> oldest blob's byte count, surplus-ACK safety, disconnect short-circuit, and
> closed-stream-unroutable. Full {{make check}} is green on Debian 12 and
> openSUSE
> Leap 16.
> * An end-to-end harness (direct-to-guacd, no web app) covers
> SSH/telnet/Kubernetes
> across 13 scenarios: positive tee, negative (default-off and disable-copy
> -> no
> pipe), a byte-bound flood with and without acks, small-unacked-not-dropped,
> and
> raw (text delivered, graphics suppressed). All pass.
> * Validated live through an unmodified guacamole-client gateway with a native
> CLI
> client, in both tee and raw modes, including a 20,000-line flood and the 15
> s
> give-up path (measured: 256 KB delivered, then a clean SERVER_ERROR abort).
> h2. Patch / branch
> Implemented on a fork branch, 23 commits atop apache/main (base 6719b20d). A
> redistributable {{git format-patch}} artifact is available and applies
> cleanly onto
> that base. Happy to open a PR against apache/main. The client side is UI-only
> convenience (protocol JSON enum + i18n strings), and a matching manual section
> documents the parameter and the backpressure contract.
> h2. Related
> * GUACAMOLE-17 (resolved) — the original {{guacctl}} output-to-pipe redirect;
> routes
> through the escape-stripping echo path, so it is not usable for a faithful
> raw
> stream. This issue is the complement, taken at the source.
> * GUACAMOLE-574 — inbound STDIN pipe (the input direction).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)