summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-05-11 18:05:41 -0400
committerJune McEnroe <june@causal.agency>2020-05-11 18:16:19 -0400
commit548c4a3a86a37cf74aac5ef91f84b9a762dc1023 (patch)
tree810cdf6ea4d26421f4b2a36f3a35bda745fec628
parentPass -1 as backlog to listen(2) (diff)
downloadpounce-548c4a3a86a37cf74aac5ef91f84b9a762dc1023.tar.gz
pounce-548c4a3a86a37cf74aac5ef91f84b9a762dc1023.zip
Add server send queueing with time interval
This addresses pounce getting killed with "Excess flood" when it sends
NAMES commands for too many channels when a client connects. These
commands, as well as automatic AWAY commands, are by default throttled
to 5 per second.

Tested on freenode with 36 channels and 200ms interval.
-rw-r--r--bounce.c23
-rw-r--r--bounce.h4
-rw-r--r--client.c4
-rw-r--r--pounce.112
-rw-r--r--server.c40
-rw-r--r--state.c2
6 files changed, 79 insertions, 6 deletions
diff --git a/bounce.c b/bounce.c
index dd5d723..6ddc023 100644
--- a/bounce.c
+++ b/bounce.c
@@ -31,6 +31,7 @@
 #include <sys/file.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sysexits.h>
 #include <tls.h>
 #include <unistd.h>
@@ -111,6 +112,16 @@ static size_t parseSize(const char *str) {
 	return size;
 }
 
