]> arthur.barton.de Git - ngircd-alex.git/blobdiff - src/ngircd/conn.c
Correctly retry outgoing conenctions when forking a resolver failed
[ngircd-alex.git] / src / ngircd / conn.c
index 4ed586206f42107b0e32132530631545fede4ea4..5505e646e0b70dfedaf5fe80012745b336d7bf7e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2014 Alexander Barton (alex@barton.de) and Contributors.
+ * Copyright (c)2001-2018 Alexander Barton (alex@barton.de) and Contributors.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -9,8 +9,6 @@
  * Please read the file COPYING, README and AUTHORS for more information.
  */
 
-#undef DEBUG_BUFFER
-
 #define CONN_MODULE
 
 #include "portab.h"
@@ -20,6 +18,9 @@
  * Connection management
  */
 
+/* Additionan debug messages related to buffer handling: 0=off / 1=on */
+#define DEBUG_BUFFER 0
+
 #include <assert.h>
 #ifdef PROTOTYPES
 # include <stdarg.h>
@@ -73,6 +74,9 @@
 
 #define SD_LISTEN_FDS_START 3          /** systemd(8) socket activation offset */
 
+#define THROTTLE_CMDS 1                        /** Throttling: max commands reached */
+#define THROTTLE_BPS 2                 /** Throttling: max bps reached */
+
 static bool Handle_Write PARAMS(( CONN_ID Idx ));
 static bool Conn_Write PARAMS(( CONN_ID Idx, char *Data, size_t Len ));
 static int New_Connection PARAMS(( int Sock, bool IsSSL ));
@@ -87,6 +91,8 @@ static void New_Server PARAMS(( int Server, ng_ipaddr_t *dest ));
 static void Simple_Message PARAMS(( int Sock, const char *Msg ));
 static int NewListener PARAMS(( const char *listen_addr, UINT16 Port ));
 static void Account_Connection PARAMS((void));
+static void Throttle_Connection PARAMS((const CONN_ID Idx, CLIENT *Client,
+                                       const int Reason, unsigned int Value));
 
 static array My_Listeners;
 static array My_ConnArray;
@@ -176,7 +182,6 @@ cb_connserver(int sock, UNUSED short what)
        CONN_ID idx = Socket2Index( sock );
 
        if (idx <= NONE) {
-               LogDebug("cb_connserver wants to write on unknown socket?!");
                io_close(sock);
                return;
        }
