123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850 |
- #include "server.h"
- #ifdef _WIN32
- /* Msft documentation has this to say: this is required to "initiates use
- * of the Winsock DLL by a process." They use technical words like initiates,
- * process, and a proper noun. In programming parlance this makes no sense,
- * so it boils down to: run this function or else our socket API will not work.
- * Bonkers. Get people to include it in their code, and now their code is
- * proprietary windows software.
- */
- int msftVendorLockInCode(WSADATA *w) {
- if(WSAStartup(MAKEWORD(2,0), w)) {
- fprintf(stderr, "WSAStartup.\n"); return 1;
- } else return 0;
- }
- /* Msft does not implement a close(int socketfd) routine like literally
- * every other operating system. Instead they provide closesocket(). Lol.
- * Lets ignore the Msft marketing team:
- */
- int close(int fd) {
- return closesocket(fd);
- } /* [?] whew, that was hard */
- /* Msft does not have a strerror routine and while they copied the Berkley
- * Socket API they thought that they could improve on it by renaming and
- * removing API constants. Later they introduced a compatibility header
- * errno.h but quickly started to erode the compatibility features as they
- * added features to their Visual Studio product line so even errno.h is
- * useless. Along with redefining the errno global variable and changing
- * it to a function (WSAGetLastError()) we still need a way to print human
- * readable error messages.
- *
- * [?] these messages are way too verbose
- */
- char * strWinsock(int e) {
- static char buf[256];
- // apparently Msft does not clear buffers
- for(int i = sizeof(buf); i--;) buf[i] = '\0';
- FormatMessage (
- /* flags */ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- /* lpsource */ NULL,
- /* message id */ e,
- /* language id */ MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
- /* output buffer */ buf,
- /* size */ sizeof (buf),
- /* va_list args */ NULL
- );
- if ('\0' == buf[0]) sprintf(buf, "%d-UnknownError", e);
- return buf;
- }
- #endif
- // nullify all the "okay" reasons for failure, otherwise signal an abort
- int acceptOkay(int e) {
- switch(errno) {
- #if !defined (_WIN32) && !defined (__linux__)
- // linux assigns EAGAIN as EWOULDBLOCK while Msft does not support
- case EAGAIN: /* same as EWOULDBLOCK */
- #endif
- case EWOULDBLOCK:
- // in our use case usually a multiplexer timeout
- #ifdef PRINT_MULTIPLEXER_TIMEOUT
- fprintf(stderr, "probably multiplexer timeout: %s\n", strerror(errno));
- #endif
- return DONE_FOR_NOW;
- #ifndef _WIN32
- // Msft winsock does not support these error conditions
- case EPROTO: /* just print "client" and errno -. */
- case ENOMEM: /* same as ENOBUFS | */
- case ENFILE: /* see EMFILE, but system wide v */
- #endif
- case ECONNABORTED: /* just print "client: errno -. */
- case EINTR: /* (these are all okay) | */
- case EMFILE: /* per-process limit on open fd | */
- case ENOBUFS: /* usually socket buffer limits | */
- case EOPNOTSUPP: /* V */
- fprintf(stderr, "client: %s\n", strerror(errno));
- return 1;
- #ifdef __linux__
- // linux also suppots firewall rule logging
- case EPERM:
- fprintf(stderr, "firewall: %s\n", strerror(errno));
- return 1;
- // in addition linux can support additional logging
- case ENOSR:
- case ESOCKTNOSUPPORT:
- case EPROTONOSUPPORT:
- case ETIMEDOUT:
- //case ERESTARTSYS:
- fprintf(stderr, "additional: %s\n", strerror(errno));
- return 1;
- #endif
- // FATAL error numbers:
- case EBADF: /* socket is a bad file descriptor */
- case ENOTSOCK: /* socket is not a file descriptor */
- case EINVAL: /* not listening, or invalid addrLen */
- case EFAULT: /* prevented access violation (addr) */
- default:
- fprintf(stderr, "FATAL [%d] accept: %s\n", errno, strerror(errno));
- return 0;
- }
- }
- int setNonBlocking(int sockfd) {
- #ifdef _WIN32
- u_long m = 1;
- return ioctlsocket(sockfd, FIONBIO, &m);
- #else
- return fcntl(sockfd, F_SETFL, O_NONBLOCK);
- #endif
- }
- /* [!] allows data loss
- * once close is called the OS will immediately delete everything
- * in the outgoing buffer, eg: the timeout message will not be
- * sent. In addition the client might send a request to this server
- * and the OS will most likely drop the packet causing the client
- * to hang, waiting on a response
- *
- * request the OS to send the client tcp stack a request to reset
- * connection (causes RST TCP flag to be set) as soon as close is
- * called on the socket
- */
- __attribute__((always_inline)) static
- void setLinger(int sockfd) {
- struct linger off = { 1, 0 };
- #ifdef _WIN32
- // Msft changed the type from void pointer to character pointer
- #define optval (char *) &off
- #else
- #define optval &off
- #endif
- if(0 > setsockopt(sockfd, SOL_SOCKET, SO_LINGER, optval, sizeof(off))) {
- fprintf(stderr, "failed to set linger: %s\n", strerror(errno));
- }
- }
- /* Determine if more than allowed number of milliseconds
- * have occurred since t
- *
- * [!] maximum of 2147 milliseconds
- */
- __attribute__((always_inline)) static
- uint32_t msRemaining(struct timespec t, uint32_t allowed) {
- struct timespec now;
- int32_t seconds, nanoSeconds;
- // nanoSecond uses signed 32-bit integer (max: 2,147,483,647)
- if(2147 < allowed) {
- allowed = 2147;
- fprintf(stderr, "[!] remaining max is: %dms\n", 2147);
- }
- // convert milliseconds allowed to nanoseconds allowed
- allowed *= 1.0e6;
- // get the current time
- clock_gettime(CLOCK_REALTIME, &now);
- seconds = now.tv_sec - t.tv_sec;
- if(0 > (nanoSeconds = now.tv_nsec - t.tv_nsec)) {
- // with negative nanoseconds, there will be at least one second
- nanoSeconds += 1.0e9;
- if(seconds > 0) seconds--; else { fprintf(stderr, "ouch\n"); return 0; }
- }
- #ifdef DEBUG_REMAINING
- fprintf(stderr, "seconds %d\n", seconds);
- fprintf(stderr, "nanoseconds %d\n", nanoSeconds);
- #endif
- // compare
- if(nanoSeconds > allowed) return 0;
- /* TODO: calculate the remaining milliseconds
- * [!] I am failing to write this code. What I want is to combine seconds
- * and nanoseconds but due to the size limitations for integer
- * datatypes and the split representation of time_t I am having a
- * difficult time.
- * [?] What I have so far: I know this function cannot perform calculations
- * on time deltas greater than 2 seconds, and the nanosecond resolution
- * can cause a fractional second to look like +/- second, so I assume
- * anything over 4 seconds is out. I then throw a very large integer
- * into the ring to absorb the 1-4 seconds and let the compiler figure
- * out the comparison.
- */
- if(seconds > 4) return 0;
- uint64_t humph = nanoSeconds;
- if(seconds > 0) while(seconds--) humph += 1.0e9;
- if(humph > allowed) return 0;
- allowed -= humph;
- return allowed / 1.0e6;
- }
- /* What a hassle. mingw32 deprecated usleep, Msft has a crazy
- * interface that you have to spend like ten years filling out
- * This is an inaccurate sleep routine.
- */
- void spookySleep(uint32_t t) {
- #ifdef _WIN32
- Sleep(t);
- #else
- usleep(t * 1000);
- #endif
- }
- /* sets up either select or poll and waits up to specified milliseconds for
- * operating system to wake up process when an event occurs
- */
- int socketReady(struct client pool[], int len, uint32_t ms) {
- int c, count = 0;
- #ifdef MULTIPLEX_USE_SELECT
- #define addToSet(x, y) FD_SET(pool[x].fd, &plexfds)
- #define isSet(x, y) FD_ISSET(pool[x].fd, &plexfds)
- #define waitfor(x) select(x, &plexfds, NULL, NULL, &tv)
- fd_set plexfds;
- struct timeval tv;
- FD_ZERO(&plexfds);
- tv.tv_sec = 0;
- tv.tv_usec = ms * 1000;
- #else
- #define addToSet(x, y) \
- do { plexfds[y].fd = pool[x].fd; plexfds[y].events = POLLIN; } while(0)
- #define isSet(x, y) (plexfds[y].revents & POLLIN)
- #define waitfor(x) poll(plexfds, x, ms)
- struct pollfd plexfds[POOL_SIZE];
- #endif
- // restrict len to POOL_SIZE, allows a fixed memory footprint for pollfd
- if(POOL_SIZE < len) {
- fprintf(stderr, "requested pool size %d limited to %d, ", len, POOL_SIZE);
- len = POOL_SIZE;
- }
- // add all sockets to the file descriptor set
- for(int i = 0; i < len; i++) {
- // skip pool entries without client socket file descriptors
- if(0 > pool[i].fd) continue;
- // ignore sockets that the client has already closed
- if(CLIENT_IS_CLOSED & pool[i].flags) continue;
- addToSet(i, count);
- #ifdef PRINT_SOCKET_ADD_TO_SET
- if(/* whatever server socket descriptor */ 3 != pool[0].fd)
- fprintf(stderr, "add socket descriptor %d to watch list\n", pool[i].fd);
- #endif
- count++;
- }
- // go to sleep, wake on OS interrupt (usually timeout or socket activity)
- struct timespec t;
- clock_gettime(CLOCK_REALTIME, &t);
- if(0 == (c = waitfor(count))) {
- #ifdef PRINT_NO_SOCKETS_READY
- /* try to suppress the server listening socket --
- * server socket is usually 3 when stdin, stdout, stderr are left open
- * 0 1 2
- */
- if(/* whatever server socket descriptor */ 3 != pool[0].fd)
- fprintf(stderr, "no socket descritprs ready: %d watched, %dms elapsed\n",
- count, 1000 - msRemaining(t, 1000));
- #endif
- return DONE_FOR_NOW;
- } else if(-1 == c) {
- // print whatever select/poll error case was and return error
- fprintf(stderr, "socketReady error: %s\n", strerror(errno));
- return /* error */ 0;
- } // otherwise...
- count = 0;
- // set flag for ready sockets
- for(int i = 0; i < len; i++) {
- // skip pool entries without client socket file descriptors
- if(0 > pool[i].fd) continue;
- // skip sockets that the client has already closed
- if(CLIENT_IS_CLOSED & pool[i].flags) continue;
- // either set or unset the READ_READY status bits
- if(isSet(i, count)) pool[i].status |= READ_READY;
- else pool[i].status &= ~READ_READY;
- count++;
- }
- // return number of clients that have data, as a negative number
- #ifdef PRINT_HOW_MANY_SOCKETS_READY
- fprintf(stderr, "there are %d socket descriptors ready\n", c);
- #endif
- return -c;
- }
- // convert characters in a buffer to only visible printable characters
- void convertToPrintable(char * s, uint32_t len) {
- char * p = s;
- if(len > FIM_BUFFER_LEN) len = FIM_BUFFER_LEN;
- for(uint32_t i = 0; i < len; i++) {
- if((' ' < *p) && ('z' >= *p)) /* do nothing */ ;
- else if('\0' == *p) break;
- else *p = '.';
- p++;
- }
- }
- /* Demo callback function:
- *
- * This is an example definition of an example prototype that could be
- * passed to the faseItMetal function. Basically it is looking for a
- * matching prototype: "char * someFunc(char * s, int len)"
- *
- * The function returns a pointer, accepts a pointer and a length.
- * both pointers are to a character datatype. Users should know that the
- * data pointed to will only be read/written up to a limited length (see:
- * faceItMetal FIM_BUFFER_LEN).
- *
- * The len that is passed to the callback is how many bytes the client has
- * currently sent to the server.
- *
- * The limit is set by the callback to tell faceItMetal how many characters
- * from the pointer it returns to send to the client. faceItMetal may send
- * fewer characters, as mentioned previously (see: faceItMetal FIM_BUFFER_LEN)
- *
- * Setting this limit to zero means that the callback should be called again
- * later with potentially a higher len, assuming the client has sent more
- * data. In this case the returned pointer is ignored.
- */
- char * alwaysGood(char *s, int len, int *limit) {
- char *out = "Hola!";
- // set how many characters to send to client (hint: see CALLBACK_ERROR_CODES)
- *limit = strlen(out);
- // print what data the client sent
- s[len -1] = '\0';
- fprintf(stderr, "alwaysGood: client sent [%s]\n", s);
- return out;
- }
- // see notes from Demo callback function: alwaysGood
- char * alwaysBad(char *s, int len, int *limit) {
- char *out = "";
- // ask to disconnect client with invalid (hint: see CALLBACK_ERROR_CODES)
- *limit = INVALID_DATA;
- // print what data the client sent
- s[len -1] = '\0';
- fprintf(stderr, "alwaysBad: client sent [%s]\n", s);
- return out;
- }
- // see notes from Demo callback function: alwaysGood
- char * alwaysNotEnough(char *s, int len, int *limit) {
- char *out = "";
- // ask to disconnect client with invalid (hint: see CALLBACK_ERROR_CODES)
- *limit = SEND_MORE;
- // print what data the client sent
- s[len -1] = '\0';
- fprintf(stderr, "alwaysNotEnough: client sent [%s]\n", s);
- return out;
- }
- __attribute__((always_inline)) static
- int waitForClients(uint32_t ms, struct client pool[]) {
- struct sockaddr_storage address;
- struct timespec spec;
- socklen_t addrSize;
- int c, count = 0;
- // store the time before waiting for new clients (ACCEPT_LISTEN_TIME)
- clock_gettime(CLOCK_REALTIME, &spec);
- /* wait for OS wake-up (okay to ignore invalid events, see: acceptOkay)
- *
- * [?] rather than check all possible fatal errors in the sleep routine
- * we will defer to accept (called in acceptOkay).
- * Check for the EAGAIN and EWOULDBLOCK conditions and short on
- * either of those (see: DONE_FOR_NOW)
- */
- if(DONE_FOR_NOW == (c = socketReady(pool, POOL_SIZE, ms))) {
- #ifdef USE_CLIENT_POOL
- prepayEstimates(pool, POOL_SIZE, spec);
- #endif
- return DONE_FOR_NOW;
- }
- // how many clients can be added to the pool
- for(int i = POOL_SIZE; i--;) if(-1 == pool[i].fd) count++;
- // accept any inbound connections, make a socket - short on error
- if(READ_READY & pool[0].status) while(count) {
- // reset the address length parameter (accept may modify it)
- addrSize = sizeof(address);
- // pull the next connection request from the listening socket queue
- if(0 > (c = accept(pool[0].fd, (struct sockaddr *) &address, &addrSize))) {
- // normal case for break is when errno is EWOULDBLOCK or EAGAIN
- if(DONE_FOR_NOW == (c = acceptOkay(errno))) break;
- // abort if there is a problem with the listening socket
- if(c) {
- fprintf(stderr, "unusual accept, skipping\n");
- continue;
- } else return /* fatal */ 0;
- } //else
- // reconfigure the client for non-blocking IO mode
- if(0 > setNonBlocking(c)) {
- fprintf(stderr, "problem setting mode: %s\n", strerror(errno));
- close(c); continue;
- }
- // add client to pool (record their file descriptor and connection time
- for(int i = /* skip listen socket */ 1; i < POOL_SIZE; i++) {
- // find an unused space in the client pool
- if(-1 < pool[i].fd) continue;
- #ifdef PRINT_CLIENT_ACCEPTED
- fprintf(stderr, "new client\n");
- #endif
- pool[i].fd = c;
- pool[i].address = address;
- pool[i].addrSize = addrSize;
- pool[i].t = spec;
- pool[i].status = 0;
- pool[i].flags = 0;
- pool[i].ms = PER_CLIENT_TIMEOUT_MS;
- // reset buffer counter
- pool[i].count = 0;
- // clear out a buffer to store user data
- for(uint32_t j = FIM_BUFFER_LEN; j--;) pool[i].buf[j] = '\0';
- // decrease the amount of space in the pool
- count--;
- break;
- }
- }
- // remove the READ_READY flag
- pool[0].status &= ~READ_READY;
- #ifdef USE_CLIENT_POOL
- prepayEstimates(pool, POOL_SIZE, spec);
- #endif
- return /* okay */ 1;
- }
- /* client socket has some pending data that has not been retrieved yet
- * collect it from the OS' buffers and then test if the data is complete enough
- * to send a response to the client.
- *
- * returns either a negative code or how many bytes were sent to the client
- *
- *
- */
- __attribute__((always_inline)) static
- int replyOnValid(struct client *c, char * (*f)(char *s, int len, int *limit)) {
- int d, e;
- char *res, response[FIM_BUFFER_LEN];
- // canned messages to send to clients
- static char * invalid = INVALID_TEXT;
- static char * error = SERVER_ERROR_TEXT;
- // skip unused spaces in pool, and clients without data to be processed
- if((0 > c->fd) || (0 == (READ_READY & c->status))) return /* skip */ -3;
- // also skip pooled clients that have closing type flags
- if(CLIENT_CLOSING & c->flags) return /* skip */ -3;
- do { // fill buffer, then flush (use response as overflow)
- if(0 < (e = FIM_BUFFER_LEN - c->count)) {
- if(0 < (d = recv(c->fd, c->buf + c->count, e, 0))) c->count += d;
- } else d = recv(c->fd, response, FIM_BUFFER_LEN, 0);
- } while(0 < d);
- // when read returns zero, client has already disconnected
- if(0 == d) {
- #ifdef PRINT_CLIENT_DISCONNECT
- fprintf(stderr, "client has disconnected\n");
- #endif
- c->flags |= CLIENT_IS_CLOSED;
- return /* it is okay */ -1;
- } else if (0 > d) {
- /* usually on the first wake-up but sometimes a bit later, we will
- * eventually consume everything a client wants to send and the client
- * will not say anything until we respond, when this happens the
- * recv function will continuously set errno to EWOULDBLOCK
- */
- //fprintf(stderr, "recv(%d): %s\n", errno, strerror(errno));
- }
- // skip callback if the server already decided to close the connection
- if(SERVER_WANTS_CLOSE & c->flags) return /* it is okay */ -1;
- // swap last character in buffer with a null terminator for safety
- c->buf[FIM_BUFFER_LEN - 1] = '\0';
- #ifdef PRINT_CLIENT_DATA
- fprintf(stderr, "client data: %s\n", c->buf);
- #endif
- { e = SERVER_ERROR;
- res = f(c->buf, c->count > FIM_BUFFER_LEN ?FIM_BUFFER_LEN :c->count, &e);
- }
- // if the limit is negative treat like a CALLBACK_ERROR_CODE
- if(0 > e) switch(e) {
- case SEND_MORE: /* partial request */
- // terminate if client filled our buffer unusable stuff
- if(FIM_BUFFER_LEN == c->count) {
- fprintf(stderr, "thanks for all the fish\n");
- break;
- } else return /* partial request */ -2;
- // these error codes cause client disconnect (use break)
- case INVALID_DATA:
- fprintf(stderr, "callback: invalid data\n");
- sendResponse(c, invalid, strlen(invalid), INVALID_MSG);
- break;
- case DROP_CLIENT:
- fprintf(stderr, "callback: drop client\n");
- c->flags = CLOSE_IMMEDIATELY;
- setLinger(c->fd);
- break;
- case SERVER_ERROR:
- fprintf(stderr, "callback: server error\n");
- sendResponse(c, error, strlen(error), SERVER_ERROR_MSG);
- break;
- default:
- fprintf(stderr, "unknown CALLBACK_ERROR_CODE [%d]\n", e);
- } if(0 > e) { c->flags |= SERVER_WANTS_CLOSE; return /* error */ -1; }
- // set the limit for how much data should be copied
- d = e < FIM_BUFFER_LEN ?e :FIM_BUFFER_LEN;
- // copy data into the response buffer
- for(uint32_t i = 0; i < d; i++) response[i] = *res++;
- // client has sent a valid request, send them the response
- if(d > (e = sendResponse(c, response, d, RESPONSE_MSG))) {
- if(0 > e); else fprintf(stderr, "truncated: %d of %d\n", e, d);
- }
- // finished with this client (server wants to close their socket)
- c->flags |= SERVER_WANTS_CLOSE;
- return d;
- }
- /* Note about send:
- * when writing to a pipe that the remote end has broken, we receive a
- * -1 from send and errno is set to EPIPE, but before that happens the
- * OS will send the application the SIGPIPE signal. Handle sigpipe, record
- * the send return value (on success number of bytes sent, otherwise -1
- * and errno is set)
- */
- int sendResponse(struct client *c, char *buf, uint32_t len, sendReason reason) {
- int d;
- if(WRITE_CLOSED & c->status) {
- fprintf(stderr, "cannot send, we already closed our end\n");
- return -1;
- }
- if(-1 == (d = send(c->fd, buf, len, 0))) {
- #ifdef _WIN32
- /* Msft winsock api does not implement anything to handle a broken
- * pipe, instead they return either zero for normal disconnect and
- * -1 for error. So that is fun.
- */
- if(1) {
- #else
- // handle general send errors
- if(EPIPE != errno) {
- #endif
- fprintf(stderr, "send error: %s\n", strerror(errno));
- c->flags |= CLIENT_IS_CLOSED;
- return d;
- }
- // on broken pipe, the remote end has already closed
- c->flags |= CLIENT_IS_CLOSED;
- #ifdef PRINT_CLIENT_DISCONNECT
- switch(reason) {
- case TIME_OUT_MSG:
- case INVALID_MSG:
- case SERVER_ERROR_MSG:
- fprintf(stderr, "client has disconnected before send complete\n");
- break;
- case RESPONSE_MSG:
- fprintf(stderr, "client has disconnected before response sent\n");
- break;
- default: ;
- }
- #endif
- return d;
- }
- // prevent further transmission, sends final packet (FIN TCP flag)
- if(0 == (WRITE_CLOSED & c->status)) {
- c->status |= WRITE_CLOSED;
- shutdown(c->fd, SHUT_WR);
- }
- return d;
- }
- __attribute__((always_inline)) static
- void sendTimeout(struct client *c) {
- // canned messages to send to clients
- static char * timeout = TIMEOUT_TEXT;
- fprintf(stderr, "client timeout\n");
- #ifndef USE_CLIENT_POOL
- /* when the write side of the pipe is closed but client has not, maybe
- * we can nudge the socket into closing
- */
- char buf[512]; memset(buf, 0, sizeof(buf));
- if(-1 == send(c->fd, buf, sizeof(buf), 0)) {
- if(EPIPE == errno) {
- fprintf(stderr, "closed using send\n");
- c->flags |= CLIENT_IS_CLOSED;
- return;
- }
- }
- #endif
- sendResponse(c, timeout, strlen(timeout), TIME_OUT_MSG);
- /* [!] allow data loss
- * once close is called the OS will immediately delete everything
- * in the outgoing buffer, eg: the timeout message will not be
- * sent. In addition the client might send a request to this server
- * and the OS will most likely drop the packet causing the client
- * to hang, waiting on a response
- *
- * request the OS to send the client tcp stack a request to reset
- * connection (causes RST TCP flag to be set) as soon as close is
- * called on the socket
- */
- setLinger(c->fd);
- c->flags |= SERVER_WANTS_CLOSE;
- }
- #ifdef USE_CLIENT_POOL
- __attribute__((always_inline)) static
- void checkTimeouts(struct client pool[], int len) {
- uint32_t ms;
- for(int i = len; i--;) {
- // do not check the server socket
- if(0 == i) continue;
- // skip unused pooled spaces, or already closing clients
- if((0 > pool[i].fd) || (CLIENT_CLOSING & pool[i].flags)) continue;
- // also skip those clients that still have remaining milliseconds
- if(0 < pool[i].ms) continue;
- // run calculations on this client to determine if really timed out
- if(0 < (ms = msRemaining(pool[i].t, PER_CLIENT_TIMEOUT_MS / 2))) {
- // restore clients actual remaining milliseconds
- pool[i].ms = ms;
- continue;
- }
- /* at this point the client has no more time left, maybe they sent some
- * data in the last possible instant and they may have a legitimate
- * request, or the server is running slow and we have not sent the
- * client a final packet, either way the timeout rule says that we are
- * done communicating with this client, cut them off completely
- *
- * [?] we should have sent the client a timeout and a final packet,
- * log when we have not done so
- */
- if(SERVER_WANTS_CLOSE & pool[i].flags) {
- if(0 == (WRITE_CLOSED & pool[i].status))
- fprintf(stderr, "never sent a packet with FIN flag\n");
- setLinger(pool[i].fd);
- pool[i].flags |= CLOSE_IMMEDIATELY;
- continue;
- }
- // okay, send this client a timeout message
- sendTimeout(&pool[i]);
- }
- }
- /* at various times it may be easy to make a guess about time passing, for
- * example, after making a sleep based system call and having no interruption
- * occur, the requested sleep time will have passed.
- * It is not really important to get this right, it is just an easy way
- * to sometimes save some CPU time.
- *
- * [?] The ACCEPT_LISTEN_TIME is used to for new clients avoiding the time the
- * sever spent waiting on old clients while listening
- */
- __attribute__((always_inline)) static
- void prepayEstimates(struct client p[], uint32_t len, struct timespec t) {
- // estimate remaining milliseconds elapsed to use for client timeouts
- uint32_t ms = PER_CLIENT_TIMEOUT_MS - msRemaining(t, PER_CLIENT_TIMEOUT_MS);
- for(int i = len; i--;) {
- // either remove milliseconds or do so next time
- if(ACCEPT_LISTEN_TIME & p[i].status) {
- p[i].ms = p[i].ms > ms ?(p[i].ms - ms) :0;
- } else p[i].status |= ACCEPT_LISTEN_TIME;
- }
- }
- #endif
- /* Three things can happen:
- * 1. just the transmit side of the full-duplex socket can be closed, in
- * which case the client may still send data and we will be unable to
- * respond, the socket will not be closed although client will be sent a
- * request to close
- * 2. we may close a socket, after the client says it is okay to do so - this
- * is the normal case, where a client has closed the receive side of the
- * full-duplex socket and we are unable to send anything else to the client
- * 3. for some reason we just want to drop a connection without notifying
- * the client. This will cause TIME_WAIT, but might be improved if the
- * Operating System is notified in advance with the SO_LINGER option
- *
- * Technically the client should be closing the connection so the nice /
- * correct thing to do is to send our goodbye intentions by setting the FIN
- * TCP flag in either the last or an empty packet and transmitting this. Upon
- * receiving the FIN flagged packet the client will send back a their close
- * and then we can close. But we do not really want to wait here for that,
- * instead we are just sending the FIN and then coming back later. Because TCP
- * guarantees packet data delivery the socket API would need to guarantee that
- * we got whatever the client sent, even though, at this point they may not
- * have sent it. We have to come back later and see if the client is finished
- * before calling close on the client socket file descriptor.
- *
- * Unless, it is a close immediately situation, so not TCP, we are just going
- * to drop the socket and call close on it. If there is still a client
- * connected it would hang / freeze.
- *
- * returns the number of socket file descriptors released to OS (one on
- * close immediately/client is already closed, zero otherwise)
- */
- int goodbyes(struct client *c) {
- // skip unused spaces in pool
- if(0 > c->fd) return 0;
- // can only say goodbye to a closed client
- if(0 == (CLIENT_CLOSING & c->flags)) return 0;
- // print what might have been sent to client, convert non-visible characters
- if(c->count) {
- // convert the users sent buffer into only visible characters and print
- convertToPrintable(c->buf, c->count);
- c->buf[FIM_BUFFER_LEN - 1] = '\0';
- fprintf(stderr, "client sent:\n%s\n", c->buf);
- }
- /* close without waiting for client to close first will cause TIME_WAIT
- * sockets. Usually the OS will reap these in several minutes, also
- * clients that are still connected will hang. So, probably better to
- * not do this all the time, but we still need to do this on occasion
- * due to the fact that we are talking to another computer that might
- * need to perform a BSOD or some other slightly more important crash
- * right now
- */
- char host[NI_MAXHOST], service[NI_MAXSERV];
- int d;
- // when close is performed without notifying client, print warning
- if(CLOSE_IMMEDIATELY & c->flags) fprintf(stderr, "[!] immediate ");
- if(0 == (d = getnameinfo(
- (struct sockaddr *) &c->address,
- c->addrSize,
- host, NI_MAXHOST,
- service, NI_MAXSERV, NI_NUMERICSERV))) {
- fprintf(stderr, "close: %s\n\n", host);
- } else {
- fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(d));
- fprintf(stderr, "close\n\n");
- }
- close(c->fd);
- // remove the socket file descriptor from the pool
- c->fd = -1;
- return 1;
- }
- /* when handling a single client and socketReady returns zero, indicating that
- * select/poll sleep returned -1, and errno was set. I guess it depends on
- * what that error is, but it seems like we can just drop the client and move
- * on. generate a log message at a minimum for now.
- *
- */
- __attribute__((always_inline)) static int wakeupError(int e, struct client * c) {
- if(e) return /* false */ 0;
- fprintf(stderr, "closing client socket due to socketReady fail.\n");
- c->flags |= CLOSE_IMMEDIATELY;
- return /* true */ 1;
- }
- /* sleep some number of milliseconds, but wake-up when a client connects
- * clients are given up to 10 milliseconds to send data
- *
- * caller will pass a function that validates a string and returns a pointer
- * to a buffer to send back to the client as a response
- * a buffer containing a leading null is used to determine if the client
- * has sent a partial request. In this case nothing will be sent to the client,
- * the clients request will be later resent, in full, to the validation function
- *
- * the buffer will have a maximum length of FIM_BUFFER_LEN
- */
- #ifdef USE_CLIENT_POOL
- int faceItMetal(uint32_t ms, char * (*f)(char *s, int len, int *limit),
- struct client pool[]) {
- int c;
- struct timespec spec;
- // wait for client connections using a timeout (fatal on failure)
- if(0 == waitForClients(ms, pool)) return 0;
- // store the time before waiting on client data
- clock_gettime(CLOCK_REALTIME, &spec);
- // latest wake up is half of PER_CLIENT_TIMEOUT_MS
- ms = PER_CLIENT_TIMEOUT_MS / 2;
- // on wake up retrieve any pending data for each client, if possible reply
- if(DONE_FOR_NOW == (c = socketReady(pool, POOL_SIZE, ms))) {
- } else if(c) {
- // if there was a new client, add them
- if((READ_READY & pool[0].status) && (0 == waitForClients(0, pool))) return 0;
- for(int i = POOL_SIZE; i--;) replyOnValid(&pool[i], f);
- }
- // estimate the remaining milliseconds before timeout for all clients
- prepayEstimates(pool, POOL_SIZE, spec);
- // send timeout messages to all timed out clients
- checkTimeouts(pool, POOL_SIZE);
- // send farewells to closing clients (skip the listening socket)
- for(int i = POOL_SIZE; i--;) { if(0 == i) continue; goodbyes(&pool[i]); }
- return /* okay */ 1;
- }
- #else
- int faceItMetal(int sock, uint32_t ms, char * (*f)(char *s, int len, int *limit)) {
- // when not using pooling, POOL_SIZE is 2
- struct client pool[POOL_SIZE];
- int c;
- // [?] even though we are not using pooling, setup pool
- pool[0].fd = sock; pool[1].fd = -1;
- // wait for client connections using a timeout (fatal on failure)
- if(0 == waitForClients(ms, pool)) return 0;
- if(-1 == pool[1].fd) return /* timeout */ 1;
- if((0 == handleClient(pool, f)) && (0 == (CLIENT_CLOSING & pool[1].flags))) {
- sendTimeout(&pool[1]);
- }
- // say goodbye to client, finished early if socket is released
- if(goodbyes(&pool[1])) return /* okay */ 1;
- // client was told to close, it has the remaining time to do so
- handleClient(pool, f);
- // set a closing flag if there is none, then initiate irrefutable farewell
- if(0 == (CLIENT_CLOSING & pool[1].flags)) pool[1].flags |= CLOSE_IMMEDIATELY;
- goodbyes(&pool[1]);
- return /* okay */ 1;
- }
- int handleClient(struct client p[], char * (*f)(char *s, int len, int *limit)) {
- struct timespec spec;
- uint32_t ms;
- int c;
- clock_gettime(CLOCK_REALTIME, &spec);
- while(0 < (ms = msRemaining(spec, PER_CLIENT_TIMEOUT_MS / 2))) {
- // [?] unsure - browsers may not always close their socket in time
- if(DONE_FOR_NOW == (c = socketReady(&p[1], 1, ms))) continue;
- if(wakeupError(c, &p[1])) break;
- // retrieve any pending data for the client, if possible reply
- replyOnValid(&p[1], f);
- // check for an early exit, client should close their connection
- if(CLIENT_CLOSING & p[1].flags) break;
- }
- return ms;
- }
- #endif
-
- #ifndef _WIN32
- /* when the application receives SIGPIPE, the OS was probably was the source
- * of the signal. It is telling us that whatever send that is currently being
- * called will fail with an EPIPE due to the remote end of a connected socket
- * being closed.
- *
- * This is fine, we cannot control the other computer or application, but not
- * handling the signal will cause the OS to close our application if the signal
- * is not handled. Install this signal handler on setup to make sure that
- * the signal is handled.
- *
- * [?] Normally we would just set the MSG_NOSIGNAL flag but Msft Winsock API
- * does not support this flag. We could just set the flag on Unix based
- * OSes.
- */
- void handle_sigpipe(int s) {
- fprintf(stderr, "recieved SIGPIPE: probably remote end broke connection\n");
- }
- #endif
- /* The main reason for this code is to hide some Msft proprietary code
- * but obviously if you want to run something at the "end" of the program
- * you can do that here. Initially non-Microsoft OSes will just return zero
- * while Msft wanders around aimlessly.
- *
- * [!] The name of this function is pretty important, it was picked as to not
- * represent anything related to exiting, goodbye, or leaving. It does not
- * have these functionality. For most operating systems it just returns
- * zero. For Msft it is used when the Winsock interface is no longer
- * needed (see: WSACleanup)
- */
- int softCuteBunnies(int c) {
- #ifdef _WIN32
- // do the shutdown dance for Msft
- WSACleanup();
- #endif
- return c;
- }
|