[ 
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)

Reply via email to