@@ -274,12 +279,11 @@ cb_clientserver(int sock, short what)
 {
        CONN_ID idx = Socket2Index(sock);
 
-       assert(idx >= 0);
-
-       if (idx < 0) {
+       if (idx <= NONE) {
                io_close(sock);
                return;
        }
+
 #ifdef SSL_SUPPORT
        if (what & IO_WANTREAD
            || (Conn_OPTION_ISSET(&My_Connections[idx], CONN_SSL_WANT_WRITE))) {
@@ -301,32 +305,20 @@ cb_clientserver(int sock, short what)
 GLOBAL void
 Conn_Init( void )
 {
-       CONN_ID i;
+       int size;
 
-       Pool_Size = CONNECTION_POOL;
-       if ((Conf_MaxConnections > 0) &&
-               (Pool_Size > Conf_MaxConnections))
-                       Pool_Size = Conf_MaxConnections;
-
-       if (!array_alloc(&My_ConnArray, sizeof(CONNECTION), (size_t)Pool_Size)) {
-               Log(LOG_EMERG, "Can't allocate memory! [Conn_Init]");
+       /* Initialize the "connection pool".
+        * FIXME: My_Connetions/Pool_Size is needed by other parts of the
+        * code; remove them! */
+       Pool_Size = 0;
+       size = Conf_MaxConnections > 0 ? Conf_MaxConnections : CONNECTION_POOL;
+       if (Socket2Index(size) <= NONE) {
+               Log(LOG_EMERG, "Failed to initialize connection pool!");
                exit(1);
        }
 
-       /* FIXME: My_Connetions/Pool_Size is needed by other parts of the
-        * code; remove them! */
-       My_Connections = (CONNECTION*) array_start(&My_ConnArray);
-
-       LogDebug("Allocated connection pool for %d items (%ld bytes).",
-               array_length(&My_ConnArray, sizeof(CONNECTION)),
-               array_bytes(&My_ConnArray));
-
-       assert(array_length(&My_ConnArray, sizeof(CONNECTION)) >= (size_t)Pool_Size);
-       
+       /* Initialize "listener" array. */
        array_free( &My_Listeners );
-
-       for (i = 0; i < Pool_Size; i++)
-               Init_Conn_Struct(i);
 } /* Conn_Init */
 
 /**
@@ -664,7 +656,7 @@ GLOBAL void
 Conn_Handler(void)
 {
        int i;
-       size_t wdatalen, bytes_processed;
+       size_t wdatalen;
        struct timeval tv;
        time_t t;
 
@@ -683,17 +675,7 @@ Conn_Handler(void)
                        if ((My_Connections[i].sock > NONE)
                            && (array_bytes(&My_Connections[i].rbuf) > 0)) {
                                /* ... and try to handle the received data */
-                               bytes_processed = Handle_Buffer(i);
-                               /* if we processed data, and there might be
-                                * more commands in the input buffer, do not
-                                * try to read any more data now */
-                               if (bytes_processed &&
-                                   array_bytes(&My_Connections[i].rbuf) > 2) {
-                                       LogDebug
-                                           ("Throttling connection %d: command limit reached!",
-                                            i);
-                                       Conn_SetPenalty(i, 1);
-                               }
+                               Handle_Buffer(i);
                        }
                }
 
@@ -800,7 +782,7 @@ Conn_Handler(void)
 GLOBAL bool
 Conn_WriteStr(CONN_ID Idx, const char *Format, ...)
 #else
-GLOBAL bool 
+GLOBAL bool
 Conn_WriteStr(Idx, Format, va_alist)
 CONN_ID Idx;
 const char *Format;
@@ -833,7 +815,7 @@ va_dcl
                 * IRC_WriteXXX() functions when the prefix of this server had
                 * to be added to an already "quite long" command line which
                 * has been received from a regular IRC client, for example.
-                * 
+                *
                 * We are not allowed to send such "oversized" messages to
                 * other servers and clients, see RFC 2812 2.3 and 2813 3.3
                 * ("these messages SHALL NOT exceed 512 characters in length,
@@ -1265,7 +1247,7 @@ Handle_Write( CONN_ID Idx )
                return true;
        }
 
-#ifdef DEBUG_BUFFER
+#if DEBUG_BUFFER
        LogDebug
            ("Handle_Write() called for connection %d, %ld bytes pending ...",
             Idx, wdatalen);
@@ -1382,8 +1364,8 @@ New_Connection(int Sock, UNUSED bool IsSSL)
        /* Check global connection limit */
        if ((Conf_MaxConnections > 0) &&
            (NumConnections >= (size_t) Conf_MaxConnections)) {
-               Log(LOG_ALERT, "Can't accept connection: limit (%d) reached!",
-                   Conf_MaxConnections);
+               Log(LOG_ALERT, "Can't accept new connection on socket %d: Limit (%d) reached!",
+                   Sock, Conf_MaxConnections);
                Simple_Message(new_sock, "ERROR :Connection limit reached");
                close(new_sock);
                return -1;
@@ -1402,23 +1384,10 @@ New_Connection(int Sock, UNUSED bool IsSSL)
                return -1;
        }
 
-       if (new_sock >= Pool_Size) {
-               if (!array_alloc(&My_ConnArray, sizeof(CONNECTION),
-                                (size_t) new_sock)) {
-                       Log(LOG_EMERG,
-                           "Can't allocate memory! [New_Connection]");
-                       Simple_Message(new_sock, "ERROR: Internal error");
-                       close(new_sock);
-                       return -1;
-               }
-               LogDebug("Bumped connection pool to %ld items (internal: %ld items, %ld bytes)",
-                        new_sock, array_length(&My_ConnArray,
-                        sizeof(CONNECTION)), array_bytes(&My_ConnArray));
-
-               /* Adjust pointer to new block */
-               My_Connections = array_start(&My_ConnArray);
-               while (Pool_Size <= new_sock)
-                       Init_Conn_Struct(Pool_Size++);
+       if (Socket2Index(new_sock) <= NONE) {
+               Simple_Message(new_sock, "ERROR: Internal error");
+               close(new_sock);
+               return -1;
        }
 
        /* register callback */
@@ -1491,16 +1460,16 @@ Conn_StartLogin(CONN_ID Idx)
                ident_sock = My_Connections[Idx].sock;
 #endif
 
-       if (Conf_NoticeAuth) {
-               /* Send "NOTICE AUTH" messages to the client */
+       if (Conf_NoticeBeforeRegistration) {
+               /* Send "NOTICE *" messages to the client */
 #ifdef IDENTAUTH
                if (Conf_Ident)
                        (void)Conn_WriteStr(Idx,
-                               "NOTICE AUTH :*** Looking up your hostname and checking ident");
+                               "NOTICE * :*** Looking up your hostname and checking ident");
                else
 #endif
                        (void)Conn_WriteStr(Idx,
-                               "NOTICE AUTH :*** Looking up your hostname");
+                               "NOTICE * :*** Looking up your hostname");
                /* Send buffered data to the client, but break on errors
                 * because Handle_Write() would have closed the connection
                 * again in this case! */
@@ -1527,24 +1496,38 @@ Account_Connection(void)
 } /* Account_Connection */
 
 /**
- * Translate socket handle into connection index.
+ * Translate socket handle into connection index (for historical reasons, it is
+ * a 1:1 mapping today) and enlarge the "connection pool" accordingly.
  *
  * @param Sock Socket handle.
- * @returns    Connecion index or NONE, if no connection could be found.
+ * @returns    Connecion index or NONE when the pool is too small.
  */
 static CONN_ID
 Socket2Index( int Sock )
 {
-       assert( Sock >= 0 );
+       assert(Sock > 0);
+       assert(Pool_Size >= 0);
 
-       if( Sock >= Pool_Size || My_Connections[Sock].sock != Sock ) {
-               /* the Connection was already closed again, likely due to
-                * an error. */
-               LogDebug("Socket2Index: can't get connection for socket %d!", Sock);
+       if (Sock < Pool_Size)
+               return Sock;
+
+       /* Try to allocate more memory ... */
+       if (!array_alloc(&My_ConnArray, sizeof(CONNECTION), (size_t)Sock)) {
+               Log(LOG_EMERG,
+                   "Can't allocate memory to enlarge connection pool!");
                return NONE;
        }
+       LogDebug("Enlarged connection pool for %ld sockets (%ld items, %ld bytes)",
+                Sock, array_length(&My_ConnArray, sizeof(CONNECTION)),
+                array_bytes(&My_ConnArray));
+
+       /* Adjust pointer to new block, update size and initialize new items. */
+       My_Connections = array_start(&My_ConnArray);
+       while (Pool_Size <= Sock)
+               Init_Conn_Struct(Pool_Size++);
+
        return Sock;
-} /* Socket2Index */
+}
 
 /**
  * Read data from the network to the read buffer. If an error occurs,
@@ -1572,8 +1555,8 @@ Read_Request( CONN_ID Idx )
        {
                /* Read buffer is full */
                Log(LOG_ERR,
-                   "Receive buffer space exhausted (connection %d): %d bytes",
-                   Idx, array_bytes(&My_Connections[Idx].rbuf));
+                   "Receive buffer space exhausted (connection %d): %d/%d bytes",
+                   Idx, array_bytes(&My_Connections[Idx].rbuf), READBUFFER_LEN);
                Conn_Close(Idx, "Receive buffer space exhausted", NULL, false);
                return;
        }
