Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69c04c819c | |||
| 558a6439f1 | |||
| 2b9e0a5794 | |||
| 3371090909 | |||
| 1f960d9f3c | |||
| 1221960baa | |||
| 2e57ee3159 | |||
| 04fd789a67 | |||
| d5216c3166 | |||
| 11f9a6cf10 | |||
| df10501615 | |||
| d94b944f93 | |||
| 52adf957b3 | |||
| 731fd20796 | |||
| 52c2e62535 | |||
| ac9d357c87 | |||
| ce07749223 | |||
| af10f458a1 | |||
| 69a8b928aa | |||
| 19b558e4cd | |||
| dcdf0a0b6c | |||
| 23045cb212 | |||
| acd276e1a2 | |||
| 999a133bc5 | |||
| f36877c684 | |||
| 303a71310c | |||
| 63209f84ca | |||
| d37e729587 | |||
| 12191d266b | |||
| 67a12a2394 | |||
| 6c526fd645 | |||
| 6c1a2e9032 | |||
| 739af6ecb4 | |||
| 56b740ef5c | |||
| 18e70c52e1 | |||
| cc3ccf0ddd | |||
| 38db12c2b0 | |||
| a749611370 | |||
| b662d185ca | |||
| 6de6933819 | |||
| f46db48399 | |||
| 8049ddfe26 | |||
| f64e3dea51 | |||
| 17455afe4d | |||
| 047ce47742 | |||
| 558402848e | |||
| db93395061 | |||
| e246e98b20 | |||
| 7ecfa4f384 | |||
| bc216fb7d1 | |||
| b92528aeba | |||
| 81e2a53692 | |||
| 602248425a | |||
| 3b802c08b7 | |||
| 972b30ddc2 | |||
| f58b49fa08 | |||
| 131f9ea645 | |||
| cbf05f84fe | |||
| 611da86353 | |||
| fb4ac5ae51 | |||
| 063c7de783 | |||
| 08d37e0deb | |||
| ff30e5461f | |||
| 4071291fa9 | |||
| 611effd83b | |||
| b63d197292 | |||
| cf901ddac5 | |||
| 122985bd67 | |||
| 19c8482236 | |||
| 33a1bd2337 | |||
| 5f8cb96bab | |||
| 6ddfaa86ae | |||
| 4dc7c0da7a | |||
| 34a1b68d79 | |||
| a04edfe80f | |||
| 32c1abd5d9 | |||
| a47be21f9f | |||
| 5e0ff7d855 | |||
| c3acdc2e38 | |||
| 13c99bcf05 | |||
| b4e0b534d5 | |||
| be4119f84f | |||
| 32b0567343 | |||
| 82fe329129 | |||
| 0e5c3ecfda | |||
| 53f1f1989e | |||
| 081046b6cd | |||
| a61ae52610 | |||
| 477dce6cf8 | |||
| 8a0a47223d | |||
| 1f798214de | |||
| 4459824cc8 | |||
| c2a40d6900 | |||
| a7fa97f0e1 | |||
| 1190fe1204 | |||
| 7026e26d68 | |||
| 37e40a78f2 | |||
| edde5cd0ff | |||
| bff3c373b3 | |||
| f30ad05c70 | |||
| 354dd6b0a2 | |||
| e06de83295 | |||
| ab389d4e71 | |||
| eee946d2cf | |||
| 73af324a3a | |||
| 832118e61a | |||
| 264f8fdd7f | |||
| 36cb8f4676 | |||
| 36fcd5784f | |||
| 46450577c2 | |||
| d3ba23fa64 | |||
| 6d27b5d321 | |||
| 888e75a8fb | |||
| b7f255ce0b | |||
| 477947ba96 | |||
| 33e9a21ce4 | |||
| 2d078e8cd8 | |||
| d6d960dbe5 | |||
| ed109d7ec8 | |||
| bc917863e0 | |||
| e964c1edff | |||
| f5e42ff6f4 | |||
| f594d70daf | |||
| d0608a63b6 | |||
| 09f3ec7125 | |||
| 95593ac4bf | |||
| 1eecc7b17a | |||
| b05b773bd7 | |||
| debc926612 | |||
| 70eb75a3e6 | |||
| 903198a724 | |||
| 7b3dd8a6f5 | |||
| 208f83b9a2 | |||
| 4e3388964a | |||
| b9e1295f7a | |||
| d3865688c8 | |||
| 5a726c2e4d | |||
| db6a7e3e28 | |||
| b2fe57c950 |
@@ -9,3 +9,5 @@ other/node_modules
|
|||||||
.pydevproject
|
.pydevproject
|
||||||
target.cfg
|
target.cfg
|
||||||
target.cfg.d
|
target.cfg.d
|
||||||
|
.tox
|
||||||
|
*.egg-info
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "include/web-socket-js-project"]
|
||||||
|
path = include/web-socket-js-project
|
||||||
|
url = https://github.com/gimite/web-socket-js.git
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- 2.6
|
||||||
|
- 2.7
|
||||||
|
- 3.3
|
||||||
|
- 3.4
|
||||||
|
|
||||||
|
install: pip install -r test-requirements.txt
|
||||||
|
|
||||||
|
script: python setup.py nosetests --verbosity=3
|
||||||
+43
@@ -1,6 +1,49 @@
|
|||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
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
|
0.4.0 - Mar 12, 2013
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
include CHANGES.txt *.py README.md LICENSE.txt
|
include CHANGES.txt README.md LICENSE.txt
|
||||||
|
graft include
|
||||||
|
|||||||
@@ -8,26 +8,41 @@ to normal socket traffic. Websockify accepts the WebSockets handshake,
|
|||||||
parses it, and then begins forwarding traffic between the client and
|
parses it, and then begins forwarding traffic between the client and
|
||||||
the target in both directions.
|
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/kanaka/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
|
### WebSockets binary data
|
||||||
|
|
||||||
Websockify supports all versions of the WebSockets protocol (Hixie and
|
Starting with websockify 0.5.0, only the HyBi / IETF
|
||||||
HyBi). The older Hixie versions of the protocol only support UTF-8
|
6455 WebSocket protocol is supported.
|
||||||
text payloads. In order to transport binary data over UTF-8 an
|
|
||||||
encoding must used to encapsulate the data within UTF-8.
|
|
||||||
|
|
||||||
With Hixie clients, Websockify uses base64 to encode all traffic to
|
Websockify negotiates whether to base64 encode traffic to and from the
|
||||||
and from the client. This does not affect the data between websockify
|
client via the subprotocol header (Sec-WebSocket-Protocol). The valid
|
||||||
and the server.
|
subprotocol values are 'binary' and 'base64' and if the client sends
|
||||||
|
both then the server (the python implementation) will prefer 'binary'.
|
||||||
With HyBi clients, websockify negotiates whether to base64 encode
|
The 'binary' subprotocol indicates that the data will be sent raw
|
||||||
traffic to and from the client via the subprotocol header
|
using binary WebSocket frames. Some HyBi clients (such as the Flash
|
||||||
(Sec-WebSocket-Protocol). The valid subprotocol values are 'binary'
|
fallback and older Chrome and iOS versions) do not support binary data
|
||||||
and 'base64' and if the client sends both then the server (the python
|
which is why the negotiation is necessary.
|
||||||
implementation) will prefer 'binary'. The 'binary' subprotocol
|
|
||||||
indicates that the data will be sent raw using binary WebSocket
|
|
||||||
frames. Some HyBi clients (such as the Flash fallback and older Chrome
|
|
||||||
and iOS versions) do not support binary data which is why the
|
|
||||||
negotiation is necessary.
|
|
||||||
|
|
||||||
|
|
||||||
### Encrypted WebSocket connections (wss://)
|
### Encrypted WebSocket connections (wss://)
|
||||||
@@ -123,7 +138,7 @@ new (moved) port of the program.
|
|||||||
The program wrap mode is invoked by replacing the target with `--`
|
The program wrap mode is invoked by replacing the target with `--`
|
||||||
followed by the program command line to wrap.
|
followed by the program command line to wrap.
|
||||||
|
|
||||||
`./websockify 2023 -- PROGRAM ARGS`
|
`./run 2023 -- PROGRAM ARGS`
|
||||||
|
|
||||||
The `--wrap-mode` option can be used to indicate what action to take
|
The `--wrap-mode` option can be used to indicate what action to take
|
||||||
when the wrapped program exits or daemonizes.
|
when the wrapped program exits or daemonizes.
|
||||||
@@ -132,16 +147,17 @@ Here is an example of using websockify to wrap the vncserver command
|
|||||||
(which backgrounds itself) for use with
|
(which backgrounds itself) for use with
|
||||||
[noVNC](https://github.com/kanaka/noVNC):
|
[noVNC](https://github.com/kanaka/noVNC):
|
||||||
|
|
||||||
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
`./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
|
||||||
|
|
||||||
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd
|
Here is an example of wrapping telnetd (from krb5-telnetd). telnetd
|
||||||
exits after the connection closes so the wrap mode is set to respawn
|
exits after the connection closes so the wrap mode is set to respawn
|
||||||
the command:
|
the command:
|
||||||
|
|
||||||
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
`sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
|
||||||
|
|
||||||
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
|
||||||
client.
|
client (use 'localhost' and '2023' for the host and port
|
||||||
|
respectively).
|
||||||
|
|
||||||
|
|
||||||
### Building the Python ssl module (for python 2.5 and older)
|
### Building the Python ssl module (for python 2.5 and older)
|
||||||
@@ -150,11 +166,10 @@ client.
|
|||||||
|
|
||||||
`sudo aptitude install python-dev bluetooth-dev`
|
`sudo aptitude install python-dev bluetooth-dev`
|
||||||
|
|
||||||
* Download, build the ssl module and symlink to it:
|
* At the top level of the websockify repostory, download, build and
|
||||||
|
symlink the ssl module:
|
||||||
|
|
||||||
`cd websockify/`
|
`wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
||||||
|
|
||||||
`wget http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
|
|
||||||
|
|
||||||
`tar xvzf ssl-1.15.tar.gz`
|
`tar xvzf ssl-1.15.tar.gz`
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Installation
|
|||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
||||||
https://github.com/kanaka/noVNC/wiki/Compiling-Websockify-to-Windows-Executable
|
https://github.com/kanaka/websockify/wiki/Compiling-Websockify-as-Windows-Executable
|
||||||
|
|
||||||
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
- Update setup.py and CHANGES.txt and commit
|
- Update setup.py, CHANGES.txt and other/package.json and commit
|
||||||
- Create version tag and tarball from tag
|
- Create version tag and tarball from tag
|
||||||
WVER=0.1.0
|
WVER=0.1.0
|
||||||
git tag v${WVER}
|
git tag v${WVER}
|
||||||
@@ -8,3 +8,6 @@
|
|||||||
python setup.py register
|
python setup.py register
|
||||||
- Upload the source distribution to pypi
|
- Upload the source distribution to pypi
|
||||||
python setup.py sdist upload
|
python setup.py sdist upload
|
||||||
|
- Register with npmjs.org (once)
|
||||||
|
- Upload websockify.js npmjs.org package
|
||||||
|
npm publish other/js
|
||||||
|
|||||||
Submodule
+1
Submodule include/web-socket-js-project added at c0855c6cae
Binary file not shown.
+9
-7
@@ -262,7 +262,7 @@ function on(evt, handler) {
|
|||||||
eventHandlers[evt] = handler;
|
eventHandlers[evt] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(protocols) {
|
function init(protocols, ws_schema) {
|
||||||
rQ = [];
|
rQ = [];
|
||||||
rQi = 0;
|
rQi = 0;
|
||||||
sQ = [];
|
sQ = [];
|
||||||
@@ -277,12 +277,13 @@ function init(protocols) {
|
|||||||
('set' in Uint8Array.prototype)) {
|
('set' in Uint8Array.prototype)) {
|
||||||
bt = true;
|
bt = true;
|
||||||
}
|
}
|
||||||
|
// Check for full binary type support in WebSocket
|
||||||
// Check for full binary type support in WebSockets
|
// Inspired by:
|
||||||
// TODO: this sucks, the property should exist on the prototype
|
// https://github.com/Modernizr/Modernizr/issues/370
|
||||||
// but it does not.
|
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
|
||||||
try {
|
try {
|
||||||
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
|
if (bt && ('binaryType' in WebSocket.prototype ||
|
||||||
|
!!(new WebSocket(ws_schema + '://.').binaryType))) {
|
||||||
Util.Info("Detected binaryType support in WebSockets");
|
Util.Info("Detected binaryType support in WebSockets");
|
||||||
wsbt = true;
|
wsbt = true;
|
||||||
}
|
}
|
||||||
@@ -325,7 +326,8 @@ function init(protocols) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function open(uri, protocols) {
|
function open(uri, protocols) {
|
||||||
protocols = init(protocols);
|
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
||||||
|
protocols = init(protocols, ws_schema);
|
||||||
|
|
||||||
if (test_mode) {
|
if (test_mode) {
|
||||||
websocket = {};
|
websocket = {};
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
A JavaScript implementation of the websockify WebSocket-to-TCP bridge/proxy.
|
||||||
|
|
||||||
|
Copyright (C) 2013 - Joel Martin (github.com/kanaka)
|
||||||
|
|
||||||
|
Licensed under LGPL-3.
|
||||||
|
|
||||||
|
See http://github.com/kanaka/websockify for more info.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
||||||
|
"name": "websockify",
|
||||||
|
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
||||||
|
"license": "LGPL-3",
|
||||||
|
"version": "0.7.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/kanaka/websockify.git"
|
||||||
|
},
|
||||||
|
"files": ["../../docs/LICENSE.LGPL-3","websockify.js"],
|
||||||
|
"bin": {
|
||||||
|
"websockify": "./websockify.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.9"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ws": ">=0.4.27",
|
||||||
|
"base64": "latest",
|
||||||
|
"optimist": "latest",
|
||||||
|
"policyfile": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,14 +7,6 @@
|
|||||||
// Known to work with node 0.8.9
|
// Known to work with node 0.8.9
|
||||||
// Requires node modules: ws, optimist and policyfile
|
// Requires node modules: ws, optimist and policyfile
|
||||||
// npm install ws optimist policyfile
|
// npm install ws optimist policyfile
|
||||||
//
|
|
||||||
// NOTE:
|
|
||||||
// This version requires a patched version of einaros/ws that supports
|
|
||||||
// subprotocol negotiation. You can use the patched version like this:
|
|
||||||
//
|
|
||||||
// cd websockify/other
|
|
||||||
// git clone https://github.com/kanaka/ws
|
|
||||||
// npm link ./ws
|
|
||||||
|
|
||||||
|
|
||||||
var argv = require('optimist').argv,
|
var argv = require('optimist').argv,
|
||||||
@@ -37,6 +29,7 @@ var argv = require('optimist').argv,
|
|||||||
// Handle new WebSocket client
|
// Handle new WebSocket client
|
||||||
new_client = function(client) {
|
new_client = function(client) {
|
||||||
var clientAddr = client._socket.remoteAddress, log;
|
var clientAddr = client._socket.remoteAddress, log;
|
||||||
|
console.log(client.upgradeReq.url);
|
||||||
log = function (msg) {
|
log = function (msg) {
|
||||||
console.log(' ' + clientAddr + ': '+ msg);
|
console.log(' ' + clientAddr + ': '+ msg);
|
||||||
};
|
};
|
||||||
@@ -61,6 +54,12 @@ new_client = function(client) {
|
|||||||
});
|
});
|
||||||
target.on('end', function() {
|
target.on('end', function() {
|
||||||
log('target disconnected');
|
log('target disconnected');
|
||||||
|
client.close();
|
||||||
|
});
|
||||||
|
target.on('error', function() {
|
||||||
|
log('target connection error');
|
||||||
|
target.end();
|
||||||
|
client.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('message', function(msg) {
|
client.on('message', function(msg) {
|
||||||
@@ -126,11 +125,9 @@ http_request = function (request, response) {
|
|||||||
|
|
||||||
// Select 'binary' or 'base64' subprotocol, preferring 'binary'
|
// Select 'binary' or 'base64' subprotocol, preferring 'binary'
|
||||||
selectProtocol = function(protocols, callback) {
|
selectProtocol = function(protocols, callback) {
|
||||||
var plist = protocols ? protocols.split(',') : "";
|
if (protocols.indexOf('binary') >= 0) {
|
||||||
var plist = protocols.split(',');
|
|
||||||
if (plist.indexOf('binary') >= 0) {
|
|
||||||
callback(true, 'binary');
|
callback(true, 'binary');
|
||||||
} else if (plist.indexOf('base64') >= 0) {
|
} else if (protocols.indexOf('base64') >= 0) {
|
||||||
callback(true, 'base64');
|
callback(true, 'base64');
|
||||||
} else {
|
} else {
|
||||||
console.log("Client must support 'binary' or 'base64' protocol");
|
console.log("Client must support 'binary' or 'base64' protocol");
|
||||||
+14
-4
@@ -16,6 +16,8 @@ usage() {
|
|||||||
echo " Default: localhost:5900"
|
echo " Default: localhost:5900"
|
||||||
echo " --cert CERT Path to combined cert/key file"
|
echo " --cert CERT Path to combined cert/key file"
|
||||||
echo " Default: self.pem"
|
echo " Default: self.pem"
|
||||||
|
echo " --web WEB Path to web files (e.g. vnc.html)"
|
||||||
|
echo " Default: ./"
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
|||||||
PORT="6080"
|
PORT="6080"
|
||||||
VNC_DEST="localhost:5900"
|
VNC_DEST="localhost:5900"
|
||||||
CERT=""
|
CERT=""
|
||||||
|
WEB=""
|
||||||
proxy_pid=""
|
proxy_pid=""
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
@@ -50,6 +53,7 @@ while [ "$*" ]; do
|
|||||||
--listen) PORT="${OPTARG}"; shift ;;
|
--listen) PORT="${OPTARG}"; shift ;;
|
||||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||||
--cert) CERT="${OPTARG}"; shift ;;
|
--cert) CERT="${OPTARG}"; shift ;;
|
||||||
|
--web) WEB="${OPTARG}"; shift ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
@@ -60,18 +64,24 @@ done
|
|||||||
which netstat >/dev/null 2>&1 \
|
which netstat >/dev/null 2>&1 \
|
||||||
|| die "Must have netstat installed"
|
|| die "Must have netstat installed"
|
||||||
|
|
||||||
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
|
||||||
&& die "Port ${PORT} in use. Try --listen PORT"
|
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||||
|
|
||||||
trap "cleanup" TERM QUIT INT EXIT
|
trap "cleanup" TERM QUIT INT EXIT
|
||||||
|
|
||||||
# Find vnc.html
|
# Find vnc.html
|
||||||
if [ -e "$(pwd)/vnc.html" ]; then
|
if [ -n "${WEB}" ]; then
|
||||||
|
if [ ! -e "${WEB}/vnc.html" ]; then
|
||||||
|
die "Could not find ${WEB}/vnc.html"
|
||||||
|
fi
|
||||||
|
elif [ -e "$(pwd)/vnc.html" ]; then
|
||||||
WEB=$(pwd)
|
WEB=$(pwd)
|
||||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||||
WEB=${HERE}/../
|
WEB=${HERE}/../
|
||||||
elif [ -e "${HERE}/vnc.html" ]; then
|
elif [ -e "${HERE}/vnc.html" ]; then
|
||||||
WEB=${HERE}
|
WEB=${HERE}
|
||||||
|
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
|
||||||
|
WEB=${HERE}/../share/novnc/
|
||||||
else
|
else
|
||||||
die "Could not find vnc.html"
|
die "Could not find vnc.html"
|
||||||
fi
|
fi
|
||||||
@@ -92,7 +102,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||||
${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||||
proxy_pid="$!"
|
proxy_pid="$!"
|
||||||
sleep 1
|
sleep 1
|
||||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||||
@@ -101,7 +111,7 @@ if ! ps -p ${proxy_pid} >/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n\nNavigate to to this URL:\n"
|
echo -e "\n\nNavigate to this URL:\n"
|
||||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||||
echo -e "Press Ctrl-C to exit\n\n"
|
echo -e "Press Ctrl-C to exit\n\n"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -795,7 +795,7 @@ void start_server() {
|
|||||||
}
|
}
|
||||||
handler_msg("handler exit\n");
|
handler_msg("handler exit\n");
|
||||||
} else {
|
} else {
|
||||||
handler_msg("wsproxy exit\n");
|
handler_msg("websockify exit\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-24
@@ -9,11 +9,22 @@
|
|||||||
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
||||||
|
|
||||||
require 'gserver'
|
require 'gserver'
|
||||||
|
require 'openssl'
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
require 'base64'
|
require 'base64'
|
||||||
|
|
||||||
|
unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock")
|
||||||
|
module OpenSSL
|
||||||
|
module SSL
|
||||||
|
class SSLSocket
|
||||||
|
alias :read_nonblock :readpartial
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class EClose < Exception
|
class EClose < Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -44,7 +55,17 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
host = opts['listen_host'] || GServer::DEFAULT_HOST
|
||||||
|
|
||||||
super(port, host)
|
super(port, host)
|
||||||
|
msg opts.inspect
|
||||||
|
if opts['server_cert']
|
||||||
|
msg "creating ssl context"
|
||||||
|
@sslContext = OpenSSL::SSL::SSLContext.new
|
||||||
|
@sslContext.cert = OpenSSL::X509::Certificate.new(File.open(opts['server_cert']))
|
||||||
|
@sslContext.key = OpenSSL::PKey::RSA.new(File.open(opts['server_key']))
|
||||||
|
@sslContext.ca_file = opts['server_cert']
|
||||||
|
@sslContext.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
@sslContext.verify_depth = 0
|
||||||
|
end
|
||||||
|
|
||||||
@@client_id = 0 # Track client number total on class
|
@@client_id = 0 # Track client number total on class
|
||||||
|
|
||||||
@verbose = opts['verbose']
|
@verbose = opts['verbose']
|
||||||
@@ -53,6 +74,18 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
def serve(io)
|
def serve(io)
|
||||||
@@client_id += 1
|
@@client_id += 1
|
||||||
|
msg self.inspect
|
||||||
|
if @sslContext
|
||||||
|
msg "Enabling SSL context"
|
||||||
|
ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
|
||||||
|
#ssl.sync_close = true
|
||||||
|
#ssl.sync = true
|
||||||
|
msg "SSL accepting"
|
||||||
|
ssl.accept
|
||||||
|
io = ssl # replace the unencrypted handle with the encrypted one
|
||||||
|
end
|
||||||
|
|
||||||
|
msg "initializing thread"
|
||||||
|
|
||||||
# Initialize per thread state
|
# Initialize per thread state
|
||||||
t = Thread.current
|
t = Thread.current
|
||||||
@@ -61,11 +94,11 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
t[:recv_part] = nil
|
t[:recv_part] = nil
|
||||||
t[:base64] = nil
|
t[:base64] = nil
|
||||||
|
|
||||||
puts "in serve, client: #{t[:client].inspect}"
|
puts "in serve, client: #{t[:my_client_id].inspect}"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
t[:client] = do_handshake(io)
|
t[:client] = do_handshake(io)
|
||||||
new_client(t[:client])
|
new_websocket_client(t[:client])
|
||||||
rescue EClose => e
|
rescue EClose => e
|
||||||
msg "Client closed: #{e.message}"
|
msg "Client closed: #{e.message}"
|
||||||
return
|
return
|
||||||
@@ -85,12 +118,12 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
if @verbose then print token; STDOUT.flush; end
|
if @verbose then print token; STDOUT.flush; end
|
||||||
end
|
end
|
||||||
|
|
||||||
def msg(msg)
|
def msg(m)
|
||||||
puts "% 3d: %s" % [Thread.current[:my_client_id], msg]
|
printf("% 3d: %s\n", Thread.current[:my_client_id] || 0, m)
|
||||||
end
|
end
|
||||||
|
|
||||||
def vmsg(msg)
|
def vmsg(m)
|
||||||
if @verbose then msg(msg) end
|
if @verbose then msg(m) end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -110,12 +143,11 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
def unmask(buf, hlen, length)
|
def unmask(buf, hlen, length)
|
||||||
pstart = hlen + 4
|
pstart = hlen + 4
|
||||||
mask = buf[hlen...hlen+4]
|
mask = buf[hlen...hlen+4].each_byte.map{|b|b}
|
||||||
data = buf[pstart...pstart+length]
|
data = buf[pstart...pstart+length]
|
||||||
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
|
||||||
for i in (0...data.length) do
|
i=-1
|
||||||
data[i] ^= mask[i%4]
|
data = data.each_byte.map{|b| i+=1; (b ^ mask[i % 4]).chr}.join("")
|
||||||
end
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -237,7 +269,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
while t[:send_parts].length > 0
|
while t[:send_parts].length > 0
|
||||||
buf = t[:send_parts].shift
|
buf = t[:send_parts].shift
|
||||||
sent = t[:client].send(buf, 0)
|
sent = t[:client].write(buf)
|
||||||
|
|
||||||
if sent == buf.length
|
if sent == buf.length
|
||||||
traffic "<"
|
traffic "<"
|
||||||
@@ -257,7 +289,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
closed = false
|
closed = false
|
||||||
bufs = []
|
bufs = []
|
||||||
|
|
||||||
buf = t[:client].recv(@@Buffer_size)
|
buf = t[:client].read_nonblock(@@Buffer_size)
|
||||||
|
|
||||||
if buf.length == 0
|
if buf.length == 0
|
||||||
return bufs, "Client closed abrubtly"
|
return bufs, "Client closed abrubtly"
|
||||||
@@ -286,10 +318,10 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if buf[0...2] == "\xff\x00":
|
if buf[0...2] == "\xff\x00"
|
||||||
closed = "Client sent orderly close frame"
|
closed = "Client sent orderly close frame"
|
||||||
break
|
break
|
||||||
elsif buf[0...2] == "\x00\xff":
|
elsif buf[0...2] == "\x00\xff"
|
||||||
buf = buf[2...buf.length]
|
buf = buf[2...buf.length]
|
||||||
continue # No-op frame
|
continue # No-op frame
|
||||||
elsif buf.count("\xff") == 0
|
elsif buf.count("\xff") == 0
|
||||||
@@ -308,7 +340,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
|
|
||||||
bufs << frame['payload']
|
bufs << frame['payload']
|
||||||
|
|
||||||
if frame['left'] > 0:
|
if frame['left'] > 0
|
||||||
buf = buf[-frame['left']...buf.length]
|
buf = buf[-frame['left']...buf.length]
|
||||||
else
|
else
|
||||||
buf = ''
|
buf = ''
|
||||||
@@ -328,10 +360,10 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
end
|
end
|
||||||
|
|
||||||
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
|
||||||
t[:client].send(buf, 0)
|
t[:client].write(buf)
|
||||||
elsif t[:version] == "hixie-76"
|
elsif t[:version] == "hixie-76"
|
||||||
buf = "\xff\x00"
|
buf = "\xff\x00"
|
||||||
t[:client].send(buf, 0)
|
t[:client].write(buf)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -344,16 +376,21 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
raise EClose, "ignoring socket not ready"
|
raise EClose, "ignoring socket not ready"
|
||||||
end
|
end
|
||||||
|
|
||||||
handshake = sock.recv(1024, Socket::MSG_PEEK)
|
handshake = ""
|
||||||
#msg "Handshake [#{handshake.inspect}]"
|
msg "About to read from sock [#{sock.inspect}]"
|
||||||
|
handshake = sock.read_nonblock(1024)
|
||||||
|
msg "Handshake [#{handshake.inspect}]"
|
||||||
|
|
||||||
if handshake == ""
|
if handshake == nil or handshake == ""
|
||||||
raise(EClose, "ignoring empty handshake")
|
raise(EClose, "ignoring empty handshake")
|
||||||
else
|
else
|
||||||
stype = "Plain non-SSL (ws://)"
|
stype = "Plain non-SSL (ws://)"
|
||||||
scheme = "ws"
|
scheme = "ws"
|
||||||
|
if sock.class == OpenSSL::SSL::SSLSocket
|
||||||
|
stype = "SSL (wss://)"
|
||||||
|
scheme = "wss"
|
||||||
|
end
|
||||||
retsock = sock
|
retsock = sock
|
||||||
sock.recv(1024)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
h = t[:headers] = {}
|
h = t[:headers] = {}
|
||||||
@@ -365,7 +402,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
|
||||||
h[hsplit[1].strip.downcase] = hsplit[2]
|
h[hsplit[1].strip.downcase] = hsplit[2]
|
||||||
end
|
end
|
||||||
#puts "Headers: #{h.inspect}"
|
puts "Headers: #{h.inspect}"
|
||||||
|
|
||||||
unless h.has_key?('upgrade') &&
|
unless h.has_key?('upgrade') &&
|
||||||
h['upgrade'].downcase == 'websocket'
|
h['upgrade'].downcase == 'websocket'
|
||||||
@@ -445,7 +482,7 @@ Sec-WebSocket-Accept: %s\r
|
|||||||
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
if t[:path] then msg "Path: '%s'" % [t[:path]] end
|
||||||
|
|
||||||
#puts "sending reponse #{response.inspect}"
|
#puts "sending reponse #{response.inspect}"
|
||||||
retsock.send(response, 0)
|
retsock.write(response)
|
||||||
|
|
||||||
# Return the WebSocket socket which may be SSL wrapped
|
# Return the WebSocket socket which may be SSL wrapped
|
||||||
return retsock
|
return retsock
|
||||||
|
|||||||
+3
-3
@@ -39,7 +39,7 @@ Traffic Legend:
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Echo back whatever is received
|
# Echo back whatever is received
|
||||||
def new_client(client)
|
def new_websocket_client(client)
|
||||||
|
|
||||||
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
||||||
tsock = TCPSocket.open(@target_host, @target_port)
|
tsock = TCPSocket.open(@target_host, @target_port)
|
||||||
@@ -92,7 +92,7 @@ Traffic Legend:
|
|||||||
# Receive target data and queue for the client
|
# Receive target data and queue for the client
|
||||||
if ins && ins.include?(target)
|
if ins && ins.include?(target)
|
||||||
buf = target.recv(@@Buffer_size)
|
buf = target.recv(@@Buffer_size)
|
||||||
if buf.length == 0:
|
if buf.length == 0
|
||||||
raise EClose, "Target closed"
|
raise EClose, "Target closed"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ parser = OptionParser.new do |o|
|
|||||||
o.parse!
|
o.parse!
|
||||||
end
|
end
|
||||||
|
|
||||||
if ARGV.length < 2:
|
if ARGV.length < 2
|
||||||
puts "Too few arguments"
|
puts "Too few arguments"
|
||||||
exit 2
|
exit 2
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
* REBIND_PORT_NEW environment variables are set then bind on the new
|
* REBIND_PORT_NEW environment variables are set then bind on the new
|
||||||
* port (of localhost) instead of the old port.
|
* port (of localhost) instead of the old port.
|
||||||
*
|
*
|
||||||
* This allows a proxy (such as wsproxy) to run on the old port and translate
|
* This allows a bridge/proxy (such as websockify) to run on the old port and
|
||||||
* traffic to/from the new port.
|
* translate traffic to/from the new port.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* LD_PRELOAD=./rebind.so \
|
* LD_PRELOAD=./rebind.so \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import websockify
|
import websockify
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.4.0'
|
version = '0.7.0'
|
||||||
name = 'websockify'
|
name = 'websockify'
|
||||||
long_description = open("README.md").read() + "\n" + \
|
long_description = open("README.md").read() + "\n" + \
|
||||||
open("CHANGES.txt").read() + "\n"
|
open("CHANGES.txt").read() + "\n"
|
||||||
@@ -11,7 +11,21 @@ setup(name=name,
|
|||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 2",
|
||||||
|
"Programming Language :: Python :: 2.6",
|
||||||
|
"Programming Language :: Python :: 2.7",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.3",
|
||||||
|
"Programming Language :: Python :: 3.4"
|
||||||
],
|
],
|
||||||
|
data_files=[('share/websockify/include',
|
||||||
|
['include/util.js',
|
||||||
|
'include/base64.js',
|
||||||
|
'include/websock.js']),
|
||||||
|
('share/websockify/include/web-socket-js',
|
||||||
|
['include/web-socket-js/WebSocketMain.swf',
|
||||||
|
'include/web-socket-js/swfobject.js',
|
||||||
|
'include/web-socket-js/web_socket.js'])],
|
||||||
keywords='noVNC websockify',
|
keywords='noVNC websockify',
|
||||||
license='LGPLv3',
|
license='LGPLv3',
|
||||||
url="https://github.com/kanaka/websockify",
|
url="https://github.com/kanaka/websockify",
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
mox
|
||||||
|
nose
|
||||||
+12
-11
@@ -10,17 +10,17 @@ 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
|
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os, sys, select, optparse
|
import os, sys, select, optparse, logging
|
||||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from websocket import WebSocketServer
|
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||||
|
|
||||||
class WebSocketEcho(WebSocketServer):
|
class WebSocketEcho(WebSocketRequestHandler):
|
||||||
"""
|
"""
|
||||||
WebSockets server that echos back whatever is received from the
|
WebSockets server that echos back whatever is received from the
|
||||||
client. """
|
client. """
|
||||||
buffer_size = 8096
|
buffer_size = 8096
|
||||||
|
|
||||||
def new_client(self):
|
def new_websocket_client(self):
|
||||||
"""
|
"""
|
||||||
Echo back whatever is received.
|
Echo back whatever is received.
|
||||||
"""
|
"""
|
||||||
@@ -28,28 +28,27 @@ class WebSocketEcho(WebSocketServer):
|
|||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
cpartial = ""
|
cpartial = ""
|
||||||
rlist = [self.client]
|
rlist = [self.request]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
wlist = []
|
wlist = []
|
||||||
|
|
||||||
if cqueue or c_pend: wlist.append(self.client)
|
if cqueue or c_pend: wlist.append(self.request)
|
||||||
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||||
if excepts: raise Exception("Socket exception")
|
if excepts: raise Exception("Socket exception")
|
||||||
|
|
||||||
if self.client in outs:
|
if self.request in outs:
|
||||||
# Send queued target data to the client
|
# Send queued target data to the client
|
||||||
c_pend = self.send_frames(cqueue)
|
c_pend = self.send_frames(cqueue)
|
||||||
cqueue = []
|
cqueue = []
|
||||||
|
|
||||||
if self.client in ins:
|
if self.request in ins:
|
||||||
# Receive client data, decode it, and send it back
|
# Receive client data, decode it, and send it back
|
||||||
frames, closed = self.recv_frames()
|
frames, closed = self.recv_frames()
|
||||||
cqueue.extend(frames)
|
cqueue.extend(frames)
|
||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
self.send_close()
|
self.send_close()
|
||||||
raise self.EClose(closed)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||||
@@ -69,7 +68,9 @@ if __name__ == '__main__':
|
|||||||
except:
|
except:
|
||||||
parser.error("Invalid arguments")
|
parser.error("Invalid arguments")
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
opts.web = "."
|
opts.web = "."
|
||||||
server = WebSocketEcho(**opts.__dict__)
|
server = WebSocketServer(WebSocketEcho, **opts.__dict__)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -12,7 +12,7 @@ require 'websocket'
|
|||||||
class WebSocketEcho < WebSocketServer
|
class WebSocketEcho < WebSocketServer
|
||||||
|
|
||||||
# Echo back whatever is received
|
# Echo back whatever is received
|
||||||
def new_client(client)
|
def new_websocket_client(client)
|
||||||
|
|
||||||
cqueue = []
|
cqueue = []
|
||||||
c_pend = 0
|
c_pend = 0
|
||||||
@@ -50,10 +50,17 @@ class WebSocketEcho < WebSocketServer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
port = ARGV[0].to_i
|
port = ARGV[0].to_i || 8080
|
||||||
puts "Starting server on port #{port}"
|
puts "Starting server on port #{port}"
|
||||||
|
server_cert = nil
|
||||||
|
server_key = nil
|
||||||
|
if ARGV.length > 2
|
||||||
|
server_cert = ARGV[1]
|
||||||
|
server_key = ARGV[2]
|
||||||
|
end
|
||||||
|
|
||||||
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true)
|
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true,
|
||||||
|
'server_cert' => server_cert, 'server_key' => server_key)
|
||||||
server.start
|
server.start
|
||||||
server.join
|
server.join
|
||||||
|
|
||||||
|
|||||||
+18
-15
@@ -6,35 +6,37 @@ that has a random payload (length and content) that is checksummed and
|
|||||||
given a sequence number. Any errors are reported and counted.
|
given a sequence number. Any errors are reported and counted.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, os, select, random, time, optparse
|
import sys, os, select, random, time, optparse, logging
|
||||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from websocket import WebSocketServer
|
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||||
|
|
||||||
class WebSocketLoad(WebSocketServer):
|
class WebSocketLoadServer(WebSocketServer):
|
||||||
|
|
||||||
buffer_size = 65536
|
|
||||||
|
|
||||||
max_packet_size = 10000
|
|
||||||
recv_cnt = 0
|
recv_cnt = 0
|
||||||
send_cnt = 0
|
send_cnt = 0
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.errors = 0
|
|
||||||
self.delay = kwargs.pop('delay')
|
self.delay = kwargs.pop('delay')
|
||||||
|
|
||||||
|
WebSocketServer.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketLoad(WebSocketRequestHandler):
|
||||||
|
|
||||||
|
max_packet_size = 10000
|
||||||
|
|
||||||
|
def new_websocket_client(self):
|
||||||
print "Prepopulating random array"
|
print "Prepopulating random array"
|
||||||
self.rand_array = []
|
self.rand_array = []
|
||||||
for i in range(0, self.max_packet_size):
|
for i in range(0, self.max_packet_size):
|
||||||
self.rand_array.append(random.randint(0, 9))
|
self.rand_array.append(random.randint(0, 9))
|
||||||
|
|
||||||
WebSocketServer.__init__(self, *args, **kwargs)
|
self.errors = 0
|
||||||
|
|
||||||
def new_client(self):
|
|
||||||
self.send_cnt = 0
|
self.send_cnt = 0
|
||||||
self.recv_cnt = 0
|
self.recv_cnt = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.responder(self.client)
|
self.responder(self.request)
|
||||||
except:
|
except:
|
||||||
print "accumulated errors:", self.errors
|
print "accumulated errors:", self.errors
|
||||||
self.errors = 0
|
self.errors = 0
|
||||||
@@ -61,14 +63,13 @@ class WebSocketLoad(WebSocketServer):
|
|||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
self.send_close()
|
self.send_close()
|
||||||
raise self.EClose(closed)
|
|
||||||
|
|
||||||
now = time.time() * 1000
|
now = time.time() * 1000
|
||||||
if client in outs:
|
if client in outs:
|
||||||
if c_pend:
|
if c_pend:
|
||||||
last_send = now
|
last_send = now
|
||||||
c_pend = self.send_frames()
|
c_pend = self.send_frames()
|
||||||
elif now > (last_send + self.delay):
|
elif now > (last_send + self.server.delay):
|
||||||
last_send = now
|
last_send = now
|
||||||
c_pend = self.send_frames([self.generate()])
|
c_pend = self.send_frames([self.generate()])
|
||||||
|
|
||||||
@@ -161,7 +162,9 @@ if __name__ == '__main__':
|
|||||||
except:
|
except:
|
||||||
parser.error("Invalid arguments")
|
parser.error("Invalid arguments")
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
opts.web = "."
|
opts.web = "."
|
||||||
server = WebSocketLoad(**opts.__dict__)
|
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
|
||||||
server.start_server()
|
server.start_server()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,430 @@
|
|||||||
|
# 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 errno
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import select
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import stubout
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import socket
|
||||||
|
import signal
|
||||||
|
from websockify import websocket
|
||||||
|
|
||||||
|
try:
|
||||||
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||||
|
except ImportError:
|
||||||
|
from http.server import SimpleHTTPRequestHandler
|
||||||
|
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
BytesIO = StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def raise_oserror(*args, **kwargs):
|
||||||
|
raise OSError('fake error')
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSocket(object):
|
||||||
|
def __init__(self, data=''):
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
self._data = data
|
||||||
|
else:
|
||||||
|
self._data = data.encode('latin_1')
|
||||||
|
|
||||||
|
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 WebSocketRequestHandlerTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(WebSocketRequestHandlerTestCase, self).setUp()
|
||||||
|
self.stubs = stubout.StubOutForTesting()
|
||||||
|
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||||
|
# Mock this out cause it screws tests up
|
||||||
|
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Called automatically after each test."""
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
os.rmdir(self.tmpdir)
|
||||||
|
super(WebSocketRequestHandlerTestCase, self).tearDown()
|
||||||
|
|
||||||
|
def _get_server(self, handler_class=websocket.WebSocketRequestHandler,
|
||||||
|
**kwargs):
|
||||||
|
web = kwargs.pop('web', self.tmpdir)
|
||||||
|
return websocket.WebSocketServer(
|
||||||
|
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)
|
||||||
|
|
||||||
|
def test_normal_get_with_only_upgrade_returns_error(self):
|
||||||
|
server = self._get_server(web=None)
|
||||||
|
handler = websocket.WebSocketRequestHandler(
|
||||||
|
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||||
|
|
||||||
|
def fake_send_response(self, code, message=None):
|
||||||
|
self.last_code = code
|
||||||
|
|
||||||
|
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||||
|
fake_send_response)
|
||||||
|
|
||||||
|
handler.do_GET()
|
||||||
|
self.assertEqual(handler.last_code, 405)
|
||||||
|
|
||||||
|
def test_list_dir_with_file_only_returns_error(self):
|
||||||
|
server = self._get_server(file_only=True)
|
||||||
|
handler = websocket.WebSocketRequestHandler(
|
||||||
|
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
|
||||||
|
|
||||||
|
def fake_send_response(self, code, message=None):
|
||||||
|
self.last_code = code
|
||||||
|
|
||||||
|
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||||
|
fake_send_response)
|
||||||
|
|
||||||
|
handler.path = '/'
|
||||||
|
handler.do_GET()
|
||||||
|
self.assertEqual(handler.last_code, 404)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketServerTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(WebSocketServerTestCase, self).setUp()
|
||||||
|
self.stubs = stubout.StubOutForTesting()
|
||||||
|
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||||
|
# Mock this out cause it screws tests up
|
||||||
|
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Called automatically after each test."""
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
os.rmdir(self.tmpdir)
|
||||||
|
super(WebSocketServerTestCase, self).tearDown()
|
||||||
|
|
||||||
|
def _get_server(self, handler_class=websocket.WebSocketRequestHandler,
|
||||||
|
**kwargs):
|
||||||
|
return websocket.WebSocketServer(
|
||||||
|
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)
|
||||||
|
self.stubs.Set(os, 'fork', lambda *args: 0)
|
||||||
|
self.stubs.Set(signal, 'signal', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'close', 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)
|
||||||
|
self.stubs.Set(os, 'fork', lambda *args: 0)
|
||||||
|
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||||
|
self.stubs.Set(signal, 'signal', lambda *args: None)
|
||||||
|
self.stubs.Set(os, 'close', raise_oserror_ebadf)
|
||||||
|
self.stubs.Set(os, 'open', 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 ([], [], [])
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', fake_select)
|
||||||
|
self.assertRaises(
|
||||||
|
websocket.WebSocketServer.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], [], [])
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', fake_select)
|
||||||
|
self.assertRaises(
|
||||||
|
websocket.WebSocketServer.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('some initial data')
|
||||||
|
|
||||||
|
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||||
|
return ([sock], [], [])
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', fake_select)
|
||||||
|
self.assertRaises(
|
||||||
|
websocket.WebSocketServer.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('some initial data')
|
||||||
|
|
||||||
|
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||||
|
return ([sock], [], [])
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', 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("\x16some ssl data")
|
||||||
|
|
||||||
|
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||||
|
return ([sock], [], [])
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', fake_select)
|
||||||
|
self.assertRaises(
|
||||||
|
websocket.WebSocketServer.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("\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)
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', fake_select)
|
||||||
|
self.stubs.Set(ssl, 'wrap_socket', fake_wrap_socket)
|
||||||
|
self.assertRaises(
|
||||||
|
websocket.WebSocketServer.EClose, server.do_handshake,
|
||||||
|
sock, '127.0.0.1')
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket',
|
||||||
|
lambda *args, **kwargs: sock)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(select, 'select', 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
|
||||||
|
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket',
|
||||||
|
lambda *args, **kwargs: sock)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(select, 'select', 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()
|
||||||
|
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket',
|
||||||
|
lambda *args, **kwargs: sock)
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
self.stubs.Set(select, 'select', 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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class HyBiEncodeDecodeTestCase(unittest.TestCase):
|
||||||
|
def test_decode_hybi_text(self):
|
||||||
|
buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58'
|
||||||
|
res = websocket.WebSocketRequestHandler.decode_hybi(buf)
|
||||||
|
|
||||||
|
self.assertEqual(res['fin'], 1)
|
||||||
|
self.assertEqual(res['opcode'], 0x1)
|
||||||
|
self.assertEqual(res['masked'], True)
|
||||||
|
self.assertEqual(res['length'], 5)
|
||||||
|
self.assertEqual(res['payload'], b'Hello')
|
||||||
|
self.assertEqual(res['left'], 0)
|
||||||
|
|
||||||
|
def test_decode_hybi_binary(self):
|
||||||
|
buf = b'\x82\x04\x01\x02\x03\x04'
|
||||||
|
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||||
|
|
||||||
|
self.assertEqual(res['fin'], 1)
|
||||||
|
self.assertEqual(res['opcode'], 0x2)
|
||||||
|
self.assertEqual(res['length'], 4)
|
||||||
|
self.assertEqual(res['payload'], b'\x01\x02\x03\x04')
|
||||||
|
self.assertEqual(res['left'], 0)
|
||||||
|
|
||||||
|
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
|
||||||
|
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||||
|
|
||||||
|
self.assertEqual(res['fin'], 1)
|
||||||
|
self.assertEqual(res['opcode'], 0x2)
|
||||||
|
self.assertEqual(res['length'], 260)
|
||||||
|
self.assertEqual(res['payload'], data)
|
||||||
|
self.assertEqual(res['left'], 0)
|
||||||
|
|
||||||
|
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
|
||||||
|
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||||
|
|
||||||
|
self.assertEqual(res['fin'], 1)
|
||||||
|
self.assertEqual(res['opcode'], 0x2)
|
||||||
|
self.assertEqual(res['length'], 260)
|
||||||
|
self.assertEqual(res['payload'], data)
|
||||||
|
self.assertEqual(res['left'], 0)
|
||||||
|
|
||||||
|
def test_decode_hybi_multi(self):
|
||||||
|
buf1 = b'\x01\x03\x48\x65\x6c'
|
||||||
|
buf2 = b'\x80\x02\x6c\x6f'
|
||||||
|
|
||||||
|
res1 = websocket.WebSocketRequestHandler.decode_hybi(buf1, strict=False)
|
||||||
|
self.assertEqual(res1['fin'], 0)
|
||||||
|
self.assertEqual(res1['opcode'], 0x1)
|
||||||
|
self.assertEqual(res1['length'], 3)
|
||||||
|
self.assertEqual(res1['payload'], b'Hel')
|
||||||
|
self.assertEqual(res1['left'], 0)
|
||||||
|
|
||||||
|
res2 = websocket.WebSocketRequestHandler.decode_hybi(buf2, strict=False)
|
||||||
|
self.assertEqual(res2['fin'], 1)
|
||||||
|
self.assertEqual(res2['opcode'], 0x0)
|
||||||
|
self.assertEqual(res2['length'], 2)
|
||||||
|
self.assertEqual(res2['payload'], b'lo')
|
||||||
|
self.assertEqual(res2['left'], 0)
|
||||||
|
|
||||||
|
def test_encode_hybi_basic(self):
|
||||||
|
res = websocket.WebSocketRequestHandler.encode_hybi(b'Hello', 0x1)
|
||||||
|
expected = (b'\x81\x05\x48\x65\x6c\x6c\x6f', 2, 0)
|
||||||
|
|
||||||
|
self.assertEqual(res, expected)
|
||||||
|
|
||||||
|
def test_strict_mode_refuses_unmasked_client_frames(self):
|
||||||
|
buf = b'\x81\x05\x48\x65\x6c\x6c\x6f'
|
||||||
|
self.assertRaises(websocket.WebSocketRequestHandler.CClose,
|
||||||
|
websocket.WebSocketRequestHandler.decode_hybi,
|
||||||
|
buf)
|
||||||
|
|
||||||
|
def test_no_strict_mode_accepts_unmasked_client_frames(self):
|
||||||
|
buf = b'\x81\x05\x48\x65\x6c\x6c\x6f'
|
||||||
|
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||||
|
|
||||||
|
self.assertEqual(res['fin'], 1)
|
||||||
|
self.assertEqual(res['opcode'], 0x1)
|
||||||
|
self.assertEqual(res['masked'], False)
|
||||||
|
self.assertEqual(res['length'], 5)
|
||||||
|
self.assertEqual(res['payload'], b'Hello')
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# 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 unittest
|
||||||
|
import unittest
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import stubout
|
||||||
|
|
||||||
|
from websockify import websocket
|
||||||
|
from websockify import websocketproxy
|
||||||
|
from websockify import token_plugins
|
||||||
|
from websockify import auth_plugins
|
||||||
|
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
BytesIO = StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSocket(object):
|
||||||
|
def __init__(self, data=''):
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
self._data = data
|
||||||
|
else:
|
||||||
|
self._data = data.encode('latin_1')
|
||||||
|
|
||||||
|
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.stubs = stubout.StubOutForTesting()
|
||||||
|
self.handler = websocketproxy.ProxyRequestHandler(
|
||||||
|
FakeSocket(''), "127.0.0.1", FakeServer())
|
||||||
|
self.handler.path = "https://localhost:6080/websockify?token=blah"
|
||||||
|
self.handler.headers = None
|
||||||
|
self.stubs.Set(websocket.WebSocketServer, 'socket',
|
||||||
|
staticmethod(lambda *args, **kwargs: None))
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
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.handler.path)
|
||||||
|
|
||||||
|
self.assertEqual(host, "some host")
|
||||||
|
self.assertEqual(port, "some port")
|
||||||
|
|
||||||
|
def test_get_target_raises_error_on_unknown_token(self):
|
||||||
|
class TestPlugin(token_plugins.BasePlugin):
|
||||||
|
def lookup(self, token):
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.assertRaises(FakeServer.EClose, self.handler.get_target,
|
||||||
|
TestPlugin(None), "https://localhost:6080/websockify?token=blah")
|
||||||
|
|
||||||
|
def test_token_plugin(self):
|
||||||
|
class TestPlugin(token_plugins.BasePlugin):
|
||||||
|
def lookup(self, token):
|
||||||
|
return (self.source + token).split(',')
|
||||||
|
|
||||||
|
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
||||||
|
lambda *args, **kwargs: None)
|
||||||
|
|
||||||
|
self.handler.server.token_plugin = TestPlugin("somehost,")
|
||||||
|
self.handler.new_websocket_client()
|
||||||
|
|
||||||
|
self.assertEqual(self.handler.server.target_host, "somehost")
|
||||||
|
self.assertEqual(self.handler.server.target_port, "blah")
|
||||||
|
|
||||||
|
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("some error")
|
||||||
|
|
||||||
|
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
||||||
|
staticmethod(lambda *args, **kwargs: None))
|
||||||
|
|
||||||
|
self.handler.server.auth_plugin = TestPlugin("somehost")
|
||||||
|
self.handler.server.target_host = "somehost"
|
||||||
|
self.handler.server.target_port = "someport"
|
||||||
|
|
||||||
|
self.assertRaises(auth_plugins.AuthenticationError,
|
||||||
|
self.handler.new_websocket_client)
|
||||||
|
|
||||||
|
self.handler.server.target_host = "someotherhost"
|
||||||
|
self.handler.new_websocket_client()
|
||||||
|
|
||||||
+4
-7
@@ -5,18 +5,15 @@ Display UTF-8 encoding for 0-255.'''
|
|||||||
|
|
||||||
import sys, os, socket, ssl, time, traceback
|
import sys, os, socket, ssl, time, traceback
|
||||||
from select import select
|
from select import select
|
||||||
|
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||||
sys.path.insert(0,os.path.dirname(__file__) + "/../")
|
from websockify.websocket import WebSocketServer
|
||||||
from websocket import WebSocketServer
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print "val: hixie | hybi_base64 | hybi_binary"
|
print "val: hybi_base64 | hybi_binary"
|
||||||
for c in range(0, 256):
|
for c in range(0, 256):
|
||||||
hixie = WebSocketServer.encode_hixie(chr(c))
|
|
||||||
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
|
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
|
||||||
base64=True)
|
base64=True)
|
||||||
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
|
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
|
||||||
base64=False)
|
base64=False)
|
||||||
print "%d: %s | %s | %s" % (c, repr(hixie), repr(hybi_base64),
|
print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary))
|
||||||
repr(hybi_binary))
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 = py24,py26,py27,py33,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,2 +1,2 @@
|
|||||||
from websocket import *
|
from websockify.websocket import *
|
||||||
from websocketproxy import *
|
from websockify.websocketproxy import *
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
class BasePlugin(object):
|
||||||
|
def __init__(self, src=None):
|
||||||
|
self.source = src
|
||||||
|
|
||||||
|
def authenticate(self, headers, target_host, target_port):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidOriginError(AuthenticationError):
|
||||||
|
def __init__(self, expected, actual):
|
||||||
|
self.expected_origin = expected
|
||||||
|
self.actual_origin = actual
|
||||||
|
|
||||||
|
super(InvalidOriginError, self).__init__(
|
||||||
|
"Invalid Origin Header: Expected one of "
|
||||||
|
"%s, got '%s'" % (expected, actual))
|
||||||
|
|
||||||
|
|
||||||
|
class ExpectOrigin(object):
|
||||||
|
def __init__(self, src=None):
|
||||||
|
if src is None:
|
||||||
|
self.source = []
|
||||||
|
else:
|
||||||
|
self.source = src.split()
|
||||||
|
|
||||||
|
def authenticate(self, headers, target_host, target_port):
|
||||||
|
origin = headers.getheader('Origin', None)
|
||||||
|
if origin is None or origin not in self.source:
|
||||||
|
raise InvalidOriginError(expected=self.source, actual=origin)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
class BasePlugin(object):
|
||||||
|
def __init__(self, src):
|
||||||
|
self.source = src
|
||||||
|
|
||||||
|
def lookup(self, token):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ReadOnlyTokenFile(BasePlugin):
|
||||||
|
# source is a token file with lines like
|
||||||
|
# token: host:port
|
||||||
|
# or a directory of such files
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ReadOnlyTokenFile, self).__init__(*args, **kwargs)
|
||||||
|
self._targets = None
|
||||||
|
|
||||||
|
def _load_targets(self):
|
||||||
|
if os.path.isdir(self.source):
|
||||||
|
cfg_files = [os.path.join(self.source, f) for
|
||||||
|
f in os.listdir(self.source)]
|
||||||
|
else:
|
||||||
|
cfg_files = [self.source]
|
||||||
|
|
||||||
|
self._targets = {}
|
||||||
|
for f in cfg_files:
|
||||||
|
for line in [l.strip() for l in open(f).readlines()]:
|
||||||
|
if line and not line.startswith('#'):
|
||||||
|
tok, target = line.split(': ')
|
||||||
|
self._targets[tok] = target.strip().split(':')
|
||||||
|
|
||||||
|
def lookup(self, token):
|
||||||
|
if self._targets is None:
|
||||||
|
self._load_targets()
|
||||||
|
|
||||||
|
if token in self._targets:
|
||||||
|
return self._targets[token]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# the above one is probably more efficient, but this one is
|
||||||
|
# more backwards compatible (although in most cases
|
||||||
|
# ReadOnlyTokenFile should suffice)
|
||||||
|
class TokenFile(ReadOnlyTokenFile):
|
||||||
|
# source is a token file with lines like
|
||||||
|
# token: host:port
|
||||||
|
# or a directory of such files
|
||||||
|
def lookup(self, token):
|
||||||
|
self._load_targets()
|
||||||
|
|
||||||
|
return super(TokenFile, self).lookup(token)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTokenAPI(BasePlugin):
|
||||||
|
# source is a url with a '%s' in it where the token
|
||||||
|
# should go
|
||||||
|
|
||||||
|
# we import things on demand so that other plugins
|
||||||
|
# in this file can be used w/o unecessary dependencies
|
||||||
|
|
||||||
|
def process_result(self, resp):
|
||||||
|
return resp.text.split(':')
|
||||||
|
|
||||||
|
def lookup(self, token):
|
||||||
|
import requests
|
||||||
|
|
||||||
|
resp = requests.get(self.source % token)
|
||||||
|
|
||||||
|
if resp.ok:
|
||||||
|
return self.process_result(resp)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class JSONTokenApi(BaseTokenAPI):
|
||||||
|
# source is a url with a '%s' in it where the token
|
||||||
|
# should go
|
||||||
|
|
||||||
|
def process_result(self, resp):
|
||||||
|
return (resp.json['host'], resp.json['port'])
|
||||||
+588
-504
File diff suppressed because it is too large
Load Diff
+338
-178
@@ -11,24 +11,20 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import signal, socket, optparse, time, os, sys, subprocess
|
import signal, socket, optparse, time, os, sys, subprocess, logging, errno
|
||||||
from select import select
|
try: from socketserver import ForkingMixIn
|
||||||
import websocket
|
except: from SocketServer import ForkingMixIn
|
||||||
|
try: from http.server import HTTPServer
|
||||||
|
except: from BaseHTTPServer import HTTPServer
|
||||||
|
import select
|
||||||
|
from websockify import websocket
|
||||||
try:
|
try:
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
except:
|
except:
|
||||||
from cgi import parse_qs
|
from cgi import parse_qs
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
class WebSocketProxy(websocket.WebSocketServer):
|
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
||||||
"""
|
|
||||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
|
||||||
socket server target. All traffic to/from the client is base64
|
|
||||||
encoded/decoded to allow binary data to be sent/received to/from
|
|
||||||
the target.
|
|
||||||
"""
|
|
||||||
|
|
||||||
buffer_size = 65536
|
|
||||||
|
|
||||||
traffic_legend = """
|
traffic_legend = """
|
||||||
Traffic Legend:
|
Traffic Legend:
|
||||||
@@ -42,7 +38,171 @@ Traffic Legend:
|
|||||||
<. - Client send partial
|
<. - Client send partial
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def new_websocket_client(self):
|
||||||
|
"""
|
||||||
|
Called after a new WebSocket connection has been established.
|
||||||
|
"""
|
||||||
|
# Checks if we receive a token, and look
|
||||||
|
# for a valid target for it then
|
||||||
|
if self.server.token_plugin:
|
||||||
|
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
||||||
|
|
||||||
|
if self.server.auth_plugin:
|
||||||
|
self.server.auth_plugin.authenticate(
|
||||||
|
headers=self.headers, target_host=self.server.target_host,
|
||||||
|
target_port=self.server.target_port)
|
||||||
|
|
||||||
|
# Connect to the target
|
||||||
|
if self.server.wrap_cmd:
|
||||||
|
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
|
||||||
|
elif self.server.unix_target:
|
||||||
|
msg = "connecting to unix socket: %s" % self.server.unix_target
|
||||||
|
else:
|
||||||
|
msg = "connecting to: %s:%s" % (
|
||||||
|
self.server.target_host, self.server.target_port)
|
||||||
|
|
||||||
|
if self.server.ssl_target:
|
||||||
|
msg += " (using SSL)"
|
||||||
|
self.log_message(msg)
|
||||||
|
|
||||||
|
tsock = websocket.WebSocketServer.socket(self.server.target_host,
|
||||||
|
self.server.target_port,
|
||||||
|
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
|
||||||
|
|
||||||
|
self.print_traffic(self.traffic_legend)
|
||||||
|
|
||||||
|
# Start proxying
|
||||||
|
try:
|
||||||
|
self.do_proxy(tsock)
|
||||||
|
except:
|
||||||
|
if tsock:
|
||||||
|
tsock.shutdown(socket.SHUT_RDWR)
|
||||||
|
tsock.close()
|
||||||
|
if self.verbose:
|
||||||
|
self.log_message("%s:%s: Closed target",
|
||||||
|
self.server.target_host, self.server.target_port)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_target(self, target_plugin, path):
|
||||||
|
"""
|
||||||
|
Parses the path, extracts a token, and looks up a target
|
||||||
|
for that token using the token plugin. Sets
|
||||||
|
target_host and target_port if successful
|
||||||
|
"""
|
||||||
|
# The files in targets contain the lines
|
||||||
|
# in the form of token: host:port
|
||||||
|
|
||||||
|
# Extract the token parameter from url
|
||||||
|
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||||
|
|
||||||
|
if not 'token' in args or not len(args['token']):
|
||||||
|
raise self.server.EClose("Token not present")
|
||||||
|
|
||||||
|
token = args['token'][0].rstrip('\n')
|
||||||
|
|
||||||
|
result_pair = target_plugin.lookup(token)
|
||||||
|
|
||||||
|
if result_pair is not None:
|
||||||
|
return result_pair
|
||||||
|
else:
|
||||||
|
raise self.server.EClose("Token '%s' not found" % token)
|
||||||
|
|
||||||
|
def do_proxy(self, target):
|
||||||
|
"""
|
||||||
|
Proxy client WebSocket to normal target socket.
|
||||||
|
"""
|
||||||
|
cqueue = []
|
||||||
|
c_pend = 0
|
||||||
|
tqueue = []
|
||||||
|
rlist = [self.request, target]
|
||||||
|
|
||||||
|
if self.server.heartbeat:
|
||||||
|
now = time.time()
|
||||||
|
self.heartbeat = now + self.server.heartbeat
|
||||||
|
else:
|
||||||
|
self.heartbeat = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
wlist = []
|
||||||
|
|
||||||
|
if self.heartbeat is not None:
|
||||||
|
now = time.time()
|
||||||
|
if now > self.heartbeat:
|
||||||
|
self.heartbeat = now + self.server.heartbeat
|
||||||
|
self.send_ping()
|
||||||
|
|
||||||
|
if tqueue: wlist.append(target)
|
||||||
|
if cqueue or c_pend: wlist.append(self.request)
|
||||||
|
try:
|
||||||
|
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||||
|
except (select.error, OSError):
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
if hasattr(exc, 'errno'):
|
||||||
|
err = exc.errno
|
||||||
|
else:
|
||||||
|
err = exc[0]
|
||||||
|
|
||||||
|
if err != errno.EINTR:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
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 queue for target
|
||||||
|
bufs, closed = self.recv_frames()
|
||||||
|
tqueue.extend(bufs)
|
||||||
|
|
||||||
|
if closed:
|
||||||
|
# TODO: What about blocking on client socket?
|
||||||
|
if self.verbose:
|
||||||
|
self.log_message("%s:%s: Client closed connection",
|
||||||
|
self.server.target_host, self.server.target_port)
|
||||||
|
raise self.CClose(closed['code'], closed['reason'])
|
||||||
|
|
||||||
|
|
||||||
|
if target in outs:
|
||||||
|
# Send queued client data to the target
|
||||||
|
dat = tqueue.pop(0)
|
||||||
|
sent = target.send(dat)
|
||||||
|
if sent == len(dat):
|
||||||
|
self.print_traffic(">")
|
||||||
|
else:
|
||||||
|
# requeue the remaining data
|
||||||
|
tqueue.insert(0, dat[sent:])
|
||||||
|
self.print_traffic(".>")
|
||||||
|
|
||||||
|
|
||||||
|
if target in ins:
|
||||||
|
# Receive target data, encode it and queue for client
|
||||||
|
buf = target.recv(self.buffer_size)
|
||||||
|
if len(buf) == 0:
|
||||||
|
if self.verbose:
|
||||||
|
self.log_message("%s:%s: Target closed connection",
|
||||||
|
self.server.target_host, self.server.target_port)
|
||||||
|
raise self.CClose(1000, "Target closed")
|
||||||
|
|
||||||
|
cqueue.append(buf)
|
||||||
|
self.print_traffic("{")
|
||||||
|
|
||||||
|
class WebSocketProxy(websocket.WebSocketServer):
|
||||||
|
"""
|
||||||
|
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||||
|
socket server target. All traffic to/from the client is base64
|
||||||
|
encoded/decoded to allow binary data to be sent/received to/from
|
||||||
|
the target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
buffer_size = 65536
|
||||||
|
|
||||||
|
def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs):
|
||||||
# Save off proxy specific options
|
# Save off proxy specific options
|
||||||
self.target_host = kwargs.pop('target_host', None)
|
self.target_host = kwargs.pop('target_host', None)
|
||||||
self.target_port = kwargs.pop('target_port', None)
|
self.target_port = kwargs.pop('target_port', None)
|
||||||
@@ -50,12 +210,19 @@ Traffic Legend:
|
|||||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||||
self.unix_target = kwargs.pop('unix_target', None)
|
self.unix_target = kwargs.pop('unix_target', None)
|
||||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
self.target_cfg = kwargs.pop('target_cfg', None)
|
self.heartbeat = kwargs.pop('heartbeat', None)
|
||||||
|
|
||||||
|
self.token_plugin = kwargs.pop('token_plugin', None)
|
||||||
|
self.auth_plugin = kwargs.pop('auth_plugin', None)
|
||||||
|
|
||||||
# Last 3 timestamps command was run
|
# Last 3 timestamps command was run
|
||||||
self.wrap_times = [0, 0, 0]
|
self.wrap_times = [0, 0, 0]
|
||||||
|
|
||||||
if self.wrap_cmd:
|
if self.wrap_cmd:
|
||||||
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
|
wsdir = os.path.dirname(sys.argv[0])
|
||||||
|
rebinder_path = [os.path.join(wsdir, "..", "lib"),
|
||||||
|
os.path.join(wsdir, "..", "lib", "websockify"),
|
||||||
|
wsdir]
|
||||||
self.rebinder = None
|
self.rebinder = None
|
||||||
|
|
||||||
for rdir in rebinder_path:
|
for rdir in rebinder_path:
|
||||||
@@ -80,13 +247,10 @@ Traffic Legend:
|
|||||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||||
"REBIND_NEW_PORT": str(self.target_port)})
|
"REBIND_NEW_PORT": str(self.target_port)})
|
||||||
|
|
||||||
if self.target_cfg:
|
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||||
self.target_cfg = os.path.abspath(self.target_cfg)
|
|
||||||
|
|
||||||
websocket.WebSocketServer.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def run_wrap_cmd(self):
|
def run_wrap_cmd(self):
|
||||||
print("Starting '%s'" % " ".join(self.wrap_cmd))
|
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
|
||||||
self.wrap_times.append(time.time())
|
self.wrap_times.append(time.time())
|
||||||
self.wrap_times.pop(0)
|
self.wrap_times.pop(0)
|
||||||
self.cmd = subprocess.Popen(
|
self.cmd = subprocess.Popen(
|
||||||
@@ -106,9 +270,9 @@ Traffic Legend:
|
|||||||
else:
|
else:
|
||||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||||
|
|
||||||
if self.target_cfg:
|
if self.token_plugin:
|
||||||
msg = " - proxying from %s:%s to targets in %s" % (
|
msg = " - proxying from %s:%s to targets generated by %s" % (
|
||||||
self.listen_host, self.listen_port, self.target_cfg)
|
self.listen_host, self.listen_port, type(self.token_plugin).__name__)
|
||||||
else:
|
else:
|
||||||
msg = " - proxying from %s:%s to %s" % (
|
msg = " - proxying from %s:%s to %s" % (
|
||||||
self.listen_host, self.listen_port, dst_string)
|
self.listen_host, self.listen_port, dst_string)
|
||||||
@@ -116,7 +280,7 @@ Traffic Legend:
|
|||||||
if self.ssl_target:
|
if self.ssl_target:
|
||||||
msg += " (using SSL)"
|
msg += " (using SSL)"
|
||||||
|
|
||||||
print(msg + "\n")
|
self.msg("%s", msg)
|
||||||
|
|
||||||
if self.wrap_cmd:
|
if self.wrap_cmd:
|
||||||
self.run_wrap_cmd()
|
self.run_wrap_cmd()
|
||||||
@@ -142,158 +306,11 @@ Traffic Legend:
|
|||||||
if (now - avg) < 10:
|
if (now - avg) < 10:
|
||||||
# 3 times in the last 10 seconds
|
# 3 times in the last 10 seconds
|
||||||
if self.spawn_message:
|
if self.spawn_message:
|
||||||
print("Command respawning too fast")
|
self.warn("Command respawning too fast")
|
||||||
self.spawn_message = False
|
self.spawn_message = False
|
||||||
else:
|
else:
|
||||||
self.run_wrap_cmd()
|
self.run_wrap_cmd()
|
||||||
|
|
||||||
#
|
|
||||||
# Routines above this point are run in the master listener
|
|
||||||
# process.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
# Routines below this point are connection handler routines and
|
|
||||||
# will be run in a separate forked process for each connection.
|
|
||||||
#
|
|
||||||
|
|
||||||
def new_client(self):
|
|
||||||
"""
|
|
||||||
Called after a new WebSocket connection has been established.
|
|
||||||
"""
|
|
||||||
# Checks if we receive a token, and look
|
|
||||||
# for a valid target for it then
|
|
||||||
if self.target_cfg:
|
|
||||||
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
|
|
||||||
|
|
||||||
# Connect to the target
|
|
||||||
if self.wrap_cmd:
|
|
||||||
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
|
||||||
elif self.unix_target:
|
|
||||||
msg = "connecting to unix socket: %s" % self.unix_target
|
|
||||||
else:
|
|
||||||
msg = "connecting to: %s:%s" % (
|
|
||||||
self.target_host, self.target_port)
|
|
||||||
|
|
||||||
if self.ssl_target:
|
|
||||||
msg += " (using SSL)"
|
|
||||||
self.msg(msg)
|
|
||||||
|
|
||||||
tsock = self.socket(self.target_host, self.target_port,
|
|
||||||
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
|
|
||||||
|
|
||||||
if self.verbose and not self.daemon:
|
|
||||||
print(self.traffic_legend)
|
|
||||||
|
|
||||||
# Start proxying
|
|
||||||
try:
|
|
||||||
self.do_proxy(tsock)
|
|
||||||
except:
|
|
||||||
if tsock:
|
|
||||||
tsock.shutdown(socket.SHUT_RDWR)
|
|
||||||
tsock.close()
|
|
||||||
self.vmsg("%s:%s: Closed target" %(
|
|
||||||
self.target_host, self.target_port))
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_target(self, target_cfg, path):
|
|
||||||
"""
|
|
||||||
Parses the path, extracts a token, and looks for a valid
|
|
||||||
target for that token in the configuration file(s). Sets
|
|
||||||
target_host and target_port if successful
|
|
||||||
"""
|
|
||||||
# The files in targets contain the lines
|
|
||||||
# in the form of token: host:port
|
|
||||||
|
|
||||||
# Extract the token parameter from url
|
|
||||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
|
||||||
|
|
||||||
if not args.has_key('token') or not len(args['token']):
|
|
||||||
raise self.EClose("Token not present")
|
|
||||||
|
|
||||||
token = args['token'][0].rstrip('\n')
|
|
||||||
|
|
||||||
# target_cfg can be a single config file or directory of
|
|
||||||
# config files
|
|
||||||
if os.path.isdir(target_cfg):
|
|
||||||
cfg_files = [os.path.join(target_cfg, f)
|
|
||||||
for f in os.listdir(target_cfg)]
|
|
||||||
else:
|
|
||||||
cfg_files = [target_cfg]
|
|
||||||
|
|
||||||
targets = {}
|
|
||||||
for f in cfg_files:
|
|
||||||
for line in [l.strip() for l in file(f).readlines()]:
|
|
||||||
if line and not line.startswith('#'):
|
|
||||||
ttoken, target = line.split(': ')
|
|
||||||
targets[ttoken] = target.strip()
|
|
||||||
|
|
||||||
self.vmsg("Target config: %s" % repr(targets))
|
|
||||||
|
|
||||||
if targets.has_key(token):
|
|
||||||
return targets[token].split(':')
|
|
||||||
else:
|
|
||||||
raise self.EClose("Token '%s' not found" % token)
|
|
||||||
|
|
||||||
def do_proxy(self, target):
|
|
||||||
"""
|
|
||||||
Proxy client WebSocket to normal target socket.
|
|
||||||
"""
|
|
||||||
cqueue = []
|
|
||||||
c_pend = 0
|
|
||||||
tqueue = []
|
|
||||||
rlist = [self.client, target]
|
|
||||||
|
|
||||||
while True:
|
|
||||||
wlist = []
|
|
||||||
|
|
||||||
if tqueue: wlist.append(target)
|
|
||||||
if cqueue or c_pend: wlist.append(self.client)
|
|
||||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
|
||||||
if excepts: raise Exception("Socket exception")
|
|
||||||
|
|
||||||
if self.client in outs:
|
|
||||||
# Send queued target data to the client
|
|
||||||
c_pend = self.send_frames(cqueue)
|
|
||||||
|
|
||||||
cqueue = []
|
|
||||||
|
|
||||||
if self.client in ins:
|
|
||||||
# Receive client data, decode it, and queue for target
|
|
||||||
bufs, closed = self.recv_frames()
|
|
||||||
tqueue.extend(bufs)
|
|
||||||
|
|
||||||
if closed:
|
|
||||||
# TODO: What about blocking on client socket?
|
|
||||||
self.vmsg("%s:%s: Client closed connection" %(
|
|
||||||
self.target_host, self.target_port))
|
|
||||||
raise self.CClose(closed['code'], closed['reason'])
|
|
||||||
|
|
||||||
|
|
||||||
if target in outs:
|
|
||||||
# Send queued client data to the target
|
|
||||||
dat = tqueue.pop(0)
|
|
||||||
sent = target.send(dat)
|
|
||||||
if sent == len(dat):
|
|
||||||
self.traffic(">")
|
|
||||||
else:
|
|
||||||
# requeue the remaining data
|
|
||||||
tqueue.insert(0, dat[sent:])
|
|
||||||
self.traffic(".>")
|
|
||||||
|
|
||||||
|
|
||||||
if target in ins:
|
|
||||||
# Receive target data, encode it and queue for client
|
|
||||||
buf = target.recv(self.buffer_size)
|
|
||||||
if len(buf) == 0:
|
|
||||||
self.vmsg("%s:%s: Target closed connection" %(
|
|
||||||
self.target_host, self.target_port))
|
|
||||||
raise self.CClose(1000, "Target closed")
|
|
||||||
|
|
||||||
cqueue.append(buf)
|
|
||||||
self.traffic("{")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _subprocess_setup():
|
def _subprocess_setup():
|
||||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
# Python installs a SIGPIPE handler by default. This is usually not what
|
||||||
@@ -301,14 +318,28 @@ def _subprocess_setup():
|
|||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
|
|
||||||
|
def logger_init():
|
||||||
|
logger = logging.getLogger(WebSocketProxy.log_prefix)
|
||||||
|
logger.propagate = False
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
h = logging.StreamHandler()
|
||||||
|
h.setLevel(logging.DEBUG)
|
||||||
|
h.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
logger.addHandler(h)
|
||||||
|
|
||||||
|
|
||||||
def websockify_init():
|
def websockify_init():
|
||||||
|
logger_init()
|
||||||
|
|
||||||
usage = "\n %prog [options]"
|
usage = "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port [target_addr:target_port]"
|
usage += " [source_addr:]source_port [target_addr:target_port]"
|
||||||
usage += "\n %prog [options]"
|
usage += "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||||
parser = optparse.OptionParser(usage=usage)
|
parser = optparse.OptionParser(usage=usage)
|
||||||
parser.add_option("--verbose", "-v", action="store_true",
|
parser.add_option("--verbose", "-v", action="store_true",
|
||||||
help="verbose messages and per frame traffic")
|
help="verbose messages")
|
||||||
|
parser.add_option("--traffic", action="store_true",
|
||||||
|
help="per frame traffic")
|
||||||
parser.add_option("--record",
|
parser.add_option("--record",
|
||||||
help="record sessions to FILE.[session_number]", metavar="FILE")
|
help="record sessions to FILE.[session_number]", metavar="FILE")
|
||||||
parser.add_option("--daemon", "-D",
|
parser.add_option("--daemon", "-D",
|
||||||
@@ -340,15 +371,56 @@ def websockify_init():
|
|||||||
parser.add_option("--prefer-ipv6", "-6",
|
parser.add_option("--prefer-ipv6", "-6",
|
||||||
action="store_true", dest="source_is_ipv6",
|
action="store_true", dest="source_is_ipv6",
|
||||||
help="prefer IPv6 when resolving source_addr")
|
help="prefer IPv6 when resolving source_addr")
|
||||||
|
parser.add_option("--libserver", action="store_true",
|
||||||
|
help="use Python library SocketServer engine")
|
||||||
parser.add_option("--target-config", metavar="FILE",
|
parser.add_option("--target-config", metavar="FILE",
|
||||||
dest="target_cfg",
|
dest="target_cfg",
|
||||||
help="Configuration file containing valid targets "
|
help="Configuration file containing valid targets "
|
||||||
"in the form 'token: host:port' or, alternatively, a "
|
"in the form 'token: host:port' or, alternatively, a "
|
||||||
"directory containing configuration files of this form")
|
"directory containing configuration files of this form "
|
||||||
|
"(DEPRECATED: use `--token-plugin TokenFile --token-source "
|
||||||
|
" path/to/token/file` instead)")
|
||||||
|
parser.add_option("--token-plugin", default=None, metavar="PLUGIN",
|
||||||
|
help="use the given Python class to process tokens "
|
||||||
|
"into host:port pairs")
|
||||||
|
parser.add_option("--token-source", default=None, metavar="ARG",
|
||||||
|
help="an argument to be passed to the token plugin"
|
||||||
|
"on instantiation")
|
||||||
|
parser.add_option("--auth-plugin", default=None, metavar="PLUGIN",
|
||||||
|
help="use the given Python class to determine if "
|
||||||
|
"a connection is allowed")
|
||||||
|
parser.add_option("--auth-source", default=None, metavar="ARG",
|
||||||
|
help="an argument to be passed to the auth plugin"
|
||||||
|
"on instantiation")
|
||||||
|
parser.add_option("--auto-pong", action="store_true",
|
||||||
|
help="Automatically respond to ping frames with a pong")
|
||||||
|
parser.add_option("--heartbeat", type=int, default=0,
|
||||||
|
help="send a ping to the client every HEARTBEAT seconds")
|
||||||
|
|
||||||
(opts, args) = parser.parse_args()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
if opts.token_source and not opts.token_plugin:
|
||||||
|
parser.error("You must use --token-plugin to use --token-source")
|
||||||
|
|
||||||
|
if opts.auth_source and not opts.auth_plugin:
|
||||||
|
parser.error("You must use --auth-plugin to use --auth-source")
|
||||||
|
|
||||||
|
|
||||||
|
# Transform to absolute path as daemon may chdir
|
||||||
|
if opts.target_cfg:
|
||||||
|
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||||
|
|
||||||
|
if opts.target_cfg:
|
||||||
|
opts.token_plugin = 'TokenFile'
|
||||||
|
opts.token_source = opts.target_cfg
|
||||||
|
|
||||||
|
del opts.target_cfg
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
|
if len(args) < 2 and not (opts.token_plugin or opts.unix_target):
|
||||||
parser.error("Too few arguments")
|
parser.error("Too few arguments")
|
||||||
if sys.argv.count('--'):
|
if sys.argv.count('--'):
|
||||||
opts.wrap_cmd = args[1:]
|
opts.wrap_cmd = args[1:]
|
||||||
@@ -373,7 +445,7 @@ def websockify_init():
|
|||||||
try: opts.listen_port = int(opts.listen_port)
|
try: opts.listen_port = int(opts.listen_port)
|
||||||
except: parser.error("Error parsing listen port")
|
except: parser.error("Error parsing listen port")
|
||||||
|
|
||||||
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
|
if opts.wrap_cmd or opts.unix_target or opts.token_plugin:
|
||||||
opts.target_host = None
|
opts.target_host = None
|
||||||
opts.target_port = None
|
opts.target_port = None
|
||||||
else:
|
else:
|
||||||
@@ -385,9 +457,97 @@ def websockify_init():
|
|||||||
try: opts.target_port = int(opts.target_port)
|
try: opts.target_port = int(opts.target_port)
|
||||||
except: parser.error("Error parsing target port")
|
except: parser.error("Error parsing target port")
|
||||||
|
|
||||||
|
if opts.token_plugin is not None:
|
||||||
|
if '.' not in opts.token_plugin:
|
||||||
|
opts.token_plugin = (
|
||||||
|
'websockify.token_plugins.%s' % opts.token_plugin)
|
||||||
|
|
||||||
|
token_plugin_module, token_plugin_cls = opts.token_plugin.rsplit('.', 1)
|
||||||
|
|
||||||
|
__import__(token_plugin_module)
|
||||||
|
token_plugin_cls = getattr(sys.modules[token_plugin_module], token_plugin_cls)
|
||||||
|
|
||||||
|
opts.token_plugin = token_plugin_cls(opts.token_source)
|
||||||
|
|
||||||
|
del opts.token_source
|
||||||
|
|
||||||
|
if opts.auth_plugin is not None:
|
||||||
|
if '.' not in opts.auth_plugin:
|
||||||
|
opts.auth_plugin = 'websockify.auth_plugins.%s' % opts.auth_plugin
|
||||||
|
|
||||||
|
auth_plugin_module, auth_plugin_cls = opts.auth_plugin.rsplit('.', 1)
|
||||||
|
|
||||||
|
__import__(auth_plugin_module)
|
||||||
|
auth_plugin_cls = getattr(sys.modules[auth_plugin_module], auth_plugin_cls)
|
||||||
|
|
||||||
|
opts.auth_plugin = auth_plugin_cls(opts.auth_source)
|
||||||
|
|
||||||
|
del opts.auth_source
|
||||||
|
|
||||||
# Create and start the WebSockets proxy
|
# Create and start the WebSockets proxy
|
||||||
server = WebSocketProxy(**opts.__dict__)
|
libserver = opts.libserver
|
||||||
server.start_server()
|
del opts.libserver
|
||||||
|
if libserver:
|
||||||
|
# Use standard Python SocketServer framework
|
||||||
|
server = LibProxyServer(**opts.__dict__)
|
||||||
|
server.serve_forever()
|
||||||
|
else:
|
||||||
|
# Use internal service framework
|
||||||
|
server = WebSocketProxy(**opts.__dict__)
|
||||||
|
server.start_server()
|
||||||
|
|
||||||
|
|
||||||
|
class LibProxyServer(ForkingMixIn, HTTPServer):
|
||||||
|
"""
|
||||||
|
Just like WebSocketProxy, but uses standard Python SocketServer
|
||||||
|
framework.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
|
||||||
|
# Save off proxy specific options
|
||||||
|
self.target_host = kwargs.pop('target_host', None)
|
||||||
|
self.target_port = kwargs.pop('target_port', None)
|
||||||
|
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
|
||||||
|
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||||
|
self.unix_target = kwargs.pop('unix_target', None)
|
||||||
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
|
self.token_plugin = kwargs.pop('token_plugin', None)
|
||||||
|
self.auth_plugin = kwargs.pop('auth_plugin', None)
|
||||||
|
self.heartbeat = kwargs.pop('heartbeat', None)
|
||||||
|
|
||||||
|
self.token_plugin = None
|
||||||
|
self.auth_plugin = None
|
||||||
|
self.daemon = False
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
listen_host = kwargs.pop('listen_host', '')
|
||||||
|
listen_port = kwargs.pop('listen_port', None)
|
||||||
|
web = kwargs.pop('web', '')
|
||||||
|
|
||||||
|
# Configuration affecting base request handler
|
||||||
|
self.only_upgrade = not web
|
||||||
|
self.verbose = kwargs.pop('verbose', False)
|
||||||
|
record = kwargs.pop('record', '')
|
||||||
|
if record:
|
||||||
|
self.record = os.path.abspath(record)
|
||||||
|
self.run_once = kwargs.pop('run_once', False)
|
||||||
|
self.handler_id = 0
|
||||||
|
|
||||||
|
for arg in kwargs.keys():
|
||||||
|
print("warning: option %s ignored when using --libserver" % arg)
|
||||||
|
|
||||||
|
if web:
|
||||||
|
os.chdir(web)
|
||||||
|
|
||||||
|
HTTPServer.__init__(self, (listen_host, listen_port),
|
||||||
|
RequestHandlerClass)
|
||||||
|
|
||||||
|
|
||||||
|
def process_request(self, request, client_address):
|
||||||
|
"""Override process_request to implement a counter"""
|
||||||
|
self.handler_id += 1
|
||||||
|
ForkingMixIn.process_request(self, request, client_address)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
websockify_init()
|
websockify_init()
|
||||||
|
|||||||
Reference in New Issue
Block a user