From a22a3cc0ea18e3006ace279c54a08d23c9c941e1 Mon Sep 17 00:00:00 2001 From: Amir Malik Date: Tue, 21 Dec 2010 10:11:15 +0800 Subject: [PATCH 01/18] set socket option SO_REUSEADDR to prevent "Address already in use" error --- utils/web.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/web.py b/utils/web.py index 80d11d55..6b7670b0 100755 --- a/utils/web.py +++ b/utils/web.py @@ -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) From b992f7c7ddb0c783c042912716165a6dce034ec0 Mon Sep 17 00:00:00 2001 From: Amir Malik Date: Tue, 21 Dec 2010 10:02:43 +0800 Subject: [PATCH 02/18] use cd, dirname, and pwd to determine path instead of readlink (Mac fix) --- utils/launch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/launch.sh b/utils/launch.sh index 7c3f6b6e..aeb875c8 100755 --- a/utils/launch.sh +++ b/utils/launch.sh @@ -20,7 +20,7 @@ usage() { } NAME="$(basename $0)" -HERE=$(readlink -f $(dirname $0)) +HERE="$(cd "$(dirname "$0")" && pwd)" WEB_PORT="6080" PROXY_PORT="6081" VNC_DEST="localhost:5900" From 0f7f146f200788c6046404016694fc5aec778a0a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 21 Dec 2010 10:17:43 -0600 Subject: [PATCH 03/18] utils/launch.sh: find top web dir (with vnc.html). --- utils/launch.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/utils/launch.sh b/utils/launch.sh index aeb875c8..1ee7d15b 100755 --- a/utils/launch.sh +++ b/utils/launch.sh @@ -73,6 +73,18 @@ netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \ trap "cleanup" TERM QUIT INT EXIT +# Find vnc.html +if [ -e "$(pwd)/vnc.html" ]; then + TOP=$(pwd) +elif [ -e "${HERE}/../vnc.html" ]; then + TOP=${HERE}/../ +elif [ -e "${HERE}/vnc.html" ]; then + TOP=${HERE} +else + die "Could not find vnc.html" +fi +cd ${TOP} + echo "Starting webserver on port ${WEB_PORT}" ${HERE}/web.py ${WEB_PORT} >/dev/null & web_pid="$!" From 318d47343880a533ed9fb7dc12b368c72af6037c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 24 Dec 2010 15:02:48 -0700 Subject: [PATCH 04/18] Tolerate fragmented ServerInit. Issue #39: https://github.com/kanaka/noVNC/issues/issue/39 --- include/rfb.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index d69595d2..96763958 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -815,9 +815,7 @@ init_msg = function() { break; case 'SecurityResult' : - if (rQlen() < 4) { - return fail("Invalid VNC auth response"); - } + if (rQwait("VNC auth response ", 24)) { return false; } switch (rQshift32()) { case 0: // OK // Fall through to ClientInitialisation @@ -848,9 +846,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(); From fce6ac5cb4771419dbd0cdf5f6b8456c8e19f814 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 24 Dec 2010 17:39:13 -0700 Subject: [PATCH 05/18] Fix to "Tolerate fragmented ServerInit". Issue #39: https://github.com/kanaka/noVNC/issues/issue/39 --- include/rfb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfb.js b/include/rfb.js index 96763958..7536ddc8 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -815,7 +815,7 @@ init_msg = function() { break; case 'SecurityResult' : - if (rQwait("VNC auth response ", 24)) { return false; } + if (rQwait("VNC auth response ", 4)) { return false; } switch (rQshift32()) { case 0: // OK // Fall through to ClientInitialisation From 6b900d25d06a0a83ae1eb67608286786e85c318f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 27 Dec 2010 13:15:59 -0700 Subject: [PATCH 06/18] wswrapper: interpose select/pselect. Cleaup. Interpose on select/pselect so that WebSockets sockets are only reported as ready if they have enough to actually decode at least 1 byte of real data. This prevents hanging in read/recv after WebSocket is reported as ready but is not actually ready because empty frames or less than four base64 bytes have been received. Split defines and constant defintions into wswrapper.h. Cleanup debug output and add TRACE for more detailed tracing debug output. Major TODO is that select needs to timeout if WebSocket socket keeps reporting ready but actually isn't ready. That condition will currently hang forever because the select timeout value is not adjusted when looping. --- utils/Makefile | 1 + utils/wswrapper.c | 420 +++++++++++++++++++++++++++++++--------------- utils/wswrapper.h | 65 +++++++ 3 files changed, 353 insertions(+), 133 deletions(-) create mode 100644 utils/wswrapper.h diff --git a/utils/Makefile b/utils/Makefile index d816e772..646adcb4 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -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 $@ diff --git a/utils/wswrapper.c b/utils/wswrapper.c index ca4bd987..b3fd716a 100644 --- a/utils/wswrapper.c +++ b/utils/wswrapper.c @@ -3,10 +3,15 @@ * 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 + */ #include #include @@ -14,55 +19,13 @@ #define __USE_GNU 1 // Pull in RTLD_NEXT #include +#include #include #include #include #include /* base64 encode/decode */ #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 @@ -70,7 +33,9 @@ typedef struct { * called on. */ 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]; /* @@ -243,6 +208,71 @@ int _WS_handshake(int sockfd) return ret; } +/* + * Check WebSockets socket 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 +280,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 +290,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 +298,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 +347,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') { @@ -391,7 +404,7 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf, /* Now consume what was processed */ 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 +436,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 +458,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 +469,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 +491,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 +510,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,16 +537,114 @@ 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; } +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; + struct timeval savetv; + struct timespec savets; + int carrycnt = 0; + int ret, i, ready, fd; + static void * (*func)(), * (*func2)(); + if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "select"); + if (!func2) func2 = (void *(*)()) dlsym(RTLD_NEXT, "pselect"); + + if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) { + if (mode == 0) { + ret = (int) func(nfds, readfds, writefds, exceptfds, + (struct timeval *)timeptr); + } else if (mode == 1) { + ret = (int) func2(nfds, readfds, writefds, exceptfds, + (struct timespec *)timeptr, sigmask); + } + return ret; + } + + TRACE(">> _WS_select(%d, %d, _, _, _, _) called\n", mode, nfds); + memcpy(&savetv, timeptr, sizeof(savetv)); + memcpy(&savets, timeptr, sizeof(savets)); + + /* 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 { + if (mode == 0) { + ret = (int) func(nfds, readfds, writefds, exceptfds, + (struct timeval *)timeptr); + } else if (mode == 1) { + ret = (int) func2(nfds, readfds, writefds, exceptfds, + (struct timespec *)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 all the ready readfds were WebSockets, but none of + * them were really ready (empty frames) then repeat. + */ + if (ret == 0) { + /* Restore original values*/ + /* TODO: fix timeptr for repeat */ + memcpy(timeptr, &savetv, sizeof(savetv)); + memcpy(timeptr, &savets, sizeof(savets)); + memcpy(readfds, &savefds, sizeof(savefds)); + } + } while (ret == 0); + + TRACE("<< _WS_select(%d, %d, _, _, _, _) ret %d, errno %d\n", + mode, nfds, ret, errno); + return ret; +} /* * Overload (LD_PRELOAD) standard library network routines @@ -562,37 +668,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) called\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 +707,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, _, _) called\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 +736,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 +756,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 +785,50 @@ 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(_, %d, _) called\n", nfds); + static void * (*func)(); + if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "poll"); + + return (int) func(fds, nfds, timeout); +} diff --git a/utils/wswrapper.h b/utils/wswrapper.h new file mode 100644 index 00000000..48d4aa2d --- /dev/null +++ b/utils/wswrapper.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +//#define DO_DEBUG 1 +//#define DO_TRACE 1 + +#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 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 +#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; + + From 64dbc6bb6398e46c465f6a82d1936dbe13b858d8 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 27 Dec 2010 16:08:27 -0700 Subject: [PATCH 07/18] wswrapper: timeout select. The select call needs to timeout if a WebSocket socket keeps reporting ready but actually isn't ready. To prevent it hanging forever in that condition, the timeout value is now adjusted now for each call. Move the DO_DEBUG and DO_TRACE settings to wswrapper.c. --- utils/wswrapper.c | 106 +++++++++++++++++++++++++++++++++++----------- utils/wswrapper.h | 3 -- 2 files changed, 81 insertions(+), 28 deletions(-) diff --git a/utils/wswrapper.c b/utils/wswrapper.c index b3fd716a..9adfda56 100644 --- a/utils/wswrapper.c +++ b/utils/wswrapper.c @@ -13,6 +13,9 @@ * - programs using ppoll or epoll will not work correctly */ +#define DO_DEBUG 1 +#define DO_TRACE 1 + #include #include @@ -24,13 +27,13 @@ #include #include #include /* base64 encode/decode */ +#include #include "md5.h" #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; int _WS_nfds = 0; @@ -38,6 +41,42 @@ 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, 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. */ + return x->tv_sec < y->tv_sec; +} + + /* * WebSocket handshake routines */ @@ -209,9 +248,9 @@ int _WS_handshake(int sockfd) } /* - * Check WebSockets socket 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). + * 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) { @@ -402,7 +441,7 @@ 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) { DEBUG("_WS_recv(%d, _, %d) MSG_PEEK, not consuming\n", sockfd, len); } else { @@ -547,28 +586,28 @@ int _WS_select(int mode, int nfds, fd_set *readfds, { _WS_connection *ws; fd_set carryfds, savefds; - struct timeval savetv; - struct timespec savets; + /* Assumes timeptr is two longs whether timeval or timespec */ + struct timeval savetv, starttv, nowtv, difftv; int carrycnt = 0; - int ret, i, ready, fd; - static void * (*func)(), * (*func2)(); - if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "select"); - if (!func2) func2 = (void *(*)()) dlsym(RTLD_NEXT, "pselect"); + int ret, less, 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) func(nfds, readfds, writefds, exceptfds, - (struct timeval *)timeptr); + ret = (int) func0(nfds, readfds, writefds, exceptfds, + (struct timeval *)timeptr); } else if (mode == 1) { - ret = (int) func2(nfds, readfds, writefds, exceptfds, - (struct timespec *)timeptr, sigmask); + ret = (int) func1(nfds, readfds, writefds, exceptfds, + (struct timespec *)timeptr, sigmask); } return ret; } TRACE(">> _WS_select(%d, %d, _, _, _, _) called\n", mode, nfds); memcpy(&savetv, timeptr, sizeof(savetv)); - memcpy(&savets, timeptr, sizeof(savets)); + gettimeofday(&starttv, NULL); /* If we have carry-over return it right away */ FD_ZERO(&carryfds); @@ -597,11 +636,14 @@ int _WS_select(int mode, int nfds, fd_set *readfds, } 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) func(nfds, readfds, writefds, exceptfds, - (struct timeval *)timeptr); + ret = (int) func0(nfds, readfds, writefds, exceptfds, + (struct timeval *)timeptr); } else if (mode == 1) { - ret = (int) func2(nfds, readfds, writefds, exceptfds, + ret = (int) func1(nfds, readfds, writefds, exceptfds, (struct timespec *)timeptr, sigmask); } if (! readfds) { @@ -630,17 +672,31 @@ int _WS_select(int mode, int nfds, fd_set *readfds, /* * If all the ready readfds were WebSockets, but none of - * them were really ready (empty frames) then repeat. + * them were really ready (empty frames) then we select again. */ + if (ret == 0) { - /* Restore original values*/ - /* TODO: fix timeptr for repeat */ - memcpy(timeptr, &savetv, sizeof(savetv)); - memcpy(timeptr, &savets, sizeof(savets)); + /* 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, mode); + /* 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; diff --git a/utils/wswrapper.h b/utils/wswrapper.h index 48d4aa2d..8550e3e1 100644 --- a/utils/wswrapper.h +++ b/utils/wswrapper.h @@ -7,9 +7,6 @@ * wswrapper.so. */ -//#define DO_DEBUG 1 -//#define DO_TRACE 1 - #ifdef DO_DEBUG #define DEBUG(...) \ if (DO_DEBUG) { \ From 56d9aa816b439f4abd877de1b4e32b9c8272838d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 27 Dec 2010 19:44:14 -0700 Subject: [PATCH 08/18] wswrapper: interpose on poll/ppoll also. poll/ppoll interposer builds but is untested. --- utils/wswrapper.c | 193 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 156 insertions(+), 37 deletions(-) diff --git a/utils/wswrapper.c b/utils/wswrapper.c index 9adfda56..d507d911 100644 --- a/utils/wswrapper.c +++ b/utils/wswrapper.c @@ -13,8 +13,8 @@ * - programs using ppoll or epoll will not work correctly */ -#define DO_DEBUG 1 -#define DO_TRACE 1 +//#define DO_DEBUG 1 +//#define DO_TRACE 1 #include #include @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -49,7 +50,7 @@ _WS_connection *_WS_connections[65536]; * 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, otherwise 0. + * Return 1 if the difference is negative or 0, otherwise 0. */ int _WS_subtract_time (result, x, y, ts) struct timeval *result, *x, *y; @@ -72,8 +73,8 @@ int _WS_subtract_time (result, x, y, ts) 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. */ - return x->tv_sec < y->tv_sec; + /* Return 1 if result is negative or 0. */ + return x->tv_sec <= y->tv_sec; } @@ -580,6 +581,17 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf, 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) @@ -588,8 +600,8 @@ int _WS_select(int mode, int nfds, fd_set *readfds, fd_set carryfds, savefds; /* Assumes timeptr is two longs whether timeval or timespec */ struct timeval savetv, starttv, nowtv, difftv; - int carrycnt = 0; - int ret, less, i, ready, fd; + 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"); @@ -597,15 +609,15 @@ int _WS_select(int mode, int nfds, fd_set *readfds, if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) { if (mode == 0) { ret = (int) func0(nfds, readfds, writefds, exceptfds, - (struct timeval *)timeptr); + timeptr); } else if (mode == 1) { ret = (int) func1(nfds, readfds, writefds, exceptfds, - (struct timespec *)timeptr, sigmask); + timeptr, sigmask); } return ret; } - TRACE(">> _WS_select(%d, %d, _, _, _, _) called\n", mode, nfds); + TRACE(">> _WS_select(%d, %d, _, _, _, _)\n", mode, nfds); memcpy(&savetv, timeptr, sizeof(savetv)); gettimeofday(&starttv, NULL); @@ -641,10 +653,10 @@ int _WS_select(int mode, int nfds, fd_set *readfds, ((struct timeval *) timeptr)->tv_usec); if (mode == 0) { ret = (int) func0(nfds, readfds, writefds, exceptfds, - (struct timeval *)timeptr); + timeptr); } else if (mode == 1) { ret = (int) func1(nfds, readfds, writefds, exceptfds, - (struct timespec *)timeptr, sigmask); + timeptr, sigmask); } if (! readfds) { break; @@ -670,17 +682,16 @@ int _WS_select(int mode, int nfds, fd_set *readfds, } errno = 0; /* errno could be set by _WS_ready */ - /* - * If all the ready readfds were WebSockets, but none of - * them were really ready (empty frames) then we select again. - */ - if (ret == 0) { - /* Restore original values less passage of time */ + /* + * 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, mode); + _WS_subtract_time(&difftv, &nowtv, &starttv, 0); /* Subtract from original timout */ less = _WS_subtract_time((struct timeval *) timeptr, &savetv, &difftv, mode); @@ -702,21 +713,125 @@ int _WS_select(int mode, int nfds, fd_set *readfds, 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)(); @@ -724,7 +839,7 @@ 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"); - TRACE(">> bind(%d, _, %d) called\n", sockfd, addrlen); + TRACE(">> bind(%d, _, %d)\n", sockfd, addrlen); ret = (int) func(sockfd, addr, addrlen); @@ -763,7 +878,7 @@ 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"); - TRACE("<< accept(%d, _, _) called\n", sockfd); + TRACE("<< accept(%d, _, _)\n", sockfd); fd = (int) func(sockfd, addr, addrlen); @@ -882,9 +997,13 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, int poll(struct pollfd *fds, nfds_t nfds, int timeout) { - //TRACE("poll(_, %d, _) called\n", nfds); - static void * (*func)(); - if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "poll"); - - return (int) func(fds, nfds, 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); } From 71ba9a7a547798347ecfe1efbaba63904b0992c3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 29 Dec 2010 14:11:28 -0700 Subject: [PATCH 09/18] wswrapper: update README, DO_MSG def for output. Update README to mention wswrapper. Add DO_MSG define which controls whether wswrapper code generates basic output. --- utils/README.md | 21 +++++++++++++++++---- utils/wswrapper.c | 2 ++ utils/wswrapper.h | 12 ++++++++---- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/utils/README.md b/utils/README.md index b0ed6018..916fde27 100644 --- a/utils/README.md +++ b/utils/README.md @@ -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,7 +51,7 @@ 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). diff --git a/utils/wswrapper.c b/utils/wswrapper.c index d507d911..7d4c9c99 100644 --- a/utils/wswrapper.c +++ b/utils/wswrapper.c @@ -13,6 +13,7 @@ * - programs using ppoll or epoll will not work correctly */ +#define DO_MSG 1 //#define DO_DEBUG 1 //#define DO_TRACE 1 @@ -828,6 +829,7 @@ int _WS_poll(int mode, struct pollfd *fds, nfds_t nfds, int timeout, return ret; } + /* * Overload (LD_PRELOAD) standard library network routines */ diff --git a/utils/wswrapper.h b/utils/wswrapper.h index 8550e3e1..afe9375b 100644 --- a/utils/wswrapper.h +++ b/utils/wswrapper.h @@ -7,6 +7,14 @@ * 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) { \ @@ -27,10 +35,6 @@ #define TRACE(...) #endif -#define MSG(...) \ - fprintf(stderr, "wswrapper: "); \ - fprintf(stderr, __VA_ARGS__); - #define RET_ERROR(eno, ...) \ fprintf(stderr, "wswrapper error: "); \ fprintf(stderr, __VA_ARGS__); \ From 2574936f604e3576c00afd99a996f210368f532a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 30 Dec 2010 11:00:50 -0700 Subject: [PATCH 10/18] utils/README.md, docs/TODO, docs/notes: updates. Add wswrapper info to utils/README.md and docs/TODO. Remove innacurate info from docs/notes. --- docs/TODO | 30 +++++++++++++++++------------- docs/notes | 37 +++++-------------------------------- utils/README.md | 31 +++++++++++++++++++++++-------- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/docs/TODO b/docs/TODO index e8d12005..87f786f5 100644 --- a/docs/TODO +++ b/docs/TODO @@ -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. diff --git a/docs/notes b/docs/notes index b41d56dd..b3cc0cfb 100644 --- a/docs/notes +++ b/docs/notes @@ -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: diff --git a/utils/README.md b/utils/README.md index 916fde27..0e75d51f 100644 --- a/utils/README.md +++ b/utils/README.md @@ -53,32 +53,36 @@ These are not necessary for the basic operation. #### Implementations of wsproxy -There are three implementations of wsproxy included: python, C, and -Node (node.js). +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 wsproxy implementations: +Here is the feature support matrix for the the wsproxy implementations +and wswrapper: - - + + + + - + + - + @@ -86,14 +90,25 @@ Here is the feature support matrix for the wsproxy implementations: + - + + + + + + + + + + +
ImplementationBasic ProxyingApplicationLanguageProxy or Interposer Multi-process Daemonizing SSL/wss Flash Policy Server Session Recording
wsproxy pythonyesproxy yes yes yes 1 yes yes
wsproxy Cyesproxy yes yes yesno
wsproxy Node (node.js)yesproxy yes no no no no
wswrapperCinterposerindirectlyindirectlynonono
* Note 1: to use SSL/wss with python 2.5 or older, see the following From bf5ee68828872f05d1356a22d3807c6ea29d017f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 3 Jan 2011 12:34:41 -0600 Subject: [PATCH 11/18] Expose getKeysym and add keyboard test. Related to issue Non-US keyboard layout option issue: https://github.com/kanaka/noVNC/issues#issue/21 --- include/canvas.js | 10 +++---- tests/keyboard.html | 67 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 tests/keyboard.html diff --git a/include/canvas.js b/include/canvas.js index 03a1e680..93aaff72 100644 --- a/include/canvas.js +++ b/include/canvas.js @@ -216,7 +216,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 +362,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; diff --git a/tests/keyboard.html b/tests/keyboard.html new file mode 100644 index 00000000..2d6df05b --- /dev/null +++ b/tests/keyboard.html @@ -0,0 +1,67 @@ + + Input Test + +

