Commit 5d8e7ec0 authored by Joel Martin's avatar Joel Martin

Add web-socket-js support with packet re-ordering.

- web-socket-js is from http://github.com/gimite/web-socket-js. It is
  a flash object that emultates WebSockets.

Unfortunately, events (or packets) from the web-socket-js object can
get re-ordered so we need to know the packet order.

- So wsproxy.py prepends the sequence number of the packet when
  sending.

- If the client receives packets out of order it queues them up and
  scans the queue for the sequence number it's looking for until
  things are back on track. Gross, but hey: It works!

- Also, add packet sequence checking to wstest.*
parent d9205954
- Better status and error feedback. - Make packet sequence number optional based on WebSockets 'path'.
- Get working in firefox using flash web-socket-js:
http://github.com/gimite/web-socket-js
- Only load Flash stuff if needed: - Better status and error feedback.
var x='< script type="text/javascript" src=';
var y='><\/script>';
var t='';
t+= x+'file1.js'+y;
t+= x+'file2.js'+y;
t+= x+'fileA.txt'+y;
document.write(t);
- Version without mootools:
- test cross-browser
- Add WSS/https/SSL support to page and wsproxy.py - Add WSS/https/SSL support to page and wsproxy.py
......
...@@ -90,25 +90,30 @@ toBinaryTable : [ ...@@ -90,25 +90,30 @@ toBinaryTable : [
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
], ],
decode: function (data) { decode: function (data, offset) {
offset = typeof(offset) != 'undefined' ? offset : 0;
var binTable = Base64.toBinaryTable; var binTable = Base64.toBinaryTable;
var pad = Base64.base64Pad; var pad = Base64.base64Pad;
var leftbits = 0; // number of bits decoded, but yet to be appended var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended var leftdata = 0; // bits decoded, but yet to be appended
/* Every four characters is 3 resulting numbers */ /* Every four characters is 3 resulting numbers */
var data_length = data.indexOf('='); var data_length = data.indexOf('=') - offset;
if (data_length == -1) data_length = data.length; if (data_length < 0) data_length = data.length - offset;
var result_length = (data_length >> 2) * 3 + (data.length % 4 - 1);
var result_length = (data_length >> 2) * 3 + ((data.length - offset) % 4 - 1);
var result = new Array(result_length); var result = new Array(result_length);
// Convert one by one. // Convert one by one.
var idx = 0; var idx = 0;
for (var i = 0; i < data.length; i++) { for (var i = offset; i < data.length; i++) {
var c = binTable[data[i].charCodeAt(0) & 0x7f]; var c = binTable[data[i].charCodeAt(0) & 0x7f];
var padding = (data[i] == pad); var padding = (data[i] == pad);
// Skip illegal characters and whitespace // Skip illegal characters and whitespace
if (c == -1) continue; if (c == -1) {
console.log("Illegal character '" + data[i].charCodeAt(0) + "'");
continue;
}
// Collect data into leftdata, update bitcount // Collect data into leftdata, update bitcount
leftdata = (leftdata << 6) | c; leftdata = (leftdata << 6) | c;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<head><title>VNC Client</title></head> <head><title>VNC Client</title></head>
<body onload="draw();"> <body>
Host: <input id='host' style='width:100'>&nbsp; Host: <input id='host' style='width:100'>&nbsp;
Port: <input id='port' style='width:50'>&nbsp; Port: <input id='port' style='width:50'>&nbsp;
...@@ -35,8 +35,26 @@ ...@@ -35,8 +35,26 @@
<script src="vnc.js"></script> <script src="vnc.js"></script>
<script> <script>
var native_ws = true;
/* If no builtin websockets then load web_socket.js */
if (! window.WebSocket) {
var extra = "<script src='include/web-socket-js/swfobject.js'><\/script>";
extra += "<script src='include/web-socket-js/FABridge.js'><\/script>";
extra += "<script src='include/web-socket-js/web_socket.js'><\/script>";
document.write(extra);
native_ws = false;
}
window.onload = function() { window.onload = function() {
console.log("onload"); console.log("onload");
if (native_ws) {
console.log("Using native WebSockets");
} else {
console.log("Using web-socket-js flash bridge");
WebSocket.__swfLocation = "web-socket-js/WebSocketMain.swf";
RFB.force_copy = true;
}
var url = document.location.href; var url = document.location.href;
$('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1]; $('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1];
$('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1]; $('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1];
......
This diff is collapsed.
...@@ -5,6 +5,7 @@ from base64 import b64encode, b64decode ...@@ -5,6 +5,7 @@ from base64 import b64encode, b64decode
from select import select from select import select
buffer_size = 65536 buffer_size = 65536
send_seq = 0
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r Upgrade: WebSocket\r
...@@ -17,6 +18,19 @@ WebSocket-Protocol: sample\r ...@@ -17,6 +18,19 @@ WebSocket-Protocol: sample\r
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>""" policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>"""
traffic_legend = """
Traffic Legend:
} - Client receive
}. - Client receive partial
{ - Target receive
> - Target send
>. - Target send partial
< - Client send
<. - Client send partial
"""
def handshake(client): def handshake(client):
handshake = client.recv(1024) handshake = client.recv(1024)
print "Handshake [%s]" % handshake print "Handshake [%s]" % handshake
...@@ -38,12 +52,14 @@ def traffic(token="."): ...@@ -38,12 +52,14 @@ def traffic(token="."):
def decode(buf): def decode(buf):
""" Parse out WebSocket packets. """ """ Parse out WebSocket packets. """
if buf.count('\xff') > 1: if buf.count('\xff') > 1:
return [d[1:] for d in buf.split('\xff')] traffic(str(buf.count('\xff')))
return [b64decode(d[1:]) for d in buf.split('\xff')]
else: else:
return [b64decode(buf[1:-1])] return [b64decode(buf[1:-1])]
def proxy(client, target): def proxy(client, target):
""" Proxy WebSocket to normal socket. """ """ Proxy WebSocket to normal socket. """
global send_seq
cqueue = [] cqueue = []
cpartial = "" cpartial = ""
tqueue = [] tqueue = []
...@@ -53,51 +69,72 @@ def proxy(client, target): ...@@ -53,51 +69,72 @@ def proxy(client, target):
ins, outs, excepts = select(socks, socks, socks, 1) ins, outs, excepts = select(socks, socks, socks, 1)
if excepts: raise Exception("Socket exception") if excepts: raise Exception("Socket exception")
if tqueue and target in outs:
#print "Target send: %s" % repr(tqueue[0])
log.write("Target send: %s\n" % map(ord, tqueue[0]))
dat = tqueue.pop(0)
sent = target.send(dat)
if sent == len(dat):
traffic(">")
else:
tqueue.insert(0, dat[sent:])
traffic(">.")
if cqueue and client in outs:
dat = cqueue.pop(0)
sent = client.send(dat)
if sent == len(dat):
traffic("<")
log.write("Client send: %s\n" % repr(dat))
else:
cqueue.insert(0, dat[sent:])
traffic("<.")
log.write("Client send partial: %s\n" % repr(dat[0:send]))
if target in ins:
buf = target.recv(buffer_size)
if len(buf) == 0: raise Exception("Target closed")
#enc = b64encode(buf)
#chksum = sum([ord(c) for c in enc])
#cqueue.append("\x00^" + str(chksum) + "@" + enc + "$\xff")
cqueue.append("\x00%d:%s\xff" % (send_seq, b64encode(buf)))
send_seq += 1
log.write("Target recv (%d): %s\n" % (len(buf), map(ord, buf)))
traffic("{")
if client in ins: if client in ins:
buf = client.recv(buffer_size) buf = client.recv(buffer_size)
if len(buf) == 0: raise Exception("Client closed") if len(buf) == 0: raise Exception("Client closed")
if buf[-1] == "\xff": if buf[-1] == "\xff":
traffic("}")
log.write("Client recv (%d): %s\n" % (len(buf), repr(buf)))
if cpartial: if cpartial:
tqueue.extend(decode(cpartial + buf)) tqueue.extend(decode(cpartial + buf))
cpartial = "" cpartial = ""
else: else:
tqueue.extend(decode(buf)) tqueue.extend(decode(buf))
traffic("}")
else: else:
traffic(".}") traffic("}.")
log.write("Client recv partial (%d): %s\n" % (len(buf), repr(buf)))
cpartial = cpartial + buf cpartial = cpartial + buf
#print "Client recv: %s (%d)" % (repr(buf, len(buf))
if target in ins:
buf = target.recv(buffer_size)
if len(buf) == 0: raise Exception("Target closed")
cqueue.append("\x00" + b64encode(buf) + "\xff")
#print "Target recv: %s (%d)" % (repr(buf), len(buf))
traffic("{")
if cqueue and client in outs:
while cqueue:
#print "Client send: %s" % repr(cqueue[0])
client.send(cqueue.pop(0))
traffic("<")
if tqueue and target in outs:
while tqueue:
#print "Target send: %s" % repr(tqueue[0])
target.send(tqueue.pop(0))
traffic(">")
def start_server(listen_port, target_host, target_port): def start_server(listen_port, target_host, target_port):
global send_seq
lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
lsock.bind(('', listen_port)) lsock.bind(('', listen_port))
lsock.listen(100) lsock.listen(100)
print traffic_legend
while True: while True:
try: try:
csock = tsock = None csock = tsock = None
print 'listening on port %s' % listen_port print 'waiting for connection on port %s' % listen_port
csock, address = lsock.accept() csock, address = lsock.accept()
print 'Got client connection from %s' % address[0] print 'Got client connection from %s' % address[0]
handshake(csock) handshake(csock)
...@@ -105,6 +142,7 @@ def start_server(listen_port, target_host, target_port): ...@@ -105,6 +142,7 @@ def start_server(listen_port, target_host, target_port):
tsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tsock.connect((target_host, target_port)) tsock.connect((target_host, target_port))
send_seq = 0
proxy(csock, tsock) proxy(csock, tsock)
except Exception: except Exception:
...@@ -114,6 +152,7 @@ def start_server(listen_port, target_host, target_port): ...@@ -114,6 +152,7 @@ def start_server(listen_port, target_host, target_port):
if tsock: tsock.close() if tsock: tsock.close()
if __name__ == '__main__': if __name__ == '__main__':
log = open("ws.log", 'w')
try: try:
if len(sys.argv) != 4: raise if len(sys.argv) != 4: raise
listen_port = int(sys.argv[1]) listen_port = int(sys.argv[1])
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
var ws = null, update_ref = null, send_ref = null; var ws = null, update_ref = null, send_ref = null;
var sent = 0, received = 0, errors = 0; var sent = 0, received = 0, errors = 0;
var max_send = 2000; var max_send = 2000;
var recv_cnt = 0, send_cnt = 0;
Array.prototype.pushStr = function (str) { Array.prototype.pushStr = function (str) {
var n = str.length; var n = str.length;
...@@ -70,18 +71,26 @@ ...@@ -70,18 +71,26 @@
arr = decoded.map(function(num) { arr = decoded.map(function(num) {
return String.fromCharCode(num); return String.fromCharCode(num);
} ).join('').split(':'); } ).join('').split(':');
length = arr[0]; cnt = arr[0];
chksum = arr[1]; length = arr[1];
nums = arr[2]; chksum = arr[2];
nums = arr[3];
//console.log(" length:" + length + " chksum:" + chksum + " nums:" + nums); //console.log(" length:" + length + " chksum:" + chksum + " nums:" + nums);
if (cnt != recv_cnt) {
console.error("Expected count " + recv_cnt + " but got " + cnt);
recv_cnt = parseInt(cnt,10) + 1; // Back on track
errors++;
return;
}
recv_cnt++;
if (nums.length != length) { if (nums.length != length) {
console.error("Real length " + nums.length + " is not " + length); console.error("Expected length " + length + " but got " + nums.length);
errors++; errors++;
return; return;
} }
real_chksum = nums.split('').reduce(add); real_chksum = nums.split('').reduce(add);
if (real_chksum != chksum) { if (real_chksum != chksum) {
console.error("Real chksum " + real_chksum + " is not " + chksum); console.error("Expected chksum " + chksum + " but real chksum is " + real_chksum);
errors++ errors++
return; return;
} }
...@@ -91,6 +100,10 @@ ...@@ -91,6 +100,10 @@
} }
function send() { function send() {
if (ws.bufferedAmount > 0) {
console.log("Delaying send");
return;
}
var length = Math.floor(Math.random()*(max_send-9)) + 10; // 10 - max_send var length = Math.floor(Math.random()*(max_send-9)) + 10; // 10 - max_send
var numlist = [], arr = []; var numlist = [], arr = [];
for (var i=0; i < length; i++) { for (var i=0; i < length; i++) {
...@@ -98,7 +111,8 @@ ...@@ -98,7 +111,8 @@
} }
chksum = numlist.reduce(add); chksum = numlist.reduce(add);
var nums = numlist.join(''); var nums = numlist.join('');
arr.pushStr("^" + length + ":" + chksum + ":" + nums + "$") arr.pushStr("^" + send_cnt + ":" + length + ":" + chksum + ":" + nums + "$")
send_cnt ++;
ws.send(Base64.encode(arr)); ws.send(Base64.encode(arr));
sent++; sent++;
} }
...@@ -126,6 +140,7 @@ ...@@ -126,6 +140,7 @@
}; };
ws.onclose = function(e) { ws.onclose = function(e) {
console.log(">> WebSockets.onclose"); console.log(">> WebSockets.onclose");
$clear(send_ref);
console.log("<< WebSockets.onclose"); console.log("<< WebSockets.onclose");
}; };
ws.onerror = function(e) { ws.onerror = function(e) {
...@@ -167,8 +182,6 @@ ...@@ -167,8 +182,6 @@
$clear(update_ref); $clear(update_ref);
update_stats(); // Final numbers update_stats(); // Final numbers
$clear(send_ref);
$('connectButton').value = "Start"; $('connectButton').value = "Start";
$('connectButton').onclick = connect; $('connectButton').onclick = connect;
console.log("<< disconnect"); console.log("<< disconnect");
......
...@@ -5,6 +5,7 @@ from base64 import b64encode, b64decode ...@@ -5,6 +5,7 @@ from base64 import b64encode, b64decode
from select import select from select import select
buffer_size = 65536 buffer_size = 65536
recv_cnt = send_cnt = 0
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r Upgrade: WebSocket\r
...@@ -35,57 +36,83 @@ def traffic(token="."): ...@@ -35,57 +36,83 @@ def traffic(token="."):
sys.stdout.write(token) sys.stdout.write(token)
sys.stdout.flush() sys.stdout.flush()
def decode(buf):
""" Parse out WebSocket packets. """
if buf.count('\xff') > 1:
traffic(str(buf.count('\xff')))
return [b64decode(d[1:]) for d in buf.split('\xff')]
else:
return [b64decode(buf[1:-1])]
def check(buf): def check(buf):
global recv_cnt
try: try:
data = b64decode(buf[1:-1]) data_list = decode(buf)
except: except:
print "\n<BOF>" + repr(buf) + "<EOF>" print "\n<BOF>" + repr(buf) + "<EOF>"
return "Failed to decode" return "Failed to decode"
chunks = data.count('$') err = ""
if chunks > 1: for data in data_list:
traffic(str(chunks)) if data.count('$') > 1:
raise Exception("Multiple parts within single packet")
for chunk in data.split("$"): if len(data) == 0:
if not chunk: continue traffic("_")
continue
if chunk[0] != "^": if data[0] != "^":
return "buf did not start with '^'" err += "buf did not start with '^'\n"
continue
try: try:
length, chksum, nums = chunk[1:].split(':') cnt, length, chksum, nums = data[1:-1].split(':')
cnt = int(cnt)
length = int(length) length = int(length)
chksum = int(chksum) chksum = int(chksum)
except: except:
print "\n<BOF>" + repr(data) + "<EOF>" print "\n<BOF>" + repr(data) + "<EOF>"
return "Invalid data format" err += "Invalid data format\n"
continue
if recv_cnt != cnt:
err += "Expected count %d but got %d\n" % (recv_cnt, cnt)
recv_cnt = cnt + 1
continue
recv_cnt += 1
if len(nums) != length: if len(nums) != length:
return "Real length %d is not %d" % (len(nums), length) err += "Expected length %d but got %d\n" % (length, len(nums))
continue
inv = nums.translate(None, "0123456789") inv = nums.translate(None, "0123456789")
if inv: if inv:
return "Invalid characters found: %s" % inv err += "Invalid characters found: %s\n" % inv
continue
real_chksum = 0 real_chksum = 0
for num in nums: for num in nums:
real_chksum += int(num) real_chksum += int(num)
if real_chksum != chksum: if real_chksum != chksum:
return "Real checksum %d is not %d" % (real_chksum, chksum) err += "Expected checksum %d but real chksum is %d\n" % (chksum, real_chksum)
return err
def generate(): def generate():
length = random.randint(10, 10000) global send_cnt
numlist = rand_array[10000-length:] length = random.randint(10, 100000)
numlist = rand_array[100000-length:]
# Error in length # Error in length
#numlist.append(5) #numlist.append(5)
chksum = sum(numlist) chksum = sum(numlist)
# Error in checksum # Error in checksum
#numlist[0] = 5 #numlist[0] = 5
nums = "".join( [str(n) for n in numlist] ) nums = "".join( [str(n) for n in numlist] )
data = "^%d:%d:%s$" % (length, chksum, nums) data = "^%d:%d:%d:%s$" % (send_cnt, length, chksum, nums)
send_cnt += 1
buf = "\x00" + b64encode(data) + "\xff" buf = "\x00" + b64encode(data) + "\xff"
return buf return buf
...@@ -129,7 +156,7 @@ def responder(client, delay=500): ...@@ -129,7 +156,7 @@ def responder(client, delay=500):
traffic("<") traffic("<")
def start_server(listen_port, delay=500): def start_server(listen_port, delay=500):
global errors global errors, send_cnt, recv_cnt
lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
lsock.bind(('', listen_port)) lsock.bind(('', listen_port))
...@@ -142,6 +169,8 @@ def start_server(listen_port, delay=500): ...@@ -142,6 +169,8 @@ def start_server(listen_port, delay=500):
print 'Got client connection from %s' % address[0] print 'Got client connection from %s' % address[0]
handshake(csock) handshake(csock)
send_cnt = 0
recv_cnt = 0
responder(csock, delay=delay) responder(csock, delay=delay)
except Exception: except Exception:
...@@ -166,7 +195,7 @@ if __name__ == '__main__': ...@@ -166,7 +195,7 @@ if __name__ == '__main__':
print "Prepopulating random array" print "Prepopulating random array"
rand_array = [] rand_array = []
for i in range(0, 10000): for i in range(0, 100000):
rand_array.append(random.randint(0, 9)) rand_array.append(random.randint(0, 9))
start_server(listen_port, delay=delay) start_server(listen_port, delay=delay)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment