GUACAMOLE-2281: Add generic req/done/cancel instructions to libguac.#675
GUACAMOLE-2281: Add generic req/done/cancel instructions to libguac.#675aleitner wants to merge 1 commit into
Conversation
e5c7912 to
4eca0a8
Compare
|
Maybe add a UT for the new framework? Similar to |
|
Thinking through shared sessions: since send_req() broadcasts to all connected users, we would receive multiple "done" responses for a single request. Could we follow the existing This follows the existing RDP & VNC pattern of prompting only the session owner for authentication-related input. |
|
I think this (and it's companion: apache/guacamole-client#1218) are actually drafts & not intended to be merged at this point. Maybe we should change the PR status? |
Okay, I've marked them as Draft for now. |
8258dcc to
1688641
Compare
|
Added unit tests in the latest push |
bbennett-ks
left a comment
There was a problem hiding this comment.
I looked over the existing primitives (msg, pipe, argv) to understand the need for a new RPC set, and this is a good extension: none of them offer a correlated request/response, whereas this provides:
- Bidirectional request/response channel (either side can initiate).
- Client-generated UUID correlation that stays unique across the multiple processes in the round-trip workflow (where a per-connection stream/object index couldn't).
- Payload breakup & reassembly: the body streams over a paired pipe as blobs and is reassembled by libguac, allowing payloads larger than a single instruction can carry.
- Cancellation support.
5e30d6f to
6923c20
Compare
mike-jumper
left a comment
There was a problem hiding this comment.
I'm mainly not sure about the set of req/done/cancel instructions and use of pipe. The structure resembles the established "object" pattern that we already use for filesystem, so in this case I'd lean more toward an object-style service that can be given a name.
For example:
servicedeclares a service object namedwebauthn.- Client retrieves the object's JSON index of available streams, which here would be
createandget. - When the client wishes to issue a request, it creates a
putstream to the relevant named stream of the object, including an opaque request ID in the path (ie:create/SOME_ID). - When the server needs to send the response for a request, it issues a
bodyfor the same path.
Would that align with what you're seeing with the structure of WebAuthN (and possibly plans for USB)?
A couple clarifications before reshaping the PR. WebAuthn passthrough is server initiated. The server forwards a challenge from upstream and the browser is the responder. The existing object pattern has get and put flowing client to server with body as the response back, and I couldn't find an example of a server-pushed body without a preceding client get. Would the shape you have in mind involve a server-issued put toward the client carrying the request, or is there an existing primitive for server-initiated push I'm missing |
|
A challenge/response flow could just be:
I add the |
6923c20 to
9be1af5
Compare
Okay so I am working on a redesign. Two new stream-opening instructions, The mimetype identifies the ceremony kind. I went with |
| * } | ||
| * @endcode | ||
| */ | ||
| void (*destructor)(void* data); |
There was a problem hiding this comment.
The established pattern here is a free_handler, which would need its own stream-specific typedef (just like the other structs).
There was a problem hiding this comment.
Renamed to free_handler and added a guac_stream_free_handler typedef in user-fntypes.h alongside the other stream handler typedefs
| /* Run the registered destructor for the stream's data, if any */ | ||
| if (stream->destructor != NULL) { | ||
| stream->destructor(stream->data); | ||
| stream->destructor = NULL; | ||
| stream->data = NULL; | ||
| } |
There was a problem hiding this comment.
Good idea to NULL out stream->data here, but that should probably be done more broadly and regardless of whether a destructor is provided. For example:
if (stream->destructor != NULL)
stream->destructor(stream->data);
/* Clean up any remaining dangling pointers before permitting reuse of the
* stream */
stream->data = NULL;
stream->destructor = NULL;
stream->another_handler = NULL;
stream->yet_another_handler = NULL;There was a problem hiding this comment.
Moved the cleanup out of the if-block. data, ack_handler, blob_handler, end_handler, and free_handler are now all NULL'd unconditionally before the slot is released. Same fix in guac_client_free_stream
| typedef struct auth_capture { | ||
| int call_count; | ||
| int stream_index; | ||
| char mimetype[64]; | ||
| char challenge_id[64]; | ||
| } auth_capture; |
There was a problem hiding this comment.
Added docs to each field
| /** | ||
| * Handler that copies its arguments into the user's data pointer | ||
| * (assumed to be an auth_capture) so the test can assert them. Used for | ||
| * both the auth-challenge and auth-response paths since they share the | ||
| * same handler signature. | ||
| */ | ||
| static int capture_auth(guac_user* user, guac_stream* stream, | ||
| char* mimetype, char* challenge_id) { |
There was a problem hiding this comment.
We need to always document all parameters and return values.
There was a problem hiding this comment.
Documented the params and return
|
|
||
| } | ||
|
|
||
| static void teardown_auth_user(guac_user* user) { |
There was a problem hiding this comment.
Please document this function.
There was a problem hiding this comment.
Documented setup_auth_user and teardown_auth_user
| * Sender for the auth-response test: emits send_auth_response referencing | ||
| * an earlier challenge id. | ||
| */ | ||
| static void write_auth_response(int fd) { |
There was a problem hiding this comment.
Same here - we need to document all parameters.
There was a problem hiding this comment.
Removed along with send_auth_response, nothing emits auth-response from the server. See the map entry for the removed auth-challenge
| {"usbconnect", __guac_handle_usbconnect}, | ||
| {"usbdata", __guac_handle_usbdata}, | ||
| {"usbdisconnect", __guac_handle_usbdisconnect}, | ||
| {"auth-challenge", __guac_handle_auth_challenge}, |
There was a problem hiding this comment.
What are the semantics of receiving an auth challenge from the user?
There was a problem hiding this comment.
Removed auth-challenge since nothing registers a handler. Also dropped send_auth_response, the typedef, and the field since the server only emits auth-challenge and only receives auth-response. Same on the JS side, dropped the auth-response handler, onauthresponse, and sendAuthChallenge. Can add the reverse direction back if anything else needs it
| static void run_sender_check(void (*sender)(int), int write_fd, int read_fd, | ||
| const char* expected, int expected_len) { |
There was a problem hiding this comment.
Here, too, please - all parameters need to be documented.
There was a problem hiding this comment.
Documented all params
| * heap-allocated buffer, returning the buffer and writing the length to | ||
| * *out_len. Caller takes ownership of the buffer. Closes the file descriptor. | ||
| */ | ||
| static char* read_all(int fd, int* out_len) { |
There was a problem hiding this comment.
And here - documentation needed for parameters and return values.
There was a problem hiding this comment.
Documented the params and return
9be1af5 to
3a1ec95
Compare
…libguac. Adds two new protocol instructions for authentication exchanges: - auth-challenge: opens a stream carrying the body of a challenge for a pending authentication exchange. The wire form is "auth-challenge,<stream_idx>,<mimetype>,<challenge_id>". The mimetype identifies the auth flavor; the challenge_id is an opaque identifier the peer references in its matching auth-response. - auth-response: opens a stream carrying the response body for a previously-issued auth-challenge identified by the same challenge_id. Same wire shape as auth-challenge. Each instruction's body rides as blobs on the announced stream, terminated by end. No paired pipe, no single-slot per-user buffering; per-stream blob and end handlers do the work. Also adds a destructor callback to guac_stream, called from guac_user_free_stream / guac_client_free_stream and from guac_user_free / guac_client_free for streams still open at disconnect, so consumers can attach per-stream state that needs cleanup if the peer drops mid-stream. The first consumer is WebAuthn passthrough (companion PR on guacamole-client) using application/x-webauthn-create+json and application/x-webauthn-get+json mimetypes.
3a1ec95 to
64d2079
Compare
Adds a generic request/response RPC primitive to the Guacamole protocol in the form of three instructions: "req", "done", and "cancel".
A "req" instruction initiates a typed request with three arguments: a correlation id, a method name (by convention namespaced like "."), and an opaque payload. The receiving side is expected to settle the request with a "done" instruction carrying the same id, a status (typically "ok", "error", or "canceled"), and a response payload. Either side may issue a "cancel" with the matching id to abort an in-flight request before its "done" arrives.
The instructions themselves carry no feature-specific semantics; the method name argument identifies what kind of request is being made, and the payload is opaque to libguac. This leaves room for any RPC-style feature to share the same plumbing without needing its own dedicated opcodes.
Adds the libguac surface for the new primitive:
The first intended consumer is WebAuthn passthrough, which will use this primitive to relay ceremonies between protocol plugins and the user's local authenticator with method names "webauthn.create" and "webauthn.get". The primitive is deliberately generic so other RPC-style features can adopt it without adding more opcodes to the protocol vocabulary.
Guacamole client PR