GUACAMOLE-249: Add example for testing arbitrary SVC support.
This commit is contained in:
parent
233c0555c3
commit
f3cef7e2f0
2
src/protocols/rdp/doc/svc-example/.gitignore
vendored
Normal file
2
src/protocols/rdp/doc/svc-example/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
!Makefile
|
||||||
|
*.exe
|
43
src/protocols/rdp/doc/svc-example/Makefile
Normal file
43
src/protocols/rdp/doc/svc-example/Makefile
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
|
LDFLAGS=-lwtsapi32
|
||||||
|
|
||||||
|
# Requires at least Windows Vista (Windows Server 2008 qualifies)
|
||||||
|
WINVER=0x600
|
||||||
|
|
||||||
|
# Windows cross compiler (MinGW)
|
||||||
|
CC=i686-w64-mingw32-gcc
|
||||||
|
|
||||||
|
all: svc-example.exe
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) svc-example.exe
|
||||||
|
|
||||||
|
svc-example.exe: svc-example.c
|
||||||
|
$(CC) svc-example.c $(LDFLAGS) \
|
||||||
|
-D_WIN32_WINNT=$(WINVER) \
|
||||||
|
-DWINVER=$(WINVER) -o svc-example.exe
|
||||||
|
|
169
src/protocols/rdp/doc/svc-example/README.md
Normal file
169
src/protocols/rdp/doc/svc-example/README.md
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
Static Virtual Channel example
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Guacamole supports use of static virtual channels (SVCs) for transmission of
|
||||||
|
arbitrary data between the JavaScript client and applications running within
|
||||||
|
RDP sessions. This example is intended to demonstrate how bidirectional
|
||||||
|
communication between the Guacamole client and applications within the RDP
|
||||||
|
server can be accomplished.
|
||||||
|
|
||||||
|
Arbitrary SVCs are enabled on RDP connections by specfying their names as the
|
||||||
|
value of [the `static-channels`
|
||||||
|
parameter](http://guacamole.apache.org/doc/gug/configuring-guacamole.html#rdp-device-redirection).
|
||||||
|
Each name is limited to a maximum of 7 characters. Multiple names may be listed
|
||||||
|
by separating those names with commas.
|
||||||
|
|
||||||
|
This example consists of a single file, [`svc-example.c`](svc-example.c), which
|
||||||
|
leverages the terminal server API exposed by Windows to:
|
||||||
|
|
||||||
|
1. Open a channel called "EXAMPLE"
|
||||||
|
2. Wait for blocks of data to be received
|
||||||
|
3. Send each received block of data back, unmodified.
|
||||||
|
|
||||||
|
Building the example
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
A `Makefile` is provided which uses MinGW to build the `svc-example.exe`
|
||||||
|
executable, and thus can be used to produce the example application on Linux.
|
||||||
|
The `Makefile` is not platform-independent, and changes may be needed for
|
||||||
|
`make` to succeed with your installation of MinGW. If not using MinGW, the C
|
||||||
|
source itself is standard and should compile with other tools.
|
||||||
|
|
||||||
|
To build on Linux using `make`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make
|
||||||
|
i686-w64-mingw32-gcc svc-example.c -lwtsapi32 \
|
||||||
|
-D_WIN32_WINNT=0x600 \
|
||||||
|
-DWINVER=0x600 -o svc-example.exe
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then copy the resulting `svc-example.exe` to the remote desktop that
|
||||||
|
you wish to test and run it within a command prompt within the remote desktop
|
||||||
|
session.
|
||||||
|
|
||||||
|
Using the example (and SVCs in general)
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
On the remote desktop server side (within the Windows application leveraging
|
||||||
|
SVCs to communicate with Guacamole), the following functions are used
|
||||||
|
specifically for reading/writing to the SVC:
|
||||||
|
|
||||||
|
* [`WTSVirtualChannelOpenEx()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelopenex)
|
||||||
|
* [`WTSVirtualChannelRead()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelread)
|
||||||
|
* [`WTSVirtualChannelWrite()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelwrite)
|
||||||
|
* [`WTSVirtualChannelClose()`](https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsvirtualchannelclose)
|
||||||
|
|
||||||
|
On the Guacamole side, bidirectional communication is established using:
|
||||||
|
|
||||||
|
* The `static-channels` connection parameter (in the case of the example, this should be set to `EXAMPLE`).
|
||||||
|
* An [`onpipe`](http://guacamole.apache.org/doc/guacamole-common-js/Guacamole.Client.html#event:onpipe)
|
||||||
|
handler which handles inbound (server-to-client) pipe streams named identically
|
||||||
|
to the SVC. The inbound pipe stream will be received upon establishing the RDP
|
||||||
|
connection and is used to transmit any data sent along the SVC **from** within
|
||||||
|
the remote desktop session. For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
client.onpipe = function pipeReceived(stream, mimetype, name) {
|
||||||
|
|
||||||
|
// Receive output of SVC
|
||||||
|
if (name === 'EXAMPLE') {
|
||||||
|
|
||||||
|
// Log start of stream
|
||||||
|
var reader = new Guacamole.StringReader(stream);
|
||||||
|
console.log('pipe: %s: stream begins', name);
|
||||||
|
|
||||||
|
// Log each received blob of text
|
||||||
|
reader.ontext = function textReceived(text) {
|
||||||
|
console.log('pipe: %s: \"%s\"', name, text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log end of stream
|
||||||
|
reader.onend = function streamEnded() {
|
||||||
|
console.log('pipe: %s: stream ends', name);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other inbound pipe streams are unsupported
|
||||||
|
else
|
||||||
|
stream.sendAck('Pipe stream not supported.',
|
||||||
|
Guacamole.Status.Code.UNSUPPORTED);
|
||||||
|
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
* Calls to [`createPipeStream()`](http://guacamole.apache.org/doc/guacamole-common-js/Guacamole.Client.html#createPipeStream)
|
||||||
|
as needed to establish outbound (client-to-server) pipe streams named
|
||||||
|
identically to the SVC. Outbound pipe streams with the same name as the SVC
|
||||||
|
will be automatically handled by the Guacamole server, with any received data
|
||||||
|
sent along the SVC **to** the remote desktop session. For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var example = new Guacamole.StringWriter(client.createPipeStream('text/plain', 'EXAMPLE'));
|
||||||
|
example.sendText('This is a test.');
|
||||||
|
example.sendEnd();
|
||||||
|
```
|
||||||
|
|
||||||
|
These pipe streams may be created and destroyed as desired. As long as they
|
||||||
|
have the same name as the SVC, data sent along the pipe stream will be sent
|
||||||
|
along the SVC.
|
||||||
|
|
||||||
|
Example output
|
||||||
|
--------------
|
||||||
|
|
||||||
|
If the `static-channels` parameter is set to `EXAMPLE`, the successful creation
|
||||||
|
of the "EXAMPLE" channel should be logged by guacd when the connection is
|
||||||
|
established:
|
||||||
|
|
||||||
|
```
|
||||||
|
guacd[12057]: INFO: Created static channel "EXAMPLE"...
|
||||||
|
guacd[12057]: INFO: Static channel "EXAMPLE" connected.
|
||||||
|
```
|
||||||
|
|
||||||
|
On the client side, the `onpipe` handler should be invoked immediately. If
|
||||||
|
using the example code shown above, receipt of the pipe stream for the
|
||||||
|
"EXAMPLE" channel is logged:
|
||||||
|
|
||||||
|
```
|
||||||
|
pipe: EXAMPLE: stream begins
|
||||||
|
```
|
||||||
|
|
||||||
|
Running `svc-example.exe` within a command prompt inside the remote desktop
|
||||||
|
session, the application logs that the "EXAMPLE" channel has been successfully
|
||||||
|
opened:
|
||||||
|
|
||||||
|
```
|
||||||
|
Microsoft Windows [Version 10.0.17763.437]
|
||||||
|
(c) 2018 Microsoft Corporation. All rights reserved.
|
||||||
|
|
||||||
|
C:\Users\test>svc-example.exe
|
||||||
|
SVC "EXAMPLE" open. Reading...
|
||||||
|
```
|
||||||
|
|
||||||
|
Once `createPipeStream()` has been invoked on the Guacamole client side and
|
||||||
|
using the same name as the SVC (in this case, "EXAMPLE") guacd should log the
|
||||||
|
inbound half the channel is now connected:
|
||||||
|
|
||||||
|
```
|
||||||
|
guacd[12057]: DEBUG: Inbound half of channel "EXAMPLE" connected.
|
||||||
|
```
|
||||||
|
|
||||||
|
Sending the string `This is a test.` along the client-to-server pipe (as shown
|
||||||
|
in the example code above) results in `svc-example.exe` logging that it
|
||||||
|
received those 15 bytes and has resent the same 15 bytes back along the SVC:
|
||||||
|
|
||||||
|
```
|
||||||
|
Received 15 bytes.
|
||||||
|
Wrote 15 bytes.
|
||||||
|
```
|
||||||
|
|
||||||
|
The data sent from within the remote desktop session is received on the client
|
||||||
|
side via the server-to-client pipe stream. If using the example code shown
|
||||||
|
above, the received data is logged:
|
||||||
|
|
||||||
|
```
|
||||||
|
pipe: EXAMPLE: "This is a test."
|
||||||
|
```
|
||||||
|
|
71
src/protocols/rdp/doc/svc-example/svc-example.c
Normal file
71
src/protocols/rdp/doc/svc-example/svc-example.c
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winbase.h>
|
||||||
|
#include <wtsapi32.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the RDP static virtual channel (SVC).
|
||||||
|
*/
|
||||||
|
#define SVC_NAME "EXAMPLE"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
ULONG bytes_read;
|
||||||
|
ULONG bytes_written;
|
||||||
|
|
||||||
|
char message[4096];
|
||||||
|
|
||||||
|
/* Open SVC */
|
||||||
|
HANDLE svc = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, SVC_NAME, 0);
|
||||||
|
|
||||||
|
/* Fail if we cannot open an SVC at all */
|
||||||
|
if (svc == NULL) {
|
||||||
|
printf("Cannot open SVC \"" SVC_NAME "\"\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("SVC \"" SVC_NAME "\" open. Reading...\n");
|
||||||
|
|
||||||
|
/* Continuously read from SVC */
|
||||||
|
while (WTSVirtualChannelRead(svc, INFINITE, message, sizeof(message), &bytes_read)) {
|
||||||
|
|
||||||
|
printf("Received %i bytes.\n", bytes_read);
|
||||||
|
|
||||||
|
/* Write all received data back to the SVC, possibly spreading the data
|
||||||
|
* across multiple writes */
|
||||||
|
char* current = message;
|
||||||
|
while (bytes_read > 0 && WTSVirtualChannelWrite(svc, current,
|
||||||
|
bytes_read, &bytes_written)) {
|
||||||
|
printf("Wrote %i bytes.\n", bytes_written);
|
||||||
|
bytes_read -= bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close SVC */
|
||||||
|
WTSVirtualChannelClose(svc);
|
||||||
|
printf("SVC \"" SVC_NAME "\" closed.\n");
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user