#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; }