+ + Canvas:
+ + Canvas not supported. + + +
+ Results:
+ + + + + + + + + + From e3716842ef03da96ce082dc47eda71b6ca14fb3f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 4 Jan 2011 10:27:25 -0600 Subject: [PATCH 12/18] include/rfb.js: show Flash version in console. --- include/rfb.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/rfb.js b/include/rfb.js index 7536ddc8..6adccd47 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -254,7 +254,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"); From 9b940131d3e44ff0e3146b637ae126d07b1d7230 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 4 Jan 2011 10:30:26 -0600 Subject: [PATCH 13/18] include/canvas.js: show full user agent in console. --- include/canvas.js | 1 + 1 file changed, 1 insertion(+) diff --git a/include/canvas.js b/include/canvas.js index 93aaff72..e28c3b04 100644 --- a/include/canvas.js +++ b/include/canvas.js @@ -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); } From 58da507bb81f960909f6e9c108f61137426c6ea5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 4 Jan 2011 12:19:54 -0600 Subject: [PATCH 14/18] README: add companies/projects using noVNC. --- README.md | 17 +++++++++++++++++ docs/links | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/README.md b/README.md index ccf51ea7..4d1b1e51 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,23 @@ Running in Chrome before and after connecting: See more screenshots here. +### 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 diff --git a/docs/links b/docs/links index 64ea7546..8cb4da11 100644 --- a/docs/links +++ b/docs/links @@ -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/ From 58dc1947ded37983c0add02ac6a7859510a52a5b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 4 Jan 2011 13:14:46 -0600 Subject: [PATCH 15/18] wsproxy: warn when no cert. C sock close cleanup. Warn early about no SSL cert and add clearer warning when a connection comes in as SSL but no cert file exists. For the C version, cleanup closing of the connection socket. Use shutdown for a cleaner cleanup with the client. --- utils/websocket.c | 31 +++++++++++++++++++++---------- utils/websocket.py | 5 +++++ utils/wsproxy.c | 10 ++++++++-- utils/wsproxy.py | 2 ++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/utils/websocket.c b/utils/websocket.c index 4a124f15..f73bb22a 100644 --- a/utils/websocket.c +++ b/utils/websocket.c @@ -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, "", 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"); + } } diff --git a/utils/websocket.py b/utils/websocket.py index b4bc01ec..70748c1d 100755 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -112,6 +112,11 @@ def do_handshake(sock): sock.close() return False elif handshake[0] in ("\x16", "\x80"): + if not os.path.exists(settings['cert']): + handler_msg("SSL connection but '%s' not found" + % settings['cert']) + sock.close() + return False retsock = ssl.wrap_socket( sock, server_side=True, diff --git a/utils/wsproxy.c b/utils/wsproxy.c index dc8b5f7d..5ba22063 100644 --- a/utils/wsproxy.c +++ b/utils/wsproxy.c @@ -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); diff --git a/utils/wsproxy.py b/utils/wsproxy.py index c5339b7a..378e4743 100755 --- a/utils/wsproxy.py +++ b/utils/wsproxy.py @@ -162,6 +162,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 From 96bc3d308835b8ef76a10ba3dcdfab06356563af Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 6 Jan 2011 18:26:54 -0600 Subject: [PATCH 16/18] wsproxy.py: add web serving capability. - Added ability to respond to normal web requests. This is basically integrating web.py functionality into wsproxy. This is only in the python version and it is off by default when calling wsproxy. Turn it on with --web DIR where DIR is the web root directory. Next task is to clean up wsproxy.py. It's gotten unwieldy and it really no longer needs to be parallel to the C version. --- README.md | 2 +- utils/launch.sh | 73 ++++++++++++--------------- utils/websocket.py | 122 ++++++++++++++++++++++++++++++++------------- utils/wsproxy.py | 5 ++ 4 files changed, 125 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 4d1b1e51..ead2abd6 100644 --- a/README.md +++ b/README.md @@ -137,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` diff --git a/utils/launch.sh b/utils/launch.sh index 1ee7d15b..4ed9f92d 100755 --- a/utils/launch.sh +++ b/utils/launch.sh @@ -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="$(cd "$(dirname "$0")" && pwd)" -WEB_PORT="6080" -PROXY_PORT="6081" +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,40 +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 # Find vnc.html if [ -e "$(pwd)/vnc.html" ]; then - TOP=$(pwd) + WEB=$(pwd) elif [ -e "${HERE}/../vnc.html" ]; then - TOP=${HERE}/../ + WEB=${HERE}/../ elif [ -e "${HERE}/vnc.html" ]; then - TOP=${HERE} + WEB=${HERE} else die "Could not find vnc.html" fi -cd ${TOP} -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 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 - web_pid= - echo "Failed to start webserver" - exit 1 + echo "Warning: could not find self.pem" fi -echo "Starting WebSockets proxy on port ${PROXY_PORT}" -${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} & +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 @@ -110,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} diff --git a/utils/websocket.py b/utils/websocket.py index 70748c1d..7efd01a3 100755 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -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 = """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(""): 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"): if not os.path.exists(settings['cert']): - handler_msg("SSL connection but '%s' not found" - % settings['cert']) - sock.close() - return False - retsock = ssl.wrap_socket( - sock, - server_side=True, - certfile=settings['cert'], - keyfile=settings['key']) + 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) @@ -177,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 @@ -209,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() diff --git a/utils/wsproxy.py b/utils/wsproxy.py index 378e4743..32a4021e 100755 --- a/utils/wsproxy.py +++ b/utils/wsproxy.py @@ -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") @@ -176,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() From 90966d6251f71ff48ee15718025c5bd4cf74bd18 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 6 Jan 2011 19:17:32 -0600 Subject: [PATCH 17/18] utils/README.md: flip table. Add web server info. Flip the feature table to be tall instead of wide. Added row about "web server" functionality in wsproxy.py. --- utils/README.md | 71 ++++++++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/utils/README.md b/utils/README.md index 0e75d51f..b9b5b53d 100644 --- a/utils/README.md +++ b/utils/README.md @@ -62,49 +62,64 @@ and wswrapper: - + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + - - - - + + + + + + + + - - - - - - + + + From 6ace64d3ae7d034d049b9bb252b9c19c1e872667 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 6 Jan 2011 19:21:17 -0600 Subject: [PATCH 18/18] utils/README.md: horizontal looks better. Flip table back to be horizontal. More readable. --- utils/README.md | 78 +++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/utils/README.md b/utils/README.md index b9b5b53d..93fdfb32 100644 --- a/utils/README.md +++ b/utils/README.md @@ -59,73 +59,63 @@ There are three implementations of wsproxy: python, C, and Node Here is the feature support matrix for the the wsproxy implementations and wswrapper: -
ApplicationFeature\Programwsproxy.pywsproxywsproxy.jswswrap/wswrapper
LanguageProxy or InterposerMulti-processDaemonizingSSL/wssFlash Policy ServerSession Recording
wsproxy pythonCNode (node.js)C
Primary Function proxyproxyproxyinterposer
Multi-process yes yesyesindirectly
Daemonizeyesyesnoindirectly
SSL/wss yes 1 yesyes
wsproxyCproxyyesyesyesyesno no
wsproxyNode (node.js)proxy
Flash Policy Serveryes yes no no
Web Serveryesno no no
wswrapperCinterposerindirectlyindirectly
Session Recordingyes no no no
- - - - - - - + - - - - - - - - - - - - - - - - - - - - + + - - - - - - + + + + + + + + + - - - - - + + + + + + + + - - + + + + + + - - - + + + + + + +
Feature\Programwsproxy.pywsproxywsproxy.jswswrap/wswrapper
Program LanguagepythonCNode (node.js)C
Primary Functionproxyproxyproxyinterposer
Multi-processyesyesyesindirectly
Proxy or InterposerMultiprocess Daemonizeyesyesnoindirectly
SSL/wssFlash Policy ServerSession RecordWeb Server
wsproxy.pypythonproxyyesyes yes 1 yesnono
Flash Policy Serveryesyes
wsproxyCproxyyesyes yes yes no no
Web Server
wsproxy.jsNode (node.js)proxy yes no no nonono
Session Recordingyes
wswrap/wswrapper.soshell/Cinterposerindirectlyindirectlyno no no no
+ * Note 1: to use SSL/wss with python 2.5 or older, see the following section on *Building the Python ssl module*.