1 Commits

Author SHA1 Message Date
Solly Ross 3ee9ff7f4d Work around lack of SIGCHLD on Windows
This only enables the SIGCHLD handler if SIGCHLD
exists, such that platforms without SIGCHLD (such
as windows) can still run websockify natively.

See #108
2014-01-30 17:26:17 -05:00
17 changed files with 358 additions and 850 deletions
-2
View File
@@ -9,5 +9,3 @@ other/node_modules
.pydevproject
target.cfg
target.cfg.d
.tox
*.egg-info
-10
View File
@@ -1,10 +0,0 @@
language: python
python:
- 2.6
- 2.7
- 3.3
- 3.4
install: pip install -r test-requirements.txt
script: python setup.py nosetests --verbosity=3
-26
View File
@@ -1,32 +1,6 @@
Changes
=======
0.7.0
-----
* Python 3 support fixes (#140, #155, #159)
* Generic token-parsing plugins support (#162)
* Generic authentication plugins support (#172)
* Fixed frame corruption on big-endian systems (#161)
* Support heartbeats (via PING) and automatic responses to PONG (#169)
* Automatically reject unmasked client frames by default (strict mode) (#174)
* Automatically restart interrupted select calls (#175)
* Make 'run' respect environment settings (including virtualenv) (#176)
0.6.1 - May 11, 2015
--------------------
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
0.6.0 - Feb 18, 2014
--------------------
* **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
* Refactor to use standard SocketServer RequestHandler design
* Fix zombie process bug on certain systems when using multiprocessing
* Add better unit tests
* Log information via python `logging` module
0.5.1 - Jun 27, 2013
--------------------
-22
View File
@@ -8,28 +8,6 @@ to normal socket traffic. Websockify accepts the WebSockets handshake,
parses it, and then begins forwarding traffic between the client and
the target in both directions.
### News/help/contact
Notable commits, announcements and news are posted to
<a href="http://www.twitter.com/noVNC">@noVNC</a>
If you are a websockify developer/integrator/user (or want to be)
please join the <a
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
discussion group</a>
Bugs and feature requests can be submitted via [github
issues](https://github.com/kanaka/websockify/issues).
If you want to show appreciation for websockify you could donate to a great
non-profits such as: [Compassion
International](http://www.compassion.com/), [SIL](http://www.sil.org),
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
Foundation](https://www.eff.org/), [Against Malaria
Foundation](http://www.againstmalaria.com/), [Nothing But
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
### WebSockets binary data
Starting with websockify 0.5.0, only the HyBi / IETF
+7 -9
View File
@@ -262,7 +262,7 @@ function on(evt, handler) {
eventHandlers[evt] = handler;
}
function init(protocols, ws_schema) {
function init(protocols) {
rQ = [];
rQi = 0;
sQ = [];
@@ -277,13 +277,12 @@ function init(protocols, ws_schema) {
('set' in Uint8Array.prototype)) {
bt = true;
}
// Check for full binary type support in WebSocket
// Inspired by:
// https://github.com/Modernizr/Modernizr/issues/370
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
// 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 WebSocket.prototype ||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
Util.Info("Detected binaryType support in WebSockets");
wsbt = true;
}
@@ -326,8 +325,7 @@ function init(protocols, ws_schema) {
}
function open(uri, protocols) {
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
protocols = init(protocols, ws_schema);
protocols = init(protocols);
if (test_mode) {
websocket = {};
+2 -2
View File
@@ -3,12 +3,12 @@
"name": "websockify",
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
"license": "LGPL-3",
"version": "0.7.0",
"version": "0.5.1",
"repository": {
"type": "git",
"url": "git://github.com/kanaka/websockify.git"
},
"files": ["../../docs/LICENSE.LGPL-3","websockify.js"],
"files": ["../../docs/LICENSE.LGPL-3"],
"bin": {
"websockify": "./websockify.js"
},
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
import websockify
+1 -7
View File
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
version = '0.7.0'
version = '0.5.1'
name = 'websockify'
long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
@@ -11,12 +11,6 @@ setup(name=name,
long_description=long_description,
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4"
],
data_files=[('share/websockify/include',
['include/util.js',
-2
View File
@@ -1,2 +0,0 @@
mox
nose
+151 -338
View File
@@ -26,303 +26,201 @@ import stubout
import sys
import tempfile
import unittest
import socket
import signal
from websockify import websocket
try:
from SimpleHTTPServer import SimpleHTTPRequestHandler
except ImportError:
from http.server import SimpleHTTPRequestHandler
try:
from StringIO import StringIO
BytesIO = StringIO
except ImportError:
from io import StringIO
from io import BytesIO
from ssl import SSLError
from websockify import websocket as websocket
from SimpleHTTPServer import SimpleHTTPRequestHandler
class MockConnection(object):
def __init__(self, path):
self.path = path
def makefile(self, mode='r', bufsize=-1):
return open(self.path, mode, bufsize)
def raise_oserror(*args, **kwargs):
raise OSError('fake error')
class WebSocketTestCase(unittest.TestCase):
def _init_logger(self, tmpdir):
name = 'websocket-unittest'
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.propagate = True
filename = "%s.log" % (name)
handler = logging.FileHandler(filename)
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
class FakeSocket(object):
def __init__(self, data=''):
if isinstance(data, bytes):
self._data = data
else:
self._data = data.encode('latin_1')
def recv(self, amt, flags=None):
res = self._data[0:amt]
if not (flags & socket.MSG_PEEK):
self._data = self._data[amt:]
return res
def makefile(self, mode='r', buffsize=None):
if 'b' in mode:
return BytesIO(self._data)
else:
return StringIO(self._data.decode('latin_1'))
class WebSocketRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
super(WebSocketRequestHandlerTestCase, self).setUp()
"""Called automatically before each test."""
super(WebSocketTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Temporary dir for test data
self.tmpdir = tempfile.mkdtemp()
# Put log somewhere persistent
self._init_logger('./')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
lambda *args, **kwargs: None)
self.server = self._get_websockserver(daemon=True,
ssl_only=False)
self.soc = self.server.socket('localhost')
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
os.rmdir(self.tmpdir)
super(WebSocketRequestHandlerTestCase, self).tearDown()
shutil.rmtree(self.tmpdir)
super(WebSocketTestCase, self).tearDown()
def _get_server(self, handler_class=websocket.WebSocketRequestHandler,
**kwargs):
web = kwargs.pop('web', self.tmpdir)
return websocket.WebSocketServer(
handler_class, listen_host='localhost',
listen_port=80, key=self.tmpdir, web=web,
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
**kwargs)
def _get_websockserver(self, **kwargs):
return websocket.WebSocketServer(listen_host='localhost',
listen_port=80,
key=self.tmpdir,
web=self.tmpdir,
record=self.tmpdir,
**kwargs)
def test_normal_get_with_only_upgrade_returns_error(self):
server = self._get_server(web=None)
handler = websocket.WebSocketRequestHandler(
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
def _mock_os_open_oserror(self, file, flags):
raise OSError('')
def fake_send_response(self, code, message=None):
self.last_code = code
def _mock_os_close_oserror(self, fd):
raise OSError('')
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
fake_send_response)
def _mock_os_close_oserror_EBADF(self, fd):
raise OSError(errno.EBADF, '')
handler.do_GET()
self.assertEqual(handler.last_code, 405)
def _mock_socket(self, *args, **kwargs):
return self.soc
def test_list_dir_with_file_only_returns_error(self):
server = self._get_server(file_only=True)
handler = websocket.WebSocketRequestHandler(
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
def _mock_select(self, rlist, wlist, xlist, timeout=None):
return '_mock_select'
def fake_send_response(self, code, message=None):
self.last_code = code
def _mock_select_exception(self, rlist, wlist, xlist, timeout=None):
raise Exception
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
fake_send_response)
def _mock_select_keyboardinterrupt(self, rlist, wlist,
xlist, timeout=None):
raise KeyboardInterrupt
handler.path = '/'
handler.do_GET()
self.assertEqual(handler.last_code, 404)
def _mock_select_systemexit(self, rlist, wlist, xlist, timeout=None):
sys.exit()
class WebSocketServerTestCase(unittest.TestCase):
def setUp(self):
super(WebSocketServerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
os.rmdir(self.tmpdir)
super(WebSocketServerTestCase, self).tearDown()
def _get_server(self, handler_class=websocket.WebSocketRequestHandler,
**kwargs):
return websocket.WebSocketServer(
handler_class, listen_host='localhost',
listen_port=80, key=self.tmpdir, web=self.tmpdir,
record=self.tmpdir, **kwargs)
def test_daemonize_raises_error_while_closing_fds(self):
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: 0)
self.stubs.Set(signal, 'signal', lambda *args: None)
def test_daemonize_error(self):
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: None)
self.stubs.Set(os, 'setsid', lambda *args: None)
self.stubs.Set(os, 'close', raise_oserror)
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
self.stubs.Set(os, 'close', self._mock_os_close_oserror)
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
def raise_oserror_ebadf(fd):
raise OSError(errno.EBADF, 'fake error')
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: 0)
def test_daemonize_EBADF_error(self):
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: None)
self.stubs.Set(os, 'setsid', lambda *args: None)
self.stubs.Set(signal, 'signal', lambda *args: None)
self.stubs.Set(os, 'close', raise_oserror_ebadf)
self.stubs.Set(os, 'open', raise_oserror)
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
self.stubs.Set(os, 'close', self._mock_os_close_oserror_EBADF)
self.stubs.Set(os, 'open', self._mock_os_open_oserror)
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
def test_handshake_fails_on_not_ready(self):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
def test_decode_hybi(self):
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
self.assertRaises(Exception, soc.decode_hybi, 'a' * 128,
base64=True)
def fake_select(rlist, wlist, xlist, timeout=None):
return ([], [], [])
def test_do_websocket_handshake(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
soc.scheme = 'scheme'
headers = {'Sec-WebSocket-Protocol': 'binary',
'Sec-WebSocket-Version': '7',
'Sec-WebSocket-Key': 'foo'}
soc.do_websocket_handshake(headers, '127.0.0.1')
self.stubs.Set(select, 'select', fake_select)
self.assertRaises(
websocket.WebSocketServer.EClose, server.do_handshake,
FakeSocket(), '127.0.0.1')
def test_do_handshake(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
self.stubs.Set(select, 'select', self._mock_select)
self.stubs.Set(socket._socketobject, 'recv', lambda *args: 'mock_recv')
self.assertRaises(Exception, soc.do_handshake, self.soc, '127.0.0.1')
def test_empty_handshake_fails(self):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
def test_do_handshake_ssl_error(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
sock = FakeSocket('')
def _mock_wrap_socket(*args, **kwargs):
from ssl import SSLError
raise SSLError('unit test exception')
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', self._mock_select)
self.stubs.Set(socket._socketobject, 'recv', lambda *args: '\x16')
self.stubs.Set(ssl, 'wrap_socket', _mock_wrap_socket)
self.assertRaises(SSLError, soc.do_handshake, self.soc, '127.0.0.1')
self.stubs.Set(select, 'select', fake_select)
self.assertRaises(
websocket.WebSocketServer.EClose, server.do_handshake,
sock, '127.0.0.1')
def test_fallback_SIGCHILD(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
soc.fallback_SIGCHLD(None, None)
def test_handshake_policy_request(self):
# TODO(directxman12): implement
pass
def test_handshake_ssl_only_without_ssl_raises_error(self):
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
sock = FakeSocket('some initial data')
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
self.assertRaises(
websocket.WebSocketServer.EClose, server.do_handshake,
sock, '127.0.0.1')
def test_do_handshake_no_ssl(self):
class FakeHandler(object):
CALLED = False
def __init__(self, *args, **kwargs):
type(self).CALLED = True
FakeHandler.CALLED = False
server = self._get_server(
handler_class=FakeHandler, daemon=True,
ssl_only=0, idle_timeout=1)
sock = FakeSocket('some initial data')
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
self.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock)
self.assertTrue(FakeHandler.CALLED, True)
def test_do_handshake_ssl(self):
# TODO(directxman12): implement this
pass
def test_do_handshake_ssl_without_ssl_raises_error(self):
# TODO(directxman12): implement this
pass
def test_do_handshake_ssl_without_cert_raises_error(self):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1,
cert='afdsfasdafdsafdsafdsafdas')
sock = FakeSocket("\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select)
self.assertRaises(
websocket.WebSocketServer.EClose, server.do_handshake,
sock, '127.0.0.1')
def test_do_handshake_ssl_error_eof_raises_close_error(self):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
sock = FakeSocket("\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
def fake_wrap_socket(*args, **kwargs):
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
self.stubs.Set(select, 'select', fake_select)
self.stubs.Set(ssl, 'wrap_socket', fake_wrap_socket)
self.assertRaises(
websocket.WebSocketServer.EClose, server.do_handshake,
sock, '127.0.0.1')
def test_fallback_sigchld_handler(self):
# TODO(directxman12): implement this
pass
def test_start_server_error(self):
server = self._get_server(daemon=False, ssl_only=1, idle_timeout=1)
sock = server.socket('localhost')
def fake_select(rlist, wlist, xlist, timeout=None):
raise Exception("fake error")
self.stubs.Set(websocket.WebSocketServer, 'socket',
lambda *args, **kwargs: sock)
def test_start_server_Exception(self):
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
server.start_server()
self.stubs.Set(select, 'select', self._mock_select_exception)
self.assertEqual(None, soc.start_server())
def test_start_server_keyboardinterrupt(self):
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
sock = server.socket('localhost')
def fake_select(rlist, wlist, xlist, timeout=None):
raise KeyboardInterrupt
self.stubs.Set(websocket.WebSocketServer, 'socket',
lambda *args, **kwargs: sock)
def test_start_server_KeyboardInterrupt(self):
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
server.start_server()
self.stubs.Set(select, 'select', self._mock_select_keyboardinterrupt)
self.assertEqual(None, soc.start_server())
def test_start_server_systemexit(self):
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
sock = server.socket('localhost')
def fake_select(rlist, wlist, xlist, timeout=None):
sys.exit()
self.stubs.Set(websocket.WebSocketServer, 'socket',
lambda *args, **kwargs: sock)
websocket.ssl = None
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
server.start_server()
self.stubs.Set(select, 'select', self._mock_select_systemexit)
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1,
verbose=True)
self.assertEqual(None, soc.start_server())
def test_socket_set_keepalive_options(self):
def test_WSRequestHandle_do_GET_nofile(self):
request = 'GET /tmp.txt HTTP/0.9'
with tempfile.NamedTemporaryFile() as test_file:
test_file.write(request)
test_file.flush()
test_file.seek(0)
con = MockConnection(test_file.name)
soc = websocket.WSRequestHandler(con, "127.0.0.1", file_only=True)
soc.path = ''
soc.headers = {'upgrade': ''}
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
lambda *args: None)
soc.do_GET()
self.assertEqual(404, soc.last_code)
def test_WSRequestHandle_do_GET_hidden_resource(self):
request = 'GET /tmp.txt HTTP/0.9'
with tempfile.NamedTemporaryFile() as test_file:
test_file.write(request)
test_file.flush()
test_file.seek(0)
con = MockConnection(test_file.name)
soc = websocket.WSRequestHandler(con, '127.0.0.1', no_parent=True)
soc.path = test_file.name + '?'
soc.headers = {'upgrade': ''}
soc.webroot = 'no match startswith'
self.stubs.Set(SimpleHTTPRequestHandler,
'send_response',
lambda *args: None)
soc.do_GET()
self.assertEqual(403, soc.last_code)
def testsocket_set_keepalive_options(self):
keepcnt = 12
keepidle = 34
keepintvl = 56
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
sock = server.socket('localhost',
tcp_keepcnt=keepcnt,
tcp_keepidle=keepidle,
tcp_keepintvl=keepintvl)
sock = self.server.socket('localhost',
tcp_keepcnt=keepcnt,
tcp_keepidle=keepidle,
tcp_keepintvl=keepintvl)
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPCNT), keepcnt)
@@ -331,11 +229,11 @@ class WebSocketServerTestCase(unittest.TestCase):
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPINTVL), keepintvl)
sock = server.socket('localhost',
tcp_keepalive=False,
tcp_keepcnt=keepcnt,
tcp_keepidle=keepidle,
tcp_keepintvl=keepintvl)
sock = self.server.socket('localhost',
tcp_keepalive=False,
tcp_keepcnt=keepcnt,
tcp_keepidle=keepidle,
tcp_keepintvl=keepintvl)
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPCNT), keepcnt)
@@ -343,88 +241,3 @@ class WebSocketServerTestCase(unittest.TestCase):
socket.TCP_KEEPIDLE), keepidle)
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPINTVL), keepintvl)
class HyBiEncodeDecodeTestCase(unittest.TestCase):
def test_decode_hybi_text(self):
buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58'
res = websocket.WebSocketRequestHandler.decode_hybi(buf)
self.assertEqual(res['fin'], 1)
self.assertEqual(res['opcode'], 0x1)
self.assertEqual(res['masked'], True)
self.assertEqual(res['length'], 5)
self.assertEqual(res['payload'], b'Hello')
self.assertEqual(res['left'], 0)
def test_decode_hybi_binary(self):
buf = b'\x82\x04\x01\x02\x03\x04'
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
self.assertEqual(res['fin'], 1)
self.assertEqual(res['opcode'], 0x2)
self.assertEqual(res['length'], 4)
self.assertEqual(res['payload'], b'\x01\x02\x03\x04')
self.assertEqual(res['left'], 0)
def test_decode_hybi_extended_16bit_binary(self):
data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260
buf = b'\x82\x7e\x01\x04' + data
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
self.assertEqual(res['fin'], 1)
self.assertEqual(res['opcode'], 0x2)
self.assertEqual(res['length'], 260)
self.assertEqual(res['payload'], data)
self.assertEqual(res['left'], 0)
def test_decode_hybi_extended_64bit_binary(self):
data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260
buf = b'\x82\x7f\x00\x00\x00\x00\x00\x00\x01\x04' + data
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
self.assertEqual(res['fin'], 1)
self.assertEqual(res['opcode'], 0x2)
self.assertEqual(res['length'], 260)
self.assertEqual(res['payload'], data)
self.assertEqual(res['left'], 0)
def test_decode_hybi_multi(self):
buf1 = b'\x01\x03\x48\x65\x6c'
buf2 = b'\x80\x02\x6c\x6f'
res1 = websocket.WebSocketRequestHandler.decode_hybi(buf1, strict=False)
self.assertEqual(res1['fin'], 0)
self.assertEqual(res1['opcode'], 0x1)
self.assertEqual(res1['length'], 3)
self.assertEqual(res1['payload'], b'Hel')
self.assertEqual(res1['left'], 0)
res2 = websocket.WebSocketRequestHandler.decode_hybi(buf2, strict=False)
self.assertEqual(res2['fin'], 1)
self.assertEqual(res2['opcode'], 0x0)
self.assertEqual(res2['length'], 2)
self.assertEqual(res2['payload'], b'lo')
self.assertEqual(res2['left'], 0)
def test_encode_hybi_basic(self):
res = websocket.WebSocketRequestHandler.encode_hybi(b'Hello', 0x1)
expected = (b'\x81\x05\x48\x65\x6c\x6c\x6f', 2, 0)
self.assertEqual(res, expected)
def test_strict_mode_refuses_unmasked_client_frames(self):
buf = b'\x81\x05\x48\x65\x6c\x6c\x6f'
self.assertRaises(websocket.WebSocketRequestHandler.CClose,
websocket.WebSocketRequestHandler.decode_hybi,
buf)
def test_no_strict_mode_accepts_unmasked_client_frames(self):
buf = b'\x81\x05\x48\x65\x6c\x6c\x6f'
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
self.assertEqual(res['fin'], 1)
self.assertEqual(res['opcode'], 0x1)
self.assertEqual(res['masked'], False)
self.assertEqual(res['length'], 5)
self.assertEqual(res['payload'], b'Hello')
+86 -95
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright(c) 2015 Red Hat, Inc All Rights Reserved.
# Copyright(c)2013 NTT corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -15,122 +15,113 @@
# under the License.
""" Unit tests for websocketproxy """
import unittest
import unittest
import socket
import os
import logging
import select
import shutil
import stubout
import subprocess
import tempfile
import time
import unittest
from websockify import websocket
from websockify import websocketproxy
from websockify import token_plugins
from websockify import auth_plugins
try:
from StringIO import StringIO
BytesIO = StringIO
except ImportError:
from io import StringIO
from io import BytesIO
class FakeSocket(object):
def __init__(self, data=''):
if isinstance(data, bytes):
self._data = data
else:
self._data = data.encode('latin_1')
def recv(self, amt, flags=None):
res = self._data[0:amt]
if not (flags & socket.MSG_PEEK):
self._data = self._data[amt:]
return res
def makefile(self, mode='r', buffsize=None):
if 'b' in mode:
return BytesIO(self._data)
else:
return StringIO(self._data.decode('latin_1'))
class FakeServer(object):
class EClose(Exception):
class MockSocket(object):
def __init__(*args, **kwargs):
pass
def __init__(self):
self.token_plugin = None
self.auth_plugin = None
self.wrap_cmd = None
self.ssl_target = None
self.unix_target = None
def shutdown(*args):
pass
def close(*args):
pass
class WebSocketProxyTest(unittest.TestCase):
def _init_logger(self, tmpdir):
name = 'websocket-unittest'
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.propagate = True
filename = "%s.log" % (name)
handler = logging.FileHandler(filename)
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
class ProxyRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
super(ProxyRequestHandlerTestCase, self).setUp()
"""Called automatically before each test."""
super(WebSocketProxyTest, self).setUp()
self.soc = ''
self.stubs = stubout.StubOutForTesting()
self.handler = websocketproxy.ProxyRequestHandler(
FakeSocket(''), "127.0.0.1", FakeServer())
self.handler.path = "https://localhost:6080/websockify?token=blah"
self.handler.headers = None
self.stubs.Set(websocket.WebSocketServer, 'socket',
staticmethod(lambda *args, **kwargs: None))
# Temporary dir for test data
self.tmpdir = tempfile.mkdtemp()
# Put log somewhere persistent
self._init_logger('./')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
super(ProxyRequestHandlerTestCase, self).tearDown()
shutil.rmtree(self.tmpdir)
super(WebSocketProxyTest, self).tearDown()
def test_get_target(self):
class TestPlugin(token_plugins.BasePlugin):
def lookup(self, token):
return ("some host", "some port")
def _get_websockproxy(self, **kwargs):
return websocketproxy.WebSocketProxy(key=self.tmpdir,
web=self.tmpdir,
record=self.tmpdir,
**kwargs)
host, port = self.handler.get_target(
TestPlugin(None), self.handler.path)
def test_run_wrap_cmd(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
self.assertEqual(host, "some host")
self.assertEqual(port, "some port")
def mock_Popen(*args, **kwargs):
return '_mock_cmd'
def test_get_target_raises_error_on_unknown_token(self):
class TestPlugin(token_plugins.BasePlugin):
def lookup(self, token):
return None
self.stubs.Set(subprocess, 'Popen', mock_Popen)
web_socket_proxy.run_wrap_cmd()
self.assertEquals(web_socket_proxy.spawn_message, True)
self.assertRaises(FakeServer.EClose, self.handler.get_target,
TestPlugin(None), "https://localhost:6080/websockify?token=blah")
def test_started(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["spawn_message"] = False
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
def test_token_plugin(self):
class TestPlugin(token_plugins.BasePlugin):
def lookup(self, token):
return (self.source + token).split(',')
def mock_run_wrap_cmd(*args, **kwargs):
web_socket_proxy.__dict__["spawn_message"] = True
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
lambda *args, **kwargs: None)
self.stubs.Set(web_socket_proxy, 'run_wrap_cmd', mock_run_wrap_cmd)
web_socket_proxy.started()
self.assertEquals(web_socket_proxy.__dict__["spawn_message"], True)
self.handler.server.token_plugin = TestPlugin("somehost,")
self.handler.new_websocket_client()
def test_poll(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
web_socket_proxy.__dict__["wrap_mode"] = "respawn"
web_socket_proxy.__dict__["wrap_times"] = [99999999]
web_socket_proxy.__dict__["spawn_message"] = True
web_socket_proxy.__dict__["cmd"] = None
self.stubs.Set(time, 'time', lambda: 100000000.000)
web_socket_proxy.poll()
self.assertEquals(web_socket_proxy.spawn_message, False)
self.assertEqual(self.handler.server.target_host, "somehost")
self.assertEqual(self.handler.server.target_port, "blah")
def test_new_client(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["verbose"] = "verbose"
web_socket_proxy.__dict__["daemon"] = None
web_socket_proxy.__dict__["client"] = "client"
def test_auth_plugin(self):
class TestPlugin(auth_plugins.BasePlugin):
def authenticate(self, headers, target_host, target_port):
if target_host == self.source:
raise auth_plugins.AuthenticationError("some error")
self.stubs.Set(web_socket_proxy, 'socket', MockSocket)
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
staticmethod(lambda *args, **kwargs: None))
self.handler.server.auth_plugin = TestPlugin("somehost")
self.handler.server.target_host = "somehost"
self.handler.server.target_port = "someport"
self.assertRaises(auth_plugins.AuthenticationError,
self.handler.new_websocket_client)
self.handler.server.target_host = "someotherhost"
self.handler.new_websocket_client()
def mock_select(*args, **kwargs):
ins = None
outs = None
excepts = "excepts"
return ins, outs, excepts
self.stubs.Set(select, 'select', mock_select)
self.assertRaises(Exception, web_socket_proxy.new_websocket_client)
+7 -4
View File
@@ -4,14 +4,17 @@
# and then run "tox" from this directory.
[tox]
envlist = py24,py26,py27,py33,py34
envlist = py24,py25,py26,py27,py30
setupdir = ../
[testenv]
commands = nosetests {posargs}
deps = -r{toxinidir}/test-requirements.txt
deps =
mox
nose
# At some point we should enable this since tox expects it to exist but
# the code will need pep8ising first.
# At some point we should enable this since tox epdctes it to exist but
# the code will need pep8ising first.
#[testenv:pep8]
#commands = flake8
#dep = flake8
+2 -2
View File
@@ -1,2 +1,2 @@
from websockify.websocket import *
from websockify.websocketproxy import *
from websocket import *
from websocketproxy import *
-33
View File
@@ -1,33 +0,0 @@
class BasePlugin(object):
def __init__(self, src=None):
self.source = src
def authenticate(self, headers, target_host, target_port):
pass
class AuthenticationError(Exception):
pass
class InvalidOriginError(AuthenticationError):
def __init__(self, expected, actual):
self.expected_origin = expected
self.actual_origin = actual
super(InvalidOriginError, self).__init__(
"Invalid Origin Header: Expected one of "
"%s, got '%s'" % (expected, actual))
class ExpectOrigin(object):
def __init__(self, src=None):
if src is None:
self.source = []
else:
self.source = src.split()
def authenticate(self, headers, target_host, target_port):
origin = headers.getheader('Origin', None)
if origin is None or origin not in self.source:
raise InvalidOriginError(expected=self.source, actual=origin)
-82
View File
@@ -1,82 +0,0 @@
import os
class BasePlugin(object):
def __init__(self, src):
self.source = src
def lookup(self, token):
return None
class ReadOnlyTokenFile(BasePlugin):
# source is a token file with lines like
# token: host:port
# or a directory of such files
def __init__(self, *args, **kwargs):
super(ReadOnlyTokenFile, self).__init__(*args, **kwargs)
self._targets = None
def _load_targets(self):
if os.path.isdir(self.source):
cfg_files = [os.path.join(self.source, f) for
f in os.listdir(self.source)]
else:
cfg_files = [self.source]
self._targets = {}
for f in cfg_files:
for line in [l.strip() for l in open(f).readlines()]:
if line and not line.startswith('#'):
tok, target = line.split(': ')
self._targets[tok] = target.strip().split(':')
def lookup(self, token):
if self._targets is None:
self._load_targets()
if token in self._targets:
return self._targets[token]
else:
return None
# the above one is probably more efficient, but this one is
# more backwards compatible (although in most cases
# ReadOnlyTokenFile should suffice)
class TokenFile(ReadOnlyTokenFile):
# source is a token file with lines like
# token: host:port
# or a directory of such files
def lookup(self, token):
self._load_targets()
return super(TokenFile, self).lookup(token)
class BaseTokenAPI(BasePlugin):
# source is a url with a '%s' in it where the token
# should go
# we import things on demand so that other plugins
# in this file can be used w/o unecessary dependencies
def process_result(self, resp):
return resp.text.split(':')
def lookup(self, token):
import requests
resp = requests.get(self.source % token)
if resp.ok:
return self.process_result(resp)
else:
return None
class JSONTokenApi(BaseTokenAPI):
# source is a url with a '%s' in it where the token
# should go
def process_result(self, resp):
return (resp.json['host'], resp.json['port'])
+46 -78
View File
@@ -74,8 +74,8 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
attributes on the server object:
* only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled,
only websocket is allowed.
* verbose: If true, verbose logging is activated.
only websocket is allowed.
* verbose: If true, verbose logging is activated.
* daemon: Running as daemon, do not write to console etc
* record: Record raw frame data as JavaScript array into specified filename
* run_once: Handle a single request
@@ -104,13 +104,11 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
self.handler_id = getattr(server, "handler_id", False)
self.file_only = getattr(server, "file_only", False)
self.traffic = getattr(server, "traffic", False)
self.auto_pong = getattr(server, "auto_pong", False)
self.strict_mode = getattr(server, "strict_mode", True)
self.logger = getattr(server, "logger", None)
if self.logger is None:
self.logger = WebSocketServer.get_logger()
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
@staticmethod
@@ -120,24 +118,20 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
if numpy:
b = c = s2b('')
if plen >= 4:
dtype=numpy.dtype('<u4')
if sys.byteorder == 'big':
dtype = dtype.newbyteorder('>')
mask = numpy.frombuffer(buf, dtype, offset=hlen, count=1)
data = numpy.frombuffer(buf, dtype, offset=pstart,
count=int(plen / 4))
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=hlen, count=1)
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=pstart, count=int(plen / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring()
if plen % 4:
#self.msg("Partial unmask")
dtype=numpy.dtype('B')
if sys.byteorder == 'big':
dtype = dtype.newbyteorder('>')
mask = numpy.frombuffer(buf, dtype, offset=hlen,
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=hlen, count=(plen % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=pend - (plen % 4),
count=(plen % 4))
data = numpy.frombuffer(buf, dtype,
offset=pend - (plen % 4), count=(plen % 4))
c = numpy.bitwise_xor(data, mask).tostring()
return b + c
else:
@@ -178,7 +172,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
return header + buf, len(header), 0
@staticmethod
def decode_hybi(buf, base64=False, logger=None, strict=True):
def decode_hybi(buf, base64=False, logger=None):
""" Decode HyBi style WebSocket packets.
Returns:
{'fin' : 0_or_1,
@@ -244,10 +238,6 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
f['length'])
else:
logger.debug("Unmasked frame: %s" % repr(buf))
if strict:
raise WebSocketRequestHandler.CClose(1002, "The client sent an unmasked frame.")
f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
if base64 and f['opcode'] in [1, 2]:
@@ -356,8 +346,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
while buf:
frame = self.decode_hybi(buf, base64=self.base64,
logger=self.logger,
strict=self.strict_mode)
logger=self.logger)
#self.msg("Received buf: %s, frame: %s", repr(buf), frame)
if frame['payload'] == None:
@@ -371,15 +360,6 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
closed = {'code': frame['close_code'],
'reason': frame['close_reason']}
break
elif self.auto_pong and frame['opcode'] == 0x9: # ping
self.print_traffic("} ping %s\n" %
repr(frame['payload']))
self.send_pong(frame['payload'])
return [], False
elif frame['opcode'] == 0xA: # pong
self.print_traffic("} pong %s\n" %
repr(frame['payload']))
return [], False
self.print_traffic("}")
@@ -408,20 +388,10 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
def send_close(self, code=1000, reason=''):
""" Send a WebSocket orderly close frame. """
msg = pack(">H%ds" % len(reason), code, s2b(reason))
msg = pack(">H%ds" % len(reason), code, reason)
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
self.request.send(buf)
def send_pong(self, data=''):
""" Send a WebSocket pong frame. """
buf, h, t = self.encode_hybi(s2b(data), opcode=0x0A, base64=False)
self.request.send(buf)
def send_ping(self, data=''):
""" Send a WebSocket ping frame. """
buf, h, t = self.encode_hybi(s2b(data), opcode=0x09, base64=False)
self.request.send(buf)
def do_websocket_handshake(self):
h = self.headers
@@ -465,16 +435,16 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
self.send_header("Sec-WebSocket-Protocol", "binary")
self.end_headers()
return True
else:
else:
self.send_error(400, "Missing Sec-WebSocket-Version header. Hixie protocols not supported.")
return False
def handle_websocket(self):
"""Upgrade a connection to Websocket, if requested. If this succeeds,
"""Upgrade a connection to Websocket, if requested. If this succeeds,
new_websocket_client() will be called. Otherwise, False is returned.
"""
if (self.headers.get('upgrade') and
if (self.headers.get('upgrade') and
self.headers.get('upgrade').lower() == 'websocket'):
if not self.do_websocket_handshake():
@@ -498,21 +468,21 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
if is_ssl:
self.stype = "SSL/TLS (wss://)"
else:
else:
self.stype = "Plain non-SSL (ws://)"
self.log_message("%s: %s WebSocket connection", client_addr,
self.stype)
self.log_message("%s: Version %s, base64: '%s'", client_addr,
self.version, self.base64)
self.log_message("%s: %s WebSocket connection" % (client_addr,
self.stype))
self.log_message("%s: Version %s, base64: '%s'" % (client_addr,
self.version, self.base64))
if self.path != '/':
self.log_message("%s: Path: '%s'", client_addr, self.path)
self.log_message("%s: Path: '%s'" % (client_addr, self.path))
if self.record:
# Record raw frame data as JavaScript array
fname = "%s.%s" % (self.record,
self.handler_id)
self.log_message("opening record file: %s", fname)
self.log_message("opening record file: %s" % fname)
self.rec = open(fname, 'w+')
encoding = "binary"
if self.base64: encoding = "base64"
@@ -525,7 +495,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
except self.CClose:
# Close the client
_, exc, _ = sys.exc_info()
self.send_close(exc.args[0], exc.args[1])
self.send_close(exc.args[0], exc.args[1])
return True
else:
return False
@@ -544,7 +514,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
self.send_error(404, "No such file")
else:
return SimpleHTTPRequestHandler.list_directory(self, path)
def new_websocket_client(self):
""" Do something with a WebSockets client connection. """
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
@@ -590,14 +560,14 @@ class WebSocketServer(object):
class Terminate(Exception):
pass
def __init__(self, RequestHandlerClass, listen_host='',
def __init__(self, RequestHandlerClass, listen_host='',
listen_port=None, source_is_ipv6=False,
verbose=False, cert='', key='', ssl_only=None,
daemon=False, record='', web='',
file_only=False,
run_once=False, timeout=0, idle_timeout=0, traffic=False,
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
tcp_keepintvl=None, auto_pong=False, strict_mode=True):
tcp_keepintvl=None):
# settings
self.RequestHandlerClass = RequestHandlerClass
@@ -611,9 +581,7 @@ class WebSocketServer(object):
self.timeout = timeout
self.idle_timeout = idle_timeout
self.traffic = traffic
self.file_only = file_only
self.strict_mode = strict_mode
self.launch_time = time.time()
self.ws_connection = False
self.handler_id = 1
@@ -624,7 +592,6 @@ class WebSocketServer(object):
self.tcp_keepidle = tcp_keepidle
self.tcp_keepintvl = tcp_keepintvl
self.auto_pong = auto_pong
# Make paths settings absolute
self.cert = os.path.abspath(cert)
self.key = self.web = self.record = ''
@@ -651,10 +618,7 @@ class WebSocketServer(object):
self.listen_host, self.listen_port)
self.msg(" - Flash security policy server")
if self.web:
if self.file_only:
self.msg(" - Web server (no directory listings). Web root: %s", self.web)
else:
self.msg(" - Web server. Web root: %s", self.web)
self.msg(" - Web server. Web root: %s", self.web)
if ssl:
if os.path.exists(self.cert):
self.msg(" - SSL/TLS support")
@@ -698,7 +662,7 @@ class WebSocketServer(object):
raise Exception("SSL only supported in connect mode (for now)")
if not connect:
flags = flags | socket.AI_PASSIVE
if not unix_socket:
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
socket.IPPROTO_TCP, flags)
@@ -729,7 +693,7 @@ class WebSocketServer(object):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4])
sock.listen(100)
else:
else:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(unix_socket)
@@ -797,7 +761,7 @@ class WebSocketServer(object):
handshake = sock.recv(1024, socket.MSG_PEEK)
#self.msg("Handshake [%s]" % handshake)
if not handshake:
if handshake == "":
raise self.EClose("ignoring empty handshake")
elif handshake.startswith(s2b("<policy-file-request/>")):
@@ -880,7 +844,7 @@ class WebSocketServer(object):
raise self.Terminate()
def multiprocessing_SIGCHLD(self, sig, stack):
self.vmsg('Reaping zombies, active child count is %s', len(multiprocessing.active_children()))
self.vmsg('Reaing zombies, active child count is %s', len(multiprocessing.active_children()))
def fallback_SIGCHLD(self, sig, stack):
# Reap zombies when using os.fork() (python 2.4)
@@ -949,17 +913,21 @@ class WebSocketServer(object):
original_signals = {
signal.SIGINT: signal.getsignal(signal.SIGINT),
signal.SIGTERM: signal.getsignal(signal.SIGTERM),
signal.SIGCHLD: signal.getsignal(signal.SIGCHLD),
}
if getattr(signal, 'SIGCHLD', None) is not None:
original_signals[signal.SIGCHLD] = signal.getsignal(signal.SIGCHLD),
signal.signal(signal.SIGINT, self.do_SIGINT)
signal.signal(signal.SIGTERM, self.do_SIGTERM)
if not multiprocessing:
# os.fork() (python 2.4) child reaper
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
else:
# make sure that _cleanup is called when children die
# by calling active_children on SIGCHLD
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
if getattr(signal, 'SIGCHLD', None) is not None:
if not multiprocessing:
# os.fork() (python 2.4) child reaper
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
else:
# make sure that _cleanup is called when children die
# by calling active_children on SIGCHLD
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
last_active_time = self.launch_time
try:
+55 -137
View File
@@ -11,13 +11,13 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess, logging, errno
import signal, socket, optparse, time, os, sys, subprocess, logging
try: from socketserver import ForkingMixIn
except: from SocketServer import ForkingMixIn
try: from http.server import HTTPServer
except: from BaseHTTPServer import HTTPServer
import select
from websockify import websocket
from select import select
import websocket
try:
from urllib.parse import parse_qs, urlparse
except:
@@ -44,13 +44,8 @@ Traffic Legend:
"""
# Checks if we receive a token, and look
# for a valid target for it then
if self.server.token_plugin:
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
if self.server.auth_plugin:
self.server.auth_plugin.authenticate(
headers=self.headers, target_host=self.server.target_host,
target_port=self.server.target_port)
if self.server.target_cfg:
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
# Connect to the target
if self.server.wrap_cmd:
@@ -78,15 +73,15 @@ Traffic Legend:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
if self.verbose:
self.log_message("%s:%s: Closed target",
self.server.target_host, self.server.target_port)
if self.verbose:
self.log_message("%s:%s: Closed target" %(
self.server.target_host, self.server.target_port))
raise
def get_target(self, target_plugin, path):
def get_target(self, target_cfg, path):
"""
Parses the path, extracts a token, and looks up a target
for that token using the token plugin. Sets
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
@@ -95,17 +90,32 @@ Traffic Legend:
# Extract the token parameter from url
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
if not 'token' in args or not len(args['token']):
raise self.server.EClose("Token not present")
if not args.has_key('token') or not len(args['token']):
raise self.EClose("Token not present")
token = args['token'][0].rstrip('\n')
result_pair = target_plugin.lookup(token)
if result_pair is not None:
return result_pair
# 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:
raise self.server.EClose("Token '%s' not found" % token)
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):
"""
@@ -116,37 +126,12 @@ Traffic Legend:
tqueue = []
rlist = [self.request, target]
if self.server.heartbeat:
now = time.time()
self.heartbeat = now + self.server.heartbeat
else:
self.heartbeat = None
while True:
wlist = []
if self.heartbeat is not None:
now = time.time()
if now > self.heartbeat:
self.heartbeat = now + self.server.heartbeat
self.send_ping()
if tqueue: wlist.append(target)
if cqueue or c_pend: wlist.append(self.request)
try:
ins, outs, excepts = select.select(rlist, wlist, [], 1)
except (select.error, OSError):
exc = sys.exc_info()[1]
if hasattr(exc, 'errno'):
err = exc.errno
else:
err = exc[0]
if err != errno.EINTR:
raise
else:
continue
ins, outs, excepts = select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if self.request in outs:
@@ -162,9 +147,9 @@ Traffic Legend:
if closed:
# TODO: What about blocking on client socket?
if self.verbose:
self.log_message("%s:%s: Client closed connection",
self.server.target_host, self.server.target_port)
if self.verbose:
self.log_message("%s:%s: Client closed connection" %(
self.server.target_host, self.server.target_port))
raise self.CClose(closed['code'], closed['reason'])
@@ -185,8 +170,8 @@ Traffic Legend:
buf = target.recv(self.buffer_size)
if len(buf) == 0:
if self.verbose:
self.log_message("%s:%s: Target closed connection",
self.server.target_host, self.server.target_port)
self.log_message("%s:%s: Target closed connection" %(
self.server.target_host, self.server.target_port))
raise self.CClose(1000, "Target closed")
cqueue.append(buf)
@@ -210,11 +195,7 @@ class WebSocketProxy(websocket.WebSocketServer):
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.heartbeat = kwargs.pop('heartbeat', None)
self.token_plugin = kwargs.pop('token_plugin', None)
self.auth_plugin = kwargs.pop('auth_plugin', None)
self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
@@ -270,9 +251,9 @@ class WebSocketProxy(websocket.WebSocketServer):
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.token_plugin:
msg = " - proxying from %s:%s to targets generated by %s" % (
self.listen_host, self.listen_port, type(self.token_plugin).__name__)
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)
@@ -371,56 +352,20 @@ def websockify_init():
parser.add_option("--prefer-ipv6", "-6",
action="store_true", dest="source_is_ipv6",
help="prefer IPv6 when resolving source_addr")
parser.add_option("--libserver", action="store_true",
help="use Python library SocketServer engine")
parser.add_option("--target-config", metavar="FILE",
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 "
"(DEPRECATED: use `--token-plugin TokenFile --token-source "
" path/to/token/file` instead)")
parser.add_option("--token-plugin", default=None, metavar="PLUGIN",
help="use the given Python class to process tokens "
"into host:port pairs")
parser.add_option("--token-source", default=None, metavar="ARG",
help="an argument to be passed to the token plugin"
"on instantiation")
parser.add_option("--auth-plugin", default=None, metavar="PLUGIN",
help="use the given Python class to determine if "
"a connection is allowed")
parser.add_option("--auth-source", default=None, metavar="ARG",
help="an argument to be passed to the auth plugin"
"on instantiation")
parser.add_option("--auto-pong", action="store_true",
help="Automatically respond to ping frames with a pong")
parser.add_option("--heartbeat", type=int, default=0,
help="send a ping to the client every HEARTBEAT seconds")
"directory containing configuration files of this form")
parser.add_option("--libserver", action="store_true",
help="use Python library SocketServer engine")
(opts, args) = parser.parse_args()
if opts.verbose:
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
if opts.token_source and not opts.token_plugin:
parser.error("You must use --token-plugin to use --token-source")
if opts.auth_source and not opts.auth_plugin:
parser.error("You must use --auth-plugin to use --auth-source")
# Transform to absolute path as daemon may chdir
if opts.target_cfg:
opts.target_cfg = os.path.abspath(opts.target_cfg)
if opts.target_cfg:
opts.token_plugin = 'TokenFile'
opts.token_source = opts.target_cfg
del opts.target_cfg
# Sanity checks
if len(args) < 2 and not (opts.token_plugin or opts.unix_target):
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
parser.error("Too few arguments")
if sys.argv.count('--'):
opts.wrap_cmd = args[1:]
@@ -445,7 +390,7 @@ def websockify_init():
try: opts.listen_port = int(opts.listen_port)
except: parser.error("Error parsing listen port")
if opts.wrap_cmd or opts.unix_target or opts.token_plugin:
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
opts.target_host = None
opts.target_port = None
else:
@@ -457,32 +402,9 @@ def websockify_init():
try: opts.target_port = int(opts.target_port)
except: parser.error("Error parsing target port")
if opts.token_plugin is not None:
if '.' not in opts.token_plugin:
opts.token_plugin = (
'websockify.token_plugins.%s' % opts.token_plugin)
token_plugin_module, token_plugin_cls = opts.token_plugin.rsplit('.', 1)
__import__(token_plugin_module)
token_plugin_cls = getattr(sys.modules[token_plugin_module], token_plugin_cls)
opts.token_plugin = token_plugin_cls(opts.token_source)
del opts.token_source
if opts.auth_plugin is not None:
if '.' not in opts.auth_plugin:
opts.auth_plugin = 'websockify.auth_plugins.%s' % opts.auth_plugin
auth_plugin_module, auth_plugin_cls = opts.auth_plugin.rsplit('.', 1)
__import__(auth_plugin_module)
auth_plugin_cls = getattr(sys.modules[auth_plugin_module], auth_plugin_cls)
opts.auth_plugin = auth_plugin_cls(opts.auth_source)
del opts.auth_source
# Transform to absolute path as daemon may chdir
if opts.target_cfg:
opts.target_cfg = os.path.abspath(opts.target_cfg)
# Create and start the WebSockets proxy
libserver = opts.libserver
@@ -511,13 +433,9 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.token_plugin = kwargs.pop('token_plugin', None)
self.auth_plugin = kwargs.pop('auth_plugin', None)
self.heartbeat = kwargs.pop('heartbeat', None)
self.token_plugin = None
self.auth_plugin = None
self.target_cfg = kwargs.pop('target_cfg', None)
self.daemon = False
self.target_cfg = None
# Server configuration
listen_host = kwargs.pop('listen_host', '')
@@ -538,8 +456,8 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
if web:
os.chdir(web)
HTTPServer.__init__(self, (listen_host, listen_port),
HTTPServer.__init__(self, (listen_host, listen_port),
RequestHandlerClass)