mirror of
https://github.com/optim-enterprises-bv/control-pane.git
synced 2025-10-29 09:02:21 +00:00
re-added websockify
This commit is contained in:
124
public/novnc/utils/websockify/CHANGES.txt
Normal file
124
public/novnc/utils/websockify/CHANGES.txt
Normal file
@@ -0,0 +1,124 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
0.11.0
|
||||
------
|
||||
|
||||
* Command line now supports disabling directory listings
|
||||
* Basic Dockerfile included
|
||||
|
||||
0.10.0
|
||||
------
|
||||
|
||||
* Python 3.4 or newer is now required
|
||||
* Empty message frames are now supported
|
||||
* Tokens can now specify a Unix domain socket file to connect to
|
||||
* Time limits on JWT tokens are now respected
|
||||
* Whitespace is better tolerated in token files
|
||||
* Lots of minor fixes...
|
||||
|
||||
0.9.0
|
||||
-----
|
||||
|
||||
* Base64 support removed and binary mode is now required
|
||||
* Low level WebSocket protocol handling now has its own class
|
||||
* Authentication now optionally required for web server
|
||||
* Server hostname can be used as the token
|
||||
* JWT/JWS/JWE can be used for the token
|
||||
* redis can be used for the token
|
||||
* Can now log to syslog
|
||||
* Improved latency by disabling Nagle for proxied connection
|
||||
* Added client certificate authentication
|
||||
* Support for password protected certificate key file
|
||||
* TLS ciphers and options are now configurable
|
||||
* Can be invoked via inetd
|
||||
* Lots of minor fixes...
|
||||
|
||||
0.8.0
|
||||
-----
|
||||
|
||||
* Make websockify properly terminate children on SIGTERM (#226)
|
||||
* Remove logging in signal handlers (this can cause Python to hang under certain conditions) (#219)
|
||||
* Make it easier to log to a file (#205)
|
||||
* Add support for IPv6 addresses in tokens in the TokenFile token plugins (#197)
|
||||
* Improve auth plugin framework to enable better support for HTTP auth (#194, #201)
|
||||
* Fix bug in JSONTokenAPI token plugin (#192)
|
||||
* Fix a missing variable in the exception handler (#178)
|
||||
|
||||
0.7.0
|
||||
-----
|
||||
|
||||
* Python 3 support fixes (#140, #155, #159)
|
||||
* Generic token-parsing plugins support (#162)
|
||||
* Generic authentication plugins support (#172)
|
||||
* Fixed frame corruption on big-endian systems (#161)
|
||||
* Support heartbeats (via PING) and automatic responses to PONG (#169)
|
||||
* Automatically reject unmasked client frames by default (strict mode) (#174)
|
||||
* Automatically restart interrupted select calls (#175)
|
||||
* Make 'run' respect environment settings (including virtualenv) (#176)
|
||||
|
||||
0.6.1 - May 11, 2015
|
||||
--------------------
|
||||
|
||||
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
|
||||
|
||||
0.6.0 - Feb 18, 2014
|
||||
--------------------
|
||||
|
||||
* **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
|
||||
* Refactor to use standard SocketServer RequestHandler design
|
||||
* Fix zombie process bug on certain systems when using multiprocessing
|
||||
* Add better unit tests
|
||||
* Log information via python `logging` module
|
||||
|
||||
0.5.1 - Jun 27, 2013
|
||||
--------------------
|
||||
|
||||
* use upstream einaros/ws (>=0.4.27) with websockify.js
|
||||
* file_only and no_parent security options for WSRequestHandler
|
||||
* Update build of web-socket-js (c0855c6cae)
|
||||
* add include/web-socket-js-project submodule to gimite/web-socket-js
|
||||
for DSFG compliance.
|
||||
* drop Hixie protocol support
|
||||
|
||||
0.4.1 - Mar 12, 2013
|
||||
--------------------
|
||||
|
||||
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
|
||||
* add include/ directory and remove some dev files from source
|
||||
distribution.
|
||||
|
||||
0.4.0 - Mar 12, 2013
|
||||
--------------------
|
||||
|
||||
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
|
||||
* use Buffer base64 support in Node.js implementation
|
||||
|
||||
0.3.0 - Jan 15, 2013
|
||||
--------------------
|
||||
|
||||
* refactor into modules: websocket, websocketproxy
|
||||
* switch to web-socket-js that uses IETF 6455
|
||||
* change to MPL 2.0 license for include/*.js
|
||||
* fix session recording
|
||||
|
||||
0.2.1 - Oct 15, 2012
|
||||
--------------------
|
||||
|
||||
* re-released with updated version number
|
||||
|
||||
0.2.0 - Sep 17, 2012
|
||||
--------------------
|
||||
|
||||
* Binary data support in websock.js
|
||||
* Target config file/dir and multiple targets with token selector
|
||||
* IPv6 fixes
|
||||
* SSL target support
|
||||
* Proxy to/from unix socket
|
||||
|
||||
|
||||
0.1.0 - May 11, 2012
|
||||
--------------------
|
||||
|
||||
* Initial versioned release.
|
||||
|
||||
165
public/novnc/utils/websockify/COPYING
Normal file
165
public/novnc/utils/websockify/COPYING
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
1
public/novnc/utils/websockify/MANIFEST.in
Normal file
1
public/novnc/utils/websockify/MANIFEST.in
Normal file
@@ -0,0 +1 @@
|
||||
include CHANGES.txt README.md COPYING
|
||||
11
public/novnc/utils/websockify/Makefile
Normal file
11
public/novnc/utils/websockify/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
TARGETS=rebind.so
|
||||
CFLAGS += -fPIC
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
rebind.so: rebind.o
|
||||
$(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -o $@
|
||||
|
||||
clean:
|
||||
rm -f rebind.o rebind.so
|
||||
|
||||
208
public/novnc/utils/websockify/README.md
Normal file
208
public/novnc/utils/websockify/README.md
Normal file
@@ -0,0 +1,208 @@
|
||||
## websockify: WebSockets support for any application/server
|
||||
|
||||
websockify was formerly named wsproxy and was part of the
|
||||
[noVNC](https://github.com/novnc/noVNC) project.
|
||||
|
||||
At the most basic level, websockify just translates WebSockets traffic
|
||||
to normal socket traffic. Websockify accepts the WebSockets handshake,
|
||||
parses it, and then begins forwarding traffic between the client and
|
||||
the target in both directions.
|
||||
|
||||
### News/help/contact
|
||||
|
||||
Notable commits, announcements and news are posted to
|
||||
<a href="http://www.twitter.com/noVNC">@noVNC</a>
|
||||
|
||||
If you are a websockify developer/integrator/user (or want to be)
|
||||
please join the <a
|
||||
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
|
||||
discussion group</a>
|
||||
|
||||
Bugs and feature requests can be submitted via [github
|
||||
issues](https://github.com/novnc/websockify/issues).
|
||||
|
||||
If you want to show appreciation for websockify you could donate to a great
|
||||
non-profits such as: [Compassion
|
||||
International](http://www.compassion.com/), [SIL](http://www.sil.org),
|
||||
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
|
||||
Foundation](https://www.eff.org/), [Against Malaria
|
||||
Foundation](http://www.againstmalaria.com/), [Nothing But
|
||||
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
|
||||
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
|
||||
|
||||
### WebSockets binary data
|
||||
|
||||
Starting with websockify 0.5.0, only the HyBi / IETF
|
||||
6455 WebSocket protocol is supported. There is no support for the older
|
||||
Base64 encoded data format.
|
||||
|
||||
|
||||
### Encrypted WebSocket connections (wss://)
|
||||
|
||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to
|
||||
generate a certificate and key for Websockify to load. By default, Websockify
|
||||
loads a certificate file name `self.pem` but the `--cert=CERT` and `--key=KEY`
|
||||
options can override the file name. You can generate a self-signed certificate
|
||||
using openssl. When asked for the common name, use the hostname of the server
|
||||
where the proxy will be running:
|
||||
|
||||
```
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
```
|
||||
|
||||
For a self-signed certificate to work, you need to make your client/browser
|
||||
understand it. You can do this by installing it as accepted certificate, or by
|
||||
using that same certificate for a HTTPS connection to which you navigate first
|
||||
and approve. Browsers generally don't give you the "trust certificate?" prompt
|
||||
by opening a WSS socket with invalid certificate, hence you need to have it
|
||||
accept it by either of those two methods.
|
||||
|
||||
The ports may be considered as distinguishing connections by the browser,
|
||||
for example, if your website url is https://my.local:8443 and your WebSocket
|
||||
url is wss://my.local:8001, first browse to https://my.local:8001, add the
|
||||
exception, then browse to https://my.local:8443 and add another exception.
|
||||
Then an html page served over :8443 will be able to open WSS to :8001
|
||||
|
||||
If you have a commercial/valid SSL certificate with one or more intermediate
|
||||
certificates, concat them into one file, server certificate first, then the
|
||||
intermediate(s) from the CA, etc. Point to this file with the `--cert` option
|
||||
and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
|
||||
|
||||
|
||||
### Additional websockify features
|
||||
|
||||
These are not necessary for the basic operation.
|
||||
|
||||
* Daemonizing: When the `-D` option is specified, websockify runs
|
||||
in the background as a daemon process.
|
||||
|
||||
* SSL (the wss:// WebSockets URI): This is detected automatically by
|
||||
websockify by sniffing the first byte sent from the client and then
|
||||
wrapping the socket if the data starts with '\x16' or '\x80'
|
||||
(indicating SSL).
|
||||
|
||||
* Session recording: This feature that allows recording of the traffic
|
||||
sent and received from the client to a file using the `--record`
|
||||
option.
|
||||
|
||||
* Mini-webserver: websockify can detect and respond to normal web
|
||||
requests on the same port as the WebSockets proxy. This functionality
|
||||
is activated with the `--web DIR` option where DIR is the root of the
|
||||
web directory to serve.
|
||||
|
||||
* Wrap a program: see the "Wrap a Program" section below.
|
||||
|
||||
* Log files: websockify can save all logging information in a file.
|
||||
This functionality is activated with the `--log-file FILE` option
|
||||
where FILE is the file where the logs should be saved.
|
||||
|
||||
* Authentication plugins: websockify can demand authentication for
|
||||
websocket connections and, if you use `--web-auth`, also for normal
|
||||
web requests. This functionality is activated with the
|
||||
`--auth-plugin CLASS` and `--auth-source ARG` options, where CLASS is
|
||||
usually one from auth_plugins.py and ARG is the plugin's configuration.
|
||||
|
||||
* Token plugins: a single instance of websockify can connect clients to
|
||||
multiple different pre-configured targets, depending on the token sent
|
||||
by the client using the `token` URL parameter, or the hostname used to
|
||||
reach websockify, if you use `--host-token`. This functionality is
|
||||
activated with the `--token-plugin CLASS` and `--token-source ARG`
|
||||
options, where CLASS is usually one from token_plugins.py and ARG is
|
||||
the plugin's configuration.
|
||||
|
||||
### Other implementations of websockify
|
||||
|
||||
The primary implementation of websockify is in python. There are
|
||||
several alternate implementations in other languages available in
|
||||
our sister repositories [websockify-js](https://github.com/novnc/websockify-js)
|
||||
(JavaScript/Node.js) and [websockify-other](https://github.com/novnc/websockify-other)
|
||||
(C, Clojure, Ruby).
|
||||
|
||||
In addition there are several other external projects that implement
|
||||
the websockify "protocol". See the alternate implementation [Feature
|
||||
Matrix](https://github.com/novnc/websockify/wiki/Feature_Matrix) for
|
||||
more information.
|
||||
|
||||
|
||||
### Wrap a Program
|
||||
|
||||
In addition to proxying from a source address to a target address
|
||||
(which may be on a different system), websockify has the ability to
|
||||
launch a program on the local system and proxy WebSockets traffic to
|
||||
a normal TCP port owned/bound by the program.
|
||||
|
||||
This is accomplished by the LD_PRELOAD library (`rebind.so`)
|
||||
which intercepts bind() system calls by the program. The specified
|
||||
port is moved to a new localhost/loopback free high port. websockify
|
||||
then proxies WebSockets traffic directed to the original port to the
|
||||
new (moved) port of the program.
|
||||
|
||||
The program wrap mode is invoked by replacing the target with `--`
|
||||
followed by the program command line to wrap.
|
||||
|
||||
`./run 2023 -- PROGRAM ARGS`
|
||||
|
||||
The `--wrap-mode` option can be used to indicate what action to take
|
||||
when the wrapped program exits or daemonizes.
|
||||
|
||||
Here is an example of using websockify to wrap the vncserver command
|
||||
(which backgrounds itself) for use with
|
||||
[noVNC](https://github.com/novnc/noVNC):
|
||||
|
||||
`./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||
|
||||
Here is an example of wrapping telnetd (from krb5-telnetd). telnetd
|
||||
exits after the connection closes so the wrap mode is set to respawn
|
||||
the command:
|
||||
|
||||
`sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||
|
||||
The `wstelnet.html` page in the [websockify-js](https://github.com/novnc/websockify-js)
|
||||
project demonstrates a simple WebSockets based telnet client (use
|
||||
'localhost' and '2023' for the host and port respectively).
|
||||
|
||||
|
||||
### Installing websockify
|
||||
|
||||
Download one of the releases or the latest development version, extract
|
||||
it and run `python3 setup.py install` as root in the directory where you
|
||||
extracted the files. Normally, this will also install numpy for better
|
||||
performance, if you don't have it installed already. However, numpy is
|
||||
optional. If you don't want to install numpy or if you can't compile it,
|
||||
you can edit setup.py and remove the `install_requires=['numpy'],` line
|
||||
before running `python3 setup.py install`.
|
||||
|
||||
Afterwards, websockify should be available in your path. Run
|
||||
`websockify --help` to confirm it's installed correctly.
|
||||
|
||||
|
||||
### Running with Docker/Podman
|
||||
You can also run websockify using Docker, Podman, Singularity, udocker or
|
||||
your favourite container runtime that support OCI container images.
|
||||
|
||||
The entrypoint of the image is the `run` command.
|
||||
|
||||
To build the image:
|
||||
```
|
||||
./docker/build.sh
|
||||
```
|
||||
|
||||
Once built you can just launch it with the same
|
||||
arguments you would give to the `run` command and taking care of
|
||||
assigning the port mappings:
|
||||
```
|
||||
docker run -it --rm -p <port>:<container_port> novnc/websockify <container_port> <run_arguments>
|
||||
```
|
||||
|
||||
For example to forward traffic from local port 7000 to 10.1.1.1:5902
|
||||
you can use:
|
||||
```
|
||||
docker run -it --rm -p 7000:80 novnc/websockify 80 10.1.1.1:5902
|
||||
```
|
||||
|
||||
If you need to include files, like for example for the `--web` or `--cert`
|
||||
options you can just mount the required files in the `/data` volume and then
|
||||
you can reference them in the usual way:
|
||||
```
|
||||
docker run -it --rm -p 443:443 -v websockify-data:/data novnc/websockify --cert /data/self.pem --web /data/noVNC :443 --token-plugin TokenRedis --token-source myredis.local:6379 --ssl-only --ssl-version tlsv1_2
|
||||
```
|
||||
@@ -0,0 +1,61 @@
|
||||
Running Websockify as a Windows service
|
||||
=======================================
|
||||
|
||||
Installation and configuration
|
||||
------------------------------
|
||||
|
||||
Download the following software:
|
||||
|
||||
* Python, from https://www.python.org/downloads/windows/
|
||||
* SrvAny, from http://simpleauto.byethost8.com/Zip/SrvAny.zip
|
||||
|
||||
Note that there is [a modern alternative for SrvAny](https://github.com/rwmjones/rhsrvany),
|
||||
but that project does not provide binaries.
|
||||
|
||||
Install Python for all users, not just the current one. Extract Websockify
|
||||
into a directory, e.g. `C:\Program Files\websockify`, so that e.g.
|
||||
`README.md` ends up there. Extract the `SrvAny.zip` archive, copy the
|
||||
`WIN7\SrvAny.exe` file into `C:\Program Files\websockify`.
|
||||
|
||||
Then create a batch file, `C:\Program Files\websockify\run.bat`, that runs
|
||||
Websockify from its directory with the correct options under the correct
|
||||
Python interpreter:
|
||||
|
||||
```
|
||||
C:
|
||||
cd "\Program Files\websockify"
|
||||
"C:\Program Files\Python39\python.exe" -m websockify 5901 127.0.0.1:5900
|
||||
```
|
||||
|
||||
Run it by hand once so that Windows asks you about a firewall exception.
|
||||
After confirming the exception, press `Ctrl+C` to terminate the script.
|
||||
|
||||
Then create a Windows service for Websockify (use an Administrator command
|
||||
prompt for that). For paths with spaces, like in this example, double-escaping
|
||||
is needed: once for `cmd.exe` and once for `SrvAny.exe`.
|
||||
|
||||
```
|
||||
C:
|
||||
cd "\Program Files\websockify"
|
||||
SrvAny.exe -install Websockify 10s \\\"C:\Program Files\websockify\run.bat\\\"
|
||||
```
|
||||
|
||||
In the Windows Control Panel, under Services, a new "Websockify" service will
|
||||
appear. In its properties dialog, you can change the startup type, e.g. make
|
||||
it start automatically at boot. Or, you can start the service manually.
|
||||
|
||||
Uninstallation
|
||||
--------------
|
||||
|
||||
If you want to remove the service, first set its startup type to Manual, then
|
||||
reboot the PC. Then run this command using the Administrator command prompt:
|
||||
|
||||
```
|
||||
C:
|
||||
cd "\Program Files\websockify"
|
||||
SrvAny.exe -remove Websockify
|
||||
```
|
||||
|
||||
After that, you will be able to remove the `C:\Program Files\websockify`
|
||||
directory completely.
|
||||
|
||||
16
public/novnc/utils/websockify/docker/Dockerfile
Normal file
16
public/novnc/utils/websockify/docker/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM python
|
||||
|
||||
COPY websockify-*.tar.gz /
|
||||
|
||||
RUN python3 -m pip install websockify-*.tar.gz
|
||||
RUN rm -rf /websockify-* /root/.cache
|
||||
|
||||
VOLUME /data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
WORKDIR /opt/websockify
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/websockify"]
|
||||
CMD ["--help"]
|
||||
5
public/novnc/utils/websockify/docker/build.sh
Executable file
5
public/novnc/utils/websockify/docker/build.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e -x
|
||||
cd "$(dirname "$0")"
|
||||
(cd .. && python3 setup.py sdist --dist-dir docker/)
|
||||
docker build -t novnc/websockify .
|
||||
114
public/novnc/utils/websockify/docs/latency_results.txt
Normal file
114
public/novnc/utils/websockify/docs/latency_results.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
This data is raw copy from the latency tester set to send a frame with
|
||||
a little over 2000 KB of data every 10ms.
|
||||
|
||||
The number of packets sent and received is just a visual counter and
|
||||
is just the total when I chose to stop the test (around 3000 or so
|
||||
packets).
|
||||
|
||||
The latency measure are from the point the packet was sent to when it
|
||||
was received back again in milliseconds. One notable data point
|
||||
missing from this is how long it actually took for the client to send
|
||||
3000 packets because sending large packets can put load on the browser
|
||||
and it may be a lot longer than 10ms before the timer event to
|
||||
send the next packet fires. So even with low latency numbers, the
|
||||
actual send rate may be fairly low because sending the WebSockets
|
||||
frames is impacting the performance of the browser in general.
|
||||
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
Native WebSockets implementations, 2000 byte payload, 10ms delay
|
||||
|
||||
Chrome 8.0.552 - native WebSockets
|
||||
Packets sent: 2998
|
||||
Packets Received: 2998
|
||||
Average Latency: 1.84
|
||||
40 Frame Running Average Latency: 1.90
|
||||
Minimum Latency: 1.00
|
||||
Maximum Latency: 10.00
|
||||
|
||||
firefox 4.0b9 - WebSockets enabled
|
||||
Packets sent: 3011
|
||||
Packets Received: 3011
|
||||
Average Latency: 6.45
|
||||
40 Frame Running Average Latency: 6.08
|
||||
Minimum Latency: 5.00
|
||||
Maximum Latency: 119.00
|
||||
|
||||
Opera 11 - WebSockets enabled
|
||||
Packets sent: 3065
|
||||
Packets Received: 3064
|
||||
Average Latency: 9.56
|
||||
40 Frame Running Average Latency: 8.15
|
||||
Minimum Latency: 4.00
|
||||
Maximum Latency: 53.00
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
New web-socket-js (20f837425d4), 2000 byte payload, 10ms delay
|
||||
|
||||
firefox 4.0b9 - no WebSockets
|
||||
Packets sent: 3088
|
||||
Packets Received: 3087
|
||||
Average Latency: 16.71
|
||||
40 Frame Running Average Latency: 16.80
|
||||
Minimum Latency: 7.00
|
||||
Maximum Latency: 75.00
|
||||
|
||||
- First 1000 sent in 13 seconds
|
||||
- Second 1000 sent in 12 seconds
|
||||
- Third 1000 sent in 12 seconds
|
||||
|
||||
firefox 3.6.10 - no WebSockets
|
||||
Packets sent: 3100
|
||||
Packets Received: 3099
|
||||
Average Latency: 17.32
|
||||
40 Frame Running Average Latency: 16.73
|
||||
Minimum Latency: 6.00
|
||||
Maximum Latency: 72.00
|
||||
|
||||
Opera 11 - no WebSockets
|
||||
Packets sent: 3007
|
||||
Packets Received: 3007
|
||||
Average Latency: 465.91
|
||||
40 Frame Running Average Latency: 147.95
|
||||
Minimum Latency: 12.00
|
||||
Maximum Latency: 9143.00
|
||||
|
||||
- average starts at around 28ms
|
||||
- time for each 500 packets: 13s, 16s, 25s, 37s, 50s, 72s
|
||||
- also start seeing sent, receive lags around 1200 packets
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
Old web-socket-js (9e7663771), 2000 byte payload, 10ms delay
|
||||
|
||||
firefox 4.0b9 - no WebSockets
|
||||
Packets sent: 3024
|
||||
Packets Received: 3020
|
||||
Average Latency: 80.59
|
||||
40 Frame Running Average Latency: 60.15
|
||||
Minimum Latency: 10.00
|
||||
Maximm Latency: 348.00
|
||||
|
||||
|
||||
firefox 3.6.10 - no WebSockets
|
||||
Packets sent: 2777
|
||||
Packets Received: 2775
|
||||
Average Latency: 34.89
|
||||
40 Frame Running Average Latency: 24.50
|
||||
Minimum Latency: 10.00
|
||||
Maximum Latency: 208.00
|
||||
|
||||
|
||||
Opera 11 - no Websockets
|
||||
Packets sent: 3012
|
||||
Packets Received: 3011
|
||||
Average Latency: 380.87
|
||||
40 Frame Running Average Latency: 341.90
|
||||
Minimum Latency: 28.00
|
||||
Maximum Latency: 2175.00
|
||||
|
||||
- average starts at around 290ms
|
||||
- time for each 1000 packets: 23s, 38s, 65s
|
||||
|
||||
6
public/novnc/utils/websockify/docs/notes
Normal file
6
public/novnc/utils/websockify/docs/notes
Normal file
@@ -0,0 +1,6 @@
|
||||
Building release tarball:
|
||||
- not really necessary since tagged revision can be downloaded
|
||||
from github as tarballs
|
||||
|
||||
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
|
||||
gzip websockify-${WVER}.tar
|
||||
10
public/novnc/utils/websockify/docs/release.txt
Normal file
10
public/novnc/utils/websockify/docs/release.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
- Update setup.py and CHANGES.txt and commit
|
||||
- Create version tag and tarball from tag
|
||||
WVER=0.1.0
|
||||
git tag v${WVER}
|
||||
git push origin master
|
||||
git push origin v${WVER}
|
||||
- Create the source distribution
|
||||
python3 setup.py sdist
|
||||
- Upload the source distribution
|
||||
python3 -m twine upload dist/websockify-${WVER}.tar.gz
|
||||
110
public/novnc/utils/websockify/docs/websockify.1
Normal file
110
public/novnc/utils/websockify/docs/websockify.1
Normal file
@@ -0,0 +1,110 @@
|
||||
.TH websockify 1 "June 7, 2012" "version 0.3" "USER COMMANDS"
|
||||
|
||||
.SH NAME
|
||||
|
||||
websockify - WebSockets to TCP socket bridge
|
||||
|
||||
.SH SYNOPSIS
|
||||
|
||||
websockify [options] [source_addr:]source_port target_addr:target_port
|
||||
websockify [options] [source_addr:]source_port \-\- WRAP_COMMAND_LINE
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose verbose messages and per frame traffic
|
||||
--record=FILE record sessions to FILE.[session_number]
|
||||
-D, --daemon become a daemon (background process)
|
||||
--run-once handle a single WebSocket connection and exit
|
||||
--timeout=TIMEOUT after TIMEOUT seconds exit when not connected
|
||||
--cert=CERT SSL certificate file
|
||||
--key=KEY SSL key file (if separate from cert)
|
||||
--ssl-only disallow non-encrypted connections
|
||||
--web=DIR run webserver on same port. Serve files from DIR.
|
||||
--wrap-mode=MODE action to take when the wrapped program exits or
|
||||
daemonizes: exit (default), ignore, respawn
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
At the most basic level, websockify just translates WebSockets traffic to normal TCP socket traffic. Websockify accepts the WebSockets handshake, parses it, and then begins forwarding traffic between the client and the target in both directions.
|
||||
|
||||
websockify was formerly named wsproxy and was part of the noVNC project.
|
||||
|
||||
.SH NOTES
|
||||
|
||||
.SS WebSockets binary data
|
||||
|
||||
Websockify supports all versions of the WebSockets protocol (Hixie and HyBI). The older Hixie versions of the protocol only support UTF-8 text payloads. In order to transport binary data over UTF-8 an encoding must used to encapsulate the data within UTF-8. Websockify uses base64 to encode all traffic to and from the client. This does not affect the data between websockify and the server.
|
||||
|
||||
.SS Encrypted WebSocket connections (wss://)
|
||||
|
||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to generate a certificate for websockify to load. By default websockify loads a certificate file name self.pem but the --cert=CERT option can override the file name. You can generate a self-signed certificate using openssl. When asked for the common name, use the hostname of the server where the proxy will be running:
|
||||
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
|
||||
.SS Additional websockify features
|
||||
|
||||
These are not necessary for the basic operation.
|
||||
|
||||
.IP *
|
||||
Daemonizing: When the -D option is specified, websockify runs in the background as a daemon process.
|
||||
|
||||
.IP *
|
||||
SSL (the wss:// WebSockets URI): This is detected automatically by websockify by sniffing the first byte sent from the client and then wrapping the socket if the data starts with '\\x16' or '\\x80' (indicating SSL).
|
||||
|
||||
.IP *
|
||||
Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option.
|
||||
|
||||
.IP *
|
||||
Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve.
|
||||
|
||||
.IP *
|
||||
Wrap a program: see the "Wrap a Program" section below.
|
||||
|
||||
.SS Wrap a Program
|
||||
|
||||
In addition to proxying from a source address to a target address (which may be on a different system), websockify has the ability to launch a program on the local system and proxy WebSockets traffic to a normal TCP port owned/bound by the program.
|
||||
|
||||
The is accomplished with a small LD_PRELOAD library (rebind.so) which intercepts bind() system calls by the program. The specified port is moved to a new localhost/loopback free high port. websockify then proxies WebSockets traffic directed to the original port to the new (moved) port of the program.
|
||||
|
||||
The program wrap mode is invoked by replacing the target with -- followed by the program command line to wrap.
|
||||
|
||||
`./websockify 2023 -- PROGRAM ARGS`
|
||||
|
||||
The --wrap-mode option can be used to indicate what action to take when the wrapped program exits or daemonizes.
|
||||
|
||||
Here is an example of using websockify to wrap the vncserver command (which backgrounds itself) for use with noVNC:
|
||||
|
||||
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||
|
||||
Here is an example of wrapping telnetd (from krb5-telnetd). telnetd exits after the connection closes so the wrap mode is set to respawn the command:
|
||||
|
||||
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||
|
||||
The wstelnet.html page demonstrates a simple WebSockets based telnet client.
|
||||
|
||||
.SS Use client certificate verification
|
||||
|
||||
This feature requires Python 2.7.9 or newer or Python 3.4 or newer.
|
||||
|
||||
The --verify-client option makes the server ask the client for a SSL certificate. Presenting a valid (not expired and trusted by any supplied certificate authority) certificate is required for the client connection. With -auth-plugin=ClientCertCNAuth, the client certificate can be checked against a list of authorised certificate users. Non-encrypted connection attempts always fail during authentication.
|
||||
|
||||
Here is an example of a vncsevrer with password-less, certificate-driven authentication:
|
||||
|
||||
`./websockify 5901 --cert=fullchain.pem --key=privkey.pem --ssl-only --verify-client --cafile=ca-certificates.crt --auth-plugin=ClientCertCNAuth --auth-source='jane@example.com Joe User9824510' --web=noVNC/ --wrap-mode=ignore -- vncserver :1 -geometry 1024x768 -SecurityTypes=None`
|
||||
|
||||
The --auth-source option takes a white-space separated list of common names. Depending on your clients certificates they can be verified email addresses, user-names or any other string used for identification.
|
||||
|
||||
The --cafile option selects a file containing concatenated certificates of authorities trusted for validating clients. If this option is omitted, system default list of CAs is used. Upon connect, the client should supply the whole certificate chain. If your clients are known not to send intermediate certificates, they can be appended to the ca-file as well.
|
||||
|
||||
Note: Most browsers ask the user to select a certificate only while connecting via HTTPS, not WebSockets. Connecting directly to the SSL secured WebSocket may cause the browser to abort the connection. If you want to connect via noVNC, the --web option should point to a copy of noVNC, so it is loaded from the same host.
|
||||
|
||||
.SH AUTHOR
|
||||
Joel Martin (github@martintribe.org)
|
||||
|
||||
.SH SEE ALSO
|
||||
|
||||
https://github.com/novnc/websockify/
|
||||
|
||||
https://github.com/novnc/websockify/wiki/
|
||||
|
||||
18
public/novnc/utils/websockify/rebind
Executable file
18
public/novnc/utils/websockify/rebind
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
usage() {
|
||||
echo "Usage: $(basename $0) OLD_PORT NEW_PORT COMMAND_LINE"
|
||||
echo
|
||||
echo "Launch COMMAND_LINE, but intercept system calls to bind"
|
||||
echo "to OLD_PORT and instead bind them to localhost:NEW_PORT"
|
||||
exit 2
|
||||
}
|
||||
|
||||
# Parameter defaults
|
||||
mydir=$(readlink -f $(dirname ${0}))
|
||||
|
||||
export REBIND_PORT_OLD="${1}"; shift
|
||||
export REBIND_PORT_NEW="${1}"; shift
|
||||
|
||||
LD_PRELOAD=${mydir}/rebind.so "${@}"
|
||||
|
||||
94
public/novnc/utils/websockify/rebind.c
Normal file
94
public/novnc/utils/websockify/rebind.c
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* rebind: Intercept bind calls and bind to a different port
|
||||
* Copyright 2010 Joel Martin
|
||||
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
*
|
||||
* Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and
|
||||
* REBIND_PORT_NEW environment variables are set then bind on the new
|
||||
* port (of localhost) instead of the old port.
|
||||
*
|
||||
* This allows a bridge/proxy (such as websockify) to run on the old port and
|
||||
* translate traffic to/from the new port.
|
||||
*
|
||||
* Usage:
|
||||
* LD_PRELOAD=./rebind.so \
|
||||
* REBIND_PORT_OLD=23 \
|
||||
* REBIND_PORT_NEW=2023 \
|
||||
* program
|
||||
*/
|
||||
|
||||
//#define DO_DEBUG 1
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define __USE_GNU 1 // Pull in RTLD_NEXT
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
|
||||
#if defined(DO_DEBUG)
|
||||
#define DEBUG(...) \
|
||||
fprintf(stderr, "rebind: "); \
|
||||
fprintf(stderr, __VA_ARGS__);
|
||||
#else
|
||||
#define DEBUG(...)
|
||||
#endif
|
||||
|
||||
|
||||
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
static void * (*func)();
|
||||
int do_move = 0;
|
||||
struct sockaddr_in * addr_in = (struct sockaddr_in *)addr;
|
||||
struct sockaddr_in addr_tmp;
|
||||
socklen_t addrlen_tmp;
|
||||
char * PORT_OLD, * PORT_NEW, * end1, * end2;
|
||||
int ret, oldport, newport, askport = htons(addr_in->sin_port);
|
||||
uint32_t askaddr = htons(addr_in->sin_addr.s_addr);
|
||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
|
||||
|
||||
DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n",
|
||||
sockfd, addrlen, askaddr, askport);
|
||||
|
||||
/* Determine if we should move this socket */
|
||||
if (addr_in->sin_family == AF_INET) {
|
||||
// TODO: support IPv6
|
||||
PORT_OLD = getenv("REBIND_OLD_PORT");
|
||||
PORT_NEW = getenv("REBIND_NEW_PORT");
|
||||
if (PORT_OLD && (*PORT_OLD != '\0') &&
|
||||
PORT_NEW && (*PORT_NEW != '\0')) {
|
||||
oldport = strtol(PORT_OLD, &end1, 10);
|
||||
newport = strtol(PORT_NEW, &end2, 10);
|
||||
if (oldport && (*end1 == '\0') &&
|
||||
newport && (*end2 == '\0') &&
|
||||
(oldport == askport)) {
|
||||
do_move = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! do_move) {
|
||||
/* Just pass everything right through to the real bind */
|
||||
ret = (long) func(sockfd, addr, addrlen);
|
||||
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n",
|
||||
sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport);
|
||||
|
||||
/* Use a temporary location for the new address information */
|
||||
addrlen_tmp = sizeof(addr_tmp);
|
||||
memcpy(&addr_tmp, addr, addrlen_tmp);
|
||||
|
||||
/* Bind to other port on the loopback instead */
|
||||
addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
addr_tmp.sin_port = htons(newport);
|
||||
ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);
|
||||
|
||||
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
|
||||
return ret;
|
||||
}
|
||||
4
public/novnc/utils/websockify/run
Executable file
4
public/novnc/utils/websockify/run
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
exec python3 -m websockify "$@"
|
||||
43
public/novnc/utils/websockify/setup.py
Normal file
43
public/novnc/utils/websockify/setup.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.11.0'
|
||||
name = 'websockify'
|
||||
long_description = open("README.md").read() + "\n" + \
|
||||
open("CHANGES.txt").read() + "\n"
|
||||
|
||||
setup(name=name,
|
||||
version=version,
|
||||
description="Websockify.",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
],
|
||||
keywords='noVNC websockify',
|
||||
license='LGPLv3',
|
||||
url="https://github.com/novnc/websockify",
|
||||
author="Joel Martin",
|
||||
author_email="github@martintribe.org",
|
||||
|
||||
packages=['websockify'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'numpy', 'requests',
|
||||
'jwcrypto',
|
||||
'redis',
|
||||
],
|
||||
zip_safe=False,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'websockify = websockify.websocketproxy:websockify_init',
|
||||
]
|
||||
},
|
||||
)
|
||||
5
public/novnc/utils/websockify/test-requirements.txt
Normal file
5
public/novnc/utils/websockify/test-requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
mock
|
||||
nose2
|
||||
six
|
||||
redis
|
||||
wrapt<=1.12.1;python_version<="3.4"
|
||||
139
public/novnc/utils/websockify/tests/echo.html
Normal file
139
public/novnc/utils/websockify/tests/echo.html
Normal file
@@ -0,0 +1,139 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebSockets Echo Test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Host: <input id='host' style='width:100'>
|
||||
Port: <input id='port' style='width:50'>
|
||||
Encrypt: <input id='encrypt' type='checkbox'>
|
||||
<input id='connectButton' type='button' value='Start' style='width:100px'
|
||||
onclick="connect();">
|
||||
|
||||
|
||||
<br>
|
||||
Log:<br>
|
||||
<textarea id="messages" style="font-size: 9;" cols=80 rows=25></textarea>
|
||||
</body>
|
||||
|
||||
|
||||
<script>
|
||||
var ws, host = null, port = null,
|
||||
msg_cnt = 0, send_cnt = 1, echoDelay = 500,
|
||||
echo_ref;
|
||||
|
||||
function message(str) {
|
||||
console.log(str);
|
||||
cell = document.getElementById('messages');
|
||||
cell.innerHTML += msg_cnt + ": " + str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
msg_cnt++;
|
||||
}
|
||||
|
||||
Array.prototype.pushStr = function (str) {
|
||||
var n = str.length;
|
||||
for (var i=0; i < n; i++) {
|
||||
this.push(str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function send_msg() {
|
||||
var str = "Message #" + send_cnt;
|
||||
var encoder = new TextEncoder();
|
||||
ws.send(encoder.encode(str));
|
||||
message("Sent message: '" + str + "'");
|
||||
send_cnt++;
|
||||
}
|
||||
|
||||
function update_stats() {
|
||||
document.getElementById('sent').innerHTML = sent;
|
||||
document.getElementById('received').innerHTML = received;
|
||||
document.getElementById('errors').innerHTML = errors;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var host = document.getElementById('host').value,
|
||||
port = document.getElementById('port').value,
|
||||
scheme = "ws://", uri;
|
||||
|
||||
console.log(">> connect");
|
||||
if ((!host) || (!port)) {
|
||||
console.log("must set host and port");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (document.getElementById('encrypt').checked) {
|
||||
scheme = "wss://";
|
||||
}
|
||||
uri = scheme + host + ":" + port;
|
||||
message("connecting to " + uri);
|
||||
|
||||
ws = new WebSocket(uri);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.addEventListener('message', function(e) {
|
||||
//console.log(">> WebSockets.onmessage");
|
||||
var decoder = new TextDecoder('UTF-8');
|
||||
var str = decoder.decode(e.data);
|
||||
|
||||
message("Received message '" + str + "'");
|
||||
//console.log("<< WebSockets.onmessage");
|
||||
});
|
||||
ws.addEventListener('open', function(e) {
|
||||
console.log(">> WebSockets.onopen");
|
||||
echo_ref = setInterval(send_msg, echoDelay);
|
||||
console.log("<< WebSockets.onopen");
|
||||
});
|
||||
ws.addEventListener('close', function(e) {
|
||||
console.log(">> WebSockets.onclose");
|
||||
if (echo_ref) {
|
||||
clearInterval(echo_ref);
|
||||
echo_ref = null;
|
||||
}
|
||||
console.log("<< WebSockets.onclose");
|
||||
});
|
||||
ws.addEventListener('error', function(e) {
|
||||
console.log(">> WebSockets.onerror");
|
||||
if (echo_ref) {
|
||||
clearInterval(echo_ref);
|
||||
echo_ref = null;
|
||||
}
|
||||
console.log("<< WebSockets.onerror");
|
||||
});
|
||||
|
||||
document.getElementById('connectButton').value = "Stop";
|
||||
document.getElementById('connectButton').onclick = disconnect;
|
||||
console.log("<< connect");
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
console.log(">> disconnect");
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (echo_ref) {
|
||||
clearInterval(echo_ref);
|
||||
}
|
||||
|
||||
document.getElementById('connectButton').value = "Start";
|
||||
document.getElementById('connectButton').onclick = connect;
|
||||
console.log("<< disconnect");
|
||||
}
|
||||
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
var url = document.location.href;
|
||||
document.getElementById('host').value = (url.match(/host=([^&#]*)/) || ['',window.location.hostname])[1];
|
||||
document.getElementById('port').value = (url.match(/port=([^&#]*)/) || ['',window.location.port])[1];
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
76
public/novnc/utils/websockify/tests/echo.py
Executable file
76
public/novnc/utils/websockify/tests/echo.py
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
A WebSocket server that echos back whatever it receives from the client.
|
||||
Copyright 2010 Joel Martin
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
You can make a cert/key with openssl using:
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
'''
|
||||
|
||||
import os, sys, select, optparse, logging
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler
|
||||
|
||||
class WebSocketEcho(WebSockifyRequestHandler):
|
||||
"""
|
||||
WebSockets server that echos back whatever is received from the
|
||||
client. """
|
||||
buffer_size = 8096
|
||||
|
||||
def new_websocket_client(self):
|
||||
"""
|
||||
Echo back whatever is received.
|
||||
"""
|
||||
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
cpartial = ""
|
||||
rlist = [self.request]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if cqueue or c_pend: wlist.append(self.request)
|
||||
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if self.request in outs:
|
||||
# Send queued target data to the client
|
||||
c_pend = self.send_frames(cqueue)
|
||||
cqueue = []
|
||||
|
||||
if self.request in ins:
|
||||
# Receive client data, decode it, and send it back
|
||||
frames, closed = self.recv_frames()
|
||||
cqueue.extend(frames)
|
||||
|
||||
if closed:
|
||||
break
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||
parser.add_option("--verbose", "-v", action="store_true",
|
||||
help="verbose messages and per frame traffic")
|
||||
parser.add_option("--cert", default="self.pem",
|
||||
help="SSL certificate file")
|
||||
parser.add_option("--key", default=None,
|
||||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted connections")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
try:
|
||||
if len(args) != 1: raise ValueError
|
||||
opts.listen_port = int(args[0])
|
||||
except ValueError:
|
||||
parser.error("Invalid arguments")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
opts.web = "."
|
||||
server = WebSockifyServer(WebSocketEcho, **opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
69
public/novnc/utils/websockify/tests/echo_client.py
Executable file
69
public/novnc/utils/websockify/tests/echo_client.py
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
import select
|
||||
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocket, \
|
||||
WebSocketWantReadError, WebSocketWantWriteError
|
||||
|
||||
parser = optparse.OptionParser(usage="%prog URL")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
if len(args) == 1:
|
||||
URL = args[0]
|
||||
else:
|
||||
parser.error("Invalid arguments")
|
||||
|
||||
sock = WebSocket()
|
||||
print("Connecting to %s..." % URL)
|
||||
sock.connect(URL)
|
||||
print("Connected.")
|
||||
|
||||
def send(msg):
|
||||
while True:
|
||||
try:
|
||||
sock.sendmsg(msg)
|
||||
break
|
||||
except WebSocketWantReadError:
|
||||
msg = ''
|
||||
ins, outs, excepts = select.select([sock], [], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
except WebSocketWantWriteError:
|
||||
msg = ''
|
||||
ins, outs, excepts = select.select([], [sock], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
def read():
|
||||
while True:
|
||||
try:
|
||||
return sock.recvmsg()
|
||||
except WebSocketWantReadError:
|
||||
ins, outs, excepts = select.select([sock], [], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
except WebSocketWantWriteError:
|
||||
ins, outs, excepts = select.select([], [sock], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
counter = 1
|
||||
while True:
|
||||
msg = "Message #%d" % counter
|
||||
counter += 1
|
||||
send(msg)
|
||||
print("Sent message: %r" % msg)
|
||||
|
||||
while True:
|
||||
ins, outs, excepts = select.select([sock], [], [], 1.0)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if ins == []:
|
||||
break
|
||||
|
||||
while True:
|
||||
msg = read()
|
||||
print("Received message: %r" % msg)
|
||||
|
||||
if not sock.pending():
|
||||
break
|
||||
27
public/novnc/utils/websockify/tests/fixtures/private.pem
vendored
Normal file
27
public/novnc/utils/websockify/tests/fixtures/private.pem
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEArwNQal2INbSfoVT50dZ0s8lQ+yMhu45TDc91iuwtDjlFBJ50
|
||||
E4m3/M6ESBW0S7UTP1bIOGkd/M+u38h0Aruo4qkngdguu9N3BnsU2kOeicdjxc+v
|
||||
tqRc7/kbkTdT4SrpG8EFP6T2U9U1gtBpLnau02gPrzjaQzyYDLGOBq+Ozt/mN0YJ
|
||||
UhJ3hlwi18dNKreTzWgJ6mmXQWS0eAmHx8TIs2Nz9x3EfRo9CIMuaaeUjRogIEg5
|
||||
Tg4xC00ZtDO0/EfgpFkeHJGVQA2DgdBJsr6rq69MjhMfFxRJItxJMJzP6an2HkJ8
|
||||
onUPBtjEBmk3/fnfiaflzRyEb5zdii6r2TD8TwIDAQABAoIBAGDrzu742WQUoYqx
|
||||
CqDAyWR/is9px1adHTW6vHexD8qewLAsKFBhpnjkzbE2A+EhaIVdRAipfifxxAC+
|
||||
fDC/SGouD2kDFe6Cz5nRM90kMXpP59s2hzL4l1d2d2PWZid+ohXysTtr2dbXbokB
|
||||
bh6DL5J4QKdjLsypk/MDqYneU5IQ1k9ezWzcRgM8/V3M+t+1dLRFLIWsSLbNUgbF
|
||||
px81efNw8E0voV/d7kZ+6RwUThPHqR0eyLm6djPwHE7/FarZIx4AImwV+9ex44CH
|
||||
OkrTFOVYenF6jEtYoUuqYCouaWtG7jNVM/f1fksoR8SD6PTq2vn7F4wTLXG1b+K7
|
||||
45PKMhECgYEA22NH8mK9ICFVd7S6caeAnki+K9cpwmiOUWEIuDInmPn5FOlv7awE
|
||||
uBFN86v14PqDBtF7Aagyib0NUPo7rIw5+V5SCBZv8gQatjZUkT0vZvpxPU5jmB++
|
||||
w58yfK7zgdAWCepLxIPyTA7CAT1dmiVmuosz2pJjbo4fecVG222IE10CgYEAzDg+
|
||||
RVlvMYGy04UMmUoUNeeRlW6km/W6dqQ7EtcxfDv4O7boRDTBSRBzfIsRdXHZhcHN
|
||||
gCeB2Uiz8IO3s0Yt0+y/6cTI60uJ4S7Mb2JvWJvDCKWhS3pE1BL+LJJC4Hn7khJH
|
||||
yHYFOLOfnuCbOs8VA7IMmbdTPHirIKWTT5j5H5sCgYEAygK/KweUUlOfWVyHGUQ9
|
||||
gIJG6iNzhlm0QmbxGnrET25N1t2kfNsadUsp1igPfhvuLocRltMDxiTYcCoabKWq
|
||||
dF5PdrcCWX1CA2o/sIUAcvhE8UiPGHKSu5qJaJnIC05KHNMq9UbyAurL5UxWNiwe
|
||||
TcMD+k01VYV0ojHvLvnKhNkCgYArkoh+xXE7D+A2zzl771lWkvz19DB88jYBoFLW
|
||||
V0HArw7str7h5pui2ja5yPZFp6/woQQWptdGpAN4erIUNxIKGIZt+0WfJnPZruGB
|
||||
lnAJaNp5GtXKQ+ExmofOvLo2KPCrHulf9QZyLakN/gBA0PQ74J5docbJrTld8tX2
|
||||
cr4cpwKBgHqr2zybmywAmjn8wY0bUjRAyhdN8eiwYaGPtOSFt6IcWxEnNbAo5Jc2
|
||||
KsywpagjFsXZsi4Obn2XsqR7VX5bNbpNXIyLaMwBOy7MixyecgPF8tu7I4zo/CWm
|
||||
7gewTKBhwVPTDAOzHqIpJGrOnUgzJM3ijkCWMn3eAh4ccOjsrKq9
|
||||
-----END RSA PRIVATE KEY-----
|
||||
9
public/novnc/utils/websockify/tests/fixtures/public.pem
vendored
Normal file
9
public/novnc/utils/websockify/tests/fixtures/public.pem
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwNQal2INbSfoVT50dZ0
|
||||
s8lQ+yMhu45TDc91iuwtDjlFBJ50E4m3/M6ESBW0S7UTP1bIOGkd/M+u38h0Aruo
|
||||
4qkngdguu9N3BnsU2kOeicdjxc+vtqRc7/kbkTdT4SrpG8EFP6T2U9U1gtBpLnau
|
||||
02gPrzjaQzyYDLGOBq+Ozt/mN0YJUhJ3hlwi18dNKreTzWgJ6mmXQWS0eAmHx8TI
|
||||
s2Nz9x3EfRo9CIMuaaeUjRogIEg5Tg4xC00ZtDO0/EfgpFkeHJGVQA2DgdBJsr6r
|
||||
q69MjhMfFxRJItxJMJzP6an2HkJ8onUPBtjEBmk3/fnfiaflzRyEb5zdii6r2TD8
|
||||
TwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
1
public/novnc/utils/websockify/tests/fixtures/symmetric.key
vendored
Normal file
1
public/novnc/utils/websockify/tests/fixtures/symmetric.key
vendored
Normal file
@@ -0,0 +1 @@
|
||||
secret_sauce
|
||||
261
public/novnc/utils/websockify/tests/latency.html
Normal file
261
public/novnc/utils/websockify/tests/latency.html
Normal file
@@ -0,0 +1,261 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebSockets Latency Test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Host: <input id='host' style='width:100'>
|
||||
Port: <input id='port' style='width:50'>
|
||||
Encrypt: <input id='encrypt' type='checkbox'>
|
||||
<br>
|
||||
Payload Size: <input id='payload_size' style='width:50'>
|
||||
Send Delay (ms): <input id='sendDelay' style='width:50' value="10">
|
||||
<input id='connectButton' type='button' value='Start' style='width:100px'
|
||||
onclick="connect();">
|
||||
|
||||
<br><br>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th align="right">Packets sent:</th>
|
||||
<td align="right"><div id='sent'></div></td>
|
||||
</tr><tr>
|
||||
<th align="right">Packets Received:</th>
|
||||
<td align="right"><div id='received'></div></td>
|
||||
</tr><tr>
|
||||
<th align="right">Average Latency:</th>
|
||||
<td align="right"><div id='laverage'></div></td>
|
||||
</tr><tr>
|
||||
<th align="right">40 Frame Running Average Latency:</th>
|
||||
<td align="right"><div id='lrunning'></div></td>
|
||||
</tr><tr>
|
||||
<th align="right">Minimum Latency:</th>
|
||||
<td align="right"><div id='lmin'></div></td>
|
||||
</tr><tr>
|
||||
<th align="right">Maximum Latency:</th>
|
||||
<td align="right"><div id='lmax'></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
Messages:<br>
|
||||
<textarea id="messages" style="font-size: 9;" cols=80 rows=10></textarea>
|
||||
</body>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var host = null, port = null, sendDelay = 0,
|
||||
ws = null, send_ref = null,
|
||||
sent, received, latencies, ltotal, laverage, lrunning, lmin, lmax,
|
||||
run_length = 40,
|
||||
payload_size = 2000, payload,
|
||||
msg_cnt = 0, recv_seq = 0, send_seq = 0;
|
||||
|
||||
Array.prototype.pushStr = function (str) {
|
||||
var n = str.length;
|
||||
for (var i=0; i < n; i++) {
|
||||
this.push(str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function message(str) {
|
||||
console.log(str);
|
||||
cell = document.getElementById('messages');
|
||||
msg_cnt++;
|
||||
cell.innerHTML += msg_cnt + ": " + str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
}
|
||||
|
||||
|
||||
function add (x,y) {
|
||||
return parseInt(x,10)+parseInt(y,10);
|
||||
}
|
||||
|
||||
function recvMsg(data) {
|
||||
//console.log(">> check_respond");
|
||||
var i, now, arr, first, last, arr, latency;
|
||||
|
||||
now = (new Date()).getTime(); // Early as possible
|
||||
|
||||
arr = new Uint8Array(data);
|
||||
first = String.fromCharCode(arr[0]);
|
||||
last = String.fromCharCode(arr[arr.length-1]);
|
||||
|
||||
if (first != "^") {
|
||||
message("Error: packet missing start char '^'");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
if (last != "$") {
|
||||
message("Error: packet missing end char '$'");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
text = ''
|
||||
for (var i = 1; i < arr.length-1; i++) {
|
||||
text += String.fromCharCode(arr[i]);
|
||||
}
|
||||
arr = text.split(':');
|
||||
seq = arr[0];
|
||||
timestamp = parseInt(arr[1],10);
|
||||
rpayload = arr[2];
|
||||
|
||||
if (seq != recv_seq) {
|
||||
message("Error: expected seq " + recv_seq + " but got " + seq);
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
recv_seq++;
|
||||
if (payload !== rpayload) {
|
||||
message("Payload corrupt");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
received++;
|
||||
|
||||
latency = now - timestamp;
|
||||
latencies.push(latency);
|
||||
if (latencies.length > run_length) {
|
||||
latencies.shift();
|
||||
}
|
||||
ltotal += latency;
|
||||
laverage = ltotal / received;
|
||||
lrunning = 0;
|
||||
for (var i=0; i < latencies.length; i++) {
|
||||
lrunning += latencies[i];
|
||||
}
|
||||
lrunning = lrunning / latencies.length;
|
||||
|
||||
if (latency < lmin) {
|
||||
lmin = latency;
|
||||
}
|
||||
if (latency > lmax) {
|
||||
lmax = latency;
|
||||
}
|
||||
|
||||
showStats();
|
||||
//console.log("<< check_respond");
|
||||
}
|
||||
|
||||
function sendMsg() {
|
||||
var arr = [];
|
||||
timestamp = (new Date()).getTime();
|
||||
arr.pushStr("^" + send_seq + ":" + timestamp + ":" + payload + "$");
|
||||
send_seq ++;
|
||||
ws.send(new Uint8Array(arr));
|
||||
sent++;
|
||||
|
||||
showStats();
|
||||
send_ref = setTimeout(sendMsg, sendDelay);
|
||||
}
|
||||
|
||||
function showStats() {
|
||||
document.getElementById('sent').innerHTML = sent;
|
||||
document.getElementById('received').innerHTML = received;
|
||||
document.getElementById('laverage').innerHTML = laverage.toFixed(2);
|
||||
document.getElementById('lrunning').innerHTML = lrunning.toFixed(2);
|
||||
document.getElementById('lmin').innerHTML = lmin.toFixed(2);
|
||||
document.getElementById('lmax').innerHTML = lmax.toFixed(2);
|
||||
}
|
||||
|
||||
function init_ws() {
|
||||
console.log(">> init_ws");
|
||||
var scheme = "ws://";
|
||||
if (document.getElementById('encrypt').checked) {
|
||||
scheme = "wss://";
|
||||
}
|
||||
var uri = scheme + host + ":" + port;
|
||||
console.log("connecting to " + uri);
|
||||
ws = new WebSocket(uri);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.addEventListener('message', function(e) {
|
||||
recvMsg(e.data);
|
||||
});
|
||||
ws.addEventListener('open', function() {
|
||||
send_ref = setTimeout(sendMsg, sendDelay);
|
||||
});
|
||||
ws.addEventListener('close', function(e) {
|
||||
disconnect();
|
||||
});
|
||||
ws.addEventListener('error', function(e) {
|
||||
message("Websock error: " + e);
|
||||
disconnect();
|
||||
});
|
||||
|
||||
console.log("<< init_ws");
|
||||
}
|
||||
|
||||
function connect() {
|
||||
console.log(">> connect");
|
||||
host = document.getElementById('host').value;
|
||||
port = document.getElementById('port').value;
|
||||
payload_size = parseInt(document.getElementById('payload_size').value, 10);
|
||||
sendDelay = parseInt(document.getElementById('sendDelay').value, 10);
|
||||
|
||||
if ((!host) || (!port)) {
|
||||
console.log("must set host and port");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
init_ws();
|
||||
|
||||
// Populate payload data
|
||||
var numlist = []
|
||||
for (var i=0; i < payload_size; i++) {
|
||||
numlist.push( Math.floor(Math.random()*10) );
|
||||
}
|
||||
payload = numlist.join('');
|
||||
|
||||
// Initialize stats
|
||||
sent = 0;
|
||||
received = 0;
|
||||
latencies = [];
|
||||
ltotal = 0;
|
||||
laverage = 0;
|
||||
lrunning = 0;
|
||||
lmin = 999999999;
|
||||
lmax = 0;
|
||||
|
||||
document.getElementById('connectButton').value = "Stop";
|
||||
document.getElementById('connectButton').onclick = disconnect;
|
||||
console.log("<< connect");
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
console.log(">> disconnect");
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (send_ref) {
|
||||
clearInterval(send_ref);
|
||||
send_ref = null;
|
||||
}
|
||||
showStats(); // Final numbers
|
||||
recv_seq = 0;
|
||||
send_seq = 0;
|
||||
|
||||
document.getElementById('connectButton').value = "Start";
|
||||
document.getElementById('connectButton').onclick = connect;
|
||||
console.log("<< disconnect");
|
||||
}
|
||||
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
var url = document.location.href;
|
||||
document.getElementById('host').value = (url.match(/host=([^&#]*)/) || ['',window.location.hostname])[1];
|
||||
document.getElementById('port').value = (url.match(/port=([^&#]*)/) || ['',window.location.port])[1];
|
||||
document.getElementById('payload_size').value = payload_size;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
1
public/novnc/utils/websockify/tests/latency.py
Symbolic link
1
public/novnc/utils/websockify/tests/latency.py
Symbolic link
@@ -0,0 +1 @@
|
||||
echo.py
|
||||
223
public/novnc/utils/websockify/tests/load.html
Normal file
223
public/novnc/utils/websockify/tests/load.html
Normal file
@@ -0,0 +1,223 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebSockets Load Test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Host: <input id='host' style='width:100'>
|
||||
Port: <input id='port' style='width:50'>
|
||||
Encrypt: <input id='encrypt' type='checkbox'>
|
||||
Send Delay (ms): <input id='sendDelay' style='width:50' value="100">
|
||||
<input id='connectButton' type='button' value='Start' style='width:100px'
|
||||
onclick="connect();">
|
||||
|
||||
<br><br>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th align="right">Packets sent:</th>
|
||||
<td align="right"><div id='sent'>0</div></td>
|
||||
</tr><tr>
|
||||
<th align="right">Good Packets Received:</th>
|
||||
<td align="right"><div id='received'>0</div></td>
|
||||
</tr><tr>
|
||||
<th align="right">Errors (Bad Packets Received:)</th>
|
||||
<td align="right"><div id='errors'>0</div></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
Errors:<br>
|
||||
<textarea id="error" style="font-size: 9;" cols=80 rows=25></textarea>
|
||||
</body>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
function error(str) {
|
||||
console.error(str);
|
||||
cell = document.getElementById('error');
|
||||
cell.innerHTML += errors + ": " + str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
}
|
||||
|
||||
var host = null, port = null, sendDelay = 0;
|
||||
var ws = null, update_ref = null, send_ref = null;
|
||||
var sent = 0, received = 0, errors = 0;
|
||||
var max_send = 2000;
|
||||
var recv_seq = 0, send_seq = 0;
|
||||
|
||||
Array.prototype.pushStr = function (str) {
|
||||
var n = str.length;
|
||||
for (var i=0; i < n; i++) {
|
||||
this.push(str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function add (x,y) {
|
||||
return parseInt(x,10)+parseInt(y,10);
|
||||
}
|
||||
|
||||
function check_respond(data) {
|
||||
//console.log(">> check_respond");
|
||||
var first, last, str, length, chksum, nums, arr;
|
||||
first = String.fromCharCode(data[0]);
|
||||
last = String.fromCharCode(data[data.length-1]);
|
||||
|
||||
if (first != "^") {
|
||||
errors++;
|
||||
error("Packet missing start char '^'");
|
||||
return;
|
||||
}
|
||||
if (last != "$") {
|
||||
errors++;
|
||||
error("Packet missing end char '$'");
|
||||
return;
|
||||
}
|
||||
text = ''
|
||||
for (var i = 1; i < data.length-1; i++) {
|
||||
text += String.fromCharCode(data[i]);
|
||||
}
|
||||
arr = text.split(':');
|
||||
seq = arr[0];
|
||||
length = arr[1];
|
||||
chksum = arr[2];
|
||||
nums = arr[3];
|
||||
|
||||
//console.log(" length:" + length + " chksum:" + chksum + " nums:" + nums);
|
||||
if (seq != recv_seq) {
|
||||
errors++;
|
||||
error("Expected seq " + recv_seq + " but got " + seq);
|
||||
recv_seq = parseInt(seq,10) + 1; // Back on track
|
||||
return;
|
||||
}
|
||||
recv_seq++;
|
||||
if (nums.length != length) {
|
||||
errors++;
|
||||
error("Expected length " + length + " but got " + nums.length);
|
||||
return;
|
||||
}
|
||||
//real_chksum = nums.reduce(add);
|
||||
real_chksum = 0;
|
||||
for (var i=0; i < nums.length; i++) {
|
||||
real_chksum += parseInt(nums.charAt(i), 10);
|
||||
}
|
||||
if (real_chksum != chksum) {
|
||||
errors++
|
||||
error("Expected chksum " + chksum + " but real chksum is " + real_chksum);
|
||||
return;
|
||||
}
|
||||
received++;
|
||||
//console.log(" Packet checks out: length:" + length + " chksum:" + chksum);
|
||||
//console.log("<< check_respond");
|
||||
}
|
||||
|
||||
function send() {
|
||||
var length = Math.floor(Math.random()*(max_send-9)) + 10; // 10 - max_send
|
||||
var numlist = [], arr = [];
|
||||
for (var i=0; i < length; i++) {
|
||||
numlist.push( Math.floor(Math.random()*10) );
|
||||
}
|
||||
//chksum = numlist.reduce(add);
|
||||
chksum = 0;
|
||||
for (var i=0; i < numlist.length; i++) {
|
||||
chksum += parseInt(numlist[i], 10);
|
||||
}
|
||||
var nums = numlist.join('');
|
||||
arr.pushStr("^" + send_seq + ":" + length + ":" + chksum + ":" + nums + "$")
|
||||
send_seq ++;
|
||||
ws.send(new Uint8Array(arr));
|
||||
sent++;
|
||||
}
|
||||
|
||||
function update_stats() {
|
||||
document.getElementById('sent').innerHTML = sent;
|
||||
document.getElementById('received').innerHTML = received;
|
||||
document.getElementById('errors').innerHTML = errors;
|
||||
}
|
||||
|
||||
function init_ws() {
|
||||
console.log(">> init_ws");
|
||||
var scheme = "ws://";
|
||||
if (document.getElementById('encrypt').checked) {
|
||||
scheme = "wss://";
|
||||
}
|
||||
var uri = scheme + host + ":" + port;
|
||||
console.log("connecting to " + uri);
|
||||
ws = new WebSocket(uri);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.addEventListener('message', function(e) {
|
||||
//console.log(">> WebSockets.onmessage");
|
||||
arr = new Uint8Array(e.data);
|
||||
check_respond(arr);
|
||||
//console.log("<< WebSockets.onmessage");
|
||||
});
|
||||
ws.addEventListener('open', function() {
|
||||
console.log(">> WebSockets.onopen");
|
||||
send_ref = setInterval(send, sendDelay);
|
||||
console.log("<< WebSockets.onopen");
|
||||
});
|
||||
ws.addEventListener('close', function(e) {
|
||||
console.log(">> WebSockets.onclose");
|
||||
clearInterval(send_ref);
|
||||
console.log("<< WebSockets.onclose");
|
||||
});
|
||||
ws.addEventListener('error', function(e) {
|
||||
console.log(">> WebSockets.onerror");
|
||||
console.log(" " + e);
|
||||
console.log("<< WebSockets.onerror");
|
||||
});
|
||||
|
||||
console.log("<< init_ws");
|
||||
}
|
||||
|
||||
function connect() {
|
||||
console.log(">> connect");
|
||||
host = document.getElementById('host').value;
|
||||
port = document.getElementById('port').value;
|
||||
sendDelay = parseInt(document.getElementById('sendDelay').value, 10);
|
||||
if ((!host) || (!port)) {
|
||||
console.log("must set host and port");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
init_ws();
|
||||
update_ref = setInterval(update_stats, 1);
|
||||
|
||||
document.getElementById('connectButton').value = "Stop";
|
||||
document.getElementById('connectButton').onclick = disconnect;
|
||||
console.log("<< connect");
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
console.log(">> disconnect");
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
clearInterval(update_ref);
|
||||
update_stats(); // Final numbers
|
||||
recv_seq = 0;
|
||||
send_seq = 0;
|
||||
|
||||
document.getElementById('connectButton').value = "Start";
|
||||
document.getElementById('connectButton').onclick = connect;
|
||||
console.log("<< disconnect");
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
var url = document.location.href;
|
||||
document.getElementById('host').value = (url.match(/host=([^&#]*)/) || ['',window.location.hostname])[1];
|
||||
document.getElementById('port').value = (url.match(/port=([^&#]*)/) || ['',window.location.port])[1];
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
168
public/novnc/utils/websockify/tests/load.py
Executable file
168
public/novnc/utils/websockify/tests/load.py
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
WebSocket server-side load test program. Sends and receives traffic
|
||||
that has a random payload (length and content) that is checksummed and
|
||||
given a sequence number. Any errors are reported and counted.
|
||||
'''
|
||||
|
||||
import sys, os, select, random, time, optparse, logging
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler
|
||||
|
||||
class WebSocketLoadServer(WebSockifyServer):
|
||||
|
||||
recv_cnt = 0
|
||||
send_cnt = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.delay = kwargs.pop('delay')
|
||||
|
||||
WebSockifyServer.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class WebSocketLoad(WebSockifyRequestHandler):
|
||||
|
||||
max_packet_size = 10000
|
||||
|
||||
def new_websocket_client(self):
|
||||
print "Prepopulating random array"
|
||||
self.rand_array = []
|
||||
for i in range(0, self.max_packet_size):
|
||||
self.rand_array.append(random.randint(0, 9))
|
||||
|
||||
self.errors = 0
|
||||
self.send_cnt = 0
|
||||
self.recv_cnt = 0
|
||||
|
||||
self.responder(self.request)
|
||||
|
||||
print "accumulated errors:", self.errors
|
||||
self.errors = 0
|
||||
|
||||
def responder(self, client):
|
||||
c_pend = 0
|
||||
cqueue = []
|
||||
cpartial = ""
|
||||
socks = [client]
|
||||
last_send = time.time() * 1000
|
||||
|
||||
while True:
|
||||
ins, outs, excepts = select.select(socks, socks, socks, 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if client in ins:
|
||||
frames, closed = self.recv_frames()
|
||||
|
||||
err = self.check(frames)
|
||||
if err:
|
||||
self.errors = self.errors + 1
|
||||
print err
|
||||
|
||||
if closed:
|
||||
break
|
||||
|
||||
now = time.time() * 1000
|
||||
if client in outs:
|
||||
if c_pend:
|
||||
last_send = now
|
||||
c_pend = self.send_frames()
|
||||
elif now > (last_send + self.server.delay):
|
||||
last_send = now
|
||||
c_pend = self.send_frames([self.generate()])
|
||||
|
||||
def generate(self):
|
||||
length = random.randint(10, self.max_packet_size)
|
||||
numlist = self.rand_array[self.max_packet_size-length:]
|
||||
# Error in length
|
||||
#numlist.append(5)
|
||||
chksum = sum(numlist)
|
||||
# Error in checksum
|
||||
#numlist[0] = 5
|
||||
nums = "".join( [str(n) for n in numlist] )
|
||||
data = "^%d:%d:%d:%s$" % (self.send_cnt, length, chksum, nums)
|
||||
self.send_cnt += 1
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def check(self, frames):
|
||||
|
||||
err = ""
|
||||
for data in frames:
|
||||
if data.count('$') > 1:
|
||||
raise Exception("Multiple parts within single packet")
|
||||
if len(data) == 0:
|
||||
self.traffic("_")
|
||||
continue
|
||||
|
||||
if data[0] != "^":
|
||||
err += "buf did not start with '^'\n"
|
||||
continue
|
||||
|
||||
try:
|
||||
cnt, length, chksum, nums = data[1:-1].split(':')
|
||||
cnt = int(cnt)
|
||||
length = int(length)
|
||||
chksum = int(chksum)
|
||||
except ValueError:
|
||||
print "\n<BOF>" + repr(data) + "<EOF>"
|
||||
err += "Invalid data format\n"
|
||||
continue
|
||||
|
||||
if self.recv_cnt != cnt:
|
||||
err += "Expected count %d but got %d\n" % (self.recv_cnt, cnt)
|
||||
self.recv_cnt = cnt + 1
|
||||
continue
|
||||
|
||||
self.recv_cnt += 1
|
||||
|
||||
if len(nums) != length:
|
||||
err += "Expected length %d but got %d\n" % (length, len(nums))
|
||||
continue
|
||||
|
||||
inv = nums.translate(None, "0123456789")
|
||||
if inv:
|
||||
err += "Invalid characters found: %s\n" % inv
|
||||
continue
|
||||
|
||||
real_chksum = 0
|
||||
for num in nums:
|
||||
real_chksum += int(num)
|
||||
|
||||
if real_chksum != chksum:
|
||||
err += "Expected checksum %d but real chksum is %d\n" % (chksum, real_chksum)
|
||||
return err
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||
parser.add_option("--verbose", "-v", action="store_true",
|
||||
help="verbose messages and per frame traffic")
|
||||
parser.add_option("--cert", default="self.pem",
|
||||
help="SSL certificate file")
|
||||
parser.add_option("--key", default=None,
|
||||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted connections")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
try:
|
||||
if len(args) != 1: raise ValueError
|
||||
opts.listen_port = int(args[0])
|
||||
|
||||
if len(args) not in [1,2]: raise ValueError
|
||||
opts.listen_port = int(args[0])
|
||||
if len(args) == 2:
|
||||
opts.delay = int(args[1])
|
||||
else:
|
||||
opts.delay = 10
|
||||
except ValueError:
|
||||
parser.error("Invalid arguments")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
opts.web = "."
|
||||
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
145
public/novnc/utils/websockify/tests/plain_echo.html
Normal file
145
public/novnc/utils/websockify/tests/plain_echo.html
Normal file
@@ -0,0 +1,145 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebSockets Echo Test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Host: <input id='host' style='width:100'>
|
||||
Port: <input id='port' style='width:50'>
|
||||
Encrypt: <input id='encrypt' type='checkbox'>
|
||||
<input id='connectButton' type='button' value='Start' style='width:100px'
|
||||
onclick="connect();">
|
||||
|
||||
|
||||
<br>
|
||||
Log:<br>
|
||||
<textarea id="messages" style="font-size: 9;" cols=80 rows=25></textarea>
|
||||
</body>
|
||||
|
||||
|
||||
<script>
|
||||
var ws, host = null, port = null,
|
||||
msg_cnt = 0, send_cnt = 1, echoDelay = 500,
|
||||
echo_ref;
|
||||
|
||||
function message(str) {
|
||||
console.log(str);
|
||||
cell = document.getElementById('messages');
|
||||
cell.innerHTML += msg_cnt + ": " + str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
msg_cnt++;
|
||||
}
|
||||
|
||||
Array.prototype.pushStr = function (str) {
|
||||
var n = str.length;
|
||||
for (var i=0; i < n; i++) {
|
||||
this.push(str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function send_msg() {
|
||||
if (ws.bufferedAmount > 0) {
|
||||
console.log("Delaying send");
|
||||
return;
|
||||
}
|
||||
var str = "Message #" + send_cnt, arr = [];
|
||||
var encoder = new TextEncoder();
|
||||
ws.send(encoder.encode(str));
|
||||
message("Sent message: '" + str + "'");
|
||||
send_cnt++;
|
||||
}
|
||||
|
||||
function update_stats() {
|
||||
document.getElementById('sent').innerHTML = sent;
|
||||
document.getElementById('received').innerHTML = received;
|
||||
document.getElementById('errors').innerHTML = errors;
|
||||
}
|
||||
|
||||
function init_ws() {
|
||||
console.log(">> init_ws");
|
||||
console.log("<< init_ws");
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var host = document.getElementById('host').value,
|
||||
port = document.getElementById('port').value,
|
||||
scheme = "ws://", uri;
|
||||
|
||||
console.log(">> connect");
|
||||
if ((!host) || (!port)) {
|
||||
console.log("must set host and port");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (document.getElementById('encrypt').checked) {
|
||||
scheme = "wss://";
|
||||
}
|
||||
uri = scheme + host + ":" + port;
|
||||
message("connecting to " + uri);
|
||||
ws = new WebSocket(uri);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.addEventListener('message', function(e) {
|
||||
//console.log(">> WebSockets.onmessage");
|
||||
var decoder = new TextDecoder('UTF-8');
|
||||
var str = decoder.decode(e.data);
|
||||
message("Received message '" + str + "'");
|
||||
//console.log("<< WebSockets.onmessage");
|
||||
});
|
||||
ws.addEventListener('open', function(e) {
|
||||
console.log(">> WebSockets.onopen");
|
||||
echo_ref = setInterval(send_msg, echoDelay);
|
||||
console.log("<< WebSockets.onopen");
|
||||
});
|
||||
ws.addEventListener('close', function(e) {
|
||||
console.log(">> WebSockets.onclose");
|
||||
if (echo_ref) {
|
||||
clearInterval(echo_ref);
|
||||
echo_ref = null;
|
||||
}
|
||||
console.log("<< WebSockets.onclose");
|
||||
});
|
||||
ws.addEventListener('error', function(e) {
|
||||
console.log(">> WebSockets.onerror");
|
||||
if (echo_ref) {
|
||||
clearInterval(echo_ref);
|
||||
echo_ref = null;
|
||||
}
|
||||
console.log("<< WebSockets.onerror");
|
||||
});
|
||||
|
||||
document.getElementById('connectButton').value = "Stop";
|
||||
document.getElementById('connectButton').onclick = disconnect;
|
||||
console.log("<< connect");
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
console.log(">> disconnect");
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (echo_ref) {
|
||||
clearInterval(echo_ref);
|
||||
}
|
||||
|
||||
document.getElementById('connectButton').value = "Start";
|
||||
document.getElementById('connectButton').onclick = connect;
|
||||
console.log("<< disconnect");
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
var url = document.location.href;
|
||||
document.getElementById('host').value = (url.match(/host=([^&#]*)/) || ['',window.location.hostname])[1];
|
||||
document.getElementById('port').value = (url.match(/port=([^&#]*)/) || ['',window.location.port])[1];
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
65
public/novnc/utils/websockify/tests/simple.html
Normal file
65
public/novnc/utils/websockify/tests/simple.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Websock Simple Client</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
WebSocket/websockify URI: <input id='target'>
|
||||
<input id='connectButton' type='button' value='Connect'
|
||||
onclick="connect();">
|
||||
<br> <br>
|
||||
<input id='sendText'>
|
||||
<input id='sendButton' type='button' value='Send' disabled
|
||||
onclick="send();">
|
||||
<br> <br>
|
||||
Log:<br><textarea id="messages" cols=80 rows=25></textarea>
|
||||
</body>
|
||||
|
||||
|
||||
<script>
|
||||
var document.getElementById = function(id) { return document.getElementById(id); },
|
||||
ws = null, msgs = document.getElementById('messages');
|
||||
|
||||
function msg(str) {
|
||||
msgs.innerHTML += str + "\n";
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var uri = document.getElementById('target').value;
|
||||
msg("connecting to: " + uri);
|
||||
ws = new WebSocket(uri);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.addEventListener('open', function () {
|
||||
msg("Connected");
|
||||
});
|
||||
ws.addEventListener('message', function (e) {
|
||||
msg("Received: " + e.data);
|
||||
});
|
||||
ws.addEventListener('close', function () {
|
||||
disconnect();
|
||||
msg("Disconnected");
|
||||
});
|
||||
|
||||
document.getElementById('connectButton').value = "Disconnect";
|
||||
document.getElementById('connectButton').onclick = disconnect;
|
||||
document.getElementById('sendButton').disabled = false;
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (ws) { ws.close(); }
|
||||
ws = null;
|
||||
|
||||
document.getElementById('connectButton').value = "Connect";
|
||||
document.getElementById('connectButton').onclick = connect;
|
||||
document.getElementById('sendButton').disabled = true;
|
||||
}
|
||||
|
||||
function send() {
|
||||
msg("Sending: " + document.getElementById('sendText').value);
|
||||
ws.send_string(document.getElementById('sendText').value);
|
||||
};
|
||||
</script>
|
||||
|
||||
</html>
|
||||
28
public/novnc/utils/websockify/tests/test_auth_plugins.py
Normal file
28
public/novnc/utils/websockify/tests/test_auth_plugins.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
""" Unit tests for Authentication plugins"""
|
||||
|
||||
from websockify.auth_plugins import BasicHTTPAuth, AuthenticationError
|
||||
import unittest
|
||||
|
||||
|
||||
class BasicHTTPAuthTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.plugin = BasicHTTPAuth('Aladdin:open sesame')
|
||||
|
||||
def test_no_auth(self):
|
||||
headers = {}
|
||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||
|
||||
def test_invalid_password(self):
|
||||
headers = {'Authorization': 'Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0'}
|
||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||
|
||||
def test_valid_password(self):
|
||||
headers = {'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}
|
||||
self.plugin.authenticate(headers, 'localhost', '1234')
|
||||
|
||||
def test_garbage_auth(self):
|
||||
headers = {'Authorization': 'Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
|
||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||
355
public/novnc/utils/websockify/tests/test_token_plugins.py
Normal file
355
public/novnc/utils/websockify/tests/test_token_plugins.py
Normal file
@@ -0,0 +1,355 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
""" Unit tests for Token plugins"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open, MagicMock
|
||||
from jwcrypto import jwt, jwk
|
||||
|
||||
from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi, TokenRedis
|
||||
|
||||
class ReadOnlyTokenFileTestCase(unittest.TestCase):
|
||||
patch('os.path.isdir', MagicMock(return_value=False))
|
||||
def test_empty(self):
|
||||
plugin = ReadOnlyTokenFile('configfile')
|
||||
|
||||
config = ""
|
||||
pyopen = mock_open(read_data=config)
|
||||
|
||||
with patch("websockify.token_plugins.open", pyopen, create=True):
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
pyopen.assert_called_once_with('configfile')
|
||||
self.assertIsNone(result)
|
||||
|
||||
patch('os.path.isdir', MagicMock(return_value=False))
|
||||
def test_simple(self):
|
||||
plugin = ReadOnlyTokenFile('configfile')
|
||||
|
||||
config = "testhost: remote_host:remote_port"
|
||||
pyopen = mock_open(read_data=config)
|
||||
|
||||
with patch("websockify.token_plugins.open", pyopen, create=True):
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
pyopen.assert_called_once_with('configfile')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
patch('os.path.isdir', MagicMock(return_value=False))
|
||||
def test_tabs(self):
|
||||
plugin = ReadOnlyTokenFile('configfile')
|
||||
|
||||
config = "testhost:\tremote_host:remote_port"
|
||||
pyopen = mock_open(read_data=config)
|
||||
|
||||
with patch("websockify.token_plugins.open", pyopen, create=True):
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
pyopen.assert_called_once_with('configfile')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
class JWSTokenTestCase(unittest.TestCase):
|
||||
def test_asymmetric_jws_token_plugin(self):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
key = jwk.JWK()
|
||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||
key.import_from_pem(private_key)
|
||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||
jwt_token.make_signed_token(key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||
plugin = JWTTokenApi("wrong.pub")
|
||||
|
||||
key = jwk.JWK()
|
||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||
key.import_from_pem(private_key)
|
||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||
jwt_token.make_signed_token(key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch('time.time')
|
||||
def test_jwt_valid_time(self, mock_time):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
key = jwk.JWK()
|
||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||
key.import_from_pem(private_key)
|
||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
|
||||
jwt_token.make_signed_token(key)
|
||||
mock_time.return_value = 150
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
@patch('time.time')
|
||||
def test_jwt_early_time(self, mock_time):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
key = jwk.JWK()
|
||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||
key.import_from_pem(private_key)
|
||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
|
||||
jwt_token.make_signed_token(key)
|
||||
mock_time.return_value = 50
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch('time.time')
|
||||
def test_jwt_late_time(self, mock_time):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
key = jwk.JWK()
|
||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||
key.import_from_pem(private_key)
|
||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
|
||||
jwt_token.make_signed_token(key)
|
||||
mock_time.return_value = 250
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_symmetric_jws_token_plugin(self):
|
||||
plugin = JWTTokenApi("./tests/fixtures/symmetric.key")
|
||||
|
||||
secret = open("./tests/fixtures/symmetric.key").read()
|
||||
key = jwk.JWK()
|
||||
key.import_key(kty="oct",k=secret)
|
||||
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||
jwt_token.make_signed_token(key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
def test_symmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||
plugin = JWTTokenApi("wrong_sauce")
|
||||
|
||||
secret = open("./tests/fixtures/symmetric.key").read()
|
||||
key = jwk.JWK()
|
||||
key.import_key(kty="oct",k=secret)
|
||||
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||
jwt_token.make_signed_token(key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_asymmetric_jwe_token_plugin(self):
|
||||
plugin = JWTTokenApi("./tests/fixtures/private.pem")
|
||||
|
||||
private_key = jwk.JWK()
|
||||
public_key = jwk.JWK()
|
||||
private_key_data = open("./tests/fixtures/private.pem", "rb").read()
|
||||
public_key_data = open("./tests/fixtures/public.pem", "rb").read()
|
||||
private_key.import_from_pem(private_key_data)
|
||||
public_key.import_from_pem(public_key_data)
|
||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||
jwt_token.make_signed_token(private_key)
|
||||
jwe_token = jwt.JWT(header={"alg": "RSA-OAEP", "enc": "A256CBC-HS512"},
|
||||
claims=jwt_token.serialize())
|
||||
jwe_token.make_encrypted_token(public_key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
class TokenRedisTestCase(unittest.TestCase):
|
||||
@patch('redis.Redis')
|
||||
def test_empty(self, mock_redis):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
instance = mock_redis.return_value
|
||||
instance.get.return_value = None
|
||||
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
instance.get.assert_called_once_with('testhost')
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch('redis.Redis')
|
||||
def test_simple(self, mock_redis):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
instance = mock_redis.return_value
|
||||
instance.get.return_value = b'{"host": "remote_host:remote_port"}'
|
||||
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
instance.get.assert_called_once_with('testhost')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], 'remote_host')
|
||||
self.assertEqual(result[1], 'remote_port')
|
||||
|
||||
@patch('redis.Redis')
|
||||
def test_json_token_with_spaces(self, mock_redis):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
instance = mock_redis.return_value
|
||||
instance.get.return_value = b' {"host": "remote_host:remote_port"} '
|
||||
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
instance.get.assert_called_once_with('testhost')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], 'remote_host')
|
||||
self.assertEqual(result[1], 'remote_port')
|
||||
|
||||
@patch('redis.Redis')
|
||||
def test_text_token(self, mock_redis):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
instance = mock_redis.return_value
|
||||
instance.get.return_value = b'remote_host:remote_port'
|
||||
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
instance.get.assert_called_once_with('testhost')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], 'remote_host')
|
||||
self.assertEqual(result[1], 'remote_port')
|
||||
|
||||
@patch('redis.Redis')
|
||||
def test_text_token_with_spaces(self, mock_redis):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
instance = mock_redis.return_value
|
||||
instance.get.return_value = b' remote_host:remote_port '
|
||||
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
instance.get.assert_called_once_with('testhost')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], 'remote_host')
|
||||
self.assertEqual(result[1], 'remote_port')
|
||||
|
||||
@patch('redis.Redis')
|
||||
def test_invalid_token(self, mock_redis):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
instance = mock_redis.return_value
|
||||
instance.get.return_value = b'{"host": "remote_host:remote_port" '
|
||||
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
instance.get.assert_called_once_with('testhost')
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_src_only_host(self):
|
||||
plugin = TokenRedis('127.0.0.1')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_port(self):
|
||||
plugin = TokenRedis('127.0.0.1:1234')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 1234)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_port_db(self):
|
||||
plugin = TokenRedis('127.0.0.1:1234:2')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 1234)
|
||||
self.assertEqual(plugin._db, 2)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_port_db_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1:1234:2:verysecret')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 1234)
|
||||
self.assertEqual(plugin._db, 2)
|
||||
self.assertEqual(plugin._password, 'verysecret')
|
||||
|
||||
def test_src_with_host_empty_port_empty_db_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1:::verysecret')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, 'verysecret')
|
||||
|
||||
def test_src_with_host_empty_port_empty_db_empty_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1:::')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_empty_port_empty_db_no_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1::')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_empty_port_no_db_no_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1:')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_empty_port_db_no_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1::2')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 2)
|
||||
self.assertEqual(plugin._password, None)
|
||||
|
||||
def test_src_with_host_port_empty_db_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1:1234::verysecret')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 1234)
|
||||
self.assertEqual(plugin._db, 0)
|
||||
self.assertEqual(plugin._password, 'verysecret')
|
||||
|
||||
def test_src_with_host_empty_port_db_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1::2:verysecret')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 2)
|
||||
self.assertEqual(plugin._password, 'verysecret')
|
||||
|
||||
def test_src_with_host_empty_port_db_empty_pass(self):
|
||||
plugin = TokenRedis('127.0.0.1::2:')
|
||||
|
||||
self.assertEqual(plugin._server, '127.0.0.1')
|
||||
self.assertEqual(plugin._port, 6379)
|
||||
self.assertEqual(plugin._db, 2)
|
||||
self.assertEqual(plugin._password, None)
|
||||
212
public/novnc/utils/websockify/tests/test_websocket.py
Normal file
212
public/novnc/utils/websockify/tests/test_websocket.py
Normal file
@@ -0,0 +1,212 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
""" Unit tests for websocket """
|
||||
import unittest
|
||||
from websockify import websocket
|
||||
|
||||
class FakeSocket:
|
||||
def __init__(self):
|
||||
self.data = b''
|
||||
|
||||
def send(self, buf):
|
||||
self.data += buf
|
||||
return len(buf)
|
||||
|
||||
class AcceptTestCase(unittest.TestCase):
|
||||
def test_success(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
ws.accept(sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.assertTrue(b'\r\nUpgrade: websocket\r\n' in sock.data)
|
||||
self.assertTrue(b'\r\nConnection: Upgrade\r\n' in sock.data)
|
||||
self.assertTrue(b'\r\nSec-WebSocket-Accept: pczpYSQsvE1vBpTQYjFQPcuoj6M=\r\n' in sock.data)
|
||||
|
||||
def test_bad_version(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '5',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '20',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
|
||||
def test_bad_upgrade(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket2',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
|
||||
def test_missing_key(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13'})
|
||||
|
||||
def test_protocol(self):
|
||||
class ProtoSocket(websocket.WebSocket):
|
||||
def select_subprotocol(self, protocol):
|
||||
return 'gazonk'
|
||||
|
||||
ws = ProtoSocket()
|
||||
sock = FakeSocket()
|
||||
ws.accept(sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
|
||||
'Sec-WebSocket-Protocol': 'foobar gazonk'})
|
||||
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.assertTrue(b'\r\nSec-WebSocket-Protocol: gazonk\r\n' in sock.data)
|
||||
|
||||
def test_no_protocol(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
ws.accept(sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.assertFalse(b'\r\nSec-WebSocket-Protocol:' in sock.data)
|
||||
|
||||
def test_missing_protocol(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
|
||||
'Sec-WebSocket-Protocol': 'foobar gazonk'})
|
||||
|
||||
def test_protocol(self):
|
||||
class ProtoSocket(websocket.WebSocket):
|
||||
def select_subprotocol(self, protocol):
|
||||
return 'oddball'
|
||||
|
||||
ws = ProtoSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
|
||||
'Sec-WebSocket-Protocol': 'foobar gazonk'})
|
||||
|
||||
class PingPongTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ws = websocket.WebSocket()
|
||||
self.sock = FakeSocket()
|
||||
self.ws.accept(self.sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertEqual(self.sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.sock.data = b''
|
||||
|
||||
def test_ping(self):
|
||||
self.ws.ping()
|
||||
self.assertEqual(self.sock.data, b'\x89\x00')
|
||||
|
||||
def test_pong(self):
|
||||
self.ws.pong()
|
||||
self.assertEqual(self.sock.data, b'\x8a\x00')
|
||||
|
||||
def test_ping_data(self):
|
||||
self.ws.ping(b'foo')
|
||||
self.assertEqual(self.sock.data, b'\x89\x03foo')
|
||||
|
||||
def test_pong_data(self):
|
||||
self.ws.pong(b'foo')
|
||||
self.assertEqual(self.sock.data, b'\x8a\x03foo')
|
||||
|
||||
class HyBiEncodeDecodeTestCase(unittest.TestCase):
|
||||
def test_decode_hybi_text(self):
|
||||
buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58'
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x1)
|
||||
self.assertEqual(res['masked'], True)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], b'Hello')
|
||||
|
||||
def test_decode_hybi_binary(self):
|
||||
buf = b'\x82\x04\x01\x02\x03\x04'
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], b'\x01\x02\x03\x04')
|
||||
|
||||
def test_decode_hybi_extended_16bit_binary(self):
|
||||
data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260
|
||||
buf = b'\x82\x7e\x01\x04' + data
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], data)
|
||||
|
||||
def test_decode_hybi_extended_64bit_binary(self):
|
||||
data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260
|
||||
buf = b'\x82\x7f\x00\x00\x00\x00\x00\x00\x01\x04' + data
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], data)
|
||||
|
||||
def test_decode_hybi_multi(self):
|
||||
buf1 = b'\x01\x03\x48\x65\x6c'
|
||||
buf2 = b'\x80\x02\x6c\x6f'
|
||||
|
||||
ws = websocket.WebSocket()
|
||||
|
||||
res1 = ws._decode_hybi(buf1)
|
||||
self.assertEqual(res1['fin'], 0)
|
||||
self.assertEqual(res1['opcode'], 0x1)
|
||||
self.assertEqual(res1['length'], len(buf1))
|
||||
self.assertEqual(res1['payload'], b'Hel')
|
||||
|
||||
res2 = ws._decode_hybi(buf2)
|
||||
self.assertEqual(res2['fin'], 1)
|
||||
self.assertEqual(res2['opcode'], 0x0)
|
||||
self.assertEqual(res2['length'], len(buf2))
|
||||
self.assertEqual(res2['payload'], b'lo')
|
||||
|
||||
def test_encode_hybi_basic(self):
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._encode_hybi(0x1, b'Hello')
|
||||
expected = b'\x81\x05\x48\x65\x6c\x6c\x6f'
|
||||
|
||||
self.assertEqual(res, expected)
|
||||
131
public/novnc/utils/websockify/tests/test_websocketproxy.py
Normal file
131
public/novnc/utils/websockify/tests/test_websocketproxy.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright(c) 2015 Red Hat, Inc All Rights Reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
""" Unit tests for websocketproxy """
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
import unittest
|
||||
import socket
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from websockify import websocketproxy
|
||||
from websockify import token_plugins
|
||||
from websockify import auth_plugins
|
||||
|
||||
|
||||
class FakeSocket(object):
|
||||
def __init__(self, data=b''):
|
||||
self._data = data
|
||||
|
||||
def recv(self, amt, flags=None):
|
||||
res = self._data[0:amt]
|
||||
if not (flags & socket.MSG_PEEK):
|
||||
self._data = self._data[amt:]
|
||||
|
||||
return res
|
||||
|
||||
def makefile(self, mode='r', buffsize=None):
|
||||
if 'b' in mode:
|
||||
return BytesIO(self._data)
|
||||
else:
|
||||
return StringIO(self._data.decode('latin_1'))
|
||||
|
||||
|
||||
class FakeServer(object):
|
||||
class EClose(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
self.token_plugin = None
|
||||
self.auth_plugin = None
|
||||
self.wrap_cmd = None
|
||||
self.ssl_target = None
|
||||
self.unix_target = None
|
||||
|
||||
class ProxyRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(ProxyRequestHandlerTestCase, self).setUp()
|
||||
self.handler = websocketproxy.ProxyRequestHandler(
|
||||
FakeSocket(), "127.0.0.1", FakeServer())
|
||||
self.handler.path = "https://localhost:6080/websockify?token=blah"
|
||||
self.handler.headers = None
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
|
||||
def tearDown(self):
|
||||
patch.stopall()
|
||||
super(ProxyRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
def test_get_target(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return ("some host", "some port")
|
||||
|
||||
host, port = self.handler.get_target(
|
||||
TestPlugin(None))
|
||||
|
||||
self.assertEqual(host, "some host")
|
||||
self.assertEqual(port, "some port")
|
||||
|
||||
def test_get_target_unix_socket(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return ("unix_socket", "/tmp/socket")
|
||||
|
||||
_, socket = self.handler.get_target(
|
||||
TestPlugin(None))
|
||||
|
||||
self.assertEqual(socket, "/tmp/socket")
|
||||
|
||||
def test_get_target_raises_error_on_unknown_token(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return None
|
||||
|
||||
with self.assertRaises(FakeServer.EClose):
|
||||
self.handler.get_target(TestPlugin(None))
|
||||
|
||||
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
|
||||
def test_token_plugin(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return (self.source + token).split(',')
|
||||
|
||||
self.handler.server.token_plugin = TestPlugin("somehost,")
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.assertEqual(self.handler.server.target_host, "somehost")
|
||||
self.assertEqual(self.handler.server.target_port, "blah")
|
||||
|
||||
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
|
||||
def test_auth_plugin(self):
|
||||
class TestPlugin(auth_plugins.BasePlugin):
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
if target_host == self.source:
|
||||
raise auth_plugins.AuthenticationError(response_msg="some_error")
|
||||
|
||||
self.handler.server.auth_plugin = TestPlugin("somehost")
|
||||
self.handler.server.target_host = "somehost"
|
||||
self.handler.server.target_port = "someport"
|
||||
|
||||
with self.assertRaises(auth_plugins.AuthenticationError):
|
||||
self.handler.auth_connection()
|
||||
|
||||
self.handler.server.target_host = "someotherhost"
|
||||
self.handler.auth_connection()
|
||||
|
||||
69
public/novnc/utils/websockify/tests/test_websocketserver.py
Normal file
69
public/novnc/utils/websockify/tests/test_websocketserver.py
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
""" Unit tests for websocketserver """
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from websockify.websocketserver import HttpWebSocket
|
||||
|
||||
|
||||
class HttpWebSocketTest(unittest.TestCase):
|
||||
@patch("websockify.websocketserver.WebSocket.__init__", autospec=True)
|
||||
def test_constructor(self, websock):
|
||||
# Given
|
||||
req_obj = MagicMock()
|
||||
|
||||
# When
|
||||
sock = HttpWebSocket(req_obj)
|
||||
|
||||
# Then
|
||||
websock.assert_called_once_with(sock)
|
||||
self.assertEqual(sock.request_handler, req_obj)
|
||||
|
||||
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
|
||||
def test_send_response(self):
|
||||
# Given
|
||||
req_obj = MagicMock()
|
||||
sock = HttpWebSocket(req_obj)
|
||||
|
||||
# When
|
||||
sock.send_response(200, "message")
|
||||
|
||||
# Then
|
||||
req_obj.send_response.assert_called_once_with(200, "message")
|
||||
|
||||
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
|
||||
def test_send_response_default_message(self):
|
||||
# Given
|
||||
req_obj = MagicMock()
|
||||
sock = HttpWebSocket(req_obj)
|
||||
|
||||
# When
|
||||
sock.send_response(200)
|
||||
|
||||
# Then
|
||||
req_obj.send_response.assert_called_once_with(200, None)
|
||||
|
||||
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
|
||||
def test_send_header(self):
|
||||
# Given
|
||||
req_obj = MagicMock()
|
||||
sock = HttpWebSocket(req_obj)
|
||||
|
||||
# When
|
||||
sock.send_header("keyword", "value")
|
||||
|
||||
# Then
|
||||
req_obj.send_header.assert_called_once_with("keyword", "value")
|
||||
|
||||
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
|
||||
def test_end_headers(self):
|
||||
# Given
|
||||
req_obj = MagicMock()
|
||||
sock = HttpWebSocket(req_obj)
|
||||
|
||||
# When
|
||||
sock.end_headers()
|
||||
|
||||
# Then
|
||||
req_obj.end_headers.assert_called_once_with()
|
||||
|
||||
400
public/novnc/utils/websockify/tests/test_websockifyserver.py
Normal file
400
public/novnc/utils/websockify/tests/test_websockifyserver.py
Normal file
@@ -0,0 +1,400 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
""" Unit tests for websockifyserver """
|
||||
import errno
|
||||
import os
|
||||
import logging
|
||||
import select
|
||||
import shutil
|
||||
import socket
|
||||
import ssl
|
||||
from unittest.mock import patch, MagicMock, ANY
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import socket
|
||||
import signal
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
from websockify import websockifyserver
|
||||
|
||||
|
||||
def raise_oserror(*args, **kwargs):
|
||||
raise OSError('fake error')
|
||||
|
||||
|
||||
class FakeSocket(object):
|
||||
def __init__(self, data=b''):
|
||||
self._data = data
|
||||
|
||||
def recv(self, amt, flags=None):
|
||||
res = self._data[0:amt]
|
||||
if not (flags & socket.MSG_PEEK):
|
||||
self._data = self._data[amt:]
|
||||
|
||||
return res
|
||||
|
||||
def makefile(self, mode='r', buffsize=None):
|
||||
if 'b' in mode:
|
||||
return BytesIO(self._data)
|
||||
else:
|
||||
return StringIO(self._data.decode('latin_1'))
|
||||
|
||||
|
||||
class WebSockifyRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSockifyRequestHandlerTestCase, self).setUp()
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# Mock this out cause it screws tests up
|
||||
patch('os.chdir').start()
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
patch.stopall()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSockifyRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
def _get_server(self, handler_class=websockifyserver.WebSockifyRequestHandler,
|
||||
**kwargs):
|
||||
web = kwargs.pop('web', self.tmpdir)
|
||||
return websockifyserver.WebSockifyServer(
|
||||
handler_class, listen_host='localhost',
|
||||
listen_port=80, key=self.tmpdir, web=web,
|
||||
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
|
||||
**kwargs)
|
||||
|
||||
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
|
||||
def test_normal_get_with_only_upgrade_returns_error(self, send_error):
|
||||
server = self._get_server(web=None)
|
||||
handler = websockifyserver.WebSockifyRequestHandler(
|
||||
FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
handler.do_GET()
|
||||
send_error.assert_called_with(405)
|
||||
|
||||
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
|
||||
def test_list_dir_with_file_only_returns_error(self, send_error):
|
||||
server = self._get_server(file_only=True)
|
||||
handler = websockifyserver.WebSockifyRequestHandler(
|
||||
FakeSocket(b'GET / HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
handler.path = '/'
|
||||
handler.do_GET()
|
||||
send_error.assert_called_with(404)
|
||||
|
||||
|
||||
class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSockifyServerTestCase, self).setUp()
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# Mock this out cause it screws tests up
|
||||
patch('os.chdir').start()
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
patch.stopall()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSockifyServerTestCase, self).tearDown()
|
||||
|
||||
def _get_server(self, handler_class=websockifyserver.WebSockifyRequestHandler,
|
||||
**kwargs):
|
||||
return websockifyserver.WebSockifyServer(
|
||||
handler_class, listen_host='localhost',
|
||||
listen_port=80, key=self.tmpdir, web=self.tmpdir,
|
||||
record=self.tmpdir, **kwargs)
|
||||
|
||||
def test_daemonize_raises_error_while_closing_fds(self):
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
patch('os.fork').start().return_value = 0
|
||||
patch('signal.signal').start()
|
||||
patch('os.setsid').start()
|
||||
patch('os.close').start().side_effect = raise_oserror
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
|
||||
def raise_oserror_ebadf(fd):
|
||||
raise OSError(errno.EBADF, 'fake error')
|
||||
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
patch('os.fork').start().return_value = 0
|
||||
patch('signal.signal').start()
|
||||
patch('os.setsid').start()
|
||||
patch('os.close').start().side_effect = raise_oserror_ebadf
|
||||
patch('os.open').start().side_effect = raise_oserror
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
def test_handshake_fails_on_not_ready(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([], [], [])
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
FakeSocket(), '127.0.0.1')
|
||||
|
||||
def test_empty_handshake_fails(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket('')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_handshake_policy_request(self):
|
||||
# TODO(directxman12): implement
|
||||
pass
|
||||
|
||||
def test_handshake_ssl_only_without_ssl_raises_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket(b'some initial data')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_do_handshake_no_ssl(self):
|
||||
class FakeHandler(object):
|
||||
CALLED = False
|
||||
def __init__(self, *args, **kwargs):
|
||||
type(self).CALLED = True
|
||||
|
||||
FakeHandler.CALLED = False
|
||||
|
||||
server = self._get_server(
|
||||
handler_class=FakeHandler, daemon=True,
|
||||
ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket(b'some initial data')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock)
|
||||
self.assertTrue(FakeHandler.CALLED, True)
|
||||
|
||||
def test_do_handshake_ssl(self):
|
||||
# TODO(directxman12): implement this
|
||||
pass
|
||||
|
||||
def test_do_handshake_ssl_without_ssl_raises_error(self):
|
||||
# TODO(directxman12): implement this
|
||||
pass
|
||||
|
||||
def test_do_handshake_ssl_without_cert_raises_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1,
|
||||
cert='afdsfasdafdsafdsafdsafdas')
|
||||
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_do_handshake_ssl_error_eof_raises_close_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
def fake_wrap_socket(*args, **kwargs):
|
||||
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
|
||||
|
||||
class fake_create_default_context():
|
||||
def __init__(self, purpose):
|
||||
self.verify_mode = None
|
||||
self.options = 0
|
||||
def load_cert_chain(self, certfile, keyfile, password):
|
||||
pass
|
||||
def set_default_verify_paths(self):
|
||||
pass
|
||||
def load_verify_locations(self, cafile):
|
||||
pass
|
||||
def wrap_socket(self, *args, **kwargs):
|
||||
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_do_handshake_ssl_sets_ciphers(self):
|
||||
test_ciphers = 'TEST-CIPHERS-1:TEST-CIPHER-2'
|
||||
|
||||
class FakeHandler(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
server = self._get_server(handler_class=FakeHandler, daemon=True,
|
||||
idle_timeout=1, ssl_ciphers=test_ciphers)
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
class fake_create_default_context():
|
||||
CIPHERS = ''
|
||||
def __init__(self, purpose):
|
||||
self.verify_mode = None
|
||||
self.options = 0
|
||||
def load_cert_chain(self, certfile, keyfile, password):
|
||||
pass
|
||||
def set_default_verify_paths(self):
|
||||
pass
|
||||
def load_verify_locations(self, cafile):
|
||||
pass
|
||||
def wrap_socket(self, *args, **kwargs):
|
||||
pass
|
||||
def set_ciphers(self, ciphers_to_set):
|
||||
fake_create_default_context.CIPHERS = ciphers_to_set
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
|
||||
server.do_handshake(sock, '127.0.0.1')
|
||||
self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers)
|
||||
|
||||
def test_do_handshake_ssl_sets_opions(self):
|
||||
test_options = 0xCAFEBEEF
|
||||
|
||||
class FakeHandler(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
server = self._get_server(handler_class=FakeHandler, daemon=True,
|
||||
idle_timeout=1, ssl_options=test_options)
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
class fake_create_default_context(object):
|
||||
OPTIONS = 0
|
||||
def __init__(self, purpose):
|
||||
self.verify_mode = None
|
||||
self._options = 0
|
||||
def load_cert_chain(self, certfile, keyfile, password):
|
||||
pass
|
||||
def set_default_verify_paths(self):
|
||||
pass
|
||||
def load_verify_locations(self, cafile):
|
||||
pass
|
||||
def wrap_socket(self, *args, **kwargs):
|
||||
pass
|
||||
def get_options(self):
|
||||
return self._options
|
||||
def set_options(self, val):
|
||||
fake_create_default_context.OPTIONS = val
|
||||
options = property(get_options, set_options)
|
||||
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
|
||||
server.do_handshake(sock, '127.0.0.1')
|
||||
self.assertEqual(fake_create_default_context.OPTIONS, test_options)
|
||||
|
||||
def test_fallback_sigchld_handler(self):
|
||||
# TODO(directxman12): implement this
|
||||
pass
|
||||
|
||||
def test_start_server_error(self):
|
||||
server = self._get_server(daemon=False, ssl_only=1, idle_timeout=1)
|
||||
sock = server.socket('localhost')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
raise Exception("fake error")
|
||||
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_keyboardinterrupt(self):
|
||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
||||
sock = server.socket('localhost')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_systemexit(self):
|
||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
||||
sock = server.socket('localhost')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
sys.exit()
|
||||
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
server.start_server()
|
||||
|
||||
def test_socket_set_keepalive_options(self):
|
||||
keepcnt = 12
|
||||
keepidle = 34
|
||||
keepintvl = 56
|
||||
|
||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
||||
sock = server.socket('localhost',
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
|
||||
if hasattr(socket, 'TCP_KEEPCNT'):
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
|
||||
sock = server.socket('localhost',
|
||||
tcp_keepalive=False,
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
|
||||
if hasattr(socket, 'TCP_KEEPCNT'):
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
17
public/novnc/utils/websockify/tox.ini
Normal file
17
public/novnc/utils/websockify/tox.ini
Normal file
@@ -0,0 +1,17 @@
|
||||
# Tox (http://tox.testrun.org/) is a tool for running tests
|
||||
# in multiple virtualenvs. This configuration file will run the
|
||||
# test suite on all supported python versions. To use it, "pip install tox"
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py34
|
||||
|
||||
[testenv]
|
||||
commands = nosetests {posargs}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
|
||||
# At some point we should enable this since tox expects it to exist but
|
||||
# the code will need pep8ising first.
|
||||
#[testenv:pep8]
|
||||
#commands = flake8
|
||||
#dep = flake8
|
||||
1
public/novnc/utils/websockify/websockify.py
Symbolic link
1
public/novnc/utils/websockify/websockify.py
Symbolic link
@@ -0,0 +1 @@
|
||||
run
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user