27 Commits

Author SHA1 Message Date
Joel Martin a4ae3230f8 websockify version 0.2.0 2012-09-17 12:52:54 -05:00
Joel Martin 17175afd73 websock.js: make protocols parameter of open().
If no protocols are selected then defaults to ['binary', 'base64'] (or
just 'base64' if there is not full binary type support.

Checks to make sure binary types are fully supported and throws an
exception if they are requested but not supported.
2012-08-16 13:28:28 -05:00
Joel Martin 53b074e8e7 websock.js: simplify rQshiftStr with apply(). 2012-08-16 13:27:50 -05:00
Joel Martin 068065e465 Simpler (but working) binary support.
Instead of trying to handle the receive queue as a typed array, just
replace the base64 encode/decode with conversion from/to typed arrays
and handle the receive and send queue as before (plain Javascript
arrays).

There is a lot of opportunity here for optimization of course, but for
now it's more important that it work properly.
2012-08-16 12:21:21 -05:00
Joel Martin 376872d993 Use WebSocket binary data (no base64 enc/dec) if available.
If typed arrays (arraybuffers) are available and the WebSocket
implementation supports them, then send and receive direct binary
frames and skip base64 encode/decode. Otherwise we just fallback to
the current method of sending base64 encoded strings (with a couple of
extra checks for mode in the send/receive path).

The check for binaryType support in WebSocket is a collosal hack right
now due to the fact that the 'binaryType' property doesn't exist on
the WebSocket prototype. So we have to create a connection to
a localhost port in order to test.

A potentionally big performance boost could probably be achieved by
re-using a larger typed array for storing the data instead of creating
a typed array every time we receive a message.
2012-08-14 15:19:38 -05:00
Joel Martin f55362ff07 websock.js: jslint comments, debug comments, copyright date 2012-08-14 15:18:14 -05:00
Joel Martin 36bdb09630 More verbosity about who closed the connection. 2012-08-14 15:14:00 -05:00
Joel Martin d575e571fd Fix split out handshake parsing for Hixie.
Need the scheme to be accessible when constructing the response for
Hixie clients.
2012-08-14 15:12:48 -05:00
Joel Martin 00e9d3bf29 websockify.rb: correct comment, no SSL support yet. 2012-07-24 09:31:26 -05:00
Joel Martin 26e8095244 websockify: rename config opt, fix config dir reading 2012-07-13 13:17:56 -05:00
Joel Martin e4b9d510f1 websocket.py: split out handshake parsing.
This makes it easier to use the websocket code in a WSGI script in the
future.
2012-07-13 13:09:32 -05:00
Joel Martin 65e96176cb websockify: change cfg file syntax and clean up parsing.
Config file syntax is now like this:

----------------------
    # Comments and blank lines are allow
    token1: host1:port1

    token2: host2:port2
----------------------
2012-07-12 19:34:27 -05:00
Joel Martin 52beba8695 Move target-list processing to websockify.
websocket.py has no concept of target/proxy so any target processing
should happen in websockify itself.

Also:

- remove URL parsing imports from websocket.py since they are not
  needed with SimpleHTTPRequestHandler doing the parsing.

- read the absolute path of the target_list file on startup since the
  --web option will change directories if set.
2012-07-12 19:10:12 -05:00
Hector Sanjuan e17e1158d8 Enable multiple targets in websockify by passing a target-list configuration file.
The --target-list option is used to pass a configuration file on websockify start.

This file contains entries in the form host:port:token, one per line.

When a new connection is open (from noVNC for example), the url should look like:

ws://websockify_host:websockify_port/?token=ABCD

The token is then extracted and checked against the entries in the configuration file.

When the token matches, the connection is proxied to the host:port indicated in the file.

If the configuration file is a folder, then all the entries from files in it are read.
2012-07-11 16:00:13 +02:00
Joel Martin 2a8df6327e Merge pull request #49 from libricoleur/master
Better support of IPv6
2012-06-26 07:40:38 -07:00
Alexandre Sicard 67d4c17516 Allow preference of IPv6 for source_addr
Add the option "-6, --prefer-ipv6". When set, the 'prefer_ipv6' flag in
websocket.py is used so that 'source_addr' is resolved to IPv6 if
available. If 'source_addr' is not set, binds to [::].
2012-06-26 13:57:50 +02:00
Alexandre Sicard 1f5b492e10 Support IPv6 brackets notation
Parse square brackets-enclosed IPv6 for 'source_addr' and 'target_addr'
parameters.
2012-06-26 13:55:19 +02:00
Joel Martin cb925eb486 doc/websockify.1: add man page for websockify command.
Mostly it is based on the project README.md but with some tweaks and
extra content removed.

Thanks for Adam Young <ayoung@redhat.com> for the original version of
this.

Also a decent man format reference is here:
http://www.fnal.gov/docs/products/ups/ReferenceManual/html/manpages.html
2012-06-22 18:40:11 -05:00
Joel Martin e295098330 Fix wrap mode when used with --web option.
- The --web option changes directory so the wrap mode needs to get an
  absolute path to the rebinder.
- Also, use long instead of int in rebind.c so avoid compile warnings.
2012-05-31 09:20:01 -05:00
Joel Martin cddc1613ff Fixup bugs from merge (pull #46).
- Rename unix socket option to '--unix-target' to be consistent with
  '--ssl-target' which is an analogous switch.
- Fix normal socket target mode which was broken after merge.
- Normalize/fix output for SSL, unix socket and wrap command modes.
2012-05-31 09:17:51 -05:00
Joel Martin 233b622e47 Merge pull request #46 from cloud9ers/master
Adding support for unix sockets as target socket.
2012-05-30 07:22:29 -07:00
Karim Allah Ahmed c7ba8c7826 Merge with kanaka's mainstream master branch 2012-05-28 13:09:07 +02:00
Joel Martin 89d2c92474 Move SSL target support into websocket.py.
This is cleanup related to:
https://github.com/kanaka/websockify/pull/45
2012-05-23 09:20:08 -05:00
Joel Martin d24f474362 Merge pull request #45 from d4nshields/master
feature from issue #8: Support connecting to SSL/TLS target socket
2012-05-23 06:47:14 -07:00
Karim Allah Ahmed f3054df53a Adding TCP_NODELAY to the source sockets 2012-05-22 16:49:00 +02:00
Karim Allah Ahmed c8018f29c9 Adding support for proxying from a unix socket 2012-05-22 16:09:07 +02:00
Daniel Shields 763d2d7c1c Feature: target_host is wrapped in SSL using --ssl-target option 2012-05-20 13:58:45 -04:00
9 changed files with 457 additions and 155 deletions
+4
View File
@@ -4,3 +4,7 @@
other/.lein-deps-sum
other/classes
other/lib
.project
.pydevproject
target.cfg
target.cfg.d
+12 -2
View File
@@ -1,8 +1,18 @@
Changes
=======
0.1.0
-----------------
0.2.0 - Sep 17, 2012
--------------------
* Binary data support in websock.js
* Target config file/dir and multiple targets with token selector
* IPv6 fixes
* SSL target support
* Proxy to/from unix socket
0.1.0 - May 11, 2012
--------------------
* Initial versioned release.
+3 -1
View File
@@ -1,7 +1,9 @@
- Update setup.py and CHANGES.txt and commit
- Create version tag and tarball from tag
WVER=0.1.0
git tag ${WVER}
git tag v${WVER}
git push origin master
git push origin v${WVER}
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
gzip websockify-${WVER}.tar
- Upload tarball to repo
+110
View File
@@ -0,0 +1,110 @@
.TH websockify 1 "June 7, 2012" "version 0.3" "USER COMMANDS"
.SH NAME
websockify - WebSockets to TCP socket bridge
.SH SYNOPSIS
websockify [options] [source_addr:]source_port target_addr:target_port
websockify [options] [source_addr:]source_port \-\- WRAP_COMMAND_LINE
.SH OPTIONS
-h, --help show this help message and exit
-v, --verbose verbose messages and per frame traffic
--record=FILE record sessions to FILE.[session_number]
-D, --daemon become a daemon (background process)
--run-once handle a single WebSocket connection and exit
--timeout=TIMEOUT after TIMEOUT seconds exit when not connected
--cert=CERT SSL certificate file
--key=KEY SSL key file (if separate from cert)
--ssl-only disallow non-encrypted connections
--web=DIR run webserver on same port. Serve files from DIR.
--wrap-mode=MODE action to take when the wrapped program exits or
daemonizes: exit (default), ignore, respawn
.SH DESCRIPTION
At the most basic level, websockify just translates WebSockets traffic to normal TCP socket traffic. Websockify accepts the WebSockets handshake, parses it, and then begins forwarding traffic between the client and the target in both directions.
websockify was formerly named wsproxy and was part of the noVNC project.
.SH NOTES
.SS WebSockets binary data
Websockify supports all versions of the WebSockets protocol (Hixie and HyBI). The older Hixie versions of the protocol only support UTF-8 text payloads. In order to transport binary data over UTF-8 an encoding must used to encapsulate the data within UTF-8. Websockify uses base64 to encode all traffic to and from the client. This does not affect the data between websockify and the server.
.SS Encrypted WebSocket connections (wss://)
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to generate a certificate for websockify to load. By default websockify loads a certificate file name self.pem but the --cert=CERT option can override the file name. You can generate a self-signed certificate using openssl. When asked for the common name, use the hostname of the server where the proxy will be running:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
.SS Websock Javascript library
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.
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.
The Websock API is documented on the websock.js API wiki page:
https://github.com/kanaka/websockify/wiki/websock.js
See the "Wrap a Program" section below for an example of using Websock and websockify as a browser telnet client (wstelnet.html).
.SS Additional websockify features
These are not necessary for the basic operation.
.IP *
Daemonizing: When the -D option is specified, websockify runs in the background as a daemon process.
.IP *
SSL (the wss:// WebSockets URI): This is detected automatically by websockify by sniffing the first byte sent from the client and then wrapping the socket if the data starts with '\\x16' or '\\x80' (indicating SSL).
.IP *
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.
.IP *
Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option.
.IP *
Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy and Flash security policy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve.
.IP *
Wrap a program: see the "Wrap a Program" section below.
.SS Wrap a Program
In addition to proxying from a source address to a target address (which may be on a different system), websockify has the ability to launch a program on the local system and proxy WebSockets traffic to a normal TCP port owned/bound by the program.
The is accomplished with a small LD_PRELOAD library (rebind.so) which intercepts bind() system calls by the program. The specified port is moved to a new localhost/loopback free high port. websockify then proxies WebSockets traffic directed to the original port to the new (moved) port of the program.
The program wrap mode is invoked by replacing the target with -- followed by the program command line to wrap.
`./websockify 2023 -- PROGRAM ARGS`
The --wrap-mode option can be used to indicate what action to take when the wrapped program exits or daemonizes.
Here is an example of using websockify to wrap the vncserver command (which backgrounds itself) for use with noVNC:
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd exits after the connection closes so the wrap mode is set to respawn the command:
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
The wstelnet.html page demonstrates a simple WebSockets based telnet client.
.SH AUTHOR
Joel Martin (github@martintribe.org)
.SH SEE ALSO
https://github.com/kanaka/websockify/
https://github.com/kanaka/websockify/wiki/
+84 -19
View File
@@ -1,6 +1,6 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2011 Joel Martin
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but Websock
@@ -14,9 +14,17 @@
* read binary data off of the receive queue.
*/
/*jslint browser: true, bitwise: false, plusplus: false */
/*global Util, Base64 */
// Load Flash WebSocket emulator if needed
// To force WebSocket emulator even when native WebSocket available
//window.WEB_SOCKET_FORCE_FLASH = true;
// To enable WebSocket emulator debug:
//window.WEB_SOCKET_DEBUG=1;
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
@@ -25,9 +33,6 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
} else {
/* no builtin WebSocket so load web_socket.js */
// To enable debug:
// window.WEB_SOCKET_DEBUG=1;
Websock_native = false;
(function () {
function get_INCLUDE_URI() {
@@ -38,11 +43,11 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = "";
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
extra += start + "web-socket-js/swfobject.js" + end;
extra += start + "web-socket-js/web_socket.js" + end;
@@ -56,6 +61,7 @@ function Websock() {
var api = {}, // Public API
websocket = null, // WebSocket object
mode = 'base64',
rQ = [], // Receive queue
rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting
@@ -87,7 +93,7 @@ function get_rQi() {
}
function set_rQi(val) {
rQi = val;
};
}
function rQlen() {
return rQ.length - rQi;
@@ -122,9 +128,7 @@ function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
return arr.map(function (num) {
return String.fromCharCode(num); } ).join('');
return String.fromCharCode.apply(null, arr);
}
function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
@@ -165,13 +169,22 @@ function rQwait(msg, num, goback) {
function encode_message() {
/* base64 encode */
return Base64.encode(sQ);
if (mode === 'binary') {
return (new Uint8Array(sQ)).buffer;
} else {
return Base64.encode(sQ);
}
}
function decode_message(data) {
//Util.Debug(">> decode_message: " + data);
/* base64 decode */
rQ = rQ.concat(Base64.decode(data, 0));
if (mode === 'binary') {
// push arraybuffer values onto the end
rQ.push.apply(rQ, (new Uint8Array(data)));
} else {
// base64 decode and concat to the end
rQ = rQ.concat(Base64.decode(data, 0));
}
//Util.Debug(">> decode_message, rQ: " + rQ);
}
@@ -254,29 +267,81 @@ function on(evt, handler) {
eventHandlers[evt] = handler;
}
function init() {
function init(protocols) {
rQ = [];
rQi = 0;
sQ = [];
websocket = null;
var bt = false,
wsbt = false,
try_binary = false;
// Check for full typed array support
if (('Uint8Array' in window) &&
('set' in Uint8Array.prototype)) {
bt = true;
}
// Check for full binary type support in WebSockets
// TODO: this sucks, the property should exist on the prototype
// but it does not.
try {
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
Util.Info("Detected binaryType support in WebSockets");
wsbt = true;
}
} catch (exc) {
// Just ignore failed test localhost connections
}
// Default protocols if not specified
if (typeof(protocols) === "undefined") {
if (wsbt) {
protocols = ['binary', 'base64'];
} else {
protocols = 'base64';
}
}
// If no binary support, make sure it was not requested
if (!wsbt) {
if (protocols === 'binary') {
throw("WebSocket binary sub-protocol requested but not supported");
}
if (typeof(protocols) === "object") {
for (var i = 0; i < protocols.length; i++) {
if (protocols[i] === 'binary') {
throw("WebSocket binary sub-protocol requested but not supported");
}
}
}
}
return protocols;
}
function open(uri) {
init();
function open(uri, protocols) {
protocols = init(protocols);
if (test_mode) {
websocket = {};
} else {
websocket = new WebSocket(uri, 'base64');
// TODO: future native binary support
//websocket = new WebSocket(uri, ['binary', 'base64']);
websocket = new WebSocket(uri, protocols);
}
websocket.onmessage = recv_message;
websocket.onopen = function() {
Util.Debug(">> WebSock.onopen");
if (websocket.protocol) {
mode = websocket.protocol;
Util.Info("Server chose sub-protocol: " + websocket.protocol);
} else {
mode = 'base64';
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
}
if (mode === 'binary') {
websocket.binaryType = 'arraybuffer';
}
eventHandlers.open();
Util.Debug("<< WebSock.onopen");
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
# A WebSocket to TCP socket proxy with support for "wss://" encryption.
# A WebSocket to TCP socket proxy
# Copyright 2011 Joel Martin
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
+2 -2
View File
@@ -72,7 +72,7 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
if (! do_move) {
/* Just pass everything right through to the real bind */
ret = (int) func(sockfd, addr, addrlen);
ret = (long) func(sockfd, addr, addrlen);
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
return ret;
}
@@ -87,7 +87,7 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
/* Bind to other port on the loopback instead */
addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr_tmp.sin_port = htons(newport);
ret = (int) func(sockfd, &addr_tmp, addrlen_tmp);
ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
return ret;
+124 -108
View File
@@ -18,7 +18,6 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
import os, sys, time, errno, signal, socket, traceback, select
import array, struct
from cgi import parse_qsl
from base64 import b64encode, b64decode
# Imports that vary by python version
@@ -36,8 +35,6 @@ try: from io import StringIO
except: from cStringIO import StringIO
try: from http.server import SimpleHTTPRequestHandler
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
try: from urllib.parse import urlsplit
except: from urlparse import urlsplit
# python 2.6 differences
try: from hashlib import md5, sha1
@@ -75,6 +72,7 @@ class WebSocketServer(object):
buffer_size = 65536
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r
Connection: Upgrade\r
@@ -109,6 +107,7 @@ Sec-WebSocket-Accept: %s\r
self.verbose = verbose
self.listen_host = listen_host
self.listen_port = listen_port
self.prefer_ipv6 = source_is_ipv6
self.ssl_only = ssl_only
self.daemon = daemon
self.run_once = run_once
@@ -163,7 +162,7 @@ Sec-WebSocket-Accept: %s\r
#
@staticmethod
def socket(host, port=None, connect=False, prefer_ipv6=False):
def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False):
""" Resolve a host (and optional port) to an IPv4 or IPv6
address. Create a socket. Bind to it if listen is set,
otherwise connect to it. Return the socket.
@@ -171,24 +170,36 @@ Sec-WebSocket-Accept: %s\r
flags = 0
if host == '':
host = None
if connect and not port:
if connect and not (port or unix_socket):
raise Exception("Connect mode requires a port")
if use_ssl and not ssl:
raise Exception("SSL socket requested but Python SSL module not loaded.");
if not connect and use_ssl:
raise Exception("SSL only supported in connect mode (for now)")
if not connect:
flags = flags | socket.AI_PASSIVE
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
socket.IPPROTO_TCP, flags)
if not addrs:
raise Exception("Could resolve host '%s'" % host)
addrs.sort(key=lambda x: x[0])
if prefer_ipv6:
addrs.reverse()
sock = socket.socket(addrs[0][0], addrs[0][1])
if connect:
sock.connect(addrs[0][4])
if not unix_socket:
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
socket.IPPROTO_TCP, flags)
if not addrs:
raise Exception("Could not resolve host '%s'" % host)
addrs.sort(key=lambda x: x[0])
if prefer_ipv6:
addrs.reverse()
sock = socket.socket(addrs[0][0], addrs[0][1])
if connect:
sock.connect(addrs[0][4])
if use_ssl:
sock = ssl.wrap_socket(sock)
else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4])
sock.listen(100)
else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4])
sock.listen(100)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(unix_socket)
return sock
@staticmethod
@@ -552,93 +563,9 @@ Sec-WebSocket-Accept: %s\r
# No orderly close for 75
def do_handshake(self, sock, address):
"""
do_handshake does the following:
- Peek at the first few bytes from the socket.
- If the connection is Flash policy request then answer it,
close the socket and return.
- If the connection is an HTTPS/SSL/TLS connection then SSL
wrap the socket.
- Read from the (possibly wrapped) socket.
- If we have received a HTTP GET request and the webserver
functionality is enabled, answer it, close the socket and
return.
- Assume we have a WebSockets connection, parse the client
handshake data.
- Send a WebSockets handshake server response.
- Return the socket for this WebSocket client.
"""
stype = ""
ready = select.select([sock], [], [], 3)[0]
if not ready:
raise self.EClose("ignoring socket not ready")
# Peek, but do not read the data so that we have a opportunity
# to SSL wrap the socket first
handshake = sock.recv(1024, socket.MSG_PEEK)
#self.msg("Handshake [%s]" % handshake)
if handshake == "":
raise self.EClose("ignoring empty handshake")
elif handshake.startswith(s2b("<policy-file-request/>")):
# Answer Flash policy request
handshake = sock.recv(1024)
sock.send(s2b(self.policy_response))
raise self.EClose("Sending flash policy response")
elif handshake[0] in ("\x16", "\x80", 22, 128):
# SSL wrap the connection
if not ssl:
raise self.EClose("SSL connection but no 'ssl' module")
if not os.path.exists(self.cert):
raise self.EClose("SSL connection but '%s' not found"
% self.cert)
retsock = None
try:
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=self.cert,
keyfile=self.key)
except ssl.SSLError:
_, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF:
if len(x.args) > 1:
raise self.EClose(x.args[1])
else:
raise self.EClose("Got SSL_ERROR_EOF")
else:
raise
scheme = "wss"
stype = "SSL/TLS (wss://)"
elif self.ssl_only:
raise self.EClose("non-SSL connection received but disallowed")
else:
retsock = sock
scheme = "ws"
stype = "Plain non-SSL (ws://)"
wsh = WSRequestHandler(retsock, address, not self.web)
if wsh.last_code == 101:
# Continue on to handle WebSocket upgrade
pass
elif wsh.last_code == 405:
raise self.EClose("Normal web request received but disallowed")
elif wsh.last_code < 200 or wsh.last_code >= 300:
raise self.EClose(wsh.last_message)
elif self.verbose:
raise self.EClose(wsh.last_message)
else:
raise self.EClose("")
h = self.headers = wsh.headers
path = self.path = wsh.path
def do_websocket_handshake(self, headers, path):
h = self.headers = headers
self.path = path
prot = 'WebSocket-Protocol'
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
@@ -691,7 +618,7 @@ Sec-WebSocket-Accept: %s\r
self.base64 = True
response = self.server_handshake_hixie % (pre,
h['Origin'], pre, scheme, h['Host'], path)
h['Origin'], pre, self.scheme, h['Host'], path)
if 'base64' in protocols:
response += "%sWebSocket-Protocol: base64\r\n" % pre
@@ -699,6 +626,96 @@ Sec-WebSocket-Accept: %s\r
self.msg("Warning: client does not report 'base64' protocol support")
response += "\r\n" + trailer
return response
def do_handshake(self, sock, address):
"""
do_handshake does the following:
- Peek at the first few bytes from the socket.
- If the connection is Flash policy request then answer it,
close the socket and return.
- If the connection is an HTTPS/SSL/TLS connection then SSL
wrap the socket.
- Read from the (possibly wrapped) socket.
- If we have received a HTTP GET request and the webserver
functionality is enabled, answer it, close the socket and
return.
- Assume we have a WebSockets connection, parse the client
handshake data.
- Send a WebSockets handshake server response.
- Return the socket for this WebSocket client.
"""
stype = ""
ready = select.select([sock], [], [], 3)[0]
if not ready:
raise self.EClose("ignoring socket not ready")
# Peek, but do not read the data so that we have a opportunity
# to SSL wrap the socket first
handshake = sock.recv(1024, socket.MSG_PEEK)
#self.msg("Handshake [%s]" % handshake)
if handshake == "":
raise self.EClose("ignoring empty handshake")
elif handshake.startswith(s2b("<policy-file-request/>")):
# Answer Flash policy request
handshake = sock.recv(1024)
sock.send(s2b(self.policy_response))
raise self.EClose("Sending flash policy response")
elif handshake[0] in ("\x16", "\x80", 22, 128):
# SSL wrap the connection
if not ssl:
raise self.EClose("SSL connection but no 'ssl' module")
if not os.path.exists(self.cert):
raise self.EClose("SSL connection but '%s' not found"
% self.cert)
retsock = None
try:
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=self.cert,
keyfile=self.key)
except ssl.SSLError:
_, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF:
if len(x.args) > 1:
raise self.EClose(x.args[1])
else:
raise self.EClose("Got SSL_ERROR_EOF")
else:
raise
self.scheme = "wss"
stype = "SSL/TLS (wss://)"
elif self.ssl_only:
raise self.EClose("non-SSL connection received but disallowed")
else:
retsock = sock
self.scheme = "ws"
stype = "Plain non-SSL (ws://)"
wsh = WSRequestHandler(retsock, address, not self.web)
if wsh.last_code == 101:
# Continue on to handle WebSocket upgrade
pass
elif wsh.last_code == 405:
raise self.EClose("Normal web request received but disallowed")
elif wsh.last_code < 200 or wsh.last_code >= 300:
raise self.EClose(wsh.last_message)
elif self.verbose:
raise self.EClose(wsh.last_message)
else:
raise self.EClose("")
response = self.do_websocket_handshake(wsh.headers, wsh.path)
self.msg("%s: %s WebSocket connection" % (address[0], stype))
self.msg("%s: Version %s, base64: '%s'" % (address[0],
self.version, self.base64))
@@ -801,7 +818,7 @@ Sec-WebSocket-Accept: %s\r
is a WebSockets client then call new_client() method (which must
be overridden) for each new client connection.
"""
lsock = self.socket(self.listen_host, self.listen_port)
lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6)
if self.daemon:
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
@@ -927,4 +944,3 @@ class WSRequestHandler(SimpleHTTPRequestHandler):
def log_message(self, f, *args):
# Save instead of printing
self.last_message = f % args
+113 -18
View File
@@ -13,9 +13,11 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
import socket, optparse, time, os, sys, subprocess
from select import select
from websocket import WebSocketServer
import websocket
try: from urllib.parse import parse_qs, urlparse
except: from urlparse import parse_qs, urlparse
class WebSocketProxy(WebSocketServer):
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
socket server target. All traffic to/from the client is base64
@@ -43,6 +45,9 @@ Traffic Legend:
self.target_port = kwargs.pop('target_port')
self.wrap_cmd = kwargs.pop('wrap_cmd')
self.wrap_mode = kwargs.pop('wrap_mode')
self.unix_target = kwargs.pop('unix_target')
self.ssl_target = kwargs.pop('ssl_target')
self.target_cfg = kwargs.pop('target_cfg')
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
@@ -58,6 +63,7 @@ Traffic Legend:
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.rebinder = os.path.abspath(self.rebinder)
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
@@ -71,7 +77,10 @@ Traffic Legend:
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
WebSocketServer.__init__(self, *args, **kwargs)
if self.target_cfg:
self.target_cfg = os.path.abspath(self.target_cfg)
websocket.WebSocketServer.__init__(self, *args, **kwargs)
def run_wrap_cmd(self):
print("Starting '%s'" % " ".join(self.wrap_cmd))
@@ -88,14 +97,26 @@ Traffic Legend:
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
print(" - proxying from %s:%s to '%s' (port %s)\n" % (
self.listen_host, self.listen_port,
" ".join(self.wrap_cmd), self.target_port))
self.run_wrap_cmd()
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
dst_string = self.unix_target
else:
print(" - proxying from %s:%s to %s:%s\n" % (
self.listen_host, self.listen_port,
self.target_host, self.target_port))
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.target_cfg:
msg = " - proxying from %s:%s to targets in %s" % (
self.listen_host, self.listen_port, self.target_cfg)
else:
msg = " - proxying from %s:%s to %s" % (
self.listen_host, self.listen_port, dst_string)
if self.ssl_target:
msg += " (using SSL)"
print(msg + "\n")
if self.wrap_cmd:
self.run_wrap_cmd()
def poll(self):
# If we are wrapping a command, check it's status
@@ -137,12 +158,26 @@ Traffic Legend:
"""
Called after a new WebSocket connection has been established.
"""
# Checks if we receive a token, and look
# for a valid target for it then
if self.target_cfg:
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
# Connect to the target
self.msg("connecting to: %s:%s" % (
self.target_host, self.target_port))
if self.wrap_cmd:
msg = "connecting to command: %s" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
msg = "connecting to unix socket: %s" % self.unix_target
else:
msg = "connecting to: %s:%s" % (
self.target_host, self.target_port)
if self.ssl_target:
msg += " (using SSL)"
self.msg(msg)
tsock = self.socket(self.target_host, self.target_port,
connect=True)
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
if self.verbose and not self.daemon:
print(self.traffic_legend)
@@ -154,10 +189,49 @@ Traffic Legend:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
self.vmsg("%s:%s: Target closed" %(
self.vmsg("%s:%s: Closed target" %(
self.target_host, self.target_port))
raise
def get_target(self, target_cfg, path):
"""
Parses the path, extracts a token, and looks for a valid
target for that token in the configuration file(s). Sets
target_host and target_port if successful
"""
# The files in targets contain the lines
# in the form of token: host:port
# Extract the token parameter from url
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
if not len(args['token']):
raise self.EClose("Token not present")
token = args['token'][0].rstrip('\n')
# target_cfg can be a single config file or directory of
# config files
if os.path.isdir(target_cfg):
cfg_files = [os.path.join(target_cfg, f)
for f in os.listdir(target_cfg)]
else:
cfg_files = [target_cfg]
targets = {}
for f in cfg_files:
for line in [l.strip() for l in file(f).readlines()]:
if line and not line.startswith('#'):
ttoken, target = line.split(': ')
targets[ttoken] = target.strip()
self.vmsg("Target config: %s" % repr(targets))
if targets.has_key(token):
return targets[token].split(':')
else:
raise self.EClose("Token '%s' not found" % token)
def do_proxy(self, target):
"""
Proxy client WebSocket to normal target socket.
@@ -191,6 +265,8 @@ Traffic Legend:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
if len(buf) == 0:
self.vmsg("%s:%s: Target closed connection" %(
self.target_host, self.target_port))
raise self.CClose(1000, "Target closed")
cqueue.append(buf)
@@ -211,11 +287,13 @@ Traffic Legend:
if closed:
# TODO: What about blocking on client socket?
self.vmsg("%s:%s: Client closed connection" %(
self.target_host, self.target_port))
raise self.CClose(closed['code'], closed['reason'])
def websockify_init():
usage = "\n %prog [options]"
usage += " [source_addr:]source_port target_addr:target_port"
usage += " [source_addr:]source_port [target_addr:target_port]"
usage += "\n %prog [options]"
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
parser = optparse.OptionParser(usage=usage)
@@ -235,17 +313,29 @@ def websockify_init():
parser.add_option("--key", default=None,
help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted connections")
help="disallow non-encrypted client connections")
parser.add_option("--ssl-target", action="store_true",
help="connect to SSL target as SSL client")
parser.add_option("--unix-target",
help="connect to unix socket target", metavar="FILE")
parser.add_option("--web", default=None, metavar="DIR",
help="run webserver on same port. Serve files from DIR.")
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
choices=["exit", "ignore", "respawn"],
help="action to take when the wrapped program exits "
"or daemonizes: exit (default), ignore, respawn")
parser.add_option("--prefer-ipv6", "-6",
action="store_true", dest="source_is_ipv6",
help="prefer IPv6 when resolving source_addr")
parser.add_option("--target-config", metavar="FILE",
dest="target_cfg",
help="Configuration file containing valid targets "
"in the form 'token: host:port' or, alternatively, a "
"directory containing configuration files of this form")
(opts, args) = parser.parse_args()
# Sanity checks
if len(args) < 2:
if len(args) < 2 and not opts.target_cfg:
parser.error("Too few arguments")
if sys.argv.count('--'):
opts.wrap_cmd = args[1:]
@@ -254,24 +344,29 @@ def websockify_init():
if len(args) > 2:
parser.error("Too many arguments")
if not websocket.ssl and opts.ssl_target:
parser.error("SSL target requested and Python SSL module not loaded.");
if opts.ssl_only and not os.path.exists(opts.cert):
parser.error("SSL only and %s not found" % opts.cert)
# Parse host:port and convert ports to numbers
if args[0].count(':') > 0:
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
opts.listen_host = opts.listen_host.strip('[]')
else:
opts.listen_host, opts.listen_port = '', args[0]
try: opts.listen_port = int(opts.listen_port)
except: parser.error("Error parsing listen port")
if opts.wrap_cmd:
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
opts.target_host = None
opts.target_port = None
else:
if args[1].count(':') > 0:
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
opts.target_host = opts.target_host.strip('[]')
else:
parser.error("Error parsing target")
try: opts.target_port = int(opts.target_port)