From 1979b37e4fa3c9b7ed8784cf8b52cfae74edd9a4 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Tue, 11 Jun 2019 19:30:19 +0100 Subject: Send a PING to the server before assuming a timeout is definite. --- blabouncer.c | 83 +++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 29 deletions(-) (limited to 'blabouncer.c') diff --git a/blabouncer.c b/blabouncer.c index d14223e..4986551 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -70,6 +70,7 @@ #define MAXRCVSIZE 2417 #define MAXTOKENS 100 // maximum number of (CRLF or space) separated tokens per server response we expect (TODO - check this is reasonable) (CRLF and spaces just grouped here due to laziness) #define SERVERTIMEOUT 300 // How many seconds to wait without hearing from the server before assuming a timeout +#define SELECTTIMEOUT 60 // How many seconds to wait before our pselect() times out, useful for detecting server timeouts // Global debug control int debug = 0; // Debug verbosity ("0" for critical only, "1" for some extra info, "2" for full debug mode) @@ -126,6 +127,7 @@ int connecttoircserver(SSL_CTX **serverctx, SSL **server_ssl, int *serversockfd, ircdstrings->capmultiprefix = 0; ircdstrings->autonicknum = 0; ircdstrings->lastmessagetime = time(NULL); + ircdstrings->timeoutcheck = 0; // ircdstrings.reconnecting is not set here as we want to track reconnections separately // Populate nick and username from our configuration file for now, real IRCd may change them later (TODO - Is this true of username?) @@ -449,37 +451,12 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { } } - // Before we wait for a message, let's make sure the server is still responding - // TODO - Code duplication, make a function and share with socket error code below - if (ircdstrings.lastmessagetime < time(NULL) - SERVERTIMEOUT) { - debugprint(DEBUG_CRIT, "Server has timed out (%ld seconds), reconnecting!\n", time(NULL) - ircdstrings.lastmessagetime); - // Tell all clients if we timed out - char alertmsg[MAXDATASIZE]; - snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Server has timed out (%ld seconds), reconnecting!", ircdstrings.ircnick, time(NULL) - ircdstrings.lastmessagetime); - sendtoallclients(clients, alertmsg, 0, settings); - if (settings->servertls) { - // Finish up with OpenSSL if using server TLS - SSL_free(server_ssl); - } - // Close the socket - close(*serversockfd); - // Make a new one - *serversockfd = createserversocket(settings->ircserver, settings->ircserverport); - - // Set reconnection marker for other functions to know we're reconnecting - ircdstrings.reconnecting = 1; - // Set oldnick in case we change nick when reconnecting so we can inform existing clients - strcpy(ircdstrings.oldnick, ircdstrings.ircnick); - connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients); - - // Back to top of loop - continue; - } - debugprint(DEBUG_FULL, "pselect()ing...\n"); // Check to see if any fd in the fd_set is waiting or a signal happened - blocks here until one one of those things happens - // (pselect() to do signal handling in additiona to fd monitoring) - if (pselect(fdmax + 1, &rfds, NULL, NULL, NULL, &oldset) < 0) { // network socket + 1, rfds, no writes, no exceptions/errors, no timeout, original sigmask + // (pselect() to do signal handling in addition to fd monitoring) + struct timespec timeout = {SELECTTIMEOUT, 0}; // pselect() should timeout after SELECTTIMEOUT seconds + int selret = pselect(fdmax + 1, &rfds, NULL, NULL, &timeout, &oldset); // network socket + 1, rfds, no writes, no exceptions/errors, no timeout, original sigmask + if (selret < 0) { // Error or signal interrupt if (errno == EINTR) { // Signal caught, do signal handling debugprint(DEBUG_CRIT, "signal '%d' happened, exiting!\n", signum); @@ -498,6 +475,54 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { } } + // pselect() timed out, let's see if a server timeout might be happening, if not, just continue to the top of the loop again + if (selret == 0) { + debugprint(DEBUG_CRIT, "pselect() timed out.\n", errno); + + // SERVERTIMEOUT seconds have expired... + if (ircdstrings.lastmessagetime < time(NULL) - SERVERTIMEOUT && !FD_ISSET(*serversockfd, &rfds)) { + if (ircdstrings.timeoutcheck == 0) { + // ...and we haven't tried a PING yet, so let's PING the server to see if things are still working + debugprint(DEBUG_CRIT, "Server might have timed out after %ld seconds, PINGing it...\n", time(NULL) - ircdstrings.lastmessagetime); + ircdstrings.timeoutcheck = 1; + char outgoingmsg[MAXDATASIZE]; + if (!snprintf(outgoingmsg, MAXDATASIZE, "PING %s", ircdstrings.ircdname)) { + fprintf(stderr, "Error while preparing timeout testing PING message!\n"); + debugprint(DEBUG_CRIT, "Error while preparing timeout testing PING message\n"); + snprintf(outgoingmsg, MAXDATASIZE, "PING timeouttest"); + } + sendtoserver(server_ssl, outgoingmsg, strlen(outgoingmsg), 0, clients, settings); + } else { + // ...and we've already PINGed the server and haven't heard back yet, so let's assume we've timed out + // TODO - Code duplication, make a function and share with socket error code below + debugprint(DEBUG_CRIT, "Server has timed out (%ld seconds), reconnecting!\n", time(NULL) - ircdstrings.lastmessagetime); + // Tell all clients if we timed out + char alertmsg[MAXDATASIZE]; + snprintf(alertmsg, MAXDATASIZE, "NOTICE %s :Server has timed out (%ld seconds), reconnecting!", ircdstrings.ircnick, time(NULL) - ircdstrings.lastmessagetime); + sendtoallclients(clients, alertmsg, 0, settings); + if (settings->servertls) { + // Finish up with OpenSSL if using server TLS + SSL_free(server_ssl); + } + // Close the socket + close(*serversockfd); + // Make a new one + *serversockfd = createserversocket(settings->ircserver, settings->ircserverport); + + // Set reconnection marker for other functions to know we're reconnecting + ircdstrings.reconnecting = 1; + // Set oldnick in case we change nick when reconnecting so we can inform existing clients + strcpy(ircdstrings.oldnick, ircdstrings.ircnick); + connecttoircserver(&serverctx, &server_ssl, serversockfd, &ircdstrings, settings, clients); + } + // Back to top of loop + continue; + } else { + // No timeout is occuring, back to the top of the loop + continue; + } + } + // TODO - switch around the serversockfd and STDIN FD_ISSET if-statements? They feel the wrong way round. Are they like this on purpose? I can't remember. // (although actually stdin may go once its not wanted for possible direct interaction for debugging) -- cgit v1.2.3