@@ -1587,7 +1570,7 @@ Read_Request( CONN_ID Idx )
        if (len == 0) {
                LogDebug("Client \"%s:%u\" is closing connection %d ...",
                         My_Connections[Idx].host,
-                        ng_ipaddr_tostr(&My_Connections[Idx].addr), Idx);
+                        ng_ipaddr_getport(&My_Connections[Idx].addr), Idx);
                Conn_Close(Idx, NULL, "Client closed connection", false);
                return;
        }
@@ -1625,6 +1608,8 @@ Read_Request( CONN_ID Idx )
 
        /* Update connection statistics */
        My_Connections[Idx].bytes_in += len;
+
+       /* Handle read buffer */
        My_Connections[Idx].bps += Handle_Buffer(Idx);
 
        /* Make sure that there is still a valid client registered */
@@ -1650,14 +1635,8 @@ Read_Request( CONN_ID Idx )
        }
 
        /* Look at the data in the (read-) buffer of this connection */
-       if (Client_Type(c) != CLIENT_SERVER
-           && Client_Type(c) != CLIENT_UNKNOWNSERVER
-           && Client_Type(c) != CLIENT_SERVICE
-           && My_Connections[Idx].bps >= maxbps) {
-               LogDebug("Throttling connection %d: BPS exceeded! (%u >= %u)",
-                        Idx, My_Connections[Idx].bps, maxbps);
-               Conn_SetPenalty(Idx, 1);
-       }
+       if (My_Connections[Idx].bps >= maxbps)
+               Throttle_Connection(Idx, c, THROTTLE_BPS, maxbps);
 } /* Read_Request */
 
 /**
@@ -1701,7 +1680,12 @@ Handle_Buffer(CONN_ID Idx)
                        maxcmd *= 5;
                break;
            case CLIENT_SERVICE:
-               maxcmd = MAX_COMMANDS_SERVICE; break;
+               maxcmd = MAX_COMMANDS_SERVICE;
+               break;
+           case CLIENT_USER:
+               if (Client_HasMode(c, 'F'))
+                       maxcmd = MAX_COMMANDS_SERVICE;
+               break;
        }
 
        for (i=0; i < maxcmd; i++) {
@@ -1798,10 +1782,6 @@ Handle_Buffer(CONN_ID Idx)
                        return 0; /* error -> connection has been closed */
 
                array_moveleft(&My_Connections[Idx].rbuf, 1, len);
