19 | Log:
20 |
21 |
22 |
23 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/other/websocket.h:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #define BUFSIZE 65536
4 | #define DBUFSIZE (BUFSIZE * 3) / 4 - 20
5 |
6 | #define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
7 | Upgrade: WebSocket\r\n\
8 | Connection: Upgrade\r\n\
9 | %sWebSocket-Origin: %s\r\n\
10 | %sWebSocket-Location: %s://%s%s\r\n\
11 | %sWebSocket-Protocol: %s\r\n\
12 | \r\n%s"
13 |
14 | #define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\
15 | Upgrade: websocket\r\n\
16 | Connection: Upgrade\r\n\
17 | Sec-WebSocket-Accept: %s\r\n\
18 | Sec-WebSocket-Protocol: %s\r\n\
19 | \r\n"
20 |
21 | #define HYBI_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
22 |
23 | #define HYBI10_ACCEPTHDRLEN 29
24 |
25 | #define HIXIE_MD5_DIGEST_LENGTH 16
26 |
27 | #define POLICY_RESPONSE "\n"
28 |
29 | typedef struct {
30 | char path[1024+1];
31 | char host[1024+1];
32 | char origin[1024+1];
33 | char version[1024+1];
34 | char connection[1024+1];
35 | char protocols[1024+1];
36 | char key1[1024+1];
37 | char key2[1024+1];
38 | char key3[8+1];
39 | } headers_t;
40 |
41 | typedef struct {
42 | int sockfd;
43 | SSL_CTX *ssl_ctx;
44 | SSL *ssl;
45 | int hixie;
46 | int hybi;
47 | headers_t *headers;
48 | char *cin_buf;
49 | char *cout_buf;
50 | char *tin_buf;
51 | char *tout_buf;
52 | } ws_ctx_t;
53 |
54 | typedef struct {
55 | int verbose;
56 | char listen_host[256];
57 | int listen_port;
58 | void (*handler)(ws_ctx_t*);
59 | int handler_id;
60 | char *cert;
61 | char *key;
62 | int ssl_only;
63 | int daemon;
64 | int run_once;
65 | } settings_t;
66 |
67 |
68 | ssize_t ws_recv(ws_ctx_t *ctx, void *buf, size_t len);
69 |
70 | ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len);
71 |
72 | /* base64.c declarations */
73 | //int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize);
74 | //int b64_pton(char const *src, u_char *target, size_t targsize);
75 |
76 | #define gen_handler_msg(stream, ...) \
77 | if (! settings.daemon) { \
78 | fprintf(stream, " %d: ", settings.handler_id); \
79 | fprintf(stream, __VA_ARGS__); \
80 | }
81 |
82 | #define handler_msg(...) gen_handler_msg(stdout, __VA_ARGS__);
83 | #define handler_emsg(...) gen_handler_msg(stderr, __VA_ARGS__);
84 |
85 |
--------------------------------------------------------------------------------
/Windows/noVNC Websocket Service Project/ProjectInstaller.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace MELT_Command_Websocket
2 | {
3 | partial class ProjectInstaller
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Component Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
32 | this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
33 | //
34 | // serviceProcessInstaller1
35 | //
36 | this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.NetworkService;
37 | this.serviceProcessInstaller1.Installers.AddRange(new System.Configuration.Install.Installer[] {
38 | this.serviceInstaller1});
39 | this.serviceProcessInstaller1.Password = null;
40 | this.serviceProcessInstaller1.Username = null;
41 | //
42 | // serviceInstaller1
43 | //
44 | this.serviceInstaller1.Description = "noVNC Websocket Service";
45 | this.serviceInstaller1.DisplayName = "noVNC Websocket Service";
46 | this.serviceInstaller1.ServiceName = "noVNC Websocket Service";
47 | this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
48 | //
49 | // ProjectInstaller
50 | //
51 | this.Installers.AddRange(new System.Configuration.Install.Installer[] {
52 | this.serviceProcessInstaller1});
53 |
54 | }
55 |
56 | #endregion
57 |
58 | private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
59 | private System.ServiceProcess.ServiceInstaller serviceInstaller1;
60 | }
61 | }
--------------------------------------------------------------------------------
/websockify/token_plugins.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | class BasePlugin(object):
4 | def __init__(self, src):
5 | self.source = src
6 |
7 | def lookup(self, token):
8 | return None
9 |
10 |
11 | class ReadOnlyTokenFile(BasePlugin):
12 | # source is a token file with lines like
13 | # token: host:port
14 | # or a directory of such files
15 | def __init__(self, *args, **kwargs):
16 | super(ReadOnlyTokenFile, self).__init__(*args, **kwargs)
17 | self._targets = None
18 |
19 | def _load_targets(self):
20 | if os.path.isdir(self.source):
21 | cfg_files = [os.path.join(self.source, f) for
22 | f in os.listdir(self.source)]
23 | else:
24 | cfg_files = [self.source]
25 |
26 | self._targets = {}
27 | for f in cfg_files:
28 | for line in [l.strip() for l in open(f).readlines()]:
29 | if line and not line.startswith('#'):
30 | tok, target = line.split(': ')
31 | self._targets[tok] = target.strip().rsplit(':', 1)
32 |
33 | def lookup(self, token):
34 | if self._targets is None:
35 | self._load_targets()
36 |
37 | if token in self._targets:
38 | return self._targets[token]
39 | else:
40 | return None
41 |
42 |
43 | # the above one is probably more efficient, but this one is
44 | # more backwards compatible (although in most cases
45 | # ReadOnlyTokenFile should suffice)
46 | class TokenFile(ReadOnlyTokenFile):
47 | # source is a token file with lines like
48 | # token: host:port
49 | # or a directory of such files
50 | def lookup(self, token):
51 | self._load_targets()
52 |
53 | return super(TokenFile, self).lookup(token)
54 |
55 |
56 | class BaseTokenAPI(BasePlugin):
57 | # source is a url with a '%s' in it where the token
58 | # should go
59 |
60 | # we import things on demand so that other plugins
61 | # in this file can be used w/o unecessary dependencies
62 |
63 | def process_result(self, resp):
64 | return resp.text.split(':')
65 |
66 | def lookup(self, token):
67 | import requests
68 |
69 | resp = requests.get(self.source % token)
70 |
71 | if resp.ok:
72 | return self.process_result(resp)
73 | else:
74 | return None
75 |
76 |
77 | class JSONTokenApi(BaseTokenAPI):
78 | # source is a url with a '%s' in it where the token
79 | # should go
80 |
81 | def process_result(self, resp):
82 | resp_json = resp.json()
83 | return (resp_json['host'], resp_json['port'])
84 |
--------------------------------------------------------------------------------
/wstelnet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Telnet client using WebSockets
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Host:
24 | Port:
25 | Encrypt:
26 |
28 |
29 |
30 |
31 |
32 |
33 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/tests/echo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | A WebSocket server that echos back whatever it receives from the client.
5 | Copyright 2010 Joel Martin
6 | Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
7 |
8 | You can make a cert/key with openssl using:
9 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
10 | as taken from http://docs.python.org/dev/library/ssl.html#certificates
11 | '''
12 |
13 | import os, sys, select, optparse, logging
14 | sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
15 | from websockify.websocket import WebSocketServer, WebSocketRequestHandler
16 |
17 | class WebSocketEcho(WebSocketRequestHandler):
18 | """
19 | WebSockets server that echos back whatever is received from the
20 | client. """
21 | buffer_size = 8096
22 |
23 | def new_websocket_client(self):
24 | """
25 | Echo back whatever is received.
26 | """
27 |
28 | cqueue = []
29 | c_pend = 0
30 | cpartial = ""
31 | rlist = [self.request]
32 |
33 | while True:
34 | wlist = []
35 |
36 | if cqueue or c_pend: wlist.append(self.request)
37 | ins, outs, excepts = select.select(rlist, wlist, [], 1)
38 | if excepts: raise Exception("Socket exception")
39 |
40 | if self.request in outs:
41 | # Send queued target data to the client
42 | c_pend = self.send_frames(cqueue)
43 | cqueue = []
44 |
45 | if self.request in ins:
46 | # Receive client data, decode it, and send it back
47 | frames, closed = self.recv_frames()
48 | cqueue.extend(frames)
49 |
50 | if closed:
51 | self.send_close()
52 |
53 | if __name__ == '__main__':
54 | parser = optparse.OptionParser(usage="%prog [options] listen_port")
55 | parser.add_option("--verbose", "-v", action="store_true",
56 | help="verbose messages and per frame traffic")
57 | parser.add_option("--cert", default="self.pem",
58 | help="SSL certificate file")
59 | parser.add_option("--key", default=None,
60 | help="SSL key file (if separate from cert)")
61 | parser.add_option("--ssl-only", action="store_true",
62 | help="disallow non-encrypted connections")
63 | (opts, args) = parser.parse_args()
64 |
65 | try:
66 | if len(args) != 1: raise
67 | opts.listen_port = int(args[0])
68 | except:
69 | parser.error("Invalid arguments")
70 |
71 | logging.basicConfig(level=logging.INFO)
72 |
73 | opts.web = "."
74 | server = WebSocketServer(WebSocketEcho, **opts.__dict__)
75 | server.start_server()
76 |
77 |
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 | Changes
2 | =======
3 |
4 | 0.8.0
5 | -----
6 |
7 | * Make websockify properly terminate children on SIGTERM (#226)
8 | * Remove logging in signal handlers (this can cause Python to hang under certain conditions) (#219)
9 | * Make it easier to log to a file (#205)
10 | * Add support for IPv6 addresses in tokens in the TokenFile token plugins (#197)
11 | * Improve auth plugin framework to enable better support for HTTP auth (#194, #201)
12 | * Fix bug in JSONTokenAPI token plugin (#192)
13 | * Fix a missing variable in the exception handler (#178)
14 |
15 | 0.7.0
16 | -----
17 |
18 | * Python 3 support fixes (#140, #155, #159)
19 | * Generic token-parsing plugins support (#162)
20 | * Generic authentication plugins support (#172)
21 | * Fixed frame corruption on big-endian systems (#161)
22 | * Support heartbeats (via PING) and automatic responses to PONG (#169)
23 | * Automatically reject unmasked client frames by default (strict mode) (#174)
24 | * Automatically restart interrupted select calls (#175)
25 | * Make 'run' respect environment settings (including virtualenv) (#176)
26 |
27 | 0.6.1 - May 11, 2015
28 | --------------------
29 |
30 | * **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
31 |
32 | 0.6.0 - Feb 18, 2014
33 | --------------------
34 |
35 | * **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
36 | * Refactor to use standard SocketServer RequestHandler design
37 | * Fix zombie process bug on certain systems when using multiprocessing
38 | * Add better unit tests
39 | * Log information via python `logging` module
40 |
41 | 0.5.1 - Jun 27, 2013
42 | --------------------
43 |
44 | * use upstream einaros/ws (>=0.4.27) with websockify.js
45 | * file_only and no_parent security options for WSRequestHandler
46 | * Update build of web-socket-js (c0855c6cae)
47 | * add include/web-socket-js-project submodule to gimite/web-socket-js
48 | for DSFG compliance.
49 | * drop Hixie protocol support
50 |
51 | 0.4.1 - Mar 12, 2013
52 | --------------------
53 |
54 | * ***NOTE*** : 0.5.0 will drop Hixie protocol support
55 | * add include/ directory and remove some dev files from source
56 | distribution.
57 |
58 | 0.4.0 - Mar 12, 2013
59 | --------------------
60 |
61 | * ***NOTE*** : 0.5.0 will drop Hixie protocol support
62 | * use Buffer base64 support in Node.js implementation
63 |
64 | 0.3.0 - Jan 15, 2013
65 | --------------------
66 |
67 | * refactor into modules: websocket, websocketproxy
68 | * switch to web-socket-js that uses IETF 6455
69 | * change to MPL 2.0 license for include/*.js
70 | * fix session recording
71 |
72 | 0.2.1 - Oct 15, 2012
73 | --------------------
74 |
75 | * re-released with updated version number
76 |
77 | 0.2.0 - Sep 17, 2012
78 | --------------------
79 |
80 | * Binary data support in websock.js
81 | * Target config file/dir and multiple targets with token selector
82 | * IPv6 fixes
83 | * SSL target support
84 | * Proxy to/from unix socket
85 |
86 |
87 | 0.1.0 - May 11, 2012
88 | --------------------
89 |
90 | * Initial versioned release.
91 |
92 |
--------------------------------------------------------------------------------
/websockify/auth_plugins.py:
--------------------------------------------------------------------------------
1 | class BasePlugin(object):
2 | def __init__(self, src=None):
3 | self.source = src
4 |
5 | def authenticate(self, headers, target_host, target_port):
6 | pass
7 |
8 |
9 | class AuthenticationError(Exception):
10 | def __init__(self, log_msg=None, response_code=403, response_headers={}, response_msg=None):
11 | self.code = response_code
12 | self.headers = response_headers
13 | self.msg = response_msg
14 |
15 | if log_msg is None:
16 | log_msg = response_msg
17 |
18 | super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
19 |
20 |
21 | class InvalidOriginError(AuthenticationError):
22 | def __init__(self, expected, actual):
23 | self.expected_origin = expected
24 | self.actual_origin = actual
25 |
26 | super(InvalidOriginError, self).__init__(
27 | response_msg='Invalid Origin',
28 | log_msg="Invalid Origin Header: Expected one of "
29 | "%s, got '%s'" % (expected, actual))
30 |
31 |
32 | class BasicHTTPAuth(object):
33 | """Verifies Basic Auth headers. Specify src as username:password"""
34 |
35 | def __init__(self, src=None):
36 | self.src = src
37 |
38 | def authenticate(self, headers, target_host, target_port):
39 | import base64
40 | auth_header = headers.get('Authorization')
41 | if auth_header:
42 | if not auth_header.startswith('Basic '):
43 | raise AuthenticationError(response_code=403)
44 |
45 | try:
46 | user_pass_raw = base64.b64decode(auth_header[6:])
47 | except TypeError:
48 | raise AuthenticationError(response_code=403)
49 |
50 | try:
51 | # http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication
52 | user_pass_as_text = user_pass_raw.decode('ISO-8859-1')
53 | except UnicodeDecodeError:
54 | raise AuthenticationError(response_code=403)
55 |
56 | user_pass = user_pass_as_text.split(':', 1)
57 | if len(user_pass) != 2:
58 | raise AuthenticationError(response_code=403)
59 |
60 | if not self.validate_creds(*user_pass):
61 | raise AuthenticationError(response_code=403)
62 |
63 | else:
64 | raise AuthenticationError(response_code=401,
65 | response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
66 |
67 | def validate_creds(self, username, password):
68 | if '%s:%s' % (username, password) == self.src:
69 | return True
70 | else:
71 | return False
72 |
73 | class ExpectOrigin(object):
74 | def __init__(self, src=None):
75 | if src is None:
76 | self.source = []
77 | else:
78 | self.source = src.split()
79 |
80 | def authenticate(self, headers, target_host, target_port):
81 | origin = headers.get('Origin', None)
82 | if origin is None or origin not in self.source:
83 | raise InvalidOriginError(expected=self.source, actual=origin)
84 |
--------------------------------------------------------------------------------
/rebind.c:
--------------------------------------------------------------------------------
1 | /*
2 | * rebind: Intercept bind calls and bind to a different port
3 | * Copyright 2010 Joel Martin
4 | * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
5 | *
6 | * Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and
7 | * REBIND_PORT_NEW environment variables are set then bind on the new
8 | * port (of localhost) instead of the old port.
9 | *
10 | * This allows a bridge/proxy (such as websockify) to run on the old port and
11 | * translate traffic to/from the new port.
12 | *
13 | * Usage:
14 | * LD_PRELOAD=./rebind.so \
15 | * REBIND_PORT_OLD=23 \
16 | * REBIND_PORT_NEW=2023 \
17 | * program
18 | */
19 |
20 | //#define DO_DEBUG 1
21 |
22 | #include
23 | #include
24 |
25 | #define __USE_GNU 1 // Pull in RTLD_NEXT
26 | #include
27 |
28 | #include
29 | #include
30 |
31 |
32 | #if defined(DO_DEBUG)
33 | #define DEBUG(...) \
34 | fprintf(stderr, "rebind: "); \
35 | fprintf(stderr, __VA_ARGS__);
36 | #else
37 | #define DEBUG(...)
38 | #endif
39 |
40 |
41 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
42 | {
43 | static void * (*func)();
44 | int do_move = 0;
45 | struct sockaddr_in * addr_in = (struct sockaddr_in *)addr;
46 | struct sockaddr_in addr_tmp;
47 | socklen_t addrlen_tmp;
48 | char * PORT_OLD, * PORT_NEW, * end1, * end2;
49 | int ret, oldport, newport, askport = htons(addr_in->sin_port);
50 | uint32_t askaddr = htons(addr_in->sin_addr.s_addr);
51 | if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
52 |
53 | DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n",
54 | sockfd, addrlen, askaddr, askport);
55 |
56 | /* Determine if we should move this socket */
57 | if (addr_in->sin_family == AF_INET) {
58 | // TODO: support IPv6
59 | PORT_OLD = getenv("REBIND_OLD_PORT");
60 | PORT_NEW = getenv("REBIND_NEW_PORT");
61 | if (PORT_OLD && (*PORT_OLD != '\0') &&
62 | PORT_NEW && (*PORT_NEW != '\0')) {
63 | oldport = strtol(PORT_OLD, &end1, 10);
64 | newport = strtol(PORT_NEW, &end2, 10);
65 | if (oldport && (*end1 == '\0') &&
66 | newport && (*end2 == '\0') &&
67 | (oldport == askport)) {
68 | do_move = 1;
69 | }
70 | }
71 | }
72 |
73 | if (! do_move) {
74 | /* Just pass everything right through to the real bind */
75 | ret = (long) func(sockfd, addr, addrlen);
76 | DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
77 | return ret;
78 | }
79 |
80 | DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n",
81 | sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport);
82 |
83 | /* Use a temporary location for the new address information */
84 | addrlen_tmp = sizeof(addr_tmp);
85 | memcpy(&addr_tmp, addr, addrlen_tmp);
86 |
87 | /* Bind to other port on the loopback instead */
88 | addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
89 | addr_tmp.sin_port = htons(newport);
90 | ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);
91 |
92 | DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
93 | return ret;
94 | }
95 |
--------------------------------------------------------------------------------
/Windows/noVNC Websocket Service Project/noVNC Websocket.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}
9 | WinExe
10 | Properties
11 | noVNC_Websocket_Service
12 | noVNC Websocket Service
13 | v3.5
14 | 512
15 |
16 |
17 | x86
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | x86
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Component
49 |
50 |
51 | ProjectInstaller.cs
52 |
53 |
54 | Component
55 |
56 |
57 | Service1.cs
58 |
59 |
60 |
61 |
62 |
63 |
64 | ProjectInstaller.cs
65 |
66 |
67 |
68 |
75 |
--------------------------------------------------------------------------------
/tests/base64.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Native Base64 Tests
5 |
6 |
7 |
8 |
9 |
10 |
Native Base64 Tests
11 |
12 |
13 | Messages:
14 |
15 |
16 |
17 |
18 |
19 |
92 |
--------------------------------------------------------------------------------
/other/launch.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | usage() {
4 | if [ "$*" ]; then
5 | echo "$*"
6 | echo
7 | fi
8 | echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
9 | echo
10 | echo "Starts the WebSockets proxy and a mini-webserver and "
11 | echo "provides a cut-and-paste URL to go to."
12 | echo
13 | echo " --listen PORT Port for proxy/webserver to listen on"
14 | echo " Default: 6080"
15 | echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
16 | echo " Default: localhost:5900"
17 | echo " --cert CERT Path to combined cert/key file"
18 | echo " Default: self.pem"
19 | echo " --web WEB Path to web files (e.g. vnc.html)"
20 | echo " Default: ./"
21 | exit 2
22 | }
23 |
24 | NAME="$(basename $0)"
25 | HERE="$(cd "$(dirname "$0")" && pwd)"
26 | PORT="6080"
27 | VNC_DEST="localhost:5900"
28 | CERT=""
29 | WEB=""
30 | proxy_pid=""
31 |
32 | die() {
33 | echo "$*"
34 | exit 1
35 | }
36 |
37 | cleanup() {
38 | trap - TERM QUIT INT EXIT
39 | trap "true" CHLD # Ignore cleanup messages
40 | echo
41 | if [ -n "${proxy_pid}" ]; then
42 | echo "Terminating WebSockets proxy (${proxy_pid})"
43 | kill ${proxy_pid}
44 | fi
45 | }
46 |
47 | # Process Arguments
48 |
49 | # Arguments that only apply to chrooter itself
50 | while [ "$*" ]; do
51 | param=$1; shift; OPTARG=$1
52 | case $param in
53 | --listen) PORT="${OPTARG}"; shift ;;
54 | --vnc) VNC_DEST="${OPTARG}"; shift ;;
55 | --cert) CERT="${OPTARG}"; shift ;;
56 | --web) WEB="${OPTARG}"; shift ;;
57 | -h|--help) usage ;;
58 | -*) usage "Unknown chrooter option: ${param}" ;;
59 | *) break ;;
60 | esac
61 | done
62 |
63 | # Sanity checks
64 | which netstat >/dev/null 2>&1 \
65 | || die "Must have netstat installed"
66 |
67 | netstat -ltn | grep -qs "${PORT} .*LISTEN" \
68 | && die "Port ${PORT} in use. Try --listen PORT"
69 |
70 | trap "cleanup" TERM QUIT INT EXIT
71 |
72 | # Find vnc.html
73 | if [ -n "${WEB}" ]; then
74 | if [ ! -e "${WEB}/vnc.html" ]; then
75 | die "Could not find ${WEB}/vnc.html"
76 | fi
77 | elif [ -e "$(pwd)/vnc.html" ]; then
78 | WEB=$(pwd)
79 | elif [ -e "${HERE}/../vnc.html" ]; then
80 | WEB=${HERE}/../
81 | elif [ -e "${HERE}/vnc.html" ]; then
82 | WEB=${HERE}
83 | elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
84 | WEB=${HERE}/../share/novnc/
85 | else
86 | die "Could not find vnc.html"
87 | fi
88 |
89 | # Find self.pem
90 | if [ -n "${CERT}" ]; then
91 | if [ ! -e "${CERT}" ]; then
92 | die "Could not find ${CERT}"
93 | fi
94 | elif [ -e "$(pwd)/self.pem" ]; then
95 | CERT="$(pwd)/self.pem"
96 | elif [ -e "${HERE}/../self.pem" ]; then
97 | CERT="${HERE}/../self.pem"
98 | elif [ -e "${HERE}/self.pem" ]; then
99 | CERT="${HERE}/self.pem"
100 | else
101 | echo "Warning: could not find self.pem"
102 | fi
103 |
104 | echo "Starting webserver and WebSockets proxy on port ${PORT}"
105 | ${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
106 | proxy_pid="$!"
107 | sleep 1
108 | if ! ps -p ${proxy_pid} >/dev/null; then
109 | proxy_pid=
110 | echo "Failed to start WebSockets proxy"
111 | exit 1
112 | fi
113 |
114 | echo -e "\n\nNavigate to this URL:\n"
115 | echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
116 | echo -e "Press Ctrl-C to exit\n\n"
117 |
118 | wait ${proxy_pid}
119 |
--------------------------------------------------------------------------------
/wsirc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | IRC Client using WebSockets
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Host:
24 | Port:
25 | Encrypt:
26 |
27 |
28 | Nick:
29 |
30 | Channel: #
31 |
32 |
33 |
34 |
35 | >
36 |
37 |
38 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/include/base64.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | // From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
6 |
7 | /*jslint white: false, bitwise: false, plusplus: false */
8 | /*global console */
9 |
10 | var Base64 = {
11 |
12 | /* Convert data (an array of integers) to a Base64 string. */
13 | toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
14 | base64Pad : '=',
15 |
16 | encode: function (data) {
17 | "use strict";
18 | var result = '';
19 | var toBase64Table = Base64.toBase64Table;
20 | var base64Pad = Base64.base64Pad;
21 | var length = data.length;
22 | var i;
23 | // Convert every three bytes to 4 ascii characters.
24 | /* BEGIN LOOP */
25 | for (i = 0; i < (length - 2); i += 3) {
26 | result += toBase64Table[data[i] >> 2];
27 | result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
28 | result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
29 | result += toBase64Table[data[i+2] & 0x3f];
30 | }
31 | /* END LOOP */
32 |
33 | // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
34 | if (length%3) {
35 | i = length - (length%3);
36 | result += toBase64Table[data[i] >> 2];
37 | if ((length%3) === 2) {
38 | result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
39 | result += toBase64Table[(data[i+1] & 0x0f) << 2];
40 | result += base64Pad;
41 | } else {
42 | result += toBase64Table[(data[i] & 0x03) << 4];
43 | result += base64Pad + base64Pad;
44 | }
45 | }
46 |
47 | return result;
48 | },
49 |
50 | /* Convert Base64 data to a string */
51 | toBinaryTable : [
52 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
53 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
54 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
55 | 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
56 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
57 | 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
58 | -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
59 | 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
60 | ],
61 |
62 | decode: function (data, offset) {
63 | "use strict";
64 | offset = typeof(offset) !== 'undefined' ? offset : 0;
65 | var toBinaryTable = Base64.toBinaryTable;
66 | var base64Pad = Base64.base64Pad;
67 | var result, result_length, idx, i, c, padding;
68 | var leftbits = 0; // number of bits decoded, but yet to be appended
69 | var leftdata = 0; // bits decoded, but yet to be appended
70 | var data_length = data.indexOf('=') - offset;
71 |
72 | if (data_length < 0) { data_length = data.length - offset; }
73 |
74 | /* Every four characters is 3 resulting numbers */
75 | result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
76 | result = new Array(result_length);
77 |
78 | // Convert one by one.
79 | /* BEGIN LOOP */
80 | for (idx = 0, i = offset; i < data.length; i++) {
81 | c = toBinaryTable[data.charCodeAt(i) & 0x7f];
82 | padding = (data.charAt(i) === base64Pad);
83 | // Skip illegal characters and whitespace
84 | if (c === -1) {
85 | console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
86 | continue;
87 | }
88 |
89 | // Collect data into leftdata, update bitcount
90 | leftdata = (leftdata << 6) | c;
91 | leftbits += 6;
92 |
93 | // If we have 8 or more bits, append 8 bits to the result
94 | if (leftbits >= 8) {
95 | leftbits -= 8;
96 | // Append if not padding.
97 | if (!padding) {
98 | result[idx++] = (leftdata >> leftbits) & 0xff;
99 | }
100 | leftdata &= (1 << leftbits) - 1;
101 | }
102 | }
103 | /* END LOOP */
104 |
105 | // If there are any bits left, the base64 string was corrupted
106 | if (leftbits) {
107 | throw {name: 'Base64-Error',
108 | message: 'Corrupted base64 string'};
109 | }
110 |
111 | return result;
112 | }
113 |
114 | }; /* End of Base64 namespace */
115 |
--------------------------------------------------------------------------------
/other/websockify.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # A WebSocket to TCP socket proxy
4 | # Copyright 2011 Joel Martin
5 | # Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
6 |
7 | require 'socket'
8 | $: << "other"
9 | $: << "../other"
10 | require 'websocket'
11 | require 'optparse'
12 |
13 | # Proxy traffic to and from a WebSockets client to a normal TCP
14 | # socket server target. All traffic to/from the client is base64
15 | # encoded/decoded to allow binary data to be sent/received to/from
16 | # the target.
17 | class WebSocketProxy < WebSocketServer
18 |
19 | @@Traffic_legend = "
20 | Traffic Legend:
21 | } - Client receive
22 | }. - Client receive partial
23 | { - Target receive
24 |
25 | > - Target send
26 | >. - Target send partial
27 | < - Client send
28 | <. - Client send partial
29 | "
30 |
31 |
32 | def initialize(opts)
33 | vmsg "in WebSocketProxy.initialize"
34 |
35 | super(opts)
36 |
37 | @target_host = opts["target_host"]
38 | @target_port = opts["target_port"]
39 | end
40 |
41 | # Echo back whatever is received
42 | def new_websocket_client(client)
43 |
44 | msg "connecting to: %s:%s" % [@target_host, @target_port]
45 | tsock = TCPSocket.open(@target_host, @target_port)
46 |
47 | if @verbose then puts @@Traffic_legend end
48 |
49 | begin
50 | do_proxy(client, tsock)
51 | rescue
52 | tsock.shutdown(Socket::SHUT_RDWR)
53 | tsock.close
54 | raise
55 | end
56 | end
57 |
58 | # Proxy client WebSocket to normal target socket.
59 | def do_proxy(client, target)
60 | cqueue = []
61 | c_pend = 0
62 | tqueue = []
63 | rlist = [client, target]
64 |
65 | loop do
66 | wlist = []
67 |
68 | if tqueue.length > 0
69 | wlist << target
70 | end
71 | if cqueue.length > 0 || c_pend > 0
72 | wlist << client
73 | end
74 |
75 | ins, outs, excepts = IO.select(rlist, wlist, nil, 0.001)
76 | if excepts && excepts.length > 0
77 | raise Exception, "Socket exception"
78 | end
79 |
80 | # Send queued client data to the target
81 | if outs && outs.include?(target)
82 | dat = tqueue.shift
83 | sent = target.send(dat, 0)
84 | if sent == dat.length
85 | traffic ">"
86 | else
87 | tqueue.unshift(dat[sent...dat.length])
88 | traffic ".>"
89 | end
90 | end
91 |
92 | # Receive target data and queue for the client
93 | if ins && ins.include?(target)
94 | buf = target.recv(@@Buffer_size)
95 | if buf.length == 0
96 | raise EClose, "Target closed"
97 | end
98 |
99 | cqueue << buf
100 | traffic "{"
101 | end
102 |
103 | # Encode and send queued data to the client
104 | if outs && outs.include?(client)
105 | c_pend = send_frames(cqueue)
106 | cqueue = []
107 | end
108 |
109 | # Receive client data, decode it, and send it back
110 | if ins && ins.include?(client)
111 | frames, closed = recv_frames
112 | tqueue += frames
113 |
114 | if closed
115 | send_close
116 | raise EClose, closed
117 | end
118 | end
119 |
120 | end # loop
121 | end
122 | end
123 |
124 | # Parse parameters
125 | opts = {}
126 | parser = OptionParser.new do |o|
127 | o.on('--verbose', '-v') { |b| opts['verbose'] = b }
128 | o.parse!
129 | end
130 |
131 | if ARGV.length < 2
132 | puts "Too few arguments"
133 | exit 2
134 | end
135 |
136 | # Parse host:port and convert ports to numbers
137 | if ARGV[0].count(":") > 0
138 | opts['listen_host'], _, opts['listen_port'] = ARGV[0].rpartition(':')
139 | else
140 | opts['listen_host'], opts['listen_port'] = nil, ARGV[0]
141 | end
142 |
143 | begin
144 | opts['listen_port'] = opts['listen_port'].to_i
145 | rescue
146 | puts "Error parsing listen port"
147 | exit 2
148 | end
149 |
150 | if ARGV[1].count(":") > 0
151 | opts['target_host'], _, opts['target_port'] = ARGV[1].rpartition(':')
152 | else
153 | puts "Error parsing target"
154 | exit 2
155 | end
156 |
157 | begin
158 | opts['target_port'] = opts['target_port'].to_i
159 | rescue
160 | puts "Error parsing target port"
161 | exit 2
162 | end
163 |
164 | puts "Starting server on #{opts['listen_host']}:#{opts['listen_port']}"
165 | server = WebSocketProxy.new(opts)
166 | server.start(100)
167 | server.join
168 |
169 | puts "Server has been terminated"
170 |
171 | # vim: sw=2
172 |
--------------------------------------------------------------------------------
/docs/latency_results.txt:
--------------------------------------------------------------------------------
1 | This data is raw copy from the latency tester set to send a frame with
2 | a little over 2000 KB of data every 10ms.
3 |
4 | The number of packets sent and received is just a visual counter and
5 | is just the total when I chose to stop the test (around 3000 or so
6 | packets).
7 |
8 | The latency measure are from the point the packet was sent to when it
9 | was received back again in milliseconds. One notable data point
10 | missing from this is how long it actually took for the client to send
11 | 3000 packets because sending large packets can put load on the browser
12 | and it may be a lot longer than 10ms before the timer event to
13 | send the next packet fires. So even with low latency numbers, the
14 | actual send rate may be fairly low because sending the WebSockets
15 | frames is impacting the performance of the browser in general.
16 |
17 |
18 | ------------------------------------------------------------
19 |
20 | Native WebSockets implementations, 2000 byte payload, 10ms delay
21 |
22 | Chrome 8.0.552 - native WebSockets
23 | Packets sent: 2998
24 | Packets Received: 2998
25 | Average Latency: 1.84
26 | 40 Frame Running Average Latency: 1.90
27 | Minimum Latency: 1.00
28 | Maximum Latency: 10.00
29 |
30 | firefox 4.0b9 - WebSockets enabled
31 | Packets sent: 3011
32 | Packets Received: 3011
33 | Average Latency: 6.45
34 | 40 Frame Running Average Latency: 6.08
35 | Minimum Latency: 5.00
36 | Maximum Latency: 119.00
37 |
38 | Opera 11 - WebSockets enabled
39 | Packets sent: 3065
40 | Packets Received: 3064
41 | Average Latency: 9.56
42 | 40 Frame Running Average Latency: 8.15
43 | Minimum Latency: 4.00
44 | Maximum Latency: 53.00
45 |
46 | ------------------------------------------------------------
47 |
48 | New web-socket-js (20f837425d4), 2000 byte payload, 10ms delay
49 |
50 | firefox 4.0b9 - no WebSockets
51 | Packets sent: 3088
52 | Packets Received: 3087
53 | Average Latency: 16.71
54 | 40 Frame Running Average Latency: 16.80
55 | Minimum Latency: 7.00
56 | Maximum Latency: 75.00
57 |
58 | - First 1000 sent in 13 seconds
59 | - Second 1000 sent in 12 seconds
60 | - Third 1000 sent in 12 seconds
61 |
62 | firefox 3.6.10 - no WebSockets
63 | Packets sent: 3100
64 | Packets Received: 3099
65 | Average Latency: 17.32
66 | 40 Frame Running Average Latency: 16.73
67 | Minimum Latency: 6.00
68 | Maximum Latency: 72.00
69 |
70 | Opera 11 - no WebSockets
71 | Packets sent: 3007
72 | Packets Received: 3007
73 | Average Latency: 465.91
74 | 40 Frame Running Average Latency: 147.95
75 | Minimum Latency: 12.00
76 | Maximum Latency: 9143.00
77 |
78 | - average starts at around 28ms
79 | - time for each 500 packets: 13s, 16s, 25s, 37s, 50s, 72s
80 | - also start seeing sent, receive lags around 1200 packets
81 |
82 | ---------------------------------------------------------------
83 |
84 | Old web-socket-js (9e7663771), 2000 byte payload, 10ms delay
85 |
86 | firefox 4.0b9 - no WebSockets
87 | Packets sent: 3024
88 | Packets Received: 3020
89 | Average Latency: 80.59
90 | 40 Frame Running Average Latency: 60.15
91 | Minimum Latency: 10.00
92 | Maximm Latency: 348.00
93 |
94 |
95 | firefox 3.6.10 - no WebSockets
96 | Packets sent: 2777
97 | Packets Received: 2775
98 | Average Latency: 34.89
99 | 40 Frame Running Average Latency: 24.50
100 | Minimum Latency: 10.00
101 | Maximum Latency: 208.00
102 |
103 |
104 | Opera 11 - no Websockets
105 | Packets sent: 3012
106 | Packets Received: 3011
107 | Average Latency: 380.87
108 | 40 Frame Running Average Latency: 341.90
109 | Minimum Latency: 28.00
110 | Maximum Latency: 2175.00
111 |
112 | - average starts at around 290ms
113 | - time for each 1000 packets: 23s, 38s, 65s
114 |
115 |
--------------------------------------------------------------------------------
/include/keysym.js:
--------------------------------------------------------------------------------
1 | /*
2 | * from noVNC: HTML5 VNC client
3 | * Copyright (C) 2010 Joel Martin
4 | * Licensed under LGPL-3 (see LICENSE.txt)
5 | */
6 |
7 | /* Translate DOM key down/up event to keysym value */
8 | function getKeysym(e) {
9 | var evt, keysym;
10 | evt = (e ? e : window.event);
11 |
12 | /* Remap modifier and special keys */
13 | switch ( evt.keyCode ) {
14 | case 8 : keysym = 0xFF08; break; // BACKSPACE
15 | case 9 : keysym = 0xFF09; break; // TAB
16 | case 13 : keysym = 0xFF0D; break; // ENTER
17 | case 27 : keysym = 0xFF1B; break; // ESCAPE
18 | case 45 : keysym = 0xFF63; break; // INSERT
19 | case 46 : keysym = 0xFFFF; break; // DELETE
20 | case 36 : keysym = 0xFF50; break; // HOME
21 | case 35 : keysym = 0xFF57; break; // END
22 | case 33 : keysym = 0xFF55; break; // PAGE_UP
23 | case 34 : keysym = 0xFF56; break; // PAGE_DOWN
24 | case 37 : keysym = 0xFF51; break; // LEFT
25 | case 38 : keysym = 0xFF52; break; // UP
26 | case 39 : keysym = 0xFF53; break; // RIGHT
27 | case 40 : keysym = 0xFF54; break; // DOWN
28 | case 112 : keysym = 0xFFBE; break; // F1
29 | case 113 : keysym = 0xFFBF; break; // F2
30 | case 114 : keysym = 0xFFC0; break; // F3
31 | case 115 : keysym = 0xFFC1; break; // F4
32 | case 116 : keysym = 0xFFC2; break; // F5
33 | case 117 : keysym = 0xFFC3; break; // F6
34 | case 118 : keysym = 0xFFC4; break; // F7
35 | case 119 : keysym = 0xFFC5; break; // F8
36 | case 120 : keysym = 0xFFC6; break; // F9
37 | case 121 : keysym = 0xFFC7; break; // F10
38 | case 122 : keysym = 0xFFC8; break; // F11
39 | case 123 : keysym = 0xFFC9; break; // F12
40 | case 16 : keysym = 0xFFE1; break; // SHIFT
41 | case 17 : keysym = 0xFFE3; break; // CONTROL
42 | //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
43 | case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
44 | default : keysym = evt.keyCode; break;
45 | }
46 |
47 | /* Remap symbols */
48 | switch (keysym) {
49 | case 186 : keysym = 59; break; // ; (IE)
50 | case 187 : keysym = 61; break; // = (IE)
51 | case 188 : keysym = 44; break; // , (Mozilla, IE)
52 | case 109 : // - (Mozilla)
53 | if (Util.Engine.gecko) {
54 | keysym = 45; }
55 | break;
56 | case 189 : keysym = 45; break; // - (IE)
57 | case 190 : keysym = 46; break; // . (Mozilla, IE)
58 | case 191 : keysym = 47; break; // / (Mozilla, IE)
59 | case 192 : keysym = 96; break; // ` (Mozilla, IE)
60 | case 219 : keysym = 91; break; // [ (Mozilla, IE)
61 | case 220 : keysym = 92; break; // \ (Mozilla, IE)
62 | case 221 : keysym = 93; break; // ] (Mozilla, IE)
63 | case 222 : keysym = 39; break; // ' (Mozilla, IE)
64 | }
65 |
66 | /* Remap shifted and unshifted keys */
67 | if (!!evt.shiftKey) {
68 | switch (keysym) {
69 | case 48 : keysym = 41 ; break; // ) (shifted 0)
70 | case 49 : keysym = 33 ; break; // ! (shifted 1)
71 | case 50 : keysym = 64 ; break; // @ (shifted 2)
72 | case 51 : keysym = 35 ; break; // # (shifted 3)
73 | case 52 : keysym = 36 ; break; // $ (shifted 4)
74 | case 53 : keysym = 37 ; break; // % (shifted 5)
75 | case 54 : keysym = 94 ; break; // ^ (shifted 6)
76 | case 55 : keysym = 38 ; break; // & (shifted 7)
77 | case 56 : keysym = 42 ; break; // * (shifted 8)
78 | case 57 : keysym = 40 ; break; // ( (shifted 9)
79 |
80 | case 59 : keysym = 58 ; break; // : (shifted `)
81 | case 61 : keysym = 43 ; break; // + (shifted ;)
82 | case 44 : keysym = 60 ; break; // < (shifted ,)
83 | case 45 : keysym = 95 ; break; // _ (shifted -)
84 | case 46 : keysym = 62 ; break; // > (shifted .)
85 | case 47 : keysym = 63 ; break; // ? (shifted /)
86 | case 96 : keysym = 126; break; // ~ (shifted `)
87 | case 91 : keysym = 123; break; // { (shifted [)
88 | case 92 : keysym = 124; break; // | (shifted \)
89 | case 93 : keysym = 125; break; // } (shifted ])
90 | case 39 : keysym = 34 ; break; // " (shifted ')
91 | }
92 | } else if ((keysym >= 65) && (keysym <=90)) {
93 | /* Remap unshifted A-Z */
94 | keysym += 32;
95 | }
96 |
97 | return keysym;
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/tests/echo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebSockets Echo Test
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Host:
21 | Port:
22 | Encrypt:
23 |
25 |
26 |
27 |
28 | Log:
29 |
30 |
31 |
32 |
33 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/tests/test_websocketproxy.py:
--------------------------------------------------------------------------------
1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4
2 |
3 | # Copyright(c) 2015 Red Hat, Inc All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """ Unit tests for websocketproxy """
18 |
19 | import unittest
20 | import unittest
21 | import socket
22 |
23 | from mox3 import stubout
24 |
25 | from websockify import websocket
26 | from websockify import websocketproxy
27 | from websockify import token_plugins
28 | from websockify import auth_plugins
29 |
30 | try:
31 | from StringIO import StringIO
32 | BytesIO = StringIO
33 | except ImportError:
34 | from io import StringIO
35 | from io import BytesIO
36 |
37 |
38 | class FakeSocket(object):
39 | def __init__(self, data=''):
40 | if isinstance(data, bytes):
41 | self._data = data
42 | else:
43 | self._data = data.encode('latin_1')
44 |
45 | def recv(self, amt, flags=None):
46 | res = self._data[0:amt]
47 | if not (flags & socket.MSG_PEEK):
48 | self._data = self._data[amt:]
49 |
50 | return res
51 |
52 | def makefile(self, mode='r', buffsize=None):
53 | if 'b' in mode:
54 | return BytesIO(self._data)
55 | else:
56 | return StringIO(self._data.decode('latin_1'))
57 |
58 |
59 | class FakeServer(object):
60 | class EClose(Exception):
61 | pass
62 |
63 | def __init__(self):
64 | self.token_plugin = None
65 | self.auth_plugin = None
66 | self.wrap_cmd = None
67 | self.ssl_target = None
68 | self.unix_target = None
69 |
70 | class ProxyRequestHandlerTestCase(unittest.TestCase):
71 | def setUp(self):
72 | super(ProxyRequestHandlerTestCase, self).setUp()
73 | self.stubs = stubout.StubOutForTesting()
74 | self.handler = websocketproxy.ProxyRequestHandler(
75 | FakeSocket(''), "127.0.0.1", FakeServer())
76 | self.handler.path = "https://localhost:6080/websockify?token=blah"
77 | self.handler.headers = None
78 | self.stubs.Set(websocket.WebSocketServer, 'socket',
79 | staticmethod(lambda *args, **kwargs: None))
80 |
81 | def tearDown(self):
82 | self.stubs.UnsetAll()
83 | super(ProxyRequestHandlerTestCase, self).tearDown()
84 |
85 | def test_get_target(self):
86 | class TestPlugin(token_plugins.BasePlugin):
87 | def lookup(self, token):
88 | return ("some host", "some port")
89 |
90 | host, port = self.handler.get_target(
91 | TestPlugin(None), self.handler.path)
92 |
93 | self.assertEqual(host, "some host")
94 | self.assertEqual(port, "some port")
95 |
96 | def test_get_target_unix_socket(self):
97 | class TestPlugin(token_plugins.BasePlugin):
98 | def lookup(self, token):
99 | return ("unix_socket", "/tmp/socket")
100 |
101 | _, socket = self.handler.get_target(
102 | TestPlugin(None), self.handler.path)
103 |
104 | self.assertEqual(socket, "/tmp/socket")
105 |
106 | def test_get_target_raises_error_on_unknown_token(self):
107 | class TestPlugin(token_plugins.BasePlugin):
108 | def lookup(self, token):
109 | return None
110 |
111 | self.assertRaises(FakeServer.EClose, self.handler.get_target,
112 | TestPlugin(None), "https://localhost:6080/websockify?token=blah")
113 |
114 | def test_token_plugin(self):
115 | class TestPlugin(token_plugins.BasePlugin):
116 | def lookup(self, token):
117 | return (self.source + token).split(',')
118 |
119 | self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
120 | staticmethod(lambda *args, **kwargs: None))
121 |
122 | self.handler.server.token_plugin = TestPlugin("somehost,")
123 | self.handler.validate_connection()
124 |
125 | self.assertEqual(self.handler.server.target_host, "somehost")
126 | self.assertEqual(self.handler.server.target_port, "blah")
127 |
128 | def test_auth_plugin(self):
129 | class TestPlugin(auth_plugins.BasePlugin):
130 | def authenticate(self, headers, target_host, target_port):
131 | if target_host == self.source:
132 | raise auth_plugins.AuthenticationError(response_msg="some_error")
133 |
134 | self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
135 | staticmethod(lambda *args, **kwargs: None))
136 |
137 | self.handler.server.auth_plugin = TestPlugin("somehost")
138 | self.handler.server.target_host = "somehost"
139 | self.handler.server.target_port = "someport"
140 |
141 | self.assertRaises(auth_plugins.AuthenticationError,
142 | self.handler.validate_connection)
143 |
144 | self.handler.server.target_host = "someotherhost"
145 | self.handler.validate_connection()
146 |
147 |
--------------------------------------------------------------------------------
/tests/load.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | WebSocket server-side load test program. Sends and receives traffic
5 | that has a random payload (length and content) that is checksummed and
6 | given a sequence number. Any errors are reported and counted.
7 | '''
8 |
9 | import sys, os, select, random, time, optparse, logging
10 | sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
11 | from websockify.websocket import WebSocketServer, WebSocketRequestHandler
12 |
13 | class WebSocketLoadServer(WebSocketServer):
14 |
15 | recv_cnt = 0
16 | send_cnt = 0
17 |
18 | def __init__(self, *args, **kwargs):
19 | self.delay = kwargs.pop('delay')
20 |
21 | WebSocketServer.__init__(self, *args, **kwargs)
22 |
23 |
24 | class WebSocketLoad(WebSocketRequestHandler):
25 |
26 | max_packet_size = 10000
27 |
28 | def new_websocket_client(self):
29 | print "Prepopulating random array"
30 | self.rand_array = []
31 | for i in range(0, self.max_packet_size):
32 | self.rand_array.append(random.randint(0, 9))
33 |
34 | self.errors = 0
35 | self.send_cnt = 0
36 | self.recv_cnt = 0
37 |
38 | try:
39 | self.responder(self.request)
40 | except:
41 | print "accumulated errors:", self.errors
42 | self.errors = 0
43 | raise
44 |
45 | def responder(self, client):
46 | c_pend = 0
47 | cqueue = []
48 | cpartial = ""
49 | socks = [client]
50 | last_send = time.time() * 1000
51 |
52 | while True:
53 | ins, outs, excepts = select.select(socks, socks, socks, 1)
54 | if excepts: raise Exception("Socket exception")
55 |
56 | if client in ins:
57 | frames, closed = self.recv_frames()
58 |
59 | err = self.check(frames)
60 | if err:
61 | self.errors = self.errors + 1
62 | print err
63 |
64 | if closed:
65 | self.send_close()
66 |
67 | now = time.time() * 1000
68 | if client in outs:
69 | if c_pend:
70 | last_send = now
71 | c_pend = self.send_frames()
72 | elif now > (last_send + self.server.delay):
73 | last_send = now
74 | c_pend = self.send_frames([self.generate()])
75 |
76 | def generate(self):
77 | length = random.randint(10, self.max_packet_size)
78 | numlist = self.rand_array[self.max_packet_size-length:]
79 | # Error in length
80 | #numlist.append(5)
81 | chksum = sum(numlist)
82 | # Error in checksum
83 | #numlist[0] = 5
84 | nums = "".join( [str(n) for n in numlist] )
85 | data = "^%d:%d:%d:%s$" % (self.send_cnt, length, chksum, nums)
86 | self.send_cnt += 1
87 |
88 | return data
89 |
90 |
91 | def check(self, frames):
92 |
93 | err = ""
94 | for data in frames:
95 | if data.count('$') > 1:
96 | raise Exception("Multiple parts within single packet")
97 | if len(data) == 0:
98 | self.traffic("_")
99 | continue
100 |
101 | if data[0] != "^":
102 | err += "buf did not start with '^'\n"
103 | continue
104 |
105 | try:
106 | cnt, length, chksum, nums = data[1:-1].split(':')
107 | cnt = int(cnt)
108 | length = int(length)
109 | chksum = int(chksum)
110 | except:
111 | print "\n" + repr(data) + ""
112 | err += "Invalid data format\n"
113 | continue
114 |
115 | if self.recv_cnt != cnt:
116 | err += "Expected count %d but got %d\n" % (self.recv_cnt, cnt)
117 | self.recv_cnt = cnt + 1
118 | continue
119 |
120 | self.recv_cnt += 1
121 |
122 | if len(nums) != length:
123 | err += "Expected length %d but got %d\n" % (length, len(nums))
124 | continue
125 |
126 | inv = nums.translate(None, "0123456789")
127 | if inv:
128 | err += "Invalid characters found: %s\n" % inv
129 | continue
130 |
131 | real_chksum = 0
132 | for num in nums:
133 | real_chksum += int(num)
134 |
135 | if real_chksum != chksum:
136 | err += "Expected checksum %d but real chksum is %d\n" % (chksum, real_chksum)
137 | return err
138 |
139 |
140 | if __name__ == '__main__':
141 | parser = optparse.OptionParser(usage="%prog [options] listen_port")
142 | parser.add_option("--verbose", "-v", action="store_true",
143 | help="verbose messages and per frame traffic")
144 | parser.add_option("--cert", default="self.pem",
145 | help="SSL certificate file")
146 | parser.add_option("--key", default=None,
147 | help="SSL key file (if separate from cert)")
148 | parser.add_option("--ssl-only", action="store_true",
149 | help="disallow non-encrypted connections")
150 | (opts, args) = parser.parse_args()
151 |
152 | try:
153 | if len(args) != 1: raise
154 | opts.listen_port = int(args[0])
155 |
156 | if len(args) not in [1,2]: raise
157 | opts.listen_port = int(args[0])
158 | if len(args) == 2:
159 | opts.delay = int(args[1])
160 | else:
161 | opts.delay = 10
162 | except:
163 | parser.error("Invalid arguments")
164 |
165 | logging.basicConfig(level=logging.INFO)
166 |
167 | opts.web = "."
168 | server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
169 | server.start_server()
170 |
171 |
--------------------------------------------------------------------------------
/tests/plain_echo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebSockets Echo Test
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Host:
19 | Port:
20 | Encrypt:
21 |
23 |
24 |
25 |
26 | Log:
27 |
28 |
29 |
30 |
31 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/docs/websockify.1:
--------------------------------------------------------------------------------
1 | .TH websockify 1 "June 7, 2012" "version 0.3" "USER COMMANDS"
2 |
3 | .SH NAME
4 |
5 | websockify - WebSockets to TCP socket bridge
6 |
7 | .SH SYNOPSIS
8 |
9 | websockify [options] [source_addr:]source_port target_addr:target_port
10 | websockify [options] [source_addr:]source_port \-\- WRAP_COMMAND_LINE
11 |
12 | .SH OPTIONS
13 |
14 | -h, --help show this help message and exit
15 | -v, --verbose verbose messages and per frame traffic
16 | --record=FILE record sessions to FILE.[session_number]
17 | -D, --daemon become a daemon (background process)
18 | --run-once handle a single WebSocket connection and exit
19 | --timeout=TIMEOUT after TIMEOUT seconds exit when not connected
20 | --cert=CERT SSL certificate file
21 | --key=KEY SSL key file (if separate from cert)
22 | --ssl-only disallow non-encrypted connections
23 | --web=DIR run webserver on same port. Serve files from DIR.
24 | --wrap-mode=MODE action to take when the wrapped program exits or
25 | daemonizes: exit (default), ignore, respawn
26 |
27 | .SH DESCRIPTION
28 |
29 | At the most basic level, websockify just translates WebSockets traffic to normal TCP socket traffic. Websockify accepts the WebSockets handshake, parses it, and then begins forwarding traffic between the client and the target in both directions.
30 |
31 | websockify was formerly named wsproxy and was part of the noVNC project.
32 |
33 | .SH NOTES
34 |
35 | .SS WebSockets binary data
36 |
37 | Websockify supports all versions of the WebSockets protocol (Hixie and HyBI). The older Hixie versions of the protocol only support UTF-8 text payloads. In order to transport binary data over UTF-8 an encoding must used to encapsulate the data within UTF-8. Websockify uses base64 to encode all traffic to and from the client. This does not affect the data between websockify and the server.
38 |
39 | .SS Encrypted WebSocket connections (wss://)
40 |
41 | To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to generate a certificate for websockify to load. By default websockify loads a certificate file name self.pem but the --cert=CERT option can override the file name. You can generate a self-signed certificate using openssl. When asked for the common name, use the hostname of the server where the proxy will be running:
42 |
43 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
44 |
45 | .SS Websock Javascript library
46 |
47 | The websock.js (see https://github.com/kanaka/websockify) Javascript library library provides a Websock object that is similar to the standard WebSocket object but Websock enables communication with raw TCP sockets (i.e. the binary stream) via websockify. This is accomplished by base64 encoding the data stream between Websock and websockify.
48 |
49 | Websock has built-in receive queue buffering; the message event does not contain actual data but is simply a notification that there is new data available. Several rQ* methods are available to read binary data off of the receive queue.
50 |
51 | The Websock API is documented on the websock.js API wiki page:
52 |
53 | https://github.com/kanaka/websockify/wiki/websock.js
54 |
55 | See the "Wrap a Program" section below for an example of using Websock and websockify as a browser telnet client (wstelnet.html).
56 |
57 | .SS Additional websockify features
58 |
59 | These are not necessary for the basic operation.
60 |
61 | .IP *
62 | Daemonizing: When the -D option is specified, websockify runs in the background as a daemon process.
63 |
64 | .IP *
65 | SSL (the wss:// WebSockets URI): This is detected automatically by websockify by sniffing the first byte sent from the client and then wrapping the socket if the data starts with '\\x16' or '\\x80' (indicating SSL).
66 |
67 | .IP *
68 | Flash security policy: websockify detects flash security policy requests (again by sniffing the first packet) and answers with an appropriate flash security policy response (and then closes the port). This means no separate flash security policy server is needed for supporting the flash WebSockets fallback emulator.
69 |
70 | .IP *
71 | Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option.
72 |
73 | .IP *
74 | Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy and Flash security policy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve.
75 |
76 | .IP *
77 | Wrap a program: see the "Wrap a Program" section below.
78 |
79 | .SS Wrap a Program
80 |
81 | In addition to proxying from a source address to a target address (which may be on a different system), websockify has the ability to launch a program on the local system and proxy WebSockets traffic to a normal TCP port owned/bound by the program.
82 |
83 | The is accomplished with a small LD_PRELOAD library (rebind.so) which intercepts bind() system calls by the program. The specified port is moved to a new localhost/loopback free high port. websockify then proxies WebSockets traffic directed to the original port to the new (moved) port of the program.
84 |
85 | The program wrap mode is invoked by replacing the target with -- followed by the program command line to wrap.
86 |
87 | `./websockify 2023 -- PROGRAM ARGS`
88 |
89 | The --wrap-mode option can be used to indicate what action to take when the wrapped program exits or daemonizes.
90 |
91 | Here is an example of using websockify to wrap the vncserver command (which backgrounds itself) for use with noVNC:
92 |
93 | `./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
94 |
95 | Here is an example of wrapping telnetd (from krb5-telnetd).telnetd exits after the connection closes so the wrap mode is set to respawn the command:
96 |
97 | `sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
98 |
99 | The wstelnet.html page demonstrates a simple WebSockets based telnet client.
100 |
101 |
102 | .SH AUTHOR
103 | Joel Martin (github@martintribe.org)
104 |
105 | .SH SEE ALSO
106 |
107 | https://github.com/kanaka/websockify/
108 |
109 | https://github.com/kanaka/websockify/wiki/
110 |
111 |
--------------------------------------------------------------------------------
/include/wsirc.js:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSockets IRC client
3 | * Copyright (C) 2011 Joel Martin
4 | * Licensed under LGPL-3 (see LICENSE.txt)
5 | *
6 | * Includes VT100.js from:
7 | * http://code.google.com/p/sshconsole
8 | * Which was modified from:
9 | * http://fzort.org/bi/o.php#vt100_js
10 |
11 | * IRC Client protocol:
12 | * http://www.faqs.org/rfcs/rfc2812.html
13 | */
14 |
15 |
16 | function IRC(target, connect_callback, disconnect_callback) {
17 |
18 | var that = {}, // Public API interface
19 | vt100, ws, sQ = [],
20 | state = "unconnected",
21 | irc_nick, irc_channel,
22 | termType = "VT100";
23 |
24 |
25 | Array.prototype.pushStr = function (str) {
26 | var n = str.length;
27 | for (var i=0; i < n; i++) {
28 | this.push(str.charCodeAt(i));
29 | }
30 | }
31 |
32 | function do_send() {
33 | if (sQ.length > 0) {
34 | Util.Debug("Sending " + sQ);
35 | ws.send(sQ);
36 | sQ = [];
37 | }
38 | }
39 |
40 | function do_recv() {
41 | console.log(">> do_recv");
42 | var rQ, rQi, i;
43 |
44 | while (ws.rQlen() > 1) {
45 | rQ = ws.get_rQ();
46 | rQi = ws.get_rQi();
47 | for (i = rQi; i < rQ.length; i++) {
48 | if (rQ[i] === 10) {
49 | break;
50 | }
51 | }
52 | if (i >= rQ.length) {
53 | // No line break found
54 | break;
55 | }
56 | recvMsg(ws.rQshiftStr((i-rQi) + 1));
57 | }
58 | //console.log("<< do_recv");
59 | }
60 |
61 | // Handle an IRC message
62 | function recvMsg(msg) {
63 | Util.Debug(">> recvMsg('" + msg + "')");
64 |
65 | var tokens = msg.split(' '), in_params = true,
66 | prefix, command, params = [], trailing = [];
67 |
68 | Util.Info(" tokens: " + tokens);
69 |
70 | if (tokens[0].charAt(0) === ":") {
71 | prefix = tokens.shift();
72 | }
73 |
74 | command = tokens.shift();
75 |
76 | while (tokens.length > 0) {
77 | if (tokens[0].charAt(0) === ":") {
78 | in_params = false;
79 | }
80 | if (in_params) {
81 | params.push(tokens.shift());
82 | } else {
83 | trailing.push(tokens.shift());
84 | }
85 | }
86 |
87 | Util.Info(" prefix: " + prefix);
88 | Util.Info(" command: " + command);
89 | Util.Info(" params: " + params);
90 | Util.Info(" trailing: " + trailing);
91 |
92 | // Show raw received
93 | vt100.write(msg);
94 |
95 | switch (command) {
96 | case "004":
97 | state = "registered";
98 | vt100.write("Joining channel #" + irc_channel);
99 | sendCmd("JOIN #" + irc_channel);
100 | break;
101 | case "JOIN":
102 | state = "joined";
103 | vt100.write("Joined channel #" + irc_channel);
104 | break;
105 |
106 | }
107 |
108 | Util.Debug("<< recvMsg('" + msg + "')");
109 | }
110 |
111 | function sendCmd(msg) {
112 | Util.Info("Sending: " + msg);
113 | sQ.pushStr(msg + "\r\n");
114 | do_send();
115 | }
116 |
117 | that.sendMsg = function(msg) {
118 | // TODO parse into message
119 | sendCmd("PRIVMSG #" + irc_channel + " :" + msg);
120 | }
121 |
122 |
123 | that.connect = function(host, port, encrypt, nick, channel) {
124 | var host = host,
125 | port = port,
126 | scheme = "ws://", uri;
127 |
128 | irc_nick = nick;
129 | irc_channel = channel;
130 |
131 | Util.Debug(">> connect");
132 | if ((!host) || (!port)) {
133 | alert("must set host and port");
134 | return false;
135 | }
136 |
137 | if (ws) {
138 | ws.close();
139 | }
140 |
141 | if (encrypt) {
142 | scheme = "wss://";
143 | }
144 | uri = scheme + host + ":" + port;
145 | Util.Info("connecting to " + uri);
146 |
147 | ws.open(uri);
148 |
149 | Util.Debug("<< connect");
150 |
151 | return true;
152 | }
153 |
154 | that.disconnect = function() {
155 | Util.Debug(">> disconnect");
156 | if (ws) {
157 | ws.close();
158 | }
159 |
160 | disconnect_callback();
161 | Util.Debug("<< disconnect");
162 | }
163 |
164 |
165 | function constructor() {
166 | /* Initialize Websock object */
167 | ws = new Websock();
168 |
169 | ws.on('message', do_recv);
170 | ws.on('open', function(e) {
171 | Util.Info(">> WebSockets.onopen");
172 | // Send registration commands
173 | state = "connected";
174 | sendCmd("NICK " + irc_nick);
175 | // TODO: how to determine this?
176 | sendCmd("USER joelm 0 * :Joel Martin");
177 | connect_callback();
178 | Util.Info("<< WebSockets.onopen");
179 | });
180 | ws.on('close', function(e) {
181 | Util.Info(">> WebSockets.onclose");
182 | that.disconnect();
183 | Util.Info("<< WebSockets.onclose");
184 | });
185 | ws.on('error', function(e) {
186 | Util.Info(">> WebSockets.onerror");
187 | that.disconnect();
188 | Util.Info("<< WebSockets.onerror");
189 | });
190 |
191 | /* Initialize the terminal emulator/renderer */
192 |
193 | vt100 = new VT100(80, 24, target);
194 |
195 | // Show cursor
196 | vt100.curs_set(true, false);
197 |
198 | /*
199 | * Override VT100 I/O routines
200 | */
201 |
202 | // Set handler for sending characters
203 | vt100.getch(
204 | function send_chr(chr, vt) {
205 | var i;
206 | Util.Debug(">> send_chr: " + chr);
207 | for (i = 0; i < chr.length; i++) {
208 | sQ.push(chr.charCodeAt(i));
209 | }
210 | do_send();
211 | vt100.getch(send_chr);
212 | }
213 | );
214 |
215 | vt100.debug = function(message) {
216 | Util.Debug(message + "\n");
217 | }
218 |
219 | vt100.warn = function(message) {
220 | Util.Warn(message + "\n");
221 | }
222 |
223 | vt100.curs_set = function(vis, grab, eventist)
224 | {
225 | this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
226 | if (vis !== undefined)
227 | this.cursor_vis_ = (vis > 0);
228 | }
229 |
230 | return that;
231 | }
232 |
233 | return constructor(); // Return the public API interface
234 |
235 | } // End of Telnet()
236 |
--------------------------------------------------------------------------------
/include/web-socket-js/README.txt:
--------------------------------------------------------------------------------
1 | * How to try
2 |
3 | Assuming you have Web server (e.g. Apache) running at http://example.com/ .
4 |
5 | - Download web_socket.rb from:
6 | http://github.com/gimite/web-socket-ruby/tree/master
7 | - Run sample Web Socket server (echo server) in example.com with: (#1)
8 | $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
9 | - If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
10 | - Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
11 | - Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
12 | - Open sample.html in your browser.
13 | - After "onopen" is shown, input something, click [Send] and confirm echo back.
14 |
15 | #1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
16 |
17 |
18 | * Troubleshooting
19 |
20 | If it doesn't work, try these:
21 |
22 | 1. Try Chrome and Firefox 3.x.
23 | - It doesn't work on Chrome:
24 | -- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
25 | - It works on Chrome but it doesn't work on Firefox:
26 | -- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
27 | - It works on both Chrome and Firefox, but it doesn't work on your browser:
28 | -- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
29 |
30 | 2. Add this line before your code:
31 | WEB_SOCKET_DEBUG = true;
32 | and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
33 |
34 | 3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
35 |
36 | 4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
37 |
38 | 5. Check if sample.html bundled with web-socket-js works.
39 |
40 | 6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
41 |
42 | 7. Install debugger version of Flash Player available here to see Flash errors:
43 | http://www.adobe.com/support/flashplayer/downloads.html
44 |
45 |
46 | * Supported environments
47 |
48 | It should work on:
49 | - Google Chrome 4 or later (just uses native implementation)
50 | - Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
51 |
52 | It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
53 |
54 |
55 | * Flash socket policy file
56 |
57 | This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
58 |
59 | If you use web-socket-ruby available at
60 | http://github.com/gimite/web-socket-ruby/tree/master
61 | , you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
62 |
63 | If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
64 | http://www.lightsphere.com/dev/articles/flash_socket_policy.html
65 | for details and sample script to run socket policy file server. node.js implementation is available here:
66 | http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
67 |
68 | Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
69 |
70 |
71 | * Cookie considerations
72 |
73 | Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
74 |
75 | Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
76 |
77 |
78 | * Proxy considerations
79 |
80 | The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
81 |
82 | The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
83 |
84 | The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
85 |
86 |
87 | * How to host HTML file and SWF file in different domains
88 |
89 | By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
90 |
91 | WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
92 |
93 | - Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
94 | - Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
95 | - In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
96 |
97 |
98 | * How to build WebSocketMain.swf
99 |
100 | Install Flex 4 SDK:
101 | http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
102 |
103 | $ cd flash-src
104 | $ ./build.sh
105 |
106 |
107 | * License
108 |
109 | New BSD License.
110 |
--------------------------------------------------------------------------------
/other/js/websockify.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // A WebSocket to TCP socket proxy
4 | // Copyright 2012 Joel Martin
5 | // Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
6 |
7 | // Known to work with node 0.8.9
8 | // Requires node modules: ws, optimist and policyfile
9 | // npm install ws optimist policyfile
10 |
11 |
12 | var argv = require('optimist').argv,
13 | net = require('net'),
14 | http = require('http'),
15 | https = require('https'),
16 | url = require('url'),
17 | path = require('path'),
18 | fs = require('fs'),
19 | policyfile = require('policyfile'),
20 |
21 | Buffer = require('buffer').Buffer,
22 | WebSocketServer = require('ws').Server,
23 |
24 | webServer, wsServer,
25 | source_host, source_port, target_host, target_port,
26 | web_path = null;
27 |
28 |
29 | // Handle new WebSocket client
30 | new_client = function(client) {
31 | var clientAddr = client._socket.remoteAddress, log;
32 | console.log(client.upgradeReq.url);
33 | log = function (msg) {
34 | console.log(' ' + clientAddr + ': '+ msg);
35 | };
36 | log('WebSocket connection');
37 | log('Version ' + client.protocolVersion + ', subprotocol: ' + client.protocol);
38 |
39 | var target = net.createConnection(target_port,target_host, function() {
40 | log('connected to target');
41 | });
42 | target.on('data', function(data) {
43 | //log("sending message: " + data);
44 | try {
45 | if (client.protocol === 'base64') {
46 | client.send(new Buffer(data).toString('base64'));
47 | } else {
48 | client.send(data,{binary: true});
49 | }
50 | } catch(e) {
51 | log("Client closed, cleaning up target");
52 | target.end();
53 | }
54 | });
55 | target.on('end', function() {
56 | log('target disconnected');
57 | client.close();
58 | });
59 | target.on('error', function() {
60 | log('target connection error');
61 | target.end();
62 | client.close();
63 | });
64 |
65 | client.on('message', function(msg) {
66 | //log('got message: ' + msg);
67 | if (client.protocol === 'base64') {
68 | target.write(new Buffer(msg, 'base64'));
69 | } else {
70 | target.write(msg,'binary');
71 | }
72 | });
73 | client.on('close', function(code, reason) {
74 | log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
75 | target.end();
76 | });
77 | client.on('error', function(a) {
78 | log('WebSocket client error: ' + a);
79 | target.end();
80 | });
81 | };
82 |
83 |
84 | // Send an HTTP error response
85 | http_error = function (response, code, msg) {
86 | response.writeHead(code, {"Content-Type": "text/plain"});
87 | response.write(msg + "\n");
88 | response.end();
89 | return;
90 | }
91 |
92 | // Process an HTTP static file request
93 | http_request = function (request, response) {
94 | // console.log("pathname: " + url.parse(req.url).pathname);
95 | // res.writeHead(200, {'Content-Type': 'text/plain'});
96 | // res.end('okay');
97 |
98 | if (! argv.web) {
99 | return http_error(response, 403, "403 Permission Denied");
100 | }
101 |
102 | var uri = url.parse(request.url).pathname
103 | , filename = path.join(argv.web, uri);
104 |
105 | fs.exists(filename, function(exists) {
106 | if(!exists) {
107 | return http_error(response, 404, "404 Not Found");
108 | }
109 |
110 | if (fs.statSync(filename).isDirectory()) {
111 | filename += '/index.html';
112 | }
113 |
114 | fs.readFile(filename, "binary", function(err, file) {
115 | if(err) {
116 | return http_error(response, 500, err);
117 | }
118 |
119 | response.writeHead(200);
120 | response.write(file, "binary");
121 | response.end();
122 | });
123 | });
124 | };
125 |
126 | // Select 'binary' or 'base64' subprotocol, preferring 'binary'
127 | selectProtocol = function(protocols, callback) {
128 | if (protocols.indexOf('binary') >= 0) {
129 | callback(true, 'binary');
130 | } else if (protocols.indexOf('base64') >= 0) {
131 | callback(true, 'base64');
132 | } else {
133 | console.log("Client must support 'binary' or 'base64' protocol");
134 | callback(false);
135 | }
136 | }
137 |
138 | // parse source and target arguments into parts
139 | try {
140 | source_arg = argv._[0].toString();
141 | target_arg = argv._[1].toString();
142 |
143 | var idx;
144 | idx = source_arg.indexOf(":");
145 | if (idx >= 0) {
146 | source_host = source_arg.slice(0, idx);
147 | source_port = parseInt(source_arg.slice(idx+1), 10);
148 | } else {
149 | source_host = "";
150 | source_port = parseInt(source_arg, 10);
151 | }
152 |
153 | idx = target_arg.indexOf(":");
154 | if (idx < 0) {
155 | throw("target must be host:port");
156 | }
157 | target_host = target_arg.slice(0, idx);
158 | target_port = parseInt(target_arg.slice(idx+1), 10);
159 |
160 | if (isNaN(source_port) || isNaN(target_port)) {
161 | throw("illegal port");
162 | }
163 | } catch(e) {
164 | console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port");
165 | process.exit(2);
166 | }
167 |
168 | console.log("WebSocket settings: ");
169 | console.log(" - proxying from " + source_host + ":" + source_port +
170 | " to " + target_host + ":" + target_port);
171 | if (argv.web) {
172 | console.log(" - Web server active. Serving: " + argv.web);
173 | }
174 |
175 | if (argv.cert) {
176 | argv.key = argv.key || argv.cert;
177 | var cert = fs.readFileSync(argv.cert),
178 | key = fs.readFileSync(argv.key);
179 | console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key);
180 | webServer = https.createServer({cert: cert, key: key}, http_request);
181 | } else {
182 | console.log(" - Running in unencrypted HTTP (ws://) mode");
183 | webServer = http.createServer(http_request);
184 | }
185 | webServer.listen(source_port, function() {
186 | wsServer = new WebSocketServer({server: webServer,
187 | handleProtocols: selectProtocol});
188 | wsServer.on('connection', new_client);
189 | });
190 |
191 | // Attach Flash policyfile answer service
192 | policyfile.createServer().listen(-1, webServer);
193 |
--------------------------------------------------------------------------------
/Windows/noVNC Websocket Service Project/ProjectInstaller.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 56
122 |
123 |
124 | 196, 17
125 |
126 |
127 | False
128 |
129 |
--------------------------------------------------------------------------------
/include/webutil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * from noVNC: HTML5 VNC client
3 | * Copyright (C) 2012 Joel Martin
4 | * Licensed under MPL 2.0 (see LICENSE.txt)
5 | *
6 | * See README.md for usage and integration instructions.
7 | */
8 |
9 | "use strict";
10 | /*jslint bitwise: false, white: false */
11 | /*global Util, window, document */
12 |
13 | // Globals defined here
14 | var WebUtil = {}, $D;
15 |
16 | /*
17 | * Simple DOM selector by ID
18 | */
19 | if (!window.$D) {
20 | window.$D = function (id) {
21 | if (document.getElementById) {
22 | return document.getElementById(id);
23 | } else if (document.all) {
24 | return document.all[id];
25 | } else if (document.layers) {
26 | return document.layers[id];
27 | }
28 | return undefined;
29 | };
30 | }
31 |
32 |
33 | /*
34 | * ------------------------------------------------------
35 | * Namespaced in WebUtil
36 | * ------------------------------------------------------
37 | */
38 |
39 | // init log level reading the logging HTTP param
40 | WebUtil.init_logging = function(level) {
41 | if (typeof level !== "undefined") {
42 | Util._log_level = level;
43 | } else {
44 | Util._log_level = (document.location.href.match(
45 | /logging=([A-Za-z0-9\._\-]*)/) ||
46 | ['', Util._log_level])[1];
47 | }
48 | Util.init_logging();
49 | };
50 |
51 |
52 | WebUtil.dirObj = function (obj, depth, parent) {
53 | var i, msg = "", val = "";
54 | if (! depth) { depth=2; }
55 | if (! parent) { parent= ""; }
56 |
57 | // Print the properties of the passed-in object
58 | for (i in obj) {
59 | if ((depth > 1) && (typeof obj[i] === "object")) {
60 | // Recurse attributes that are objects
61 | msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
62 | } else {
63 | //val = new String(obj[i]).replace("\n", " ");
64 | if (typeof(obj[i]) === "undefined") {
65 | val = "undefined";
66 | } else {
67 | val = obj[i].toString().replace("\n", " ");
68 | }
69 | if (val.length > 30) {
70 | val = val.substr(0,30) + "...";
71 | }
72 | msg += parent + "." + i + ": " + val + "\n";
73 | }
74 | }
75 | return msg;
76 | };
77 |
78 | // Read a query string variable
79 | WebUtil.getQueryVar = function(name, defVal) {
80 | var re = new RegExp('[?][^#]*' + name + '=([^]*)'),
81 | match = document.location.href.match(re);
82 | if (typeof defVal === 'undefined') { defVal = null; }
83 | if (match) {
84 | return decodeURIComponent(match[1]);
85 | } else {
86 | return defVal;
87 | }
88 | };
89 |
90 |
91 | /*
92 | * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
93 | */
94 |
95 | // No days means only for this browser session
96 | WebUtil.createCookie = function(name,value,days) {
97 | var date, expires;
98 | if (days) {
99 | date = new Date();
100 | date.setTime(date.getTime()+(days*24*60*60*1000));
101 | expires = "; expires="+date.toGMTString();
102 | }
103 | else {
104 | expires = "";
105 | }
106 | document.cookie = name+"="+value+expires+"; path=/";
107 | };
108 |
109 | WebUtil.readCookie = function(name, defaultValue) {
110 | var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
111 | for(i=0; i < ca.length; i += 1) {
112 | c = ca[i];
113 | while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
114 | if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
115 | }
116 | return (typeof defaultValue !== 'undefined') ? defaultValue : null;
117 | };
118 |
119 | WebUtil.eraseCookie = function(name) {
120 | WebUtil.createCookie(name,"",-1);
121 | };
122 |
123 | /*
124 | * Setting handling.
125 | */
126 |
127 | WebUtil.initSettings = function(callback) {
128 | var callbackArgs = Array.prototype.slice.call(arguments, 1);
129 | if (window.chrome && window.chrome.storage) {
130 | window.chrome.storage.sync.get(function (cfg) {
131 | WebUtil.settings = cfg;
132 | console.log(WebUtil.settings);
133 | if (callback) {
134 | callback.apply(this, callbackArgs);
135 | }
136 | });
137 | } else {
138 | // No-op
139 | if (callback) {
140 | callback.apply(this, callbackArgs);
141 | }
142 | }
143 | };
144 |
145 | // No days means only for this browser session
146 | WebUtil.writeSetting = function(name, value) {
147 | if (window.chrome && window.chrome.storage) {
148 | //console.log("writeSetting:", name, value);
149 | if (WebUtil.settings[name] !== value) {
150 | WebUtil.settings[name] = value;
151 | window.chrome.storage.sync.set(WebUtil.settings);
152 | }
153 | } else {
154 | localStorage.setItem(name, value);
155 | }
156 | };
157 |
158 | WebUtil.readSetting = function(name, defaultValue) {
159 | var value;
160 | if (window.chrome && window.chrome.storage) {
161 | value = WebUtil.settings[name];
162 | } else {
163 | value = localStorage.getItem(name);
164 | }
165 | if (typeof value === "undefined") {
166 | value = null;
167 | }
168 | if (value === null && typeof defaultValue !== undefined) {
169 | return defaultValue;
170 | } else {
171 | return value;
172 | }
173 | };
174 |
175 | WebUtil.eraseSetting = function(name) {
176 | if (window.chrome && window.chrome.storage) {
177 | window.chrome.storage.sync.remove(name);
178 | delete WebUtil.settings[name];
179 | } else {
180 | localStorage.removeItem(name);
181 | }
182 | };
183 |
184 | /*
185 | * Alternate stylesheet selection
186 | */
187 | WebUtil.getStylesheets = function() { var i, links, sheets = [];
188 | links = document.getElementsByTagName("link");
189 | for (i = 0; i < links.length; i += 1) {
190 | if (links[i].title &&
191 | links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
192 | sheets.push(links[i]);
193 | }
194 | }
195 | return sheets;
196 | };
197 |
198 | // No sheet means try and use value from cookie, null sheet used to
199 | // clear all alternates.
200 | WebUtil.selectStylesheet = function(sheet) {
201 | var i, link, sheets = WebUtil.getStylesheets();
202 | if (typeof sheet === 'undefined') {
203 | sheet = 'default';
204 | }
205 | for (i=0; i < sheets.length; i += 1) {
206 | link = sheets[i];
207 | if (link.title === sheet) {
208 | Util.Debug("Using stylesheet " + sheet);
209 | link.disabled = false;
210 | } else {
211 | //Util.Debug("Skipping stylesheet " + link.title);
212 | link.disabled = true;
213 | }
214 | }
215 | return sheet;
216 | };
217 |
--------------------------------------------------------------------------------
/docs/LICENSE.LGPL-3:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## websockify: WebSockets support for any application/server
2 |
3 | websockify was formerly named wsproxy and was part of the
4 | [noVNC](https://github.com/kanaka/noVNC) project.
5 |
6 | At the most basic level, websockify just translates WebSockets traffic
7 | to normal socket traffic. Websockify accepts the WebSockets handshake,
8 | parses it, and then begins forwarding traffic between the client and
9 | the target in both directions.
10 |
11 | ### News/help/contact
12 |
13 | Notable commits, announcements and news are posted to
14 | @noVNC
15 |
16 | If you are a websockify developer/integrator/user (or want to be)
17 | please join the noVNC/websockify
19 | discussion group
20 |
21 | Bugs and feature requests can be submitted via [github
22 | issues](https://github.com/kanaka/websockify/issues).
23 |
24 | If you want to show appreciation for websockify you could donate to a great
25 | non-profits such as: [Compassion
26 | International](http://www.compassion.com/), [SIL](http://www.sil.org),
27 | [Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
28 | Foundation](https://www.eff.org/), [Against Malaria
29 | Foundation](http://www.againstmalaria.com/), [Nothing But
30 | Nets](http://www.nothingbutnets.net/), etc. Please tweet @noVNC if you do.
32 |
33 | ### WebSockets binary data
34 |
35 | Starting with websockify 0.5.0, only the HyBi / IETF
36 | 6455 WebSocket protocol is supported.
37 |
38 | Websockify negotiates whether to base64 encode traffic to and from the
39 | client via the subprotocol header (Sec-WebSocket-Protocol). The valid
40 | subprotocol values are 'binary' and 'base64' and if the client sends
41 | both then the server (the python implementation) will prefer 'binary'.
42 | The 'binary' subprotocol indicates that the data will be sent raw
43 | using binary WebSocket frames. Some HyBi clients (such as the Flash
44 | fallback and older Chrome and iOS versions) do not support binary data
45 | which is why the negotiation is necessary.
46 |
47 |
48 | ### Encrypted WebSocket connections (wss://)
49 |
50 | To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to
51 | generate a certificate and key for Websockify to load. By default, Websockify
52 | loads a certificate file name `self.pem` but the `--cert=CERT` and `--key=KEY`
53 | options can override the file name. You can generate a self-signed certificate
54 | using openssl. When asked for the common name, use the hostname of the server
55 | where the proxy will be running:
56 |
57 | ```
58 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
59 | ```
60 |
61 | For a self-signed certificate to work, you need to make your client/browser
62 | understand it. You can do this by installing it as accepted certificate, or by
63 | using that same certificate for a HTTPS connection to which you navigate first
64 | and approve. Browsers generally don't give you the "trust certificate?" prompt
65 | by opening a WSS socket with invalid certificate, hence you need to have it
66 | acccept it by either of those two methods.
67 |
68 | If you have a commercial/valid SSL certificate with one ore more intermediate
69 | certificates, concat them into one file, server certificate first, then the
70 | intermediate(s) from the CA, etc. Point to this file with the `--cert` option
71 | and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
72 |
73 |
74 | ### Websock Javascript library
75 |
76 |
77 | The `include/websock.js` Javascript library library provides a Websock
78 | object that is similar to the standard WebSocket object but Websock
79 | enables communication with raw TCP sockets (i.e. the binary stream)
80 | via websockify. This is accomplished by base64 encoding the data
81 | stream between Websock and websockify.
82 |
83 | Websock has built-in receive queue buffering; the message event
84 | does not contain actual data but is simply a notification that
85 | there is new data available. Several rQ* methods are available to
86 | read binary data off of the receive queue.
87 |
88 | The Websock API is documented on the [websock.js API wiki page](https://github.com/kanaka/websockify/wiki/websock.js)
89 |
90 | See the "Wrap a Program" section below for an example of using Websock
91 | and websockify as a browser telnet client (`wstelnet.html`).
92 |
93 |
94 | ### Additional websockify features
95 |
96 | These are not necessary for the basic operation.
97 |
98 | * Daemonizing: When the `-D` option is specified, websockify runs
99 | in the background as a daemon process.
100 |
101 | * SSL (the wss:// WebSockets URI): This is detected automatically by
102 | websockify by sniffing the first byte sent from the client and then
103 | wrapping the socket if the data starts with '\x16' or '\x80'
104 | (indicating SSL).
105 |
106 | * Flash security policy: websockify detects flash security policy
107 | requests (again by sniffing the first packet) and answers with an
108 | appropriate flash security policy response (and then closes the
109 | port). This means no separate flash security policy server is needed
110 | for supporting the flash WebSockets fallback emulator.
111 |
112 | * Session recording: This feature that allows recording of the traffic
113 | sent and received from the client to a file using the `--record`
114 | option.
115 |
116 | * Mini-webserver: websockify can detect and respond to normal web
117 | requests on the same port as the WebSockets proxy and Flash security
118 | policy. This functionality is activated with the `--web DIR` option
119 | where DIR is the root of the web directory to serve.
120 |
121 | * Wrap a program: see the "Wrap a Program" section below.
122 |
123 | * Log files: websockify can save all logging information in a file.
124 | This functionality is activated with the `--log-file FILE` option
125 | where FILE is the file where the logs should be saved.
126 |
127 | ### Implementations of websockify
128 |
129 | The primary implementation of websockify is in python. There are
130 | several alternate implementations in other languages (C, Node.js,
131 | Clojure, Ruby) in the `other/` subdirectory (with varying levels of
132 | functionality).
133 |
134 | In addition there are several other external projects that implement
135 | the websockify "protocol". See the alternate implementation [Feature
136 | Matrix](https://github.com/kanaka/websockify/wiki/Feature_Matrix) for
137 | more information.
138 |
139 |
140 | ### Wrap a Program
141 |
142 | In addition to proxying from a source address to a target address
143 | (which may be on a different system), websockify has the ability to
144 | launch a program on the local system and proxy WebSockets traffic to
145 | a normal TCP port owned/bound by the program.
146 |
147 | The is accomplished with a small LD_PRELOAD library (`rebind.so`)
148 | which intercepts bind() system calls by the program. The specified
149 | port is moved to a new localhost/loopback free high port. websockify
150 | then proxies WebSockets traffic directed to the original port to the
151 | new (moved) port of the program.
152 |
153 | The program wrap mode is invoked by replacing the target with `--`
154 | followed by the program command line to wrap.
155 |
156 | `./run 2023 -- PROGRAM ARGS`
157 |
158 | The `--wrap-mode` option can be used to indicate what action to take
159 | when the wrapped program exits or daemonizes.
160 |
161 | Here is an example of using websockify to wrap the vncserver command
162 | (which backgrounds itself) for use with
163 | [noVNC](https://github.com/kanaka/noVNC):
164 |
165 | `./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
166 |
167 | Here is an example of wrapping telnetd (from krb5-telnetd). telnetd
168 | exits after the connection closes so the wrap mode is set to respawn
169 | the command:
170 |
171 | `sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
172 |
173 | The `wstelnet.html` page demonstrates a simple WebSockets based telnet
174 | client (use 'localhost' and '2023' for the host and port
175 | respectively).
176 |
177 |
178 | ### Building the Python ssl module (for python 2.5 and older)
179 |
180 | * Install the build dependencies. On Ubuntu use this command:
181 |
182 | `sudo aptitude install python-dev bluetooth-dev`
183 |
184 | * At the top level of the websockify repostory, download, build and
185 | symlink the ssl module:
186 |
187 | `wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
188 |
189 | `tar xvzf ssl-1.15.tar.gz`
190 |
191 | `cd ssl-1.15`
192 |
193 | `make`
194 |
195 | `cd ../`
196 |
197 | `ln -sf ssl-1.15/build/lib.linux-*/ssl ssl`
198 |
199 |
--------------------------------------------------------------------------------
/other/websockify.clj:
--------------------------------------------------------------------------------
1 | (ns websockify
2 | ;(:use ring.adapter.jetty)
3 | (:require [clojure.tools.cli :as cli]
4 | [clojure.string :as string])
5 |
6 | (:import
7 |
8 | ;; Netty TCP Client
9 | [java.util.concurrent Executors]
10 | [java.net InetSocketAddress]
11 | [org.jboss.netty.channel
12 | Channels SimpleChannelHandler ChannelPipelineFactory]
13 | [org.jboss.netty.buffer ChannelBuffers]
14 | [org.jboss.netty.channel.socket.nio NioClientSocketChannelFactory]
15 | [org.jboss.netty.bootstrap ClientBootstrap]
16 | [org.jboss.netty.handler.codec.base64 Base64]
17 | [org.jboss.netty.util CharsetUtil]
18 |
19 | ;; Jetty WebSocket Server
20 | [org.eclipse.jetty.server Server]
21 | [org.eclipse.jetty.server.nio BlockingChannelConnector]
22 | [org.eclipse.jetty.servlet
23 | ServletContextHandler ServletHolder DefaultServlet]
24 | [org.eclipse.jetty.websocket
25 | WebSocket WebSocket$OnTextMessage
26 | WebSocketClientFactory WebSocketClient WebSocketServlet]))
27 |
28 |
29 | ;; TCP / NIO
30 |
31 | ;; (defn tcp-channel [host port]
32 | ;; (try
33 | ;; (let [address (InetSocketAddress. host port)
34 | ;; channel (doto (SocketChannel/open)
35 | ;; (.connect address))]
36 | ;; channel)
37 | ;; (catch Exception e
38 | ;; (println (str "Failed to connect to'" host ":" port "':" e))
39 | ;; nil)))
40 |
41 | ;; http://docs.jboss.org/netty/3.2/guide/html/start.html#d0e51
42 | ;; http://stackoverflow.com/questions/5453602/highly-concurrent-http-with-netty-and-nio
43 | ;; https://github.com/datskos/ring-netty-adapter/blob/master/src/ring/adapter/netty.clj
44 |
45 |
46 | (defn netty-client [host port open close message]
47 | (let [handler (proxy [SimpleChannelHandler] []
48 | (channelConnected [ctx e] (open ctx e))
49 | (channelDisconnected [ctx e] (close ctx e))
50 | (messageReceived [ctx e] (message ctx e))
51 | (exceptionCaught [ctx e]
52 | (println "exceptionCaught:" e)))
53 | pipeline (proxy [ChannelPipelineFactory] []
54 | (getPipeline []
55 | (doto (Channels/pipeline)
56 | (.addLast "handler" handler))))
57 | bootstrap (doto (ClientBootstrap.
58 | (NioClientSocketChannelFactory.
59 | (Executors/newCachedThreadPool)
60 | (Executors/newCachedThreadPool)))
61 | (.setPipelineFactory pipeline)
62 | (.setOption "tcpNoDelay" true)
63 | (.setOption "keepAlive" true))
64 | channel-future (.connect bootstrap (InetSocketAddress. host port))
65 | channel (.. channel-future (awaitUninterruptibly) (getChannel))]
66 | channel))
67 |
68 |
69 |
70 | ;; WebSockets
71 |
72 | ;; http://wiki.eclipse.org/Jetty/Feature/WebSockets
73 | (defn make-websocket-servlet [open close message]
74 | (proxy [WebSocketServlet] []
75 | (doGet [request response]
76 | ;;(println "doGet" request)
77 | (.. (proxy-super getServletContext)
78 | (getNamedDispatcher (proxy-super getServletName))
79 | (forward request response)))
80 | (doWebSocketConnect [request response]
81 | (println "doWebSocketConnect")
82 | (reify WebSocket$OnTextMessage
83 | (onOpen [this connection] (open this connection))
84 | (onClose [this code message] (close this code message))
85 | (onMessage [this data] (message this data))))))
86 |
87 | (defn websocket-server
88 | [port & {:keys [open close message ws-path web]
89 | :or {open (fn [_ conn]
90 | (println "New websocket client:" conn))
91 | close (fn [_ code reason]
92 | (println "Websocket client closed:" code reason))
93 | message (fn [_ data]
94 | (println "Websocket message:" data))
95 |
96 | ws-path "/websocket"}}]
97 | (let [http-servlet (doto (ServletHolder. (DefaultServlet.))
98 | (.setInitParameter "dirAllowed" "true")
99 | (.setInitParameter "resourceBase" web))
100 | ws-servlet (ServletHolder.
101 | (make-websocket-servlet open close message))
102 | context (doto (ServletContextHandler.)
103 | (.setContextPath "/")
104 | (.addServlet ws-servlet ws-path))
105 | connector (doto (BlockingChannelConnector.)
106 | (.setPort port)
107 | (.setMaxIdleTime Integer/MAX_VALUE))
108 | server (doto (Server.)
109 | (.setHandler context)
110 | (.addConnector connector))]
111 |
112 | (when web (.addServlet context http-servlet "/"))
113 | server))
114 |
115 |
116 |
117 | ;; Websockify
118 |
119 | (defonce settings (atom {}))
120 |
121 | ;; WebSocket client to TCP target mappings
122 |
123 | (defonce clients (atom {}))
124 | (defonce targets (atom {}))
125 |
126 |
127 | (defn target-open [ctx e]
128 | (println "Connected to target")
129 | #_(println "channelConnected:" e))
130 |
131 | (defn target-close [ctx e]
132 | #_(println "channelDisconnected:" e)
133 | (println "Target closed")
134 | (when-let [channel (get @targets (.getChannel ctx))]
135 | (.disconnect channel)))
136 |
137 | (defn target-message [ctx e]
138 | (let [channel (.getChannel ctx)
139 | client (get @targets channel)
140 | msg (.getMessage e)
141 | len (.readableBytes msg)
142 | b64 (Base64/encode msg false)
143 | blen (.readableBytes b64)]
144 | #_(println "received" len "bytes from target")
145 | #_(println "target receive:" (.toString msg 0 len CharsetUtil/UTF_8))
146 | #_(println "sending to client:" (.toString b64 0 blen CharsetUtil/UTF_8))
147 | (.sendMessage client (.toString b64 0 blen CharsetUtil/UTF_8))))
148 |
149 | (defn client-open [this connection]
150 | #_(println "Got WebSocket connection:" connection)
151 | (println "New client")
152 | (let [target (netty-client
153 | (:target-host @settings)
154 | (:target-port @settings)
155 | target-open target-close target-message)]
156 | (swap! clients assoc this {:client connection
157 | :target target})
158 | (swap! targets assoc target connection)))
159 |
160 | (defn client-close [this code message]
161 | (println "WebSocket connection closed")
162 | (when-let [target (:target (get @clients this))]
163 | (println "Closing target")
164 | (.close target)
165 | (println "Target closed")
166 | (swap! targets dissoc target))
167 | (swap! clients dissoc this))
168 |
169 | (defn client-message [this data]
170 | #_(println "WebSocket onMessage:" data)
171 | (let [target (:target (get @clients this))
172 | cbuf (ChannelBuffers/copiedBuffer data CharsetUtil/UTF_8)
173 | decbuf (Base64/decode cbuf)
174 | rlen (.readableBytes decbuf)]
175 | #_(println "Sending" rlen "bytes to target")
176 | #_(println "Sending to target:" (.toString decbuf 0 rlen CharsetUtil/UTF_8))
177 | (.write target decbuf)))
178 |
179 | (defn start-websockify
180 | [& {:keys [listen-port target-host target-port web]
181 | :or {listen-port 6080
182 | target-host "localhost"
183 | target-port 5900
184 | }}]
185 |
186 | (reset! clients {})
187 | (reset! targets {})
188 |
189 | (reset! settings {:target-host target-host
190 | :target-port target-port})
191 | (let [server (websocket-server listen-port
192 | :web web
193 | :ws-path "/websockify"
194 | :open client-open
195 | :close client-close
196 | :message client-message)]
197 |
198 | (.start server)
199 |
200 | (if web
201 | (println "Serving web requests from:" web)
202 | (println "Not serving web requests"))
203 |
204 | (defn stop-websockify []
205 | (doseq [client (vals @clients)]
206 | (.disconnect (:client client))
207 | (.close (:target client)))
208 | (.stop server)
209 | (reset! clients {})
210 | (reset! targets {})
211 | nil)))
212 |
213 | (defn -main [& args]
214 | (let [[options args banner]
215 | (cli/cli
216 | args
217 | ["-v" "--[no-]verbose" "Verbose output"]
218 | ["--web" "Run webserver with root at given location"]
219 | ["-h" "--help" "Show help" :default false :flag true]
220 | )]
221 | (when (or (:help options)
222 | (not= 2 (count args)))
223 | (println banner)
224 | (System/exit 0))
225 | (println options)
226 | (println args)
227 | (let [target (second args)
228 | [target-host target-port] (string/split target #":")]
229 | (start-websockify :listen-port (Integer/parseInt (first args))
230 | :target-host target-host
231 | :target-port (Integer/parseInt target-port)
232 | :web (:web options))))
233 | nil)
--------------------------------------------------------------------------------
/tests/load.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebSockets Load Test
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Host:
21 | Port:
22 | Encrypt:
23 | Send Delay (ms):
24 |
26 |
27 |