From 548c4a3a86a37cf74aac5ef91f84b9a762dc1023 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 11 May 2020 18:05:41 -0400 Subject: 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. --- bounce.c | 23 +++++++++++++++++++++-- bounce.h | 4 ++++ client.c | 4 ++-- pounce.1 | 12 +++++++++++- server.c | 40 ++++++++++++++++++++++++++++++++++++++++ state.c | 2 +- 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 #include #include +#include #include #include #include @@ -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); } } -- cgit 1.4.1