+static struct timeval parseInterval(const char *str) {
+	char *rest;
+	long ms = strtol(str, &rest, 0);
+	if (*rest) errx(EX_USAGE, "invalid interval: %s", str);
+	return (struct timeval) {
+		.tv_sec = ms / 1000,
+		.tv_usec = 1000 * (ms % 1000),
+	};
+}
+
 static FILE *saveFile;
 
 static void saveSave(void) {
@@ -280,6 +291,7 @@ int main(int argc, char *argv[]) {
 		{ .val = 'K', .name = "local-priv", required_argument },
 		{ .val = 'N', .name = "no-names", no_argument },
 		{ .val = 'P', .name = "local-port", required_argument },
+		{ .val = 'Q', .name = "queue-interval", required_argument },
 		{ .val = 'S', .name = "bind", required_argument },
 		{ .val = 'T', .name = "no-sts", no_argument },
 		{ .val = 'U', .name = "local-path", required_argument },
@@ -329,6 +341,7 @@ int main(int argc, char *argv[]) {
 			break; case 'K': strlcpy(privPath, optarg, sizeof(privPath));
 			break; case 'N': stateNoNames = true;
 			break; case 'P': bindPort = optarg;
+			break; case 'Q': serverQueueInterval = parseInterval(optarg);
 			break; case 'S': serverBindHost = optarg;
 			break; case 'T': clientSTS = false;
 			break; case 'U': strlcpy(bindPath, optarg, sizeof(bindPath));
@@ -447,6 +460,7 @@ int main(int argc, char *argv[]) {
 	signal(SIGINT, signalHandler);
 	signal(SIGTERM, signalHandler);
 	signal(SIGPIPE, SIG_IGN);
+	signal(SIGALRM, signalHandler);
 	signal(SIGINFO, signalHandler);
 	signal(SIGUSR1, signalHandler);
 
@@ -509,18 +523,23 @@ int main(int argc, char *argv[]) {
 
 		if (signals[SIGINT] || signals[SIGTERM]) break;
 
+		if (signals[SIGALRM]) {
+			signals[SIGALRM] = 0;
+			serverDequeue();
+		}
+
 		if (signals[SIGINFO]) {
-			ringInfo();
 			signals[SIGINFO] = 0;
+			ringInfo();
 		}
 
 		if (signals[SIGUSR1]) {
+			signals[SIGUSR1] = 0;
 			cert = splitOpen(certSplit);
 			priv = splitOpen(privSplit);
 			localConfig(cert, priv, localCA, !clientPass);
 			fclose(cert);
 			fclose(priv);
-			signals[SIGUSR1] = 0;
 		}
 	}
 
diff --git a/bounce.h b/bounce.h
index a5dc836..20852cb 100644
--- a/bounce.h
+++ b/bounce.h
@@ -159,12 +159,16 @@ size_t localBind(int fds[], size_t cap, const char *host, const char *port);
 size_t localUnix(int fds[], size_t cap, const char *path);
 struct tls *localAccept(int *fd, int bind);
 
+extern struct timeval serverQueueInterval;
 void serverConfig(bool insecure, const char *cert, const char *priv);
 int serverConnect(const char *bindHost, const char *host, const char *port);
 void serverRecv(void);
 void serverSend(const char *ptr, size_t len);
 void serverFormat(const char *format, ...)
 	__attribute__((format(printf, 1, 2)));
+void serverEnqueue(const char *format, ...)
+	__attribute__((format(printf, 1, 2)));
+void serverDequeue(void);
 
 extern bool clientCA;
 extern bool clientSTS;
diff --git a/client.c b/client.c
index 25707a8..e260bc7 100644
--- a/client.c
+++ b/client.c
@@ -69,7 +69,7 @@ struct Client *clientAlloc(struct tls *tls) {
 void clientFree(struct Client *client) {
 	if (!client->need) {
 		if (!(client->caps & CapPassive) && !--active) {
-			serverFormat("AWAY :%s\r\n", clientAway);
+			serverEnqueue("AWAY :%s\r\n", clientAway);
 		}
 	}
 	tls_close(client->tls);
@@ -122,7 +122,7 @@ static void maybeSync(struct Client *client) {
 		stateSync(client);
 		if (client->setPos) ringSet(client->consumer, client->setPos);
 		if (!(client->caps & CapPassive) && !active++) {
-			serverFormat("AWAY\r\n");
+			serverEnqueue("AWAY\r\n");
 		}
 	}
 }
diff --git a/pounce.1 b/pounce.1
index b61527a..094382d 100644
--- a/pounce.1
+++ b/pounce.1
@@ -1,4 +1,4 @@
-.Dd February 27, 2020
+.Dd May 11, 2020
 .Dt POUNCE 1
 .Os
 .
@@ -14,6 +14,7 @@
 .Op Fl H Ar host
 .Op Fl K Ar priv
 .Op Fl P Ar port
+.Op Fl Q Ar time
 .Op Fl S Ar bind
 .Op Fl U Ar unix
 .Op Fl W Ar pass
@@ -122,6 +123,15 @@ Bind to
 .Ar port .
 The default port is 6697.
 .
+.It Fl Q Ar ms , Cm queue-interval = Ar ms
+Set the server send queue interval in milliseconds.
+The queue is only used
+for automated messages sent by
+.Nm .
+Messages from clients
+are sent to the server immediately.
+The default interval is 200 milliseconds.
+.
 .It Fl S Ar host , Cm bind = Ar host
 Bind to source address
 .Ar host
diff --git a/server.c b/server.c
index 20d94e3..1edeab5 100644
--- a/server.c
+++ b/server.c
@@ -148,6 +148,46 @@ void serverFormat(const char *format, ...) {
 	serverSend(buf, len);
 }
 
+enum { QueueCap = 256 };
+static struct {
+	size_t enq;
+	size_t deq;
+	char *msgs[QueueCap];
+} queue;
+
+void serverDequeue(void) {
+	if (queue.enq - queue.deq) {
+		char *msg = queue.msgs[queue.deq++ % QueueCap];
+		serverSend(msg, strlen(msg));
+		free(msg);
+	} else {
+		struct itimerval timer = { .it_value = {0} };
+		int error = setitimer(ITIMER_REAL, &timer, NULL);
+		if (error) err(EX_OSERR, "setitimer");
+	}
+}
+
+struct timeval serverQueueInterval = { .tv_usec = 1000 * 200 };
+
+void serverEnqueue(const char *format, ...) {
+	if (queue.enq - queue.deq == QueueCap) {
+		warnx("server send queue full");
+		serverDequeue();
+	} else if (queue.enq == queue.deq) {
+		struct itimerval timer = {
+			.it_interval = serverQueueInterval,
+			.it_value = { .tv_usec = 1 },
+		};
+		int error = setitimer(ITIMER_REAL, &timer, NULL);
+		if (error) err(EX_OSERR, "setitimer");
+	}
+	va_list ap;
+	va_start(ap, format);
+	int len = vasprintf(&queue.msgs[queue.enq++ % QueueCap], format, ap);
+	va_end(ap);
+	if (len < 0) err(EX_OSERR, "vasprintf");
+}
+
 void serverRecv(void) {
 	static char buf[MessageCap];
 	static size_t len;
diff --git a/state.c b/state.c
index 5da5bcc..3a5a154 100644
--- a/state.c
+++ b/state.c
@@ -413,6 +413,6 @@ void stateSync(struct Client *client) {
 			);
 		}
 		if (stateNoNames) continue;
-		serverFormat("NAMES %s\r\n", chan->name);
+		serverEnqueue("NAMES %s\r\n", chan->name);
 	}
 }