-#ifdef DEBUG_BUFFER
-               LogDebug("Connection %d: %d bytes left in read buffer.",
-                        Idx, array_bytes(&My_Connections[Idx].rbuf));
-#endif
 #ifdef ZLIB
                if ((!old_z) && (My_Connections[Idx].options & CONN_ZIP) &&
                    (array_bytes(&My_Connections[Idx].rbuf) > 0)) {
@@ -1824,6 +1804,17 @@ Handle_Buffer(CONN_ID Idx)
                }
 #endif
        }
+#if DEBUG_BUFFER
+       LogDebug("Connection %d: Processed %ld commands (max=%ld), %ld bytes. %ld bytes left in read buffer.",
+                Idx, i, maxcmd, len_processed,
+                array_bytes(&My_Connections[Idx].rbuf));
+#endif
+
+       /* If data has been processed but there is still data in the read
+        * buffer, the command limit triggered. Enforce the penalty time: */
+       if (len_processed && array_bytes(&My_Connections[Idx].rbuf) > 2)
+               Throttle_Connection(Idx, c, THROTTLE_CMDS, maxcmd);
+
        return len_processed;
 } /* Handle_Buffer */
 
@@ -1900,7 +1891,7 @@ Check_Servers(void)
        for (i = 0; i < MAX_SERVERS; i++) {
                if (Conf_Server[i].conn_id != NONE)
                        continue;       /* Already establishing or connected */
-               if (!Conf_Server[i].host[0] || !Conf_Server[i].port > 0)
+               if (!Conf_Server[i].host[0] || Conf_Server[i].port <= 0)
                        continue;       /* No host and/or port configured */
                if (Conf_Server[i].flags & CONF_SFLAG_DISABLED)
                        continue;       /* Disabled configuration entry */
@@ -1927,8 +1918,11 @@ Check_Servers(void)
                Conf_Server[i].lasttry = time_now;
                Conf_Server[i].conn_id = SERVER_WAIT;
                assert(Proc_GetPipeFd(&Conf_Server[i].res_stat) < 0);
-               Resolve_Name(&Conf_Server[i].res_stat, Conf_Server[i].host,
-                            cb_Connect_to_Server);
+
+               /* Start resolver subprocess ... */
+               if (!Resolve_Name(&Conf_Server[i].res_stat, Conf_Server[i].host,
+                                 cb_Connect_to_Server))
+                       Conf_Server[i].conn_id = NONE;
        }
 } /* Check_Servers */
 
@@ -2003,10 +1997,7 @@ New_Server( int Server , ng_ipaddr_t *dest)
                return;
        }
 
