// Sleep until an event happens (process it with CheckAll) or the timeout expires void connections_SleepWait(int timeoutms) { // initialise socket system (just in case it's not done yet): if (!so_Startup()) { return; } // use select to wait for events: so_SelectWait(timeoutms); }
int listeners_Create(int port, int ssl, void* userdata) { // startup socket system if (!so_Startup()) { return 0; } // check if a server is already using this port: if (listeners_GetByPort(port, NULL)) { #ifdef CONNECTIONSDEBUG printinfo("[connections-server] port %d already used"); #endif return 0; } // allocate struct: struct listener* l = malloc(sizeof(*l)); if (!l) { #ifdef CONNECTIONSDEBUG printinfo("[connections-server] malloc failed"); #endif return 0; } // initialise struct: memset(l, 0, sizeof(*l)); l->socket = so_CreateSocket(1, IPTYPE_IPV6); l->port = port; l->ssl = ssl; l->userdata = userdata; if (l->socket < 0) { #ifdef CONNECTIONSDEBUG printinfo("[connections-server] so_CreateSocket() failed"); #endif free(l); return 0; } if (!so_MakeSocketListen(l->socket, port, IPTYPE_IPV6, "any")) { #ifdef CONNECTIONSDEBUG printinfo("[connections-server] so_MakeSocketListen() failed"); #endif so_CloseSocket(l->socket); free(l); return 0; } l->next = listeners; listeners = l; #ifdef CONNECTIONSDEBUG printinfo("[connections-server] new server at port %d (%d)", l->port, l->socket); #endif return 1; }
// Initialise the given connection struct and open the connection void connections_Init(struct connection* c, const char* target, int port, int linebuffered, int lowdelay, int canautoclose, void (*autoclosecallback)(struct connection* c), void* userdata) { int oldluaref = c->luarefcount; memset(c, 0, sizeof(*c)); c->luarefcount = oldluaref; c->socket = -1; c->error = -1; c->canautoclose = canautoclose; c->autoclosecallback = autoclosecallback; c->userdata = userdata; c->lowdelay = lowdelay; c->targetport = port; c->linebuffered = linebuffered; c->lastreadtime = time_getMilliseconds(); if (connectionlist) { c->next = connectionlist; } connectionlist = c; #ifdef CONNECTIONSDEBUG printinfo("[connections] Adding connection to list"); #endif // initialise socket system (just in case it's not done yet): if (!so_Startup()) { c->error = CONNECTIONERROR_INITIALISATIONFAILED; return; } // with an empty target, we simply won't do anything except instant-error: if (!target) { c->error = CONNECTIONERROR_NOSUCHHOST; return; } // allocate buffers: c->inbuf = malloc(CONNECTIONINBUFSIZE); if (!c->inbuf) { c->error = CONNECTIONERROR_INITIALISATIONFAILED; return; } c->inbufsize = CONNECTIONINBUFSIZE; c->outbuf = malloc(CONNECTIONOUTBUFSIZE); if (!c->outbuf) { c->error = CONNECTIONERROR_INITIALISATIONFAILED; return; } c->outbufsize = CONNECTIONOUTBUFSIZE; // get a socket if (isipv4ip(target)) { // only take ipv4 when it's obvious if (!connections_SetSocket(c, IPTYPE_IPV4)) { c->error = CONNECTIONERROR_INITIALISATIONFAILED; return; } } else { // otherwise, try ipv6 first if (!connections_SetSocket(c, IPTYPE_IPV6)) { c->error = CONNECTIONERROR_INITIALISATIONFAILED; return; } } // we probably need to resolve the host first: if (!isipv4ip(target) && !isipv6ip(target)) { c->hostresolveptr = hostresolv_LookupRequest(target, 0); c->hostresolveptrv6 = hostresolv_LookupRequest(target, 1); if (!c->hostresolveptrv6) { // we really want v6 resolution, so we work well with ipv6-only if (c->hostresolveptr) { hostresolv_CancelRequest(c->hostresolveptr); } c->hostresolveptr = NULL; } #ifdef CONNECTIONSDEBUG printinfo("[connections] resolving target: %s",target); #endif return; } // connect: int result = connections_TryConnect(c, target); if (!result) { return; } // we want to know when ready for writing, since that means we're connected: so_SelectWantWrite(c->socket, 1); #ifdef CONNECTIONSDEBUG printinfo("[connections] connecting to ip: %s", target); #endif }
// Check all connections for events, updates etc int connections_CheckAll(int (*connectedcallback)(struct connection* c), int (*readcallback)(struct connection* c, char* data, unsigned int datalength), int (*errorcallback)(struct connection* c, int error)) { // initialise socket system (just in case it's not done yet): if (!so_Startup()) { return 1; } struct connection* c = connectionlist; while (c) { struct connection* cnext = c->next; // don't process connections with errors if (c->error >= 0) { if (!c->errorreported) { c->errorreported = 1; if (errorcallback) { if (!errorcallback(c, c->error)) { // an error occured. proceed to next connect: c = cnext; continue; } } } } // check for auto close: if (c->canautoclose && c->wantautoclose && (c->error >= 0 || c->lastreadtime + 30000 < time_getMilliseconds()) && !c->closewhensent) { if (c->error >= 0) { // connection already error'd, we can get rid of it: #ifdef CONNECTIONSDEBUG printinfo("[connections] autoclosing connection %d", c->socket); #endif c->autoclosecallback(c); connections_Close(c); c = cnext; } else { // make the connection error: connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONAUTOCLOSE); } continue; } // check for close after send: if (c->outbufbytes <= 0 && c->closewhensent) { if (c->luarefcount <= 0) { #ifdef CONNECTIONSDEBUG printinfo("[connections] closing connection %d " "since data is sent", c->socket); #endif connections_Close(c); } else { // lua still has a reference, don't close } c = cnext; continue; } // check host resolve requests first: if (c->error < 0 && !c->closewhensent && (c->hostresolveptr || c->hostresolveptrv6)) { int rqstate1 = RESOLVESTATUS_SUCCESS; if (c->hostresolveptr) { rqstate1 = hostresolv_GetRequestStatus(c->hostresolveptr); } int rqstate2 = RESOLVESTATUS_SUCCESS; if (c->hostresolveptrv6) { rqstate2 = hostresolv_GetRequestStatus(c->hostresolveptrv6); } if (rqstate1 != RESOLVESTATUS_PENDING && rqstate2 != RESOLVESTATUS_PENDING) { // requests are done, get ips: char* ipv4 = NULL; char* ipv6 = NULL; // ipv4 ip: const char* p = NULL; if (rqstate1 == RESOLVESTATUS_SUCCESS && c->hostresolveptr) { p = hostresolv_GetRequestResult(c->hostresolveptr); } if (p) { ipv4 = strdup(p); } // ipv6 ip: p = NULL; if (rqstate2 == RESOLVESTATUS_SUCCESS && c->hostresolveptrv6) { p = hostresolv_GetRequestResult(c->hostresolveptrv6); } if (p) { ipv6 = strdup(p); } // free requests: if (c->hostresolveptr) { hostresolv_CancelRequest(c->hostresolveptr); } if (c->hostresolveptrv6) { hostresolv_CancelRequest(c->hostresolveptrv6); } c->hostresolveptr = NULL; c->hostresolveptrv6 = NULL; #ifdef CONNECTIONSDEBUG printinfo("[connections] resolved host, results: v4: %s, v6: %s", ipv4, ipv6); #endif // if we got a v6 ip, connect there first: int result; if (ipv6) { // Prefer an IPv6 connection: result = connections_TryConnect(c, ipv6); free(ipv6); if (!result) { if (ipv4) { c->error = -1; // Try an IPv4 connection: result = connections_TryConnect(c, ipv4); free(ipv4); if (!result) { connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONFAILED); } } c = cnext; continue; } // ok we are about to connect, preserve v4 ip for later trying in case it fails: c->retryv4ip = ipv4; // then continue: c = cnext; continue; } else { if (ipv4) { // Attempt an IPv4 connection: result = connections_TryConnect(c, ipv4); if (result) { free(ipv4); c = cnext; continue; } // Didn't work, so nothing we can do: free(ipv4); connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONFAILED); } else { connections_E(c, errorcallback, CONNECTIONERROR_NOSUCHHOST); } c = cnext; continue; } } } // if the connection is attempting to connect, check if it succeeded: if (c->error < 0 && !c->closewhensent && !c->connected && c->socket >= 0) { if (so_SelectSaysWrite(c->socket, &c->sslptr)) { if (c->outbufbytes <= 0) { so_SelectWantWrite(c->socket, 0); } if (so_CheckIfConnected(c->socket, &c->sslptr)) { #ifdef CONNECTIONSDEBUG printinfo("[connections] now connected"); #endif c->connected = 1; if (connectedcallback) { if (!connectedcallback(c)) { // a callback error occured. // we will simply continue. } } } else { // we aren't connected! if (c->retryv4ip) { // we tried ipv6, now try ipv4 #ifdef CONNECTIONSDEBUG printinfo("[connections] retrying v4 after v6 " "fail..."); #endif // attempt to connect: int result = connections_TryConnect(c, c->retryv4ip); free(c->retryv4ip); c->retryv4ip = NULL; if (!result) { connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONFAILED); } c = cnext; continue; } #ifdef CONNECTIONSDEBUG printinfo("[connections] connection couldn't be " "established"); #endif connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONFAILED); c = cnext; continue; } } } // read things if we can: if (c->error < 0 && c->socket >= 0 && !c->closewhensent && c->connected) { if (so_SelectSaysRead(c->socket, &c->sslptr)) { if (c->inbufbytes >= c->inbufsize && c->linebuffered == 1) { // we will break this mega line into two: // first, send without line processing: c->linebuffered = 0; int closed = 0; if (!connections_ProcessReceivedData(c, readcallback, &closed)) { // an error occured in te read callback. // we will simply continue. } if (closed) { // connection was closed by callback c = cnext; continue; } if (c->error >= 0) { // lua closed this connection c = cnext; continue; } // then, turn it back on: c->linebuffered = 1; } // read new bytes into the buffer: int r = so_ReceiveSSLData(c->socket, c->inbuf + c->inbufbytes, c->inbufsize - c->inbufbytes, &c->sslptr); if (r == 0) { // connection closed. send out all data we still have: if (c->inbufbytes > 0) { int closed = 0; if (!connections_ProcessReceivedData( c, readcallback, &closed)) { // an error occured in the read callback. // we will simply continue. } if (closed) { // connection was closed by callback c = cnext; continue; } } // then error: connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONCLOSED); #ifdef CONNECTIONSDEBUG printinfo("[connections] receive on %d returned " "end of stream", c->socket); #endif c = cnext; continue; } if (r > 0) { // we successfully received new bytes c->inbufbytes += r; c->lastreadtime = time_getMilliseconds(); int closed = 0; if (!connections_ProcessReceivedData(c, readcallback, &closed)) { // an error occured in the read callback. // we will simply continue. } if (closed) { // connection was closed by callback c = cnext; continue; } } } } // write things if we can: if ((c->error < 0 || (c->closewhensent)) && c->connected && c->outbufbytes > 0 && c->socket >= 0) { if (so_SelectSaysWrite(c->socket, &c->sslptr)) { int r = so_SendSSLData(c->socket, c->outbuf + c->outbufoffset, c->outbufbytes, &c->sslptr); if (r == 0) { // connection closed: connections_E(c, errorcallback, CONNECTIONERROR_CONNECTIONCLOSED); #ifdef CONNECTIONSDEBUG printinfo("[connections] send returned end of stream"); #endif c = cnext; continue; } // remove sent bytes from buffer: if (r > 0) { c->outbufbytes -= r; c->outbufoffset += r; so_SelectWantWrite(c->socket, 1); if (c->outbufbytes <= 0) { c->outbufoffset = 0; so_SelectWantWrite(c->socket, 0); } else { if (c->outbufoffset > CONNECTIONOUTBUFSIZE/2) { // move buffer contents back to beginning memmove(c->outbuf, c->outbuf + c->outbufoffset, c->outbufbytes); c->outbufoffset = 0; } } } } } // check for close after send: if (c->outbufbytes <= 0 && c->closewhensent) { if (c->luarefcount <= 0) { #ifdef CONNECTIONSDEBUG printinfo("[connections] closing connection %d since data is sent", c->socket); #endif connections_Close(c); } else { // lua still has a reference, but close at least the socket: so_CloseSSLSocket(c->socket, &c->sslptr); c->socket = -1; } c = cnext; continue; } c = cnext; } return 1; }