Merge branch 'master' into add-desktop-size-change-callback

This commit is contained in:
Antoine Mercadal 2011-01-07 11:32:00 +01:00
commit ab9dcee309
17 changed files with 882 additions and 303 deletions

View File

@ -24,6 +24,23 @@ Running in Chrome before and after connecting:
See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
### Projects/Companies using noVNC
* [Sentry Data Systems](http://www.sentryds.com): uses noVNC in the
[Datanex Cloud Computing Platform](http://www.sentryds.com/products/datanex/).
* [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr):
Feature [#1935](http://code.osuosl.org/issues/1935).
* [Archipel](http://archipelproject.org):
[Video demo](http://antoinemercadal.fr/archipelblog/wp-content/themes/ArchipelWPTemplate/video_youtube.php?title=VNC%20Demonstration&id=te_bzW574Zo)
* [openQRM](http://www.openqrm.com/): VNC plugin available
by request. Probably included in [version
4.8](http://www.openqrm.com/?q=node/15). [Video
demo](http://www.openqrm-enterprise.com/news/details/article/remote-vm-console-plugin-available.html).
### Browser Requirements
* HTML5 Canvas: Except for Internet Explorer, most
@ -120,7 +137,7 @@ There a few reasons why a proxy is required:
`./utils/wsproxy.py -f 8787 localhost:5901`
* To run the mini python web server without the launch script:
* To run a mini python web server without the launch script:
`./utils/web.py PORT`

View File

@ -1,19 +1,10 @@
Short Term:
- Playback/demo on website.
- VNC performance and regression playback suite.
- Keyboard layout/internationalization support
- convert keyCode into proper charCode
- Test on IE 9 preview 3.
- Fix cursor URI detection in Arora:
- allows data URI, but doesn't actually work
Medium Term:
- Viewport support
- Status bar menu/buttons:
- Explanatory hover text over buttons
@ -29,7 +20,20 @@ Medium Term:
- Clipboard button -> popup:
- text, clear and send buttons
- wswrapper:
- Flash policy request support.
- SSL/TLS support.
- Tests suite:
- test pselect/poll/ppoll
Longer Term:
Medium Term:
- VNC performance and regression playback suite.
- Viewport support
- Touchscreen testing/support.
- wswrapper:
- epoll_* support
- Get web-socket-js RFC2817 proxying working again.

View File

@ -35,6 +35,10 @@ Cursor appearance/style (for Cursor pseudo-encoding):
https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
RDP Protocol specification:
http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx
Related projects:
guacamole: http://guacamole.sourceforge.net/

View File

@ -4,39 +4,12 @@ There is an included flash object (web-socket-js) that is used to
emulate websocket support on browsers without websocket support
(currently only Chrome has WebSocket support).
The performance on Chrome is great. It only takes about 150ms on my
system to render a complete 800x600 hextile frame. Most updates are
partial or use copyrect which is much faster.
When using the flash websocket emulator, packets event aren't always
delivered in order. This may be a bug in the way I'm using the
FABridge interface. Or a bug in FABridge itself, or just a browser
event delivery issue. Anyways, to get around this problem, when the
flash emulator is being used, the client issues the WebSocket GET
request with the "seq_num" variable. This switches on in-band sequence
numbering of the packets in the proxy, and the client has a queue of
out-of-order packets (20 deep currently) that it uses to re-order.
Gross!
The performance on firefox 3.5 is usable. It takes 2.3 seconds to
render a 800x600 hextile frame on my system (the initial connect).
Once the initial first update is done though, it's quite usable (as
above, most updates are partial or copyrect). I haven't tested firefox
3.7 yet, it might be a lot faster.
Opera sucks big time. The flash websocket emulator fails to deliver as
many as 40% of packet events when used on Opera. It may or not be
Opera's fault, but something goes badly wrong at the FABridge
boundary.
Javascript doesn't have a bytearray type, so what you get out of
a WebSocket object is just Javascript strings. I believe that
Javascript has UTF-16 unicode strings and anything sent through the
WebSocket gets converted to UTF-8 and vice-versa. So, one additional
(and necessary) function of the proxy is base64 encoding/decoding what
is sent to/from the browser. Another option that I want to explore is
UTF-8 encoding in the proxy.
a WebSocket object is just Javascript strings. Javascript has UTF-16
unicode strings and anything sent through the WebSocket gets converted
to UTF-8 and vice-versa. So, one additional (and necessary) function
of wsproxy is base64 encoding/decoding what is sent to/from the
browser.
Building web-socket-js emulator:

View File

@ -109,6 +109,7 @@ function constructor() {
if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
ctx = conf.ctx;
Util.Debug("User Agent: " + navigator.userAgent);
if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
@ -216,7 +217,7 @@ function constructor() {
}
/* Translate DOM key down/up event to keysym value */
function getKeysym(e) {
that.getKeysym = function(e) {
var evt, keysym;
evt = (e ? e : window.event);
@ -362,24 +363,24 @@ function onMouseMove(e) {
}
function onKeyDown(e) {
//Util.Debug("keydown: " + getKeysym(e));
//Util.Debug("keydown: " + that.getKeysym(e));
if (! conf.focused) {
return true;
}
if (c_keyPress) {
c_keyPress(getKeysym(e), 1);
c_keyPress(that.getKeysym(e), 1);
}
Util.stopEvent(e);
return false;
}
function onKeyUp(e) {
//Util.Debug("keyup: " + getKeysym(e));
//Util.Debug("keyup: " + that.getKeysym(e));
if (! conf.focused) {
return true;
}
if (c_keyPress) {
c_keyPress(getKeysym(e), 0);
c_keyPress(that.getKeysym(e), 0);
}
Util.stopEvent(e);
return false;

View File

@ -258,7 +258,8 @@ function constructor() {
Util.Info("Using native WebSockets");
updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
} else {
Util.Warn("Using web-socket-js flash bridge");
Util.Warn("Using web-socket-js bridge. Flash version: " +
Util.Flash.version);
if ((! Util.Flash) ||
(Util.Flash.version < 9)) {
updateState('fatal', "WebSockets or Adobe Flash is required");
@ -819,9 +820,7 @@ init_msg = function() {
break;
case 'SecurityResult' :
if (rQlen() < 4) {
return fail("Invalid VNC auth response");
}
if (rQwait("VNC auth response ", 4)) { return false; }
switch (rQshift32()) {
case 0: // OK
// Fall through to ClientInitialisation
@ -852,9 +851,7 @@ init_msg = function() {
break;
case 'ServerInitialisation' :
if (rQlen() < 24) {
return fail("Invalid server initialisation");
}
if (rQwait("server initialization", 24)) { return false; }
/* Screen size */
fb_width = rQshift16();

67
tests/keyboard.html Normal file
View File

@ -0,0 +1,67 @@
<html>
<head><title>Input Test</title></head>
<body>
<br><br>
Canvas:<br>
<canvas id="canvas" width="640" height="20"
style="border-style: dotted; border-width: 1px;">
Canvas not supported.
</canvas>
<br>
Results:<br>
<textarea id="messages" style="font-size: 9;" cols=80 rows=25></textarea>
</body>
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="include/util.js"></script>
<script src="include/webutil.js"></script>
<script src="include/base64.js"></script>
<script src="include/canvas.js"></script>
<script>
var msg_cnt = 0;
var width = 400, height = 200;
var canvas;
function message(str) {
console.log(str);
msg_cnt++;
cell = $D('messages');
cell.innerHTML += msg_cnt + ": " + str + "\n";
cell.scrollTop = cell.scrollHeight;
}
function keyDown(evt) {
var e = (evt ? evt : window.event);
msg = "Dn: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " ksym:" + canvas.getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
message(msg);
}
function keyUp(evt) {
var e = (evt ? evt : window.event);
msg = "Up: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " ksym:" + canvas.getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
message(msg);
}
function keyPress(evt) {
var e = (evt ? evt : window.event);
msg = "Pr: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " ksym:" + canvas.getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
message(msg);
}
window.onload = function() {
var c = $D('canvas');
canvas = new Canvas({'target' : c});
canvas.resize(width, height, true);
//canvas.start(keyPress);
Util.addEvent(document, 'keydown', keyDown);
Util.addEvent(document, 'keyup', keyUp);
Util.addEvent(document, 'keypress', keyPress);
message("Canvas initialized");
}
</script>
</html>

View File

@ -6,6 +6,7 @@ all: $(TARGETS)
wsproxy: wsproxy.o websocket.o md5.o
$(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@
wswrapper.o: wswrapper.h
wswrapper.so: wswrapper.o md5.o
$(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -lresolv -o $@

View File

@ -1,7 +1,20 @@
## wsproxy: WebSockets to TCP Proxy
## WebSockets Utilities: wswrapper and wsproxy
### How it works
### wswrapper
wswrapper is an LD_PRELOAD library that converts a TCP listen socket
of an existing program to a be a WebSockets socket. The `wswrap`
script can be used to easily launch a program using wswrapper. Here is
an example of using wswrapper with vncserver. wswrapper will convert
the socket listening on port 5901 to be a WebSockets port:
`cd noVNC/utils`
`./wswrap 5901 vncserver -geometry 640x480 :1`
### wsproxy
At the most basic level, wsproxy just translates WebSockets traffic
to normal socket traffic. wsproxy accepts the WebSockets handshake,
@ -15,7 +28,7 @@ case the data from the client is not a full WebSockets frame (i.e.
does not end in 255).
### Additional features
#### Additional wsproxy features
These are not necessary for the basic operation.
@ -38,44 +51,63 @@ These are not necessary for the basic operation.
option.
### Implementations
#### Implementations of wsproxy
There are three implementations of wsproxy included: python, C, and
Node (node.js).
Here is the feature support matrix for the wsproxy implementations:
There are three implementations of wsproxy: python, C, and Node
(node.js). wswrapper is only implemented in C.
Here is the feature support matrix for the the wsproxy implementations
and wswrapper:
<table>
<tr>
<th>Implementation</th>
<th>Basic Proxying</th>
<th>Multi-process</th>
<th>Daemonizing</th>
<th>Program</th>
<th>Language</th>
<th>Proxy or Interposer</th>
<th>Multiprocess</th>
<th>Daemonize</th>
<th>SSL/wss</th>
<th>Flash Policy Server</th>
<th>Session Recording</th>
<th>Session Record</th>
<th>Web Server</th>
</tr> <tr>
<td>wsproxy.py</td>
<td>python</td>
<td>yes</td>
<td>proxy</td>
<td>yes</td>
<td>yes</td>
<td>yes 1</td>
<td>yes</td>
<td>yes</td>
</tr> <tr>
<td>C</td>
<td>yes</td>
</tr> <tr>
<td>wsproxy</td>
<td>C</td>
<td>proxy</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>no</td>
</tr>
</tr> <tr>
<td>wsproxy.js</td>
<td>Node (node.js)</td>
<td>proxy</td>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>no</td>
<td>no</td>
<td>no</td>
<td>no</td>
</tr>
</tr> <tr>
<td>wswrap/wswrapper.so</td>
<td>shell/C</td>
<td>interposer</td>
<td>indirectly</td>
<td>indirectly</td>
<td>no</td>
<td>no</td>
<td>no</td>
@ -83,6 +115,7 @@ Here is the feature support matrix for the wsproxy implementations:
</tr>
</table>
* Note 1: to use SSL/wss with python 2.5 or older, see the following
section on *Building the Python ssl module*.

View File

@ -5,26 +5,25 @@ usage() {
echo "$*"
echo
fi
echo "Usage: ${NAME} [--web WEB_PORT] [--proxy PROXY_PORT] [--vnc VNC_HOST:PORT]"
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
echo
echo "Starts a mini-webserver and the WebSockets proxy and"
echo "provides a cut and paste URL to go to."
echo
echo " --web WEB_PORT Port to serve web pages at"
echo " Default: 8080"
echo " --proxy PROXY_PORT Port for proxy to listen on"
echo " Default: 8081"
echo " --listen PORT Port for webserver/proxy to listen on"
echo " Default: 6080"
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
echo " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file"
echo " Default: self.pem"
exit 2
}
NAME="$(basename $0)"
HERE=$(readlink -f $(dirname $0))
WEB_PORT="6080"
PROXY_PORT="6081"
HERE="$(cd "$(dirname "$0")" && pwd)"
PORT="6080"
VNC_DEST="localhost:5900"
web_pid=""
CERT=""
proxy_pid=""
die() {
@ -36,10 +35,6 @@ cleanup() {
trap - TERM QUIT INT EXIT
trap "true" CHLD # Ignore cleanup messages
echo
if [ -n "${web_pid}" ]; then
echo "Terminating webserver (${web_pid})"
kill ${web_pid}
fi
if [ -n "${proxy_pid}" ]; then
echo "Terminating WebSockets proxy (${proxy_pid})"
kill ${proxy_pid}
@ -52,12 +47,12 @@ cleanup() {
while [ "$*" ]; do
param=$1; shift; OPTARG=$1
case $param in
--web) WEB_PORT="${OPTARG}"; shift ;;
--proxy) PROXY_PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;;
-h|--help) usage ;;
--listen) PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;;
-h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;;
*) break ;;
*) break ;;
esac
done
@ -65,28 +60,39 @@ done
which netstat >/dev/null 2>&1 \
|| die "Must have netstat installed"
netstat -ltn | grep -qs "${WEB_PORT}.*LISTEN" \
&& die "Port ${WEB_PORT} in use. Try --web WEB_PORT"
netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \
&& die "Port ${PROXY_PORT} in use. Try --proxy PROXY_PORT"
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
&& die "Port ${PORT} in use. Try --listen PORT"
trap "cleanup" TERM QUIT INT EXIT
echo "Starting webserver on port ${WEB_PORT}"
${HERE}/web.py ${WEB_PORT} >/dev/null &
web_pid="$!"
sleep 1
if ps -p ${web_pid} >/dev/null; then
echo "Started webserver (pid: ${web_pid})"
# Find vnc.html
if [ -e "$(pwd)/vnc.html" ]; then
WEB=$(pwd)
elif [ -e "${HERE}/../vnc.html" ]; then
WEB=${HERE}/../
elif [ -e "${HERE}/vnc.html" ]; then
WEB=${HERE}
else
web_pid=
echo "Failed to start webserver"
exit 1
die "Could not find vnc.html"
fi
echo "Starting WebSockets proxy on port ${PROXY_PORT}"
${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} &
# Find self.pem
if [ -n "${CERT}" ]; then
if [ ! -e "${CERT}" ]; then
die "Could not find ${CERT}"
fi
elif [ -e "$(pwd)/self.pem" ]; then
CERT="$(pwd)/self.pem"
elif [ -e "${HERE}/../self.pem" ]; then
CERT="${HERE}/../self.pem"
elif [ -e "${HERE}/self.pem" ]; then
CERT="${HERE}/self.pem"
else
echo "Warning: could not find self.pem"
fi
echo "Starting webserver and WebSockets proxy on port ${PORT}"
${HERE}/wsproxy.py -f --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
proxy_pid="$!"
sleep 1
if ps -p ${proxy_pid} >/dev/null; then
@ -98,8 +104,7 @@ else
fi
echo -e "\n\nNavigate to to this URL:\n"
echo -e " http://$(hostname):${WEB_PORT}/vnc.html?host=$(hostname)&port=${PROXY_PORT}\n"
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
echo -e "Press Ctrl-C to exit\n\n"
wait ${web_pid}
wait ${proxy_pid}

View File

@ -21,6 +21,7 @@ def do_request(connstream, from_addr):
def serve():
bindsocket = socket.socket()
bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#bindsocket.bind(('localhost', PORT))
bindsocket.bind(('', PORT))
bindsocket.listen(5)

View File

@ -187,6 +187,7 @@ int ws_socket_free(ws_ctx_t *ctx) {
ctx->ssl_ctx = NULL;
}
if (ctx->sockfd) {
shutdown(ctx->sockfd, SHUT_RDWR);
close(ctx->sockfd);
ctx->sockfd = 0;
}
@ -350,26 +351,30 @@ ws_ctx_t *do_handshake(int sock) {
handshake[len] = 0;
if (len == 0) {
handler_msg("ignoring empty handshake\n");
close(sock);
return NULL;
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
len = recv(sock, handshake, 1024, 0);
handshake[len] = 0;
handler_msg("sending flash policy response\n");
send(sock, policy_response, sizeof(policy_response), 0);
close(sock);
return NULL;
} else if ((bcmp(handshake, "\x16", 1) == 0) ||
(bcmp(handshake, "\x80", 1) == 0)) {
// SSL
if (! settings.cert) { return NULL; }
if (!settings.cert) {
handler_msg("SSL connection but no cert specified\n");
return NULL;
} else if (access(settings.cert, R_OK) != 0) {
handler_msg("SSL connection but '%s' not found\n",
settings.cert);
return NULL;
}
ws_ctx = ws_socket_ssl(sock, settings.cert, settings.key);
if (! ws_ctx) { return NULL; }
scheme = "wss";
handler_msg("using SSL socket\n");
} else if (settings.ssl_only) {
handler_msg("non-SSL connection disallowed\n");
close(sock);
return NULL;
} else {
ws_ctx = ws_socket(sock);
@ -380,14 +385,12 @@ ws_ctx_t *do_handshake(int sock) {
len = ws_recv(ws_ctx, handshake, 4096);
if (len == 0) {
handler_emsg("Client closed during handshake\n");
close(sock);
return NULL;
}
handshake[len] = 0;
if (!parse_handshake(handshake, &headers)) {
handler_emsg("Invalid WS request\n");
close(sock);
return NULL;
}
@ -524,8 +527,7 @@ void start_server() {
if (pid == 0) { // handler process
ws_ctx = do_handshake(csock);
if (ws_ctx == NULL) {
close(csock);
handler_msg("No connection after handshake");
handler_msg("No connection after handshake\n");
break; // Child process exits
}
@ -533,13 +535,22 @@ void start_server() {
if (pipe_error) {
handler_emsg("Closing due to SIGPIPE\n");
}
close(csock);
handler_msg("handler exit\n");
break; // Child process exits
} else { // parent process
settings.handler_id += 1;
}
}
if (pid == 0) {
if (ws_ctx) {
ws_socket_free(ws_ctx);
} else {
shutdown(csock, SHUT_RDWR);
close(csock);
}
handler_msg("handler exit\n");
} else {
handler_msg("wsproxy exit\n");
}
}

View File

@ -13,6 +13,8 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
import sys, socket, ssl, struct, traceback
import os, resource, errno, signal # daemonizing
from SimpleHTTPServer import SimpleHTTPRequestHandler
from cStringIO import StringIO
from base64 import b64encode, b64decode
try:
from hashlib import md5
@ -31,7 +33,8 @@ settings = {
'key' : None,
'ssl_only' : False,
'daemon' : True,
'record' : None, }
'record' : None,
'web' : False, }
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r
@ -47,6 +50,29 @@ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports
class EClose(Exception):
pass
# HTTP handler with request from a string and response to a socket
class SplitHTTPHandler(SimpleHTTPRequestHandler):
def __init__(self, req, resp, addr):
# Save the response socket
self.response = resp
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
def setup(self):
self.connection = self.response
# Duck type request string to file object
self.rfile = StringIO(self.request)
self.wfile = self.connection.makefile('wb', self.wbufsize)
def send_response(self, code, message=None):
# Save the status code
self.last_code = code
SimpleHTTPRequestHandler.send_response(self, code, message)
def log_message(self, f, *args):
# Save instead of printing
self.last_message = f % args
def traffic(token="."):
if settings['verbose'] and not settings['daemon']:
sys.stdout.write(token)
@ -54,7 +80,10 @@ def traffic(token="."):
def handler_msg(msg):
if not settings['daemon']:
print " %d: %s" % (settings['handler_id'], msg)
print "% 3d: %s" % (settings['handler_id'], msg)
def handler_vmsg(msg):
if settings['verbose']: handler_msg(msg)
def encode(buf):
buf = b64encode(buf)
@ -96,51 +125,77 @@ def gen_md5(keys):
return md5(struct.pack('>II8s', num1, num2, key3)).digest()
def do_handshake(sock):
def do_handshake(sock, address):
stype = ""
# Peek, but don't read the data
handshake = sock.recv(1024, socket.MSG_PEEK)
#handler_msg("Handshake [%s]" % repr(handshake))
if handshake == "":
handler_msg("ignoring empty handshake")
sock.close()
return False
raise EClose("ignoring empty handshake")
elif handshake.startswith("<policy-file-request/>"):
handshake = sock.recv(1024)
handler_msg("Sending flash policy response")
sock.send(policy_response)
sock.close()
return False
raise EClose("Sending flash policy response")
elif handshake[0] in ("\x16", "\x80"):
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=settings['cert'],
keyfile=settings['key'])
if not os.path.exists(settings['cert']):
raise EClose("SSL connection but '%s' not found"
% settings['cert'])
try:
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=settings['cert'],
keyfile=settings['key'])
except ssl.SSLError, x:
if x.args[0] == ssl.SSL_ERROR_EOF:
raise EClose("")
else:
raise
scheme = "wss"
handler_msg("using SSL/TLS")
stype = "SSL/TLS (wss://)"
elif settings['ssl_only']:
handler_msg("non-SSL connection disallowed")
sock.close()
return False
raise EClose("non-SSL connection received but disallowed")
else:
retsock = sock
scheme = "ws"
handler_msg("using plain (not SSL) socket")
stype = "Plain non-SSL (ws://)"
# Now get the data from the socket
handshake = retsock.recv(4096)
#handler_msg("handshake: " + repr(handshake))
if len(handshake) == 0:
raise EClose("Client closed during handshake")
# Handle normal web requests
if handshake.startswith('GET ') and \
handshake.find('Upgrade: WebSocket\r\n') == -1:
if not settings['web']:
raise EClose("Normal web request received but disallowed")
sh = SplitHTTPHandler(handshake, retsock, address)
if sh.last_code < 200 or sh.last_code >= 300:
raise EClose(sh.last_message)
elif settings['verbose']:
raise EClose(sh.last_message)
else:
raise EClose("")
# Do WebSockets handshake and return the socket
h = parse_handshake(handshake)
if h.get('key3'):
trailer = gen_md5(h)
pre = "Sec-"
handler_msg("using protocol version 76")
ver = 76
else:
trailer = ""
pre = ""
handler_msg("using protocol version 75")
ver = 75
handler_msg("%s WebSocket connection (version %s) from %s"
% (stype, ver, address[0]))
response = server_handshake % (pre, h['Origin'], pre, scheme,
h['Host'], h['path'], pre, trailer)
@ -172,8 +227,8 @@ def daemonize(keepfd=None):
try:
if fd != keepfd:
os.close(fd)
elif settings['verbose']:
print "Keeping fd: %d" % fd
else:
handler_vmsg("Keeping fd: %d" % fd)
except OSError, exc:
if exc.errno != errno.EBADF: raise
@ -204,25 +259,25 @@ def start_server():
csock = startsock = None
pid = 0
startsock, address = lsock.accept()
handler_msg('got client connection from %s' % address[0])
handler_msg("forking handler process")
handler_vmsg('%s: forking handler' % address[0])
pid = os.fork()
if pid == 0: # handler process
csock = do_handshake(startsock)
if not csock:
handler_msg("No connection after handshake");
break
csock = do_handshake(startsock, address)
settings['handler'](csock)
else: # parent process
settings['handler_id'] += 1
except EClose, exc:
handler_msg("handler exit: %s" % exc.args)
if csock and csock != startsock:
csock.close()
startsock.close()
if exc.args[0]:
handler_msg("%s: %s" % (address[0], exc.args[0]))
except Exception, exc:
handler_msg("handler exception: %s" % str(exc))
#handler_msg(traceback.format_exc())
if settings['verbose']:
handler_msg(traceback.format_exc())
if pid == 0:
if csock: csock.close()

View File

@ -257,6 +257,10 @@ int main(int argc, char *argv[])
};
settings.cert = realpath("self.pem", NULL);
if (!settings.cert) {
/* Make sure it's always set to something */
settings.cert = "self.pem";
}
settings.key = "";
while (1) {
@ -326,9 +330,11 @@ int main(int argc, char *argv[])
}
if (ssl_only) {
if (!settings.cert || !access(settings.cert, R_OK)) {
usage("SSL only and cert file not found\n");
if (!access(settings.cert, R_OK)) {
usage("SSL only and cert file '%s' not found\n", settings.cert);
}
} else if (access(settings.cert, R_OK) != 0) {
fprintf(stderr, "Warning: '%s' not found\n", settings.cert);
}
//printf(" verbose: %d\n", settings.verbose);

View File

@ -143,6 +143,8 @@ if __name__ == '__main__':
help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted connections")
parser.add_option("--web", default=None, metavar="DIR",
help="run webserver on same port. Serve files from DIR.")
(options, args) = parser.parse_args()
if len(args) > 2: parser.error("Too many arguments")
@ -162,6 +164,8 @@ if __name__ == '__main__':
if options.ssl_only and not os.path.exists(options.cert):
parser.error("SSL only and %s not found" % options.cert)
elif not os.path.exists(options.cert):
print "Warning: %s not found" % options.cert
settings['verbose'] = options.verbose
settings['listen_host'] = host
@ -174,4 +178,7 @@ if __name__ == '__main__':
settings['daemon'] = options.daemon
if options.record:
settings['record'] = os.path.abspath(options.record)
if options.web:
os.chdir = options.web
settings['web'] = options.web
start_server()

View File

@ -3,10 +3,19 @@
* Copyright 2010 Joel Martin
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
*
* Use wswrap to run a program using the wrapper.
* wswrapper.so is meant to be LD preloaded. Use wswrap to run a program using
* wswrapper.so.
*/
/* WARNING: multi-threaded programs may not work */
/*
* Limitations:
* - multi-threaded programs may not work
* - programs using ppoll or epoll will not work correctly
*/
#define DO_MSG 1
//#define DO_DEBUG 1
//#define DO_TRACE 1
#include <stdio.h>
#include <stdlib.h>
@ -14,63 +23,60 @@
#define __USE_GNU 1 // Pull in RTLD_NEXT
#include <dlfcn.h>
#include <poll.h>
#include <sys/poll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <resolv.h> /* base64 encode/decode */
#include <sys/time.h>
#include "md5.h"
//#define DO_DEBUG 1
#ifdef DO_DEBUG
#define DEBUG(...) \
if (DO_DEBUG) { \
fprintf(stderr, "wswrapper: "); \
fprintf(stderr, __VA_ARGS__); \
}
#else
#define DEBUG(...)
#endif
#define MSG(...) \
fprintf(stderr, "wswrapper: "); \
fprintf(stderr, __VA_ARGS__);
#define RET_ERROR(eno, ...) \
fprintf(stderr, "wswrapper error: "); \
fprintf(stderr, __VA_ARGS__); \
errno = eno; \
return -1;
const char _WS_response[] = "\
HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
Upgrade: WebSocket\r\n\
Connection: Upgrade\r\n\
%sWebSocket-Origin: %s\r\n\
%sWebSocket-Location: %s://%s%s\r\n\
%sWebSocket-Protocol: sample\r\n\
\r\n%s";
#define WS_BUFSIZE 65536
/* Buffers and state for each wrapped WebSocket connection */
typedef struct {
char rbuf[WS_BUFSIZE];
char sbuf[WS_BUFSIZE];
int rcarry_cnt;
char rcarry[3];
int newframe;
} _WS_connection;
#include "wswrapper.h"
/*
* If WSWRAP_PORT environment variable is set then listen to the bind fd that
* matches WSWRAP_PORT, otherwise listen to the first socket fd that bind is
* called on.
* matches WSWRAP_PORT
*/
int _WS_listen_fd = -1;
_WS_connection *_WS_connections[65546];
int _WS_nfds = 0;
int _WS_fds[WS_MAX_FDS];
_WS_connection *_WS_connections[65536];
/*
* Utillity routines
*/
/*
* Subtract the `struct timeval' values X and Y, storing the
* result in RESULT. If TS is set then RESULT and X are really
* type-cast `struct timespec` so scale them appropriately.
* Return 1 if the difference is negative or 0, otherwise 0.
*/
int _WS_subtract_time (result, x, y, ts)
struct timeval *result, *x, *y;
{
int scale = ts ? 1000 : 1;
/* Perform the carry for the later subtraction by updating y. */
if ((x->tv_usec / scale) < y->tv_usec) {
int sec = (y->tv_usec - (x->tv_usec / scale)) / 1000000 + 1;
y->tv_usec -= 1000000 * sec;
y->tv_sec += sec;
}
if ((x->tv_usec / scale) - y->tv_usec > 1000000) {
int sec = ((x->tv_usec / scale) - y->tv_usec) / 1000000;
y->tv_usec += 1000000 * sec;
y->tv_sec -= sec;
}
/* Compute the time remaining to wait.
* tv_usec is certainly positive. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_usec = x->tv_usec - (y->tv_usec * scale);
/* Return 1 if result is negative or 0. */
return x->tv_sec <= y->tv_sec;
}
/*
@ -243,6 +249,71 @@ int _WS_handshake(int sockfd)
return ret;
}
/*
* Strip empty WebSockets frames and return a positive value if there is
* enough data to base64 decode (a 4 byte chunk). If nonblock is not set then
* it will block until there is enough data (or until an error occurs).
*/
ssize_t _WS_ready(int sockfd, int nonblock)
{
_WS_connection *ws = _WS_connections[sockfd];
char buf[6];
int count, len, flags, i;
static void * (*rfunc)();
if (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv");
TRACE(">> _WS_ready(%d, %d)\n", sockfd, nonblock);
count = 4 + ws->newframe;
flags = MSG_PEEK;
if (nonblock) {
flags |= MSG_DONTWAIT;
}
while (1) {
len = (int) rfunc(sockfd, buf, count, flags);
if (len < 1) {
TRACE("<< _WS_ready(%d, %d) len < 1, errno: %d\n",
sockfd, nonblock, errno);
return len;
}
if (len >= 2 && buf[0] == '\x00' && buf[1] == '\xff') {
/* Strip emtpy frame */
DEBUG("_WS_ready(%d, %d), strip empty\n", sockfd, nonblock);
len = (int) rfunc(sockfd, buf, 2, 0);
if (len < 2) {
MSG("Failed to strip empty frame headers\n");
TRACE("<< _WS_ready: failed to strip empty frame headers\n");
return len;
} else if (len == 2 && nonblock) {
errno = EAGAIN;
TRACE("<< _WS_ready(%d, %d), len == 2, EAGAIN\n",
sockfd, nonblock);
return -1;
}
continue;
}
if (len < count) {
if (nonblock) {
errno = EAGAIN;
TRACE("<< _WS_ready(%d, %d), len < count, EAGAIN\n",
sockfd, nonblock);
return -1;
} else {
fprintf(stderr, "_WS_ready(%d, %d), loop: len %d, buf:",
sockfd, nonblock, len, (unsigned char) buf[0]);
for (i = 0; i < len; i++) {
fprintf(stderr, "%d", (unsigned char) buf[i]);
}
fprintf(stderr, "\n");
continue;
}
}
TRACE("<< _WS_ready(%d, %d) len: %d\n", sockfd, nonblock, len);
return len;
}
}
/*
* WebSockets recv/read interposer routine
*/
@ -250,7 +321,8 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
size_t len, int flags)
{
_WS_connection *ws = _WS_connections[sockfd];
int rawcount, deccount, left, rawlen, retlen, decodelen;
int rawcount, deccount, left, striplen, decodelen, ready;
ssize_t retlen, rawlen;
int sockflags;
int i;
char *fstart, *fend, *cstart;
@ -259,10 +331,6 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
if (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv");
if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read");
if (len == 0) {
return 0;
}
if (! ws) {
// Not our file descriptor, just pass through
if (recvf) {
@ -271,9 +339,20 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
return (ssize_t) rfunc2(sockfd, buf, len);
}
}
DEBUG("_WS_recv(%d, _, %d) called\n", sockfd, len);
TRACE(">> _WS_recv(%d)\n", sockfd);
if (len == 0) {
TRACE("<< _WS_recv(%d) len == 0\n", sockfd);
return 0;
}
sockflags = fcntl(sockfd, F_GETFL, 0);
if (sockflags & O_NONBLOCK) {
TRACE("_WS_recv(%d, _, %d) with O_NONBLOCK\n", sockfd, len);
} else {
TRACE("_WS_recv(%d, _, %d) without O_NONBLOCK\n", sockfd, len);
}
left = len;
retlen = 0;
@ -309,54 +388,29 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
RET_ERROR(ENOMEM, "recv of %d bytes is larger than buffer\n", rawcount);
}
i = 0;
while (1) {
/* Peek at everything available */
rawlen = (int) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
flags | MSG_PEEK);
if (rawlen <= 0) {
DEBUG("_WS_recv: returning because rawlen %d\n", rawlen);
return (ssize_t) rawlen;
}
fstart = ws->rbuf;
/* Strip empty frames */
if (rawlen >= 2 && fstart[0] == '\x00' && fstart[1] == '\xff') {
rawlen = (int) rfunc(sockfd, ws->rbuf, 2, flags);
if (rawlen != 2) {
RET_ERROR(EIO, "Could not strip empty frame headers\n");
}
continue;
}
fstart[rawlen] = '\x00';
if (rawlen - ws->newframe >= 4) {
/* We have enough to base64 decode at least 1 byte */
break;
}
/* Not enough to base64 decode */
if (sockflags & O_NONBLOCK) {
/* Just tell the caller to call again */
DEBUG("_WS_recv: returning because O_NONBLOCK, rawlen %d\n", rawlen);
errno = EAGAIN;
return -1;
}
/* Repeat until at least 1 byte (4 raw bytes) to decode */
i++;
if (i > 1000000) {
MSG("Could not send final part of frame\n");
ready = _WS_ready(sockfd, 0);
if (ready < 1) {
if (retlen) {
/* We had some carry over, don't error until next call */
errno = 0;
} else {
retlen = ready;
}
TRACE("<< _WS_recv(%d, _, %d) retlen %d\n", sockfd, len, retlen);
return retlen;
}
/*
DEBUG("_WS_recv, left: %d, len: %d, rawlen: %d, newframe: %d, raw: ",
left, len, rawlen, _WS_newframe);
for (i = 0; i < rawlen; i++) {
DEBUG("%u,", (unsigned char) ((char *) fstart)[i]);
/* We have enough data to return something */
/* Peek at everything available */
rawlen = (ssize_t) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
flags | MSG_PEEK);
if (rawlen <= 0) {
RET_ERROR(EPROTO, "Socket was ready but then had failure");
}
DEBUG("\n");
*/
fstart = ws->rbuf;
fstart[rawlen] = '\x00';
if (ws->newframe) {
if (fstart[0] != '\x00') {
@ -389,9 +443,9 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
deccount = fend - fstart;
}
/* Now consume what was processed */
/* Now consume what was processed (if not MSG_PEEK) */
if (flags & MSG_PEEK) {
MSG("*** Got MSG_PEEK ***\n");
DEBUG("_WS_recv(%d, _, %d) MSG_PEEK, not consuming\n", sockfd, len);
} else {
rfunc(sockfd, ws->rbuf, fstart - ws->rbuf + deccount + ws->newframe, flags);
}
@ -423,19 +477,13 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
DEBUG("Saving carry bytes: %u,%u\n", ws->rcarry[0],
ws->rcarry[1]);
} else {
MSG("Waah2!\n");
RET_ERRO(EPROTO, "Too many carry bytes!\n");
}
}
}
((char *) buf)[retlen] = '\x00';
/*
DEBUG("*** recv %s as ", fstart);
for (i = 0; i < retlen; i++) {
DEBUG("%u,", (unsigned char) ((char *) buf)[i]);
}
DEBUG(" (%d -> %d): %d\n", deccount, decodelen, retlen);
*/
TRACE("<< _WS_recv(%d) retlen %d\n", sockfd, retlen);
return retlen;
}
@ -451,7 +499,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
char * target;
int i;
static void * (*sfunc)(), * (*sfunc2)();
if (!sfunc) sfunc = (void *(*)()) dlsym(RTLD_NEXT, "send");
if (!sfunc) sfunc = (void *(*)()) dlsym(RTLD_NEXT, "send");
if (!sfunc2) sfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "write");
if (! ws) {
@ -462,7 +510,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
return (ssize_t) sfunc2(sockfd, buf, len);
}
}
DEBUG("_WS_send(%d, _, %d) called\n", sockfd, len);
TRACE(">> _WS_send(%d, _, %d)\n", sockfd, len);
sockflags = fcntl(sockfd, F_GETFL, 0);
@ -484,7 +532,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
rlen = (int) sfunc(sockfd, ws->sbuf, rawlen, flags);
if (rlen <= 0) {
/* Couldn't send, just return */
TRACE("<< _WS_send(%d, _, %d) send failed, returning\n", sockfd, len);
return rlen;
} else if (rlen < rawlen) {
/* Spin until we can send a whole base64 chunck and frame end */
@ -503,13 +551,14 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
} else if (clen == 0) {
MSG("_WS_send: got clen %d\n", clen);
} else if (!(sockflags & O_NONBLOCK)) {
MSG("_WS_send: clen %d\n", clen);
MSG("<< _WS_send: clen %d\n", clen);
return clen;
}
if (i > 1000000) {
MSG("Could not send final part of frame\n");
RET_ERROR(EIO, "Could not send final part of frame\n");
}
} while (left > 0);
//DEBUG("_WS_send: spins until finished %d\n", i);
DEBUG("_WS_send: spins until finished %d\n", i);
}
@ -529,32 +578,262 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
/* Scale return value for base64 encoding size */
retlen = (retlen*3)/4;
/*
DEBUG("*** send ");
for (i = 0; i < retlen; i++) {
DEBUG("%u,", (unsigned char) ((char *)buf)[i]);
}
DEBUG(" as '%s' (%d)\n", ws->sbuf+1, rlen);
*/
TRACE(">> _WS_send(%d, _, %d) retlen %d\n", sockfd, len, retlen);
return (ssize_t) retlen;
}
/*
* Interpose select/pselect/poll.
*
* WebSocket descriptors are not ready until we have received a frame start
* ('\x00') and at least 4 bytes of base64 encoded data. In addition we may
* have carry-over data from the last 4 bytes of base64 data in which case the
* WebSockets socket is ready even though there might not be data in the raw
* socket itself.
*/
/* Interpose on select (mode==0) and pselect (mode==1) */
int _WS_select(int mode, int nfds, fd_set *readfds,
fd_set *writefds, fd_set *exceptfds,
void *timeptr, const sigset_t *sigmask)
{
_WS_connection *ws;
fd_set carryfds, savefds;
/* Assumes timeptr is two longs whether timeval or timespec */
struct timeval savetv, starttv, nowtv, difftv;
int carrycnt = 0, less = 0;
int ret, i, ready, fd;
static void * (*func0)(), * (*func1)();
if (!func0) func0 = (void *(*)()) dlsym(RTLD_NEXT, "select");
if (!func1) func1 = (void *(*)()) dlsym(RTLD_NEXT, "pselect");
if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) {
if (mode == 0) {
ret = (int) func0(nfds, readfds, writefds, exceptfds,
timeptr);
} else if (mode == 1) {
ret = (int) func1(nfds, readfds, writefds, exceptfds,
timeptr, sigmask);
}
return ret;
}
TRACE(">> _WS_select(%d, %d, _, _, _, _)\n", mode, nfds);
memcpy(&savetv, timeptr, sizeof(savetv));
gettimeofday(&starttv, NULL);
/* If we have carry-over return it right away */
FD_ZERO(&carryfds);
if (readfds) {
memcpy(&savefds, readfds, sizeof(savefds));
for (i = 0; i < _WS_nfds; i++) {
fd = _WS_fds[i];
ws = _WS_connections[fd];
if ((ws->rcarry_cnt) && (FD_ISSET(fd, readfds))) {
FD_SET(fd, &carryfds);
carrycnt++;
}
}
}
if (carrycnt) {
if (writefds) {
FD_ZERO(writefds);
}
if (exceptfds) {
FD_ZERO(writefds);
}
memcpy(readfds, &carryfds, sizeof(carryfds));
TRACE("<< _WS_select(%d, %d, _, _, _, _) carrycnt %d\n",
mode, nfds, carrycnt);
return carrycnt;
}
do {
TRACE(" _WS_select(%d, %d, _, _, _, _) tv/ts: %ld:%ld\n", mode, nfds,
((struct timeval *) timeptr)->tv_sec,
((struct timeval *) timeptr)->tv_usec);
if (mode == 0) {
ret = (int) func0(nfds, readfds, writefds, exceptfds,
timeptr);
} else if (mode == 1) {
ret = (int) func1(nfds, readfds, writefds, exceptfds,
timeptr, sigmask);
}
if (! readfds) {
break;
}
if (ret <= 0) {
break;
}
for (i = 0; i < _WS_nfds; i++) {
fd = _WS_fds[i];
ws = _WS_connections[fd];
if (FD_ISSET(fd, readfds)) {
ready = _WS_ready(fd, 1);
if (ready == 0) {
/* 0 means EOF which is also a ready condition */
DEBUG("_WS_select: detected %d is closed\n", fd);
} else if (ready < 0) {
DEBUG("_WS_select: FD_CLR(%d,readfds) - not enough to decode\n", fd);
FD_CLR(fd, readfds);
ret--;
}
}
}
errno = 0; /* errno could be set by _WS_ready */
if (ret == 0) {
/*
* If all the ready readfds were WebSockets, but none of
* them were really ready (empty frames) then we select again. But
* first restore original values less passage of time.
*/
memcpy(readfds, &savefds, sizeof(savefds));
gettimeofday(&nowtv, NULL);
/* Amount of time that has passed */
_WS_subtract_time(&difftv, &nowtv, &starttv, 0);
/* Subtract from original timout */
less = _WS_subtract_time((struct timeval *) timeptr,
&savetv, &difftv, mode);
if (less) {
/* Timer has expired */
TRACE(" _WS_select expired timer\n", mode, nfds);
break;
}
}
} while (ret == 0);
/* Restore original time value for pselect glibc does */
if (mode == 1) {
memcpy(timeptr, &savetv, sizeof(savetv));
}
TRACE("<< _WS_select(%d, %d, _, _, _, _) ret %d, errno %d\n",
mode, nfds, ret, errno);
return ret;
}
/* Interpose on poll (mode==0) and ppoll (mode==1) */
int _WS_poll(int mode, struct pollfd *fds, nfds_t nfds, int timeout,
struct timespec *ptimeout, sigset_t *sigmask)
{
_WS_connection *ws;
int savetimeout;
struct timespec savets;
struct timeval starttv, nowtv, difftv;
struct pollfd *pfd;
int carrycnt = 0, less = 0;
int ret, i, ready, fd;
static void * (*func0)(), * (*func1)();
if (!func0) func0 = (void *(*)()) dlsym(RTLD_NEXT, "poll");
if (!func1) func1 = (void *(*)()) dlsym(RTLD_NEXT, "ppoll");
if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) {
if (mode == 0) {
ret = (int) func0(fds, nfds, timeout);
} else if (mode == 1) {
ret = (int) func1(fds, nfds, ptimeout, sigmask);
}
return ret;
}
TRACE(">> _WS_poll(%d, %ld, _, _, _, _)\n", mode, nfds);
if (mode == 0) {
savetimeout = timeout;
} else if (mode == 1) {
memcpy(&savets, ptimeout, sizeof(savets));
}
gettimeofday(&starttv, NULL);
do {
TRACE(" _WS_poll(%d, %ld, _, _, _, _) tv/ts: %ld:%ld\n", mode, nfds,
ptimeout->tv_sec, ptimeout->tv_nsec);
if (mode == 0) {
ret = (int) func0(fds, nfds, timeout);
} else if (mode == 1) {
ret = (int) func1(fds, nfds, ptimeout, sigmask);
}
if (ret <= 0) {
break;
}
for (i = 0; i < nfds; i++) {
pfd = &fds[i];
if (! (pfd->events & POLLIN)) {
continue;
}
ws = _WS_connections[pfd->fd];
if (! ws) {
continue;
}
if (ws->rcarry_cnt) {
if (! (pfd->revents & POLLIN)) {
pfd->revents |= POLLIN;
ret++;
}
} else if (pfd->revents & POLLIN) {
ready = _WS_ready(pfd->fd, 1);
if (ready == 0) {
/* 0 means EOF which is also a ready condition */
DEBUG("_WS_poll: detected %d is closed\n", fd);
} else if (ready < 0) {
DEBUG("_WS_poll: not enough to decode\n", fd);
pfd->revents -= POLLIN;
ret--;
}
}
}
errno = 0; /* errno could be set by _WS_ready */
if (ret == 0) {
/*
* If all the ready readfds were WebSockets, but none of
* them were really ready (empty frames) then we select again. But
* first restore original values less passage of time.
*/
gettimeofday(&nowtv, NULL);
/* Amount of time that has passed */
_WS_subtract_time(&difftv, &nowtv, &starttv, 0);
if (mode == 0) {
if (timeout < 0) {
/* Negative timeout means infinite */
continue;
}
timeout -= difftv.tv_sec * 1000 + difftv.tv_usec / 1000;
if (timeout <= 0) {
less = 1;
}
} else if (mode == 1) {
/* Subtract from original timout */
less = _WS_subtract_time((struct timeval *) ptimeout,
(struct timeval *) &savets,
&difftv, 1);
}
if (less) {
/* Timer has expired */
TRACE(" _WS_poll expired timer\n", mode, nfds);
break;
}
}
} while (ret == 0);
/* Restore original time value for pselect glibc does */
if (mode == 1) {
memcpy(ptimeout, &savets, sizeof(savets));
}
TRACE("<< _WS_poll(%d, %ld, _, _, _, _) ret %d, errno %d\n",
mode, nfds, ret, errno);
return ret;
}
/*
* Overload (LD_PRELOAD) standard library network routines
*/
/*
int socket(int domain, int type, int protocol)
{
static void * (*func)();
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "socket");
DEBUG("socket(_, %d, _) called\n", type);
return (int) func(domain, type, protocol);
}
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
static void * (*func)();
@ -562,37 +841,37 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
char * WSWRAP_PORT, * end;
int ret, envport, bindport = htons(addr_in->sin_port);
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
DEBUG("bind(%d, _, %d) called\n", sockfd, addrlen);
TRACE(">> bind(%d, _, %d)\n", sockfd, addrlen);
ret = (int) func(sockfd, addr, addrlen);
if (addr_in->sin_family != AF_INET) {
// TODO: handle IPv6
DEBUG("bind, ignoring non-IPv4 socket\n");
TRACE("<< bind, ignoring non-IPv4 socket\n");
return ret;
}
WSWRAP_PORT = getenv("WSWRAP_PORT");
if ((! WSWRAP_PORT) || (*WSWRAP_PORT == '\0')) {
// TODO: interpose on all sockets
DEBUG("bind, not interposing: WSWRAP_PORT is not set\n");
// TODO: interpose on all sockets when WSWRAP_PORT not set
TRACE("<< bind, not interposing: WSWRAP_PORT is not set\n");
return ret;
}
envport = strtol(WSWRAP_PORT, &end, 10);
if ((envport == 0) || (*end != '\0')) {
MSG("bind, not interposing: WSWRAP_PORT is not a number\n");
TRACE("<< bind, not interposing: WSWRAP_PORT is not a number\n");
return ret;
}
if (envport != bindport) {
DEBUG("bind, not interposing on port: %d (fd %d)\n", bindport, sockfd);
TRACE("<< bind, not interposing on port: %d (fd %d)\n", bindport, sockfd);
return ret;
}
MSG("bind, interposing on port: %d (fd %d)\n", envport, sockfd);
_WS_listen_fd = sockfd;
TRACE("<< bind, interposing on port: %d (fd %d)\n", envport, sockfd);
return ret;
}
@ -601,25 +880,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
int fd, ret, envfd;
static void * (*func)();
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "accept");
DEBUG("accept(%d, _, _) called\n", sockfd);
TRACE("<< accept(%d, _, _)\n", sockfd);
fd = (int) func(sockfd, addr, addrlen);
if (_WS_listen_fd == -1) {
DEBUG("not interposing\n");
TRACE("<< accept: not interposing\n");
return fd;
}
if (_WS_listen_fd != sockfd) {
DEBUG("not interposing on fd %d\n", sockfd);
TRACE("<< accept: not interposing on fd %d\n", sockfd);
return fd;
}
if (_WS_connections[fd]) {
MSG("error, already interposing on fd %d\n", fd);
RET_ERROR(EINVAL, "already interposing on fd %d\n", fd);
} else {
/* It's a port we're interposing on so allocate memory for it */
if (_WS_nfds >= WS_MAX_FDS) {
RET_ERROR(ENOMEM, "Too many interposer fds\n");
}
if (! (_WS_connections[fd] = malloc(sizeof(_WS_connection)))) {
RET_ERROR(ENOMEM, "Could not allocate interposer memory\n");
}
@ -627,11 +909,16 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
_WS_connections[fd]->rcarry[0] = '\0';
_WS_connections[fd]->newframe = 1;
/* Add to search list for select/pselect */
_WS_fds[_WS_nfds] = fd;
_WS_nfds++;
ret = _WS_handshake(fd);
if (ret < 0) {
free(_WS_connections[fd]);
_WS_connections[fd] = NULL;
errno = EPROTO;
TRACE("<< accept(%d, _, _): ret %d\n", sockfd, ret);
return ret;
}
MSG("interposing on fd %d (allocated memory)\n", fd);
@ -642,13 +929,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
int close(int fd)
{
int i;
static void * (*func)();
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close");
if (_WS_connections[fd]) {
TRACE(">> close(%d)\n", fd);
free(_WS_connections[fd]);
_WS_connections[fd] = NULL;
/* Remove from the search list for select/pselect */
for (i = 0; i < _WS_nfds; i++) {
if (_WS_fds[i] == fd) {
break;
}
}
if (_WS_nfds - i - 1 > 0) {
memmove(_WS_fds + i, _WS_fds + i + 1, _WS_nfds - i - 1);
}
_WS_nfds--;
MSG("finished interposing on fd %d (freed memory)\n", fd);
TRACE("<< close(%d)\n", fd);
}
return (int) func(fd);
}
@ -656,25 +958,54 @@ int close(int fd)
ssize_t read(int fd, void *buf, size_t count)
{
//DEBUG("read(%d, _, %d) called\n", fd, count);
//TRACE("read(%d, _, %d) called\n", fd, count);
return (ssize_t) _WS_recv(0, fd, buf, count, 0);
}
ssize_t write(int fd, const void *buf, size_t count)
{
//DEBUG("write(%d, _, %d) called\n", fd, count);
//TRACE("write(%d, _, %d) called\n", fd, count);
return (ssize_t) _WS_send(0, fd, buf, count, 0);
}
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
{
//DEBUG("recv(%d, _, %d, %d) called\n", sockfd, len, flags);
//TRACE("recv(%d, _, %d, %d) called\n", sockfd, len, flags);
return (ssize_t) _WS_recv(1, sockfd, buf, len, flags);
}
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
//DEBUG("send(%d, _, %d, %d) called\n", sockfd, len, flags);
//TRACE("send(%d, _, %d, %d) called\n", sockfd, len, flags);
return (ssize_t) _WS_send(1, sockfd, buf, len, flags);
}
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
//TRACE("select(%d, _, _, _, _) called\n", nfds);
return _WS_select(0, nfds, readfds, writefds, exceptfds,
(void *) timeout, NULL);
}
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask)
{
TRACE("pselect(%d, _, _, _, _, _) called\n", nfds);
return _WS_select(1, nfds, readfds, writefds, exceptfds,
(void *) timeout, sigmask);
}
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
TRACE("poll(_, %ld, %d) called\n", nfds, timeout);
return _WS_poll(0, fds, nfds, timeout, NULL, NULL);
}
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout, const sigset_t *sigmask)
{
TRACE("ppoll(_, %ld, _, _) called\n", nfds);
return _WS_poll(0, fds, nfds, 0, timeout, sigmask);
}

