Handle "throttling" in a single function
authorAlexander Barton <alex@barton.de>
Tue, 18 Mar 2014 13:48:52 +0000 (14:48 +0100)
committerAlexander Barton <alex@barton.de>
Tue, 18 Mar 2014 13:53:29 +0000 (14:53 +0100)
ngIRCd uses "command throttling" and "bps throttling" (bytes per second).
The states are detected in different functions, Conn_Handler() and
Read_Request(), but handle the actual "throttling" in a common function:
this enables us to guarantee consistent behavior and to disable throttling
for special connections in only one place, eventually.

src/ngircd/conn.c

index 4dfe62f..77c8cd8 100644 (file)
@@ -74,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 ));
@@ -88,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;
@@ -665,7 +670,7 @@ GLOBAL void
 Conn_Handler(void)
 {
        int i;
-       size_t wdatalen, bytes_processed;
+       size_t wdatalen;
        struct timeval tv;
        time_t t;
 
@@ -684,17 +689,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);
                        }
                }
 
@@ -1573,8 +1568,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;
        }
@@ -1626,6 +1621,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 */
@@ -1651,14 +1648,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 */
 
 /**
@@ -1827,6 +1818,11 @@ Handle_Buffer(CONN_ID Idx)
                 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 */
 
@@ -2410,6 +2406,31 @@ 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;
+
+       LogDebug("Throttling connection %d: code %d, value %d!", Idx,
+                Reason, Value);
+       Conn_SetPenalty(Idx, 1);
+}
 
 #ifndef STRICT_RFC