/* * wswrap/wswrapper: Add WebSockets support to any service. * Copyright 2010 Joel Martin * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) * * 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 * * This is tricky a subtle process so there are some serious limitations: * - multi-threaded programs may not work * - programs that fork may behave in strange and mysterious ways (such as * fork bombing your system) * - programs using ppoll or epoll will not work correctly * - doesn't support fopencookie, streams, putc, etc. * * ********************************************************************** * WARNING: * Due to the above limitations, this code should be considered an experiment * only. Consider using the program wrap mode of wsproxy.py instead. * ********************************************************************** */ #define DO_MSG 1 #define DO_DEBUG 1 #define DO_TRACE 1 #include #include #define __USE_GNU 1 // Pull in RTLD_NEXT #include #include #include #include #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 */ int _WS_listen_fd = -1; 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; } int _WS_alloc(int fd) { if (_WS_connections[fd]) { RET_ERROR(ENOMEM, "Memory already allocated for fd %d\n", fd); } if (! (_WS_connections[fd] = malloc(sizeof(_WS_connection)))) { RET_ERROR(ENOMEM, "Could not allocate interposer memory\n"); } _WS_connections[fd]->rcarry_cnt = 0; _WS_connections[fd]->rcarry[0] = '\0'; _WS_connections[fd]->newframe = 1; _WS_connections[fd]->refcnt = 1; /* Add to search list for select/pselect */ _WS_fds[_WS_nfds] = fd; _WS_nfds++; return 0; } int _WS_free(int fd) { int i; _WS_connection * wsptr; wsptr = _WS_connections[fd]; if (wsptr) { TRACE(">> _WS_free(%d)\n", fd); wsptr->refcnt--; if (wsptr->refcnt <= 0) { free(wsptr); DEBUG("freed memory for fd %d\n", 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\n", fd); TRACE("<< _WS_free(%d)\n", fd); } } /* * WebSocket handshake routines */ /* For WebSockets v76, use key1, key2 and key3 to generate md5 hash */ int _WS_gen_md5(char *key1, char *key2, char *key3, char *target) { unsigned int i, spaces1 = 0, spaces2 = 0; unsigned long num1 = 0, num2 = 0; unsigned char buf[17]; /* Parse number 1 from key 1 */ for (i=0; i < strlen(key1); i++) { if (key1[i] == ' ') { spaces1 += 1; } if ((key1[i] >= 48) && (key1[i] <= 57)) { num1 = num1 * 10 + (key1[i] - 48); } } num1 = num1 / spaces1; /* Parse number 2 from key 2 */ for (i=0; i < strlen(key2); i++) { if (key2[i] == ' ') { spaces2 += 1; } if ((key2[i] >= 48) && (key2[i] <= 57)) { num2 = num2 * 10 + (key2[i] - 48); } } num2 = num2 / spaces2; /* Pack it big-endian as the first 8 bytes */ buf[0] = (num1 & 0xff000000) >> 24; buf[1] = (num1 & 0xff0000) >> 16; buf[2] = (num1 & 0xff00) >> 8; buf[3] = num1 & 0xff; buf[4] = (num2 & 0xff000000) >> 24; buf[5] = (num2 & 0xff0000) >> 16; buf[6] = (num2 & 0xff00) >> 8; buf[7] = num2 & 0xff; /* Add key 3 as the last 8 bytes */ strncpy(buf+8, key3, 8); buf[16] = '\0'; /* md5 hash all 16 bytes to generate 16 byte target */ md5_buffer(buf, 16, target); target[16] = '\0'; return 1; } /* Do v75 and v76 handshake on WebSocket connection */ int _WS_handshake(int sockfd) { int sz = 0, len, idx; int ret = -1, save_errno = EPROTO; char *last, *start, *end; long flags; char handshake[4096], response[4096], path[1024], prefix[5] = "", scheme[10] = "ws", host[1024], origin[1024], key1[100], key2[100], key3[9], chksum[17]; static void * (*rfunc)(), * (*wfunc)(); if (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv"); if (!wfunc) wfunc = (void *(*)()) dlsym(RTLD_NEXT, "send"); DEBUG("_WS_handshake starting\n"); /* Disable NONBLOCK if set */ flags = fcntl(sockfd, F_GETFL, 0); if (flags & O_NONBLOCK) { fcntl(sockfd, F_SETFL, flags^O_NONBLOCK); } while (1) { len = (int) rfunc(sockfd, handshake+sz, 4095, 0); if (len < 1) { ret = len; save_errno = errno; break; } sz += len; handshake[sz] = '\x00'; if (sz < 4) { // Not enough yet continue; } if (strstr(handshake, "GET ") != handshake) { MSG("Got non-WebSockets client connection\n"); break; } last = strstr(handshake, "\r\n\r\n"); if (! last) { continue; } if (! strstr(handshake, "Upgrade: WebSocket\r\n")) { MSG("Invalid WebSockets handshake\n"); break; } /* Now parse out the data elements */ start = handshake+4; end = strstr(start, " HTTP/1.1"); if (!end) { break; } snprintf(path, end-start+1, "%s", start); start = strstr(handshake, "\r\nHost: "); if (!start) { break; } start += 8; end = strstr(start, "\r\n"); snprintf(host, end-start+1, "%s", start); start = strstr(handshake, "\r\nOrigin: "); if (!start) { break; } start += 10; end = strstr(start, "\r\n"); snprintf(origin, end-start+1, "%s", start); start = strstr(handshake, "\r\n\r\n") + 4; if (strlen(start) == 8) { sprintf(prefix, "Sec-"); snprintf(key3, 8+1, "%s", start); start = strstr(handshake, "\r\nSec-WebSocket-Key1: "); if (!start) { break; } start += 22; end = strstr(start, "\r\n"); snprintf(key1, end-start+1, "%s", start); start = strstr(handshake, "\r\nSec-WebSocket-Key2: "); if (!start) { break; } start += 22; end = strstr(start, "\r\n"); snprintf(key2, end-start+1, "%s", start); _WS_gen_md5(key1, key2, key3, chksum); //DEBUG("Got handshake (v76): %s\n", handshake); MSG("New WebSockets client (v76)\n"); } else { sprintf(prefix, ""); sprintf(key1, ""); sprintf(key2, ""); sprintf(key3, ""); sprintf(chksum, ""); //DEBUG("Got handshake (v75): %s\n", handshake); MSG("New WebSockets client (v75)\n"); } sprintf(response, _WS_response, prefix, origin, prefix, scheme, host, path, prefix, chksum); //DEBUG("Handshake response: %s\n", response); wfunc(sockfd, response, strlen(response), 0); save_errno = 0; ret = 0; break; } /* Re-enable NONBLOCK if it was set */ if (flags & O_NONBLOCK) { fcntl(sockfd, F_SETFL, flags); } errno = save_errno; 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 %d, errno: %d\n", sockfd, nonblock, len, 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 */ 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, striplen, decodelen, ready; ssize_t retlen, rawlen; int sockflags; int i; char *fstart, *fend, *cstart; static void * (*rfunc)(), * (*rfunc2)(); if (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv"); if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read"); if (! ws) { // Not our file descriptor, just pass through if (recvf) { return (ssize_t) rfunc(sockfd, buf, len, flags); } else { return (ssize_t) rfunc2(sockfd, buf, 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; /* first copy in carry-over bytes from previous recv/read */ if (ws->rcarry_cnt) { if (ws->rcarry_cnt == 1) { DEBUG("Using carry byte: %u (", ws->rcarry[0]); } else if (ws->rcarry_cnt == 2) { DEBUG("Using carry bytes: %u,%u (", ws->rcarry[0], ws->rcarry[1]); } else { RET_ERROR(EIO, "Too many carry-over bytes\n"); } if (len <= ws->rcarry_cnt) { DEBUG("final)\n"); memcpy((char *) buf, ws->rcarry, len); ws->rcarry_cnt -= len; return len; } else { DEBUG("prepending)\n"); memcpy((char *) buf, ws->rcarry, ws->rcarry_cnt); retlen += ws->rcarry_cnt; left -= ws->rcarry_cnt; ws->rcarry_cnt = 0; } } /* Determine the number of base64 encoded bytes needed */ rawcount = (left * 4) / 3 + 3; rawcount -= rawcount%4; if (rawcount > WS_BUFSIZE - 1) { RET_ERROR(ENOMEM, "recv of %d bytes is larger than buffer\n", rawcount); } 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; } /* 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"); } fstart = ws->rbuf; fstart[rawlen] = '\x00'; if (ws->newframe) { if (fstart[0] != '\x00') { RET_ERROR(EPROTO, "Missing frame start\n"); } fstart++; rawlen--; ws->newframe = 0; } fend = memchr(fstart, '\xff', rawlen); if (fend) { ws->newframe = 1; if ((fend - fstart) % 4) { RET_ERROR(EPROTO, "Frame length is not multiple of 4\n"); } } else { fend = fstart + rawlen - (rawlen % 4); if (fend - fstart < 4) { RET_ERROR(EPROTO, "Frame too short\n"); } } /* Determine amount to consume */ if (rawcount < fend - fstart) { ws->newframe = 0; deccount = rawcount; } else { deccount = fend - fstart; } /* 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 { rfunc(sockfd, ws->rbuf, fstart - ws->rbuf + deccount + ws->newframe, flags); } fstart[deccount] = '\x00'; // base64 terminator /* Do base64 decode into the return buffer */ decodelen = b64_pton(fstart, (char *) buf + retlen, deccount); if (decodelen <= 0) { RET_ERROR(EPROTO, "Base64 decode error\n"); } /* Calculate return length and carry-over */ if (decodelen <= left) { retlen += decodelen; } else { retlen += left; if (! (flags & MSG_PEEK)) { /* Add anything left over to the carry-over */ ws->rcarry_cnt = decodelen - left; if (ws->rcarry_cnt > 2) { RET_ERROR(EPROTO, "Got too much base64 data\n"); } memcpy(ws->rcarry, buf + retlen, ws->rcarry_cnt); if (ws->rcarry_cnt == 1) { DEBUG("Saving carry byte: %u\n", ws->rcarry[0]); } else if (ws->rcarry_cnt == 2) { DEBUG("Saving carry bytes: %u,%u\n", ws->rcarry[0], ws->rcarry[1]); } else { RET_ERRO(EPROTO, "Too many carry bytes!\n"); } } } ((char *) buf)[retlen] = '\x00'; TRACE("<< _WS_recv(%d) retlen %d\n", sockfd, retlen); return retlen; } /* * WebSockets send and write interposer routine */ ssize_t _WS_send(int sendf, int sockfd, const void *buf, size_t len, int flags) { _WS_connection *ws = _WS_connections[sockfd]; int rawlen, enclen, rlen, over, left, clen, retlen, dbufsize; int sockflags; char * target; int i; static void * (*sfunc)(), * (*sfunc2)(); if (!sfunc) sfunc = (void *(*)()) dlsym(RTLD_NEXT, "send"); if (!sfunc2) sfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "write"); if (! ws) { // Not our file descriptor, just pass through if (sendf) { return (ssize_t) sfunc(sockfd, buf, len, flags); } else { return (ssize_t) sfunc2(sockfd, buf, len); } } TRACE(">> _WS_send(%d, _, %d)\n", sockfd, len); sockflags = fcntl(sockfd, F_GETFL, 0); dbufsize = (WS_BUFSIZE * 3)/4 - 2; if (len > dbufsize) { RET_ERROR(ENOMEM, "send of %d bytes is larger than send buffer\n", len); } /* base64 encode and add frame markers */ rawlen = 0; ws->sbuf[rawlen++] = '\x00'; enclen = b64_ntop(buf, len, ws->sbuf+rawlen, WS_BUFSIZE-rawlen); if (enclen < 0) { RET_ERROR(EPROTO, "Base64 encoding error\n"); } rawlen += enclen; ws->sbuf[rawlen++] = '\xff'; rlen = (int) sfunc(sockfd, ws->sbuf, rawlen, flags); if (rlen <= 0) { 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 */ over = (rlen - 1) % 4; left = (4 - over) % 4 + 1; // left to send DEBUG("_WS_send: rlen: %d (over: %d, left: %d), rawlen: %d\n", rlen, over, left, rawlen); rlen += left; ws->sbuf[rlen-1] = '\xff'; i = 0; do { i++; clen = (int) sfunc(sockfd, ws->sbuf + rlen - left, left, flags); if (clen > 0) { left -= clen; } else if (clen == 0) { MSG("_WS_send: got clen %d\n", clen); } else if (!(sockflags & O_NONBLOCK)) { MSG("<< _WS_send: clen %d\n", clen); return clen; } if (i > 1000000) { 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); } /* * Report back the number of original characters sent, * not the raw number sent */ /* Adjust for framing */ retlen = rlen - 2; /* Adjust for base64 padding */ if (ws->sbuf[rlen-1] == '=') { retlen --; } if (ws->sbuf[rlen-2] == '=') { retlen --; } /* Scale return value for base64 encoding size */ retlen = (retlen*3)/4; TRACE(">> _WS_send(%d, _, %d) retlen %d\n", sockfd, len, retlen); return (ssize_t) retlen; } /* * Interpose select/pselect/poll. * * WebSocket descriptors are not ready until we have received a frame start * ('\x00') and at least 4 bytes of base64 encoded data. In addition we may * have carry-over data from the last 4 bytes of base64 data in which case the * WebSockets socket is ready even though there might not be data in the raw * socket itself. */ /* Interpose on select (mode==0) and pselect (mode==1) */ int _WS_select(int mode, int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, void *timeptr, const sigset_t *sigmask) { _WS_connection *ws; fd_set carryfds, savefds; /* Assumes timeptr is two longs whether timeval or timespec */ struct timeval savetv, starttv, nowtv, difftv; int carrycnt = 0, less = 0; int ret, i, ready, fd; static void * (*func0)(), * (*func1)(); if (!func0) func0 = (void *(*)()) dlsym(RTLD_NEXT, "select"); if (!func1) func1 = (void *(*)()) dlsym(RTLD_NEXT, "pselect"); if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) { if (mode == 0) { ret = (int) func0(nfds, readfds, writefds, exceptfds, timeptr); } else if (mode == 1) { ret = (int) func1(nfds, readfds, writefds, exceptfds, timeptr, sigmask); } return ret; } #ifdef DO_TRACE TRACE(">> _WS_select(%d, %d, _, _, _, _)\n", mode, nfds); for (i = 0; i < _WS_nfds; i++) { fd = _WS_fds[i]; if (readfds && (FD_ISSET(fd, readfds))) { TRACE(" WS %d is in readfds\n", fd, nfds); } if (writefds && (FD_ISSET(fd, writefds))) { TRACE(" WS %d is in writefds\n", fd, nfds); } if (exceptfds && (FD_ISSET(fd, exceptfds))) { TRACE(" WS %d is in exceptfds\n", fd, nfds); } } #endif if (timeptr) { 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 { if (timeptr) { TRACE(" _WS_select tv/ts: %ld:%ld\n", ((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. */ if (! timeptr) { /* No timeout, spin forever */ continue; } 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 (timeptr && mode == 1) { memcpy(timeptr, &savetv, sizeof(savetv)); } #ifdef DO_TRACE TRACE("<< _WS_select(%d, %d, _, _, _, _) ret %d, errno %d\n", mode, nfds, ret, errno); for (i = 0; i < _WS_nfds; i++) { fd = _WS_fds[i]; if (readfds && (FD_ISSET(fd, readfds))) { TRACE(" WS %d is set in readfds\n", fd, nfds); } if (writefds && (FD_ISSET(fd, writefds))) { TRACE(" WS %d is set in writefds\n", fd, nfds); } if (exceptfds && (FD_ISSET(fd, exceptfds))) { TRACE(" WS %d is set in exceptfds\n", fd, nfds); } } #endif 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 bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { static void * (*func)(); struct sockaddr_in * addr_in = (struct sockaddr_in *)addr; 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)\n", sockfd, addrlen); ret = (int) func(sockfd, addr, addrlen); if (addr_in->sin_family != AF_INET) { // TODO: handle IPv6 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 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')) { TRACE("<< bind, not interposing: WSWRAP_PORT is not a number\n"); return ret; } if (envport != bindport) { TRACE("<< bind, not interposing on port: %d (fd %d)\n", bindport, sockfd); return ret; } _WS_listen_fd = sockfd; TRACE("<< bind, listening for WebSockets connections on port: %d (fd %d)\n", envport, sockfd); return ret; } 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, _, _)\n", sockfd); fd = (int) func(sockfd, addr, addrlen); if (_WS_listen_fd == -1) { TRACE("<< accept: not interposing\n"); return fd; } if (_WS_listen_fd != sockfd) { TRACE("<< accept: not interposing on fd %d\n", sockfd); return fd; } if (_WS_connections[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_alloc(fd) < 0) { return -1; } ret = _WS_handshake(fd); if (ret < 0) { _WS_free(fd); errno = EPROTO; TRACE("<< accept(%d, _, _): ret %d\n", sockfd, ret); return ret; } MSG("interposing on fd %d (allocated memory)\n", fd); } return fd; } int close(int fd) { static void * (*func)(); if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close"); TRACE("close(%d) called\n", fd); _WS_free(fd); return (int) func(fd); } ssize_t read(int fd, void *buf, size_t 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) { 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) { 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) { TRACE("send(%d, _, %d, %d) called\n", sockfd, len, flags); return (ssize_t) _WS_send(1, sockfd, buf, len, flags); } int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { TRACE("select(%d, _, _, _, _) called\n", nfds); return _WS_select(0, nfds, readfds, writefds, exceptfds, (void *) timeout, NULL); } int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) { TRACE("pselect(%d, _, _, _, _, _) called\n", nfds); return _WS_select(1, nfds, readfds, writefds, exceptfds, (void *) timeout, sigmask); } int poll(struct pollfd *fds, nfds_t nfds, int timeout) { TRACE("poll(_, %ld, %d) called\n", nfds, timeout); return _WS_poll(0, fds, nfds, timeout, NULL, NULL); } int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask) { TRACE("ppoll(_, %ld, _, _) called\n", nfds); return _WS_poll(0, fds, nfds, 0, (struct timespec *)timeout, (sigset_t *)sigmask); } int dup(int oldfd) { int ret; static void * (*func)(); if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "dup"); TRACE(">> dup(%d) called\n", oldfd); ret = (int) func(oldfd); TRACE("<< dup(%d) ret %d\n", oldfd, ret); return ret; } int dup2(int oldfd, int newfd) { int ret; static void * (*func)(); if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "dup2"); TRACE(">> dup2(%d, %d) called\n", oldfd, newfd); ret = (int) func(oldfd, newfd); if ((! _WS_connections[oldfd]) && (! _WS_connections[newfd])) { return ret; } if ((ret < 0) || (oldfd == newfd) || (_WS_connections[oldfd] == _WS_connections[newfd])) { TRACE("<< dup2(%d, %d) ret %d\n", oldfd, newfd, ret); return ret; } /* dup2 behavior is to close newfd if it's open */ if (_WS_connections[newfd]) { _WS_free(newfd); } if (! _WS_connections[oldfd]) { TRACE("<< dup2(%d, %d) ret %d\n", oldfd, newfd, ret); return ret; } MSG("interposing on duplicated fd %d\n", newfd); /* oldfd and newfd are now descriptors for the same socket, * re-use the same context memory area */ _WS_connections[newfd] = _WS_connections[oldfd]; _WS_connections[newfd]->refcnt++; /* Add to search list for select/pselect */ _WS_fds[_WS_nfds] = newfd; _WS_nfds++; TRACE("<< dup2(%d, %d) ret %d\n", oldfd, newfd, ret); return ret; } int dup3(int oldfd, int newfd, int flags) { int ret; static void * (*func)(); if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "dup3"); TRACE(">> dup3(%d, %d, %d) called\n", oldfd, newfd, flags); ret = (int) func(oldfd, newfd, flags); TRACE("<< dup3(%d, %d, %d) ret %d\n", oldfd, newfd, flags, ret); return ret; }