66
utils/wswrapper.h Normal file
View File

@ -0,0 +1,66 @@
/*
* wswrap/wswrapper: Add WebSockets support to any service.
* Copyright 2010 Joel Martin
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
*
* wswrapper.so is meant to be LD preloaded. Use wswrap to run a program using
* wswrapper.so.
*/
#ifdef DO_MSG
#define MSG(...) \
fprintf(stderr, "wswrapper: "); \
fprintf(stderr, __VA_ARGS__);
#else
#define MSG(...)
#endif
#ifdef DO_DEBUG
#define DEBUG(...) \
if (DO_DEBUG) { \
fprintf(stderr, "wswrapper: "); \
fprintf(stderr, __VA_ARGS__); \
}
#else
#define DEBUG(...)
#endif
#ifdef DO_TRACE
#define TRACE(...) \
if (DO_TRACE) { \
fprintf(stderr, "wswrapper: "); \
fprintf(stderr, __VA_ARGS__); \
}
#else
#define TRACE(...)
#endif
#define RET_ERROR(eno, ...) \
fprintf(stderr, "wswrapper error: "); \
fprintf(stderr, __VA_ARGS__); \
errno = eno; \
return -1;
const char _WS_response[] = "\
HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
Upgrade: WebSocket\r\n\
Connection: Upgrade\r\n\
%sWebSocket-Origin: %s\r\n\
%sWebSocket-Location: %s://%s%s\r\n\
%sWebSocket-Protocol: sample\r\n\
\r\n%s";
#define WS_BUFSIZE 65536
#define WS_MAX_FDS 1024
/* Buffers and state for each wrapped WebSocket connection */
typedef struct {
char rbuf[WS_BUFSIZE];
char sbuf[WS_BUFSIZE];
int rcarry_cnt;
char rcarry[3];
int newframe;
} _WS_connection;