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>. 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 ### Browser Requirements
* HTML5 Canvas: Except for Internet Explorer, most * 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` `./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` `./utils/web.py PORT`

View File

@ -1,19 +1,10 @@
Short Term: Short Term:
- Playback/demo on website. - Keyboard layout/internationalization support
- convert keyCode into proper charCode
- VNC performance and regression playback suite.
- Test on IE 9 preview 3. - 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: - Status bar menu/buttons:
- Explanatory hover text over buttons - Explanatory hover text over buttons
@ -29,7 +20,20 @@ Medium Term:
- Clipboard button -> popup: - Clipboard button -> popup:
- text, clear and send buttons - 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 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: Related projects:
guacamole: http://guacamole.sourceforge.net/ 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 emulate websocket support on browsers without websocket support
(currently only Chrome has 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 Javascript doesn't have a bytearray type, so what you get out of
a WebSocket object is just Javascript strings. I believe that a WebSocket object is just Javascript strings. Javascript has UTF-16
Javascript has UTF-16 unicode strings and anything sent through the unicode strings and anything sent through the WebSocket gets converted
WebSocket gets converted to UTF-8 and vice-versa. So, one additional to UTF-8 and vice-versa. So, one additional (and necessary) function
(and necessary) function of the proxy is base64 encoding/decoding what of wsproxy is base64 encoding/decoding what is sent to/from the
is sent to/from the browser. Another option that I want to explore is browser.
UTF-8 encoding in the proxy.
Building web-socket-js emulator: Building web-socket-js emulator:

View File

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

View File

@ -258,7 +258,8 @@ function constructor() {
Util.Info("Using native WebSockets"); Util.Info("Using native WebSockets");
updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
} else { } else {
Util.Warn("Using web-socket-js flash bridge"); Util.Warn("Using web-socket-js bridge. Flash version: " +
Util.Flash.version);
if ((! Util.Flash) || if ((! Util.Flash) ||
(Util.Flash.version < 9)) { (Util.Flash.version < 9)) {
updateState('fatal', "WebSockets or Adobe Flash is required"); updateState('fatal', "WebSockets or Adobe Flash is required");
@ -819,9 +820,7 @@ init_msg = function() {
break; break;
case 'SecurityResult' : case 'SecurityResult' :
if (rQlen() < 4) { if (rQwait("VNC auth response ", 4)) { return false; }
return fail("Invalid VNC auth response");
}
switch (rQshift32()) { switch (rQshift32()) {
case 0: // OK case 0: // OK
// Fall through to ClientInitialisation // Fall through to ClientInitialisation
@ -852,9 +851,7 @@ init_msg = function() {
break; break;
case 'ServerInitialisation' : case 'ServerInitialisation' :
if (rQlen() < 24) { if (rQwait("server initialization", 24)) { return false; }
return fail("Invalid server initialisation");
}
/* Screen size */ /* Screen size */
fb_width = rQshift16(); 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 wsproxy: wsproxy.o websocket.o md5.o
$(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@ $(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@
wswrapper.o: wswrapper.h
wswrapper.so: wswrapper.o md5.o wswrapper.so: wswrapper.o md5.o
$(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -lresolv -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 At the most basic level, wsproxy just translates WebSockets traffic
to normal socket traffic. wsproxy accepts the WebSockets handshake, 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). does not end in 255).
### Additional features #### Additional wsproxy features
These are not necessary for the basic operation. These are not necessary for the basic operation.
@ -38,44 +51,63 @@ These are not necessary for the basic operation.
option. option.
### Implementations #### Implementations of wsproxy
There are three implementations of wsproxy included: python, C, and There are three implementations of wsproxy: python, C, and Node
Node (node.js). (node.js). wswrapper is only implemented in C.
Here is the feature support matrix for the wsproxy implementations:
Here is the feature support matrix for the the wsproxy implementations
and wswrapper:
<table> <table>
<tr> <tr>
<th>Implementation</th> <th>Program</th>
<th>Basic Proxying</th> <th>Language</th>
<th>Multi-process</th> <th>Proxy or Interposer</th>
<th>Daemonizing</th> <th>Multiprocess</th>
<th>Daemonize</th>
<th>SSL/wss</th> <th>SSL/wss</th>
<th>Flash Policy Server</th> <th>Flash Policy Server</th>
<th>Session Recording</th> <th>Session Record</th>
<th>Web Server</th>
</tr> <tr> </tr> <tr>
<td>wsproxy.py</td>
<td>python</td> <td>python</td>
<td>yes</td> <td>proxy</td>
<td>yes</td> <td>yes</td>
<td>yes</td> <td>yes</td>
<td>yes 1</td> <td>yes 1</td>
<td>yes</td> <td>yes</td>
<td>yes</td> <td>yes</td>
</tr> <tr>
<td>C</td>
<td>yes</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>yes</td> <td>yes</td>
<td>yes</td> <td>yes</td>
<td>no</td> <td>no</td>
<td>no</td>
</tr> </tr>
</tr> <tr> </tr> <tr>
<td>wsproxy.js</td>
<td>Node (node.js)</td> <td>Node (node.js)</td>
<td>proxy</td>
<td>yes</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> <td>no</td>
<td>no</td> <td>no</td>
@ -83,6 +115,7 @@ Here is the feature support matrix for the wsproxy implementations:
</tr> </tr>
</table> </table>
* Note 1: to use SSL/wss with python 2.5 or older, see the following * Note 1: to use SSL/wss with python 2.5 or older, see the following
section on *Building the Python ssl module*. section on *Building the Python ssl module*.

View File

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

View File

@ -187,6 +187,7 @@ int ws_socket_free(ws_ctx_t *ctx) {
ctx->ssl_ctx = NULL; ctx->ssl_ctx = NULL;
} }
if (ctx->sockfd) { if (ctx->sockfd) {
shutdown(ctx->sockfd, SHUT_RDWR);
close(ctx->sockfd); close(ctx->sockfd);
ctx->sockfd = 0; ctx->sockfd = 0;
} }
@ -350,26 +351,30 @@ ws_ctx_t *do_handshake(int sock) {
handshake[len] = 0; handshake[len] = 0;
if (len == 0) { if (len == 0) {
handler_msg("ignoring empty handshake\n"); handler_msg("ignoring empty handshake\n");
close(sock);
return NULL; return NULL;
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) { } else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
len = recv(sock, handshake, 1024, 0); len = recv(sock, handshake, 1024, 0);
handshake[len] = 0; handshake[len] = 0;
handler_msg("sending flash policy response\n"); handler_msg("sending flash policy response\n");
send(sock, policy_response, sizeof(policy_response), 0); send(sock, policy_response, sizeof(policy_response), 0);
close(sock);
return NULL; return NULL;
} else if ((bcmp(handshake, "\x16", 1) == 0) || } else if ((bcmp(handshake, "\x16", 1) == 0) ||
(bcmp(handshake, "\x80", 1) == 0)) { (bcmp(handshake, "\x80", 1) == 0)) {
// SSL // 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); ws_ctx = ws_socket_ssl(sock, settings.cert, settings.key);
if (! ws_ctx) { return NULL; } if (! ws_ctx) { return NULL; }
scheme = "wss"; scheme = "wss";
handler_msg("using SSL socket\n"); handler_msg("using SSL socket\n");
} else if (settings.ssl_only) { } else if (settings.ssl_only) {
handler_msg("non-SSL connection disallowed\n"); handler_msg("non-SSL connection disallowed\n");
close(sock);
return NULL; return NULL;
} else { } else {
ws_ctx = ws_socket(sock); ws_ctx = ws_socket(sock);
@ -380,14 +385,12 @@ ws_ctx_t *do_handshake(int sock) {
len = ws_recv(ws_ctx, handshake, 4096); len = ws_recv(ws_ctx, handshake, 4096);
if (len == 0) { if (len == 0) {
handler_emsg("Client closed during handshake\n"); handler_emsg("Client closed during handshake\n");
close(sock);
return NULL; return NULL;
} }
handshake[len] = 0; handshake[len] = 0;
if (!parse_handshake(handshake, &headers)) { if (!parse_handshake(handshake, &headers)) {
handler_emsg("Invalid WS request\n"); handler_emsg("Invalid WS request\n");
close(sock);
return NULL; return NULL;
} }
@ -524,8 +527,7 @@ void start_server() {
if (pid == 0) { // handler process if (pid == 0) { // handler process
ws_ctx = do_handshake(csock); ws_ctx = do_handshake(csock);
if (ws_ctx == NULL) { if (ws_ctx == NULL) {
close(csock); handler_msg("No connection after handshake\n");
handler_msg("No connection after handshake");
break; // Child process exits break; // Child process exits
} }
@ -533,13 +535,22 @@ void start_server() {
if (pipe_error) { if (pipe_error) {
handler_emsg("Closing due to SIGPIPE\n"); handler_emsg("Closing due to SIGPIPE\n");
} }
close(csock);
handler_msg("handler exit\n");
break; // Child process exits break; // Child process exits
} else { // parent process } else { // parent process
settings.handler_id += 1; 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 sys, socket, ssl, struct, traceback
import os, resource, errno, signal # daemonizing import os, resource, errno, signal # daemonizing
from SimpleHTTPServer import SimpleHTTPRequestHandler
from cStringIO import StringIO
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
try: try:
from hashlib import md5 from hashlib import md5
@ -31,7 +33,8 @@ settings = {
'key' : None, 'key' : None,
'ssl_only' : False, 'ssl_only' : False,
'daemon' : True, 'daemon' : True,
'record' : None, } 'record' : None,
'web' : False, }
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
@ -47,6 +50,29 @@ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports
class EClose(Exception): class EClose(Exception):
pass 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="."): def traffic(token="."):
if settings['verbose'] and not settings['daemon']: if settings['verbose'] and not settings['daemon']:
sys.stdout.write(token) sys.stdout.write(token)
@ -54,7 +80,10 @@ def traffic(token="."):
def handler_msg(msg): def handler_msg(msg):
if not settings['daemon']: 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): def encode(buf):
buf = b64encode(buf) buf = b64encode(buf)
@ -96,51 +125,77 @@ def gen_md5(keys):
return md5(struct.pack('>II8s', num1, num2, key3)).digest() 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 # Peek, but don't read the data
handshake = sock.recv(1024, socket.MSG_PEEK) handshake = sock.recv(1024, socket.MSG_PEEK)
#handler_msg("Handshake [%s]" % repr(handshake)) #handler_msg("Handshake [%s]" % repr(handshake))
if handshake == "": if handshake == "":
handler_msg("ignoring empty handshake") raise EClose("ignoring empty handshake")
sock.close()
return False
elif handshake.startswith("<policy-file-request/>"): elif handshake.startswith("<policy-file-request/>"):
handshake = sock.recv(1024) handshake = sock.recv(1024)
handler_msg("Sending flash policy response")
sock.send(policy_response) sock.send(policy_response)
sock.close() raise EClose("Sending flash policy response")
return False
elif handshake[0] in ("\x16", "\x80"): elif handshake[0] in ("\x16", "\x80"):
if not os.path.exists(settings['cert']):
raise EClose("SSL connection but '%s' not found"
% settings['cert'])
try:
retsock = ssl.wrap_socket( retsock = ssl.wrap_socket(
sock, sock,
server_side=True, server_side=True,
certfile=settings['cert'], certfile=settings['cert'],
keyfile=settings['key']) keyfile=settings['key'])
except ssl.SSLError, x:
if x.args[0] == ssl.SSL_ERROR_EOF:
raise EClose("")
else:
raise
scheme = "wss" scheme = "wss"
handler_msg("using SSL/TLS") stype = "SSL/TLS (wss://)"
elif settings['ssl_only']: elif settings['ssl_only']:
handler_msg("non-SSL connection disallowed") raise EClose("non-SSL connection received but disallowed")
sock.close()
return False
else: else:
retsock = sock retsock = sock
scheme = "ws" 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) handshake = retsock.recv(4096)
#handler_msg("handshake: " + repr(handshake)) #handler_msg("handshake: " + repr(handshake))
if len(handshake) == 0: if len(handshake) == 0:
raise EClose("Client closed during handshake") 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) h = parse_handshake(handshake)
if h.get('key3'): if h.get('key3'):
trailer = gen_md5(h) trailer = gen_md5(h)
pre = "Sec-" pre = "Sec-"
handler_msg("using protocol version 76") ver = 76
else: else:
trailer = "" trailer = ""
pre = "" 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, response = server_handshake % (pre, h['Origin'], pre, scheme,
h['Host'], h['path'], pre, trailer) h['Host'], h['path'], pre, trailer)
@ -172,8 +227,8 @@ def daemonize(keepfd=None):
try: try:
if fd != keepfd: if fd != keepfd:
os.close(fd) os.close(fd)
elif settings['verbose']: else:
print "Keeping fd: %d" % fd handler_vmsg("Keeping fd: %d" % fd)
except OSError, exc: except OSError, exc:
if exc.errno != errno.EBADF: raise if exc.errno != errno.EBADF: raise
@ -204,25 +259,25 @@ def start_server():
csock = startsock = None csock = startsock = None
pid = 0 pid = 0
startsock, address = lsock.accept() startsock, address = lsock.accept()
handler_msg('got client connection from %s' % address[0]) handler_vmsg('%s: forking handler' % address[0])
handler_msg("forking handler process")
pid = os.fork() pid = os.fork()
if pid == 0: # handler process if pid == 0: # handler process
csock = do_handshake(startsock) csock = do_handshake(startsock, address)
if not csock:
handler_msg("No connection after handshake");
break
settings['handler'](csock) settings['handler'](csock)
else: # parent process else: # parent process
settings['handler_id'] += 1 settings['handler_id'] += 1
except EClose, exc: 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: except Exception, exc:
handler_msg("handler exception: %s" % str(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 pid == 0:
if csock: csock.close() if csock: csock.close()

View File

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

View File

@ -143,6 +143,8 @@ if __name__ == '__main__':
help="SSL key file (if separate from cert)") help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true", parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted connections") 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() (options, args) = parser.parse_args()
if len(args) > 2: parser.error("Too many arguments") 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): if options.ssl_only and not os.path.exists(options.cert):
parser.error("SSL only and %s not found" % 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['verbose'] = options.verbose
settings['listen_host'] = host settings['listen_host'] = host
@ -174,4 +178,7 @@ if __name__ == '__main__':
settings['daemon'] = options.daemon settings['daemon'] = options.daemon
if options.record: if options.record:
settings['record'] = os.path.abspath(options.record) settings['record'] = os.path.abspath(options.record)
if options.web:
os.chdir = options.web
settings['web'] = options.web
start_server() start_server()

View File

@ -3,10 +3,19 @@
* Copyright 2010 Joel Martin * Copyright 2010 Joel Martin
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) * 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 <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -14,63 +23,60 @@
#define __USE_GNU 1 // Pull in RTLD_NEXT #define __USE_GNU 1 // Pull in RTLD_NEXT
#include <dlfcn.h> #include <dlfcn.h>
#include <poll.h>
#include <sys/poll.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <resolv.h> /* base64 encode/decode */ #include <resolv.h> /* base64 encode/decode */
#include <sys/time.h>
#include "md5.h" #include "md5.h"
#include "wswrapper.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;
/* /*
* If WSWRAP_PORT environment variable is set then listen to the bind fd that * 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 * matches WSWRAP_PORT
* called on.
*/ */
int _WS_listen_fd = -1; 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; 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 * 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) size_t len, int flags)
{ {
_WS_connection *ws = _WS_connections[sockfd]; _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 sockflags;
int i; int i;
char *fstart, *fend, *cstart; 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 (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv");
if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read"); if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read");
if (len == 0) {
return 0;
}
if (! ws) { if (! ws) {
// Not our file descriptor, just pass through // Not our file descriptor, just pass through
if (recvf) { if (recvf) {
@ -271,9 +339,20 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
return (ssize_t) rfunc2(sockfd, buf, len); 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); 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; left = len;
retlen = 0; 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); RET_ERROR(ENOMEM, "recv of %d bytes is larger than buffer\n", rawcount);
} }
i = 0; ready = _WS_ready(sockfd, 0);
while (1) { 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;
}
/* We have enough data to return something */
/* Peek at everything available */ /* Peek at everything available */
rawlen = (int) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1, rawlen = (ssize_t) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
flags | MSG_PEEK); flags | MSG_PEEK);
if (rawlen <= 0) { if (rawlen <= 0) {
DEBUG("_WS_recv: returning because rawlen %d\n", rawlen); RET_ERROR(EPROTO, "Socket was ready but then had failure");
return (ssize_t) rawlen;
} }
fstart = ws->rbuf; 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'; 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");
}
}
/*
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]);
}
DEBUG("\n");
*/
if (ws->newframe) { if (ws->newframe) {
if (fstart[0] != '\x00') { if (fstart[0] != '\x00') {
@ -389,9 +443,9 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
deccount = fend - fstart; deccount = fend - fstart;
} }
/* Now consume what was processed */ /* Now consume what was processed (if not MSG_PEEK) */
if (flags & MSG_PEEK) { if (flags & MSG_PEEK) {
MSG("*** Got MSG_PEEK ***\n"); DEBUG("_WS_recv(%d, _, %d) MSG_PEEK, not consuming\n", sockfd, len);
} else { } else {
rfunc(sockfd, ws->rbuf, fstart - ws->rbuf + deccount + ws->newframe, flags); 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], DEBUG("Saving carry bytes: %u,%u\n", ws->rcarry[0],
ws->rcarry[1]); ws->rcarry[1]);
} else { } else {
MSG("Waah2!\n"); RET_ERRO(EPROTO, "Too many carry bytes!\n");
} }
} }
} }
((char *) buf)[retlen] = '\x00'; ((char *) buf)[retlen] = '\x00';
/* TRACE("<< _WS_recv(%d) retlen %d\n", sockfd, retlen);
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);
*/
return retlen; return retlen;
} }
@ -462,7 +510,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
return (ssize_t) sfunc2(sockfd, buf, len); 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); 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); rlen = (int) sfunc(sockfd, ws->sbuf, rawlen, flags);
if (rlen <= 0) { if (rlen <= 0) {
/* Couldn't send, just return */ TRACE("<< _WS_send(%d, _, %d) send failed, returning\n", sockfd, len);
return rlen; return rlen;
} else if (rlen < rawlen) { } else if (rlen < rawlen) {
/* Spin until we can send a whole base64 chunck and frame end */ /* 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) { } else if (clen == 0) {
MSG("_WS_send: got clen %d\n", clen); MSG("_WS_send: got clen %d\n", clen);
} else if (!(sockflags & O_NONBLOCK)) { } else if (!(sockflags & O_NONBLOCK)) {
MSG("_WS_send: clen %d\n", clen); MSG("<< _WS_send: clen %d\n", clen);
return clen; return clen;
} }
if (i > 1000000) { 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); } while (left > 0);
//DEBUG("_WS_send: spins until finished %d\n", i);
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 */ /* Scale return value for base64 encoding size */
retlen = (retlen*3)/4; retlen = (retlen*3)/4;
/* TRACE(">> _WS_send(%d, _, %d) retlen %d\n", sockfd, len, retlen);
DEBUG("*** send ");
for (i = 0; i < retlen; i++) {
DEBUG("%u,", (unsigned char) ((char *)buf)[i]);
}
DEBUG(" as '%s' (%d)\n", ws->sbuf+1, rlen);
*/
return (ssize_t) 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 * 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) int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{ {
static void * (*func)(); static void * (*func)();
@ -562,37 +841,37 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
char * WSWRAP_PORT, * end; char * WSWRAP_PORT, * end;
int ret, envport, bindport = htons(addr_in->sin_port); int ret, envport, bindport = htons(addr_in->sin_port);
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind"); 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); ret = (int) func(sockfd, addr, addrlen);
if (addr_in->sin_family != AF_INET) { if (addr_in->sin_family != AF_INET) {
// TODO: handle IPv6 // TODO: handle IPv6
DEBUG("bind, ignoring non-IPv4 socket\n"); TRACE("<< bind, ignoring non-IPv4 socket\n");
return ret; return ret;
} }
WSWRAP_PORT = getenv("WSWRAP_PORT"); WSWRAP_PORT = getenv("WSWRAP_PORT");
if ((! WSWRAP_PORT) || (*WSWRAP_PORT == '\0')) { if ((! WSWRAP_PORT) || (*WSWRAP_PORT == '\0')) {
// TODO: interpose on all sockets // TODO: interpose on all sockets when WSWRAP_PORT not set
DEBUG("bind, not interposing: WSWRAP_PORT is not set\n"); TRACE("<< bind, not interposing: WSWRAP_PORT is not set\n");
return ret; return ret;
} }
envport = strtol(WSWRAP_PORT, &end, 10); envport = strtol(WSWRAP_PORT, &end, 10);
if ((envport == 0) || (*end != '\0')) { 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; return ret;
} }
if (envport != bindport) { 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; return ret;
} }
MSG("bind, interposing on port: %d (fd %d)\n", envport, sockfd);
_WS_listen_fd = sockfd; _WS_listen_fd = sockfd;
TRACE("<< bind, interposing on port: %d (fd %d)\n", envport, sockfd);
return ret; return ret;
} }
@ -601,25 +880,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
int fd, ret, envfd; int fd, ret, envfd;
static void * (*func)(); static void * (*func)();
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "accept"); 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); fd = (int) func(sockfd, addr, addrlen);
if (_WS_listen_fd == -1) { if (_WS_listen_fd == -1) {
DEBUG("not interposing\n"); TRACE("<< accept: not interposing\n");
return fd; return fd;
} }
if (_WS_listen_fd != sockfd) { if (_WS_listen_fd != sockfd) {
DEBUG("not interposing on fd %d\n", sockfd); TRACE("<< accept: not interposing on fd %d\n", sockfd);
return fd; return fd;
} }
if (_WS_connections[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 { } else {
/* It's a port we're interposing on so allocate memory for it */ /* 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)))) { if (! (_WS_connections[fd] = malloc(sizeof(_WS_connection)))) {
RET_ERROR(ENOMEM, "Could not allocate interposer memory\n"); 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]->rcarry[0] = '\0';
_WS_connections[fd]->newframe = 1; _WS_connections[fd]->newframe = 1;
/* Add to search list for select/pselect */
_WS_fds[_WS_nfds] = fd;
_WS_nfds++;
ret = _WS_handshake(fd); ret = _WS_handshake(fd);
if (ret < 0) { if (ret < 0) {
free(_WS_connections[fd]); free(_WS_connections[fd]);
_WS_connections[fd] = NULL; _WS_connections[fd] = NULL;
errno = EPROTO; errno = EPROTO;
TRACE("<< accept(%d, _, _): ret %d\n", sockfd, ret);
return ret; return ret;
} }
MSG("interposing on fd %d (allocated memory)\n", fd); 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 close(int fd)
{ {
int i;
static void * (*func)(); static void * (*func)();
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close"); if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close");
if (_WS_connections[fd]) { if (_WS_connections[fd]) {
TRACE(">> close(%d)\n", fd);
free(_WS_connections[fd]); free(_WS_connections[fd]);
_WS_connections[fd] = NULL; _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); MSG("finished interposing on fd %d (freed memory)\n", fd);
TRACE("<< close(%d)\n", fd);
} }
return (int) func(fd); return (int) func(fd);
} }
@ -656,25 +958,54 @@ int close(int fd)
ssize_t read(int fd, void *buf, size_t count) 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); return (ssize_t) _WS_recv(0, fd, buf, count, 0);
} }
ssize_t write(int fd, const void *buf, size_t count) 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); return (ssize_t) _WS_send(0, fd, buf, count, 0);
} }
ssize_t recv(int sockfd, void *buf, size_t len, int flags) 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); return (ssize_t) _WS_recv(1, sockfd, buf, len, flags);
} }
ssize_t send(int sockfd, const void *buf, size_t len, int 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); 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;