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.
835 lines
25 KiB
C
835 lines
25 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* Limitations:
|
|
* - multi-threaded programs may not work
|
|
* - programs using ppoll or epoll will not work correctly
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define __USE_GNU 1 // Pull in RTLD_NEXT
|
|
#include <dlfcn.h>
|
|
|
|
#include <poll.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <resolv.h> /* base64 encode/decode */
|
|
#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.
|
|
*/
|
|
int _WS_listen_fd = -1;
|
|
int _WS_nfds = 0;
|
|
int _WS_fds[WS_MAX_FDS];
|
|
_WS_connection *_WS_connections[65536];
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
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 (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;
|
|
}
|
|
|
|
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
|
|
*/
|
|
|
|
/*
|
|
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)();
|
|
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) called\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, interposing 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, _, _) called\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_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;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
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(_, %d, _) called\n", nfds);
|
|
static void * (*func)();
|
|
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "poll");
|
|
|
|
return (int) func(fds, nfds, timeout);
|
|
}
|