-       if (!array_alloc(&My_ConnArray, sizeof(CONNECTION), (size_t)new_sock)) {
-               Log(LOG_ALERT,
-                   "Cannot allocate memory for server connection (socket %d)",
-                   new_sock);
+       if (Socket2Index(new_sock) <= NONE) {
                close( new_sock );
                Conf_Server[Server].conn_id = NONE;
                return;
@@ -2020,8 +2011,6 @@ New_Server( int Server , ng_ipaddr_t *dest)
                return;
        }
 
-       My_Connections = array_start(&My_ConnArray);
-
        assert(My_Connections[new_sock].sock <= 0);
 
        Init_Conn_Struct(new_sock);
@@ -2261,9 +2250,9 @@ cb_Read_Resolver_Result( int r_fd, UNUSED short events )
                strlcpy(My_Connections[i].host, readbuf,
                        sizeof(My_Connections[i].host));
                Client_SetHostname(c, readbuf);
-               if (Conf_NoticeAuth)
+               if (Conf_NoticeBeforeRegistration)
                        (void)Conn_WriteStr(i,
-                                       "NOTICE AUTH :*** Found your hostname: %s",
+                                       "NOTICE * :*** Found your hostname: %s",
                                        My_Connections[i].host);
 #ifdef IDENTAUTH
                ++identptr;
@@ -2287,22 +2276,22 @@ cb_Read_Resolver_Result( int r_fd, UNUSED short events )
                                    i, identptr);
                                Client_SetUser(c, identptr, true);
                        }
-                       if (Conf_NoticeAuth) {
+                       if (Conf_NoticeBeforeRegistration) {
                                (void)Conn_WriteStr(i,
-                                       "NOTICE AUTH :*** Got %sident response%s%s",
+                                       "NOTICE * :*** Got %sident response%s%s",
                                        *ptr ? "invalid " : "",
                                        *ptr ? "" : ": ",
                                        *ptr ? "" : identptr);
                        }
                } else if(Conf_Ident) {
                        Log(LOG_INFO, "IDENT lookup for connection %d: no result.", i);
-                       if (Conf_NoticeAuth)
+                       if (Conf_NoticeBeforeRegistration)
                                (void)Conn_WriteStr(i,
-                                       "NOTICE AUTH :*** No ident response");
+                                       "NOTICE * :*** No ident response");
                }
 #endif
 
-               if (Conf_NoticeAuth) {
+               if (Conf_NoticeBeforeRegistration) {
                        /* Send buffered data to the client, but break on
                         * errors because Handle_Write() would have closed
                         * the connection again in this case! */
@@ -2360,7 +2349,7 @@ Simple_Message(int Sock, const char *Msg)
  * @returns    Pointer to CLIENT structure.
  */
 GLOBAL CLIENT *
-Conn_GetClient( CONN_ID Idx ) 
+Conn_GetClient( CONN_ID Idx )
 {
        CONNECTION *c;
 
@@ -2407,6 +2396,35 @@ Conn_GetFromProc(int fd)
        return NONE;
 } /* Conn_GetFromProc */
 
+/**
+ * Throttle a connection because of excessive usage.
+ *
+ * @param Reason The reason, see THROTTLE_xxx constants.
+ * @param Idx The connection index.
+ * @param Client The client of this connection.
+ * @param Seconds The time to delay this connection.
+ */
+static void
+Throttle_Connection(const CONN_ID Idx, CLIENT *Client, const int Reason,
+                   unsigned int Value)
+{
+       assert(Idx > NONE);
+       assert(Client != NULL);
+
+       /* Never throttle servers or services, only interrupt processing */
+       if (Client_Type(Client) == CLIENT_SERVER
+           || Client_Type(Client) == CLIENT_UNKNOWNSERVER
+           || Client_Type(Client) == CLIENT_SERVICE)
+               return;
+
+       /* Don't throttle clients with user mode 'F' set */
+       if (Client_HasMode(Client, 'F'))
+               return;
+
+       LogDebug("Throttling connection %d: code %d, value %d!", Idx,
+                Reason, Value);
+       Conn_SetPenalty(Idx, 1);
+}
 
 #ifndef STRICT_RFC
 
@@ -2439,9 +2457,7 @@ cb_clientserver_ssl(int sock, UNUSED short what)
 {
        CONN_ID idx = Socket2Index(sock);
 
-       assert(idx >= 0);
-
-       if (idx < 0) {
+       if (idx <= NONE) {
                io_close(sock);
                return;
        }
@@ -2491,12 +2507,13 @@ cb_connserver_login_ssl(int sock, short unused)
 {
        CONN_ID idx = Socket2Index(sock);
 
-       assert(idx >= 0);
-       if (idx < 0) {
+       (void) unused;
+
+       if (idx <= NONE) {
                io_close(sock);
                return;
        }
-       (void) unused;
+
        switch (ConnSSL_Connect( &My_Connections[idx])) {
                case 1: break;
                case 0: LogDebug("ConnSSL_Connect: not ready");