Commit Diff
Diff:
9b1c47220fafeacda1e83b4732e1c8574062aac6
643ae1b48bc83edad5ba5fa337f81564c79fd0cd
Commit:
643ae1b48bc83edad5ba5fa337f81564c79fd0cd
Tree:
e645bf2b5137af13dd7ec04ef440149a9218b1e9
Author:
Florian Westphal <fw@strlen.de>
Committer:
Florian Westphal <fw@strlen.de>
Date:
Mon May 25 20:25:18 2009 UTC
Message:
enforce upper limit on maximum number of handled commands reported on #ngircd: pasting lots of lines into a channel can kill off many people on the channel if the read buffer is drained quickly enough and the client-side TCP can't keep up with the incoming data. This implements a throttling scheme: - an irc client may send up to 3 commands per second before a one second pause is enforced. - an irc client may send up to 256 bytes per second before a one second pause is enforced. After discussion with Alexander Barton, server <-> server links are treated specially: There is no artificial limit on the number of bytes sent per second, and up to 10 commands are processed per second before a pause is enforced. It may be neccessary to make those limits tuneable to accomondate larger networks, but for now they are compile time values.
blob - 77a2bc19d486262bccd5586415f3569b3220f86e
blob + ffd0b89c6a479ff9a194b42e96103dca3d45dac0
--- src/ngircd/conn.c
+++ src/ngircd/conn.c
@@ -75,13 +75,16 @@
#define SERVER_WAIT (NONE - 1)
+#define MAX_COMMANDS 3
+#define MAX_COMMANDS_SERVER 10
+
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 ));
static CONN_ID Socket2Index PARAMS(( int Sock ));
static void Read_Request PARAMS(( CONN_ID Idx ));
-static void Handle_Buffer PARAMS(( CONN_ID Idx ));
+static unsigned int Handle_Buffer PARAMS(( CONN_ID Idx ));
static void Check_Connections PARAMS(( void ));
static void Check_Servers PARAMS(( void ));
static void Init_Conn_Struct PARAMS(( CONN_ID Idx ));
@@ -622,7 +625,7 @@ GLOBAL void
Conn_Handler(void)
{
int i;
- unsigned int wdatalen;
+ unsigned int wdatalen, bytes_processed;
struct timeval tv;
time_t t;
@@ -645,9 +648,19 @@ Conn_Handler(void)
for (i = 0; i < Pool_Size; i++) {
if ((My_Connections[i].sock > NONE)
&& (array_bytes(&My_Connections[i].rbuf) > 0)
- && (My_Connections[i].delaytime < t)) {
+ && (My_Connections[i].delaytime <= t)) {
/* ... and try to handle the received data */
- Handle_Buffer(i);
+ 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);
+ }
}
}
@@ -1307,7 +1320,9 @@ static void
Read_Request( CONN_ID Idx )
{
ssize_t len;
+ static const unsigned int maxbps = COMMAND_LEN / 2;
char readbuf[READBUFFER_LEN];
+ time_t t;
CLIENT *c;
assert( Idx > NONE );
assert( My_Connections[Idx].sock > NONE );
@@ -1384,21 +1399,34 @@ Read_Request( CONN_ID Idx )
if (c && (Client_Type(c) == CLIENT_USER
|| Client_Type(c) == CLIENT_SERVER
|| Client_Type(c) == CLIENT_SERVICE)) {
- My_Connections[Idx].lastdata = time(NULL);
+ t = time(NULL);
+ if (My_Connections[Idx].lastdata != t)
+ My_Connections[Idx].bps = 0;
+
+ My_Connections[Idx].lastdata = t;
My_Connections[Idx].lastping = My_Connections[Idx].lastdata;
}
/* Look at the data in the (read-) buffer of this connection */
- Handle_Buffer(Idx);
+ My_Connections[Idx].bps += Handle_Buffer(Idx);
+ if (Client_Type(c) != CLIENT_SERVER
+ && My_Connections[Idx].bps >= maxbps) {
+ LogDebug("Throttling connection %d: BPS exceeded! (%u >= %u)",
+ Idx, My_Connections[Idx].bps, maxbps);
+ Conn_SetPenalty(Idx, 1);
+ }
} /* Read_Request */
/**
* Handle all data in the connection read-buffer.
- * All data is precessed until no complete command is left. When a fatal
- * error occurs, the connection is shut down.
+ * Data is processed until no complete command is left in the read buffer,
+ * or MAX_COMMANDS[_SERVER] commands were processed.
+ * When a fatal error occurs, the connection is shut down.
+ * @param Idx Index of the connection.
+ * @return number of bytes processed.
*/
-static void
+static unsigned int
Handle_Buffer(CONN_ID Idx)
{
#ifndef STRICT_RFC
@@ -1410,31 +1438,41 @@ Handle_Buffer(CONN_ID Idx)
#ifdef ZLIB
bool old_z;
#endif
+ unsigned int i, maxcmd = MAX_COMMANDS, len_processed = 0;
+ CLIENT *c;
+ c = Conn_GetClient(Idx);
+ assert( c != NULL);
+
+ /* Servers do get special command limits, so they can process
+ * all the messages that are required while peering. */
+ if (Client_Type(c) == CLIENT_SERVER)
+ maxcmd = MAX_COMMANDS_SERVER;
+
starttime = time(NULL);
- for (;;) {
+ for (i=0; i < maxcmd; i++) {
/* Check penalty */
if (My_Connections[Idx].delaytime > starttime)
- return;
+ return 0;
#ifdef ZLIB
/* Unpack compressed data, if compression is in use */
if (Conn_OPTION_ISSET(&My_Connections[Idx], CONN_ZIP)) {
/* When unzipping fails, Unzip_Buffer() shuts
* down the connection itself */
if (!Unzip_Buffer(Idx))
- return;
+ return 0;
}
#endif
if (0 == array_bytes(&My_Connections[Idx].rbuf))
- return;
+ break;
/* Make sure that the buffer is NULL terminated */
if (!array_cat0_temporary(&My_Connections[Idx].rbuf)) {
Conn_Close(Idx, NULL,
"Can't allocate memory [Handle_Buffer]",
true);
- return;
+ return 0;
}
/* RFC 2812, section "2.3 Messages", 5th paragraph:
@@ -1470,7 +1508,7 @@ Handle_Buffer(CONN_ID Idx)
#endif
if (!ptr)
- return;
+ break;
/* Complete (=line terminated) request found, handle it! */
*ptr = '\0';
@@ -1485,16 +1523,16 @@ Handle_Buffer(CONN_ID Idx)
Idx, array_bytes(&My_Connections[Idx].rbuf),
COMMAND_LEN - 1);
Conn_Close(Idx, NULL, "Request too long", true);
- return;
+ return 0;
}
+ len_processed += len;
if (len <= delta) {
/* Request is empty (only '\r\n', '\r' or '\n');
* delta is 2 ('\r\n') or 1 ('\r' or '\n'), see above */
array_moveleft(&My_Connections[Idx].rbuf, 1, len);
- return;
+ continue;
}
-
#ifdef ZLIB
/* remember if stream is already compressed */
old_z = My_Connections[Idx].options & CONN_ZIP;
@@ -1503,7 +1541,7 @@ Handle_Buffer(CONN_ID Idx)
My_Connections[Idx].msg_in++;
if (!Parse_Request
(Idx, (char *)array_start(&My_Connections[Idx].rbuf)))
- return;
+ return 0; /* error -> connection has been closed */
array_moveleft(&My_Connections[Idx].rbuf, 1, len);
LogDebug("Connection %d: %d bytes left in read buffer.",
@@ -1520,7 +1558,7 @@ Handle_Buffer(CONN_ID Idx)
Conn_Close(Idx, NULL,
"Can't allocate memory [Handle_Buffer]",
true);
- return;
+ return 0;
}
array_trunc(&My_Connections[Idx].rbuf);
@@ -1530,6 +1568,7 @@ Handle_Buffer(CONN_ID Idx)
}
#endif
}
+ return len_processed;
} /* Handle_Buffer */
blob - 28bbda357860efdcbf938afc127b296c480bd389
blob + 5e9140206e83f2e621a1bdb8a085ab88e98e50e8
--- src/ngircd/conn.h
+++ src/ngircd/conn.h
@@ -82,6 +82,7 @@ typedef struct _Connection
long msg_in, msg_out; /* Received and sent IRC messages */
int flag; /* Flag (see "irc-write" module) */
UINT16 options; /* Link options / connection state */
+ UINT16 bps; /* bytes processed within last second */
CLIENT *client; /* pointer to client structure */
#ifdef ZLIB
ZIPDATA zip; /* Compression information */
IRCNow