From 8ab097afb743061c6c9b865bdb401ba51285c347 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Mon, 4 Feb 2013 21:46:20 +0100 Subject: Implement support for systemd(8) "socket activation" This patch enables ngIRCd to work with listening sockets already initialized and passed-in by systemd(8) and hereby to support on-demand "socket activation". systemd(8) uses two environment variables to pass information about the sockets to ngIRCd, LISTEN_PID and LISTEN_FDS, and this mechanism only kicks in when both variables are set. In all other cases, and therefore in most installations out there, nothing changes at all. Please note: If socket activation is in effect, ngIRCd will not initialize any (other) soeckets on its own! All sockets must be configured in the systemd(8) socket unit configuration file in this case, see ./contrib/ngircd.socket for example. Probably it would be interesting to match passed-in sockets to configured listening sockets and to initialize all the remaining ones not already set up by systemd(8), but this is kept back for an other patch ... See - - - --- src/ngircd/conn.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'src') diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index 14d337b9..378509f9 100644 --- a/src/ngircd/conn.c +++ b/src/ngircd/conn.c @@ -82,6 +82,8 @@ #define MAX_COMMANDS_SERVER_MIN 10 #define MAX_COMMANDS_SERVICE 10 +#define SD_LISTEN_FDS_START 3 + static bool Handle_Write PARAMS(( CONN_ID Idx )); static bool Conn_Write PARAMS(( CONN_ID Idx, char *Data, size_t Len )); @@ -120,6 +122,40 @@ static void cb_Connect_to_Server PARAMS((int sock, UNUSED short what)); static void cb_clientserver PARAMS((int sock, short what)); +/** + * Get number of sockets available from systemd(8). + * + * ngIRCd needs to implement its own sd_listen_fds(3) function and can't + * use the one provided by systemd itself, becaus the sockets will be + * used in a forked child process with a new PID, and this would trigger + * an error in the standard implementation. + * + * @return Number of sockets available, -1 if sockets have already been + * initialized, or 0 when no sockets have been passed. + */ +static int +my_sd_listen_fds(void) +{ + const char *e; + long count; + + /* Check if LISTEN_PID exists; but we ignore the result, because + * normally ngircd forks a child before checking this, and therefore + * the PID set in the environment is always wrong ... */ + e = getenv("LISTEN_PID"); + if (!e || !*e) + return 0; + + e = getenv("LISTEN_FDS"); + if (!e || !*e) + return -1; + count = atol(e); + unsetenv("LISTEN_FDS"); + + return count; +} + + /** * IO callback for listening sockets: handle new connections. This callback * gets called when a new non-SSL connection should be accepted. @@ -495,9 +531,38 @@ Conn_InitListeners( void ) /* Initialize ports on which the server should accept connections */ unsigned int created = 0; char *copy, *listen_addr; + int count, fd, i; assert(Conf_ListenAddress); + count = my_sd_listen_fds(); + if (count < 0) { + Log(LOG_INFO, + "Not re-initializing listening sockets of systemd(8) ..."); + return 0; + } + if (count > 0) { + /* systemd(8) passed sockets to us, so don't try to initialize + * listening sockets on our own but use the passed ones */ + LogDebug("Initializing %d systemd sockets ...", count); + for (i = 0; i < count; i++) { + fd = SD_LISTEN_FDS_START + i; + Init_Socket(fd); + if (!io_event_create(fd, IO_WANTREAD, cb_listen)) { + Log(LOG_ERR, + "io_event_create(): Can't add fd %d: %s!", + fd, strerror(errno)); + continue; + } + Log(LOG_INFO, + "Initialized socket %d from systemd.", fd); + created++; + } + return created; + } + + /* not using systemd socket activation, initialize listening sockets: */ + /* can't use Conf_ListenAddress directly, see below */ copy = strdup(Conf_ListenAddress); if (!copy) { @@ -541,7 +606,12 @@ Conn_ExitListeners( void ) int *fd; size_t arraylen; + /* Get number of listening sockets to shut down. There can be none + * if ngIRCd has been "socket activated" by systemd. */ arraylen = array_length(&My_Listeners, sizeof (int)); + if (arraylen < 1) + return; + Log(LOG_INFO, "Shutting down all listening sockets (%d total) ...", arraylen); fd = array_start(&My_Listeners); -- cgit 1.4.1 From f295117fba615333908e707a656b6cd0fb0493ed Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Mon, 4 Feb 2013 23:15:53 +0100 Subject: New configuration option "IdleTimeout": exit daemon when idle This patch implements a new configuration option "IdleTimeout" in the [Limits] section of the configuration file which can be used to set a timeout (in seconds) after which the whole daemon will shutdown when no more connections are left active after handling at least one client. The default is 0, "never". This can be useful for testing or when ngIRCd is started using "socket activation" with systemd(8), for example. --- doc/sample-ngircd.conf.tmpl | 7 +++++++ man/ngircd.conf.5.tmpl | 8 +++++++- src/ngircd/conf.c | 9 +++++++++ src/ngircd/conf.h | 5 ++++- src/ngircd/conn.c | 16 +++++++++++++++- 5 files changed, 42 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/doc/sample-ngircd.conf.tmpl b/doc/sample-ngircd.conf.tmpl index 1c3998ad..822fd5d4 100644 --- a/doc/sample-ngircd.conf.tmpl +++ b/doc/sample-ngircd.conf.tmpl @@ -88,6 +88,13 @@ # to not yet (or no longer) connected servers. ;ConnectRetry = 60 + # Number of seconds after which the whole daemon should shutdown when + # no connections are left active after handling at least one client + # (0: never, which is the default). + # This can be useful for testing or when ngIRCd is started using + # "socket activation" with systemd(8), for example. + ;IdleTimeout = 0 + # Maximum number of simultaneous in- and outbound connections the # server is allowed to accept (0: unlimited): ;MaxConnections = 0 diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl index 859c6a8a..e5485dbf 100644 --- a/man/ngircd.conf.5.tmpl +++ b/man/ngircd.conf.5.tmpl @@ -1,7 +1,7 @@ .\" .\" ngircd.conf(5) manual page template .\" -.TH ngircd.conf 5 "Nov 2012" ngIRCd "ngIRCd Manual" +.TH ngircd.conf 5 "Feb 2013" ngIRCd "ngIRCd Manual" .SH NAME ngircd.conf \- configuration file of ngIRCd .SH SYNOPSIS @@ -170,6 +170,12 @@ should be safe, but it is wise to double-check :-) The server tries every seconds to establish a link to not yet (or no longer) connected servers. Default: 60. .TP +\fBIdleTimeout\fR (number) +Number of seconds after which the whole daemon should shutdown when no +connections are left active after handling at least one client (0: never). This +can be useful for testing or when ngIRCd is started using "socket activation" +with systemd(8), for example. Default: 0. +.TP \fBMaxConnections\fR (number) Maximum number of simultaneous in- and outbound connections the server is allowed to accept (0: unlimited). Default: 0. diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c index 929ab054..835b5ea4 100644 --- a/src/ngircd/conf.c +++ b/src/ngircd/conf.c @@ -370,6 +370,7 @@ Conf_Test( void ) puts("[LIMITS]"); printf(" ConnectRetry = %d\n", Conf_ConnectRetry); + printf(" IdleTimeout = %d\n", Conf_IdleTimeout); printf(" MaxConnections = %d\n", Conf_MaxConnections); printf(" MaxConnectionsIP = %d\n", Conf_MaxConnectionsIP); printf(" MaxJoins = %d\n", Conf_MaxJoins > 0 ? Conf_MaxJoins : -1); @@ -736,6 +737,7 @@ Set_Defaults(bool InitServers) /* Limits */ Conf_ConnectRetry = 60; + Conf_IdleTimeout = 0; Conf_MaxConnections = 0; Conf_MaxConnectionsIP = 5; Conf_MaxJoins = 10; @@ -1241,6 +1243,7 @@ CheckLegacyGlobalOption(int Line, char *Var, char *Arg) return "[Options]"; } if (strcasecmp(Var, "ConnectRetry") == 0 + || strcasecmp(Var, "IdleTimeout") == 0 || strcasecmp(Var, "MaxConnections") == 0 || strcasecmp(Var, "MaxConnectionsIP") == 0 || strcasecmp(Var, "MaxJoins") == 0 @@ -1490,6 +1493,12 @@ Handle_LIMITS(int Line, char *Var, char *Arg) } return; } + if (strcasecmp(Var, "IdleTimeout") == 0) { + Conf_IdleTimeout = atoi(Arg); + if (!Conf_IdleTimeout && strcmp(Arg, "0")) + Config_Error_NaN(Line, Var); + return; + } if (strcasecmp(Var, "MaxConnections") == 0) { Conf_MaxConnections = atoi(Arg); if (!Conf_MaxConnections && strcmp(Arg, "0")) diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h index c203b570..bbf4f36c 100644 --- a/src/ngircd/conf.h +++ b/src/ngircd/conf.h @@ -1,6 +1,6 @@ /* * ngIRCd -- The Next Generation IRC Daemon - * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors. + * Copyright (c)2001-2013 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 @@ -211,6 +211,9 @@ GLOBAL bool Conf_ConnectIPv6; /** Try to connect to remote systems using the IPv4 protocol (true) */ GLOBAL bool Conf_ConnectIPv4; +/** Idle timout (seconds), after which the daemon should exit */ +GLOBAL int Conf_IdleTimeout; + /** Maximum number of simultaneous connections to this server */ GLOBAL int Conf_MaxConnections; diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index 378509f9..cfa67eaf 100644 --- a/src/ngircd/conn.c +++ b/src/ngircd/conn.c @@ -1,6 +1,6 @@ /* * ngIRCd -- The Next Generation IRC Daemon - * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors. + * Copyright (c)2001-2013 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 @@ -121,6 +121,8 @@ static void cb_Read_Resolver_Result PARAMS((int sock, UNUSED short what)); static void cb_Connect_to_Server PARAMS((int sock, UNUSED short what)); static void cb_clientserver PARAMS((int sock, short what)); +time_t idle_t = 0; + /** * Get number of sockets available from systemd(8). @@ -906,6 +908,15 @@ Conn_Handler(void) PACKAGE_NAME); exit(1); } + + /* Should ngIRCd timeout when idle? */ + if (Conf_IdleTimeout > 0 && NumConnectionsAccepted > 0 + && idle_t > 0 && time(NULL) - idle_t >= Conf_IdleTimeout) { + LogDebug("Server idle timeout reached: %d second%s. Initiating shutdown ...", + Conf_IdleTimeout, + Conf_IdleTimeout == 1 ? "" : "s"); + NGIRCd_SignalQuit = true; + } } if (NGIRCd_SignalQuit) @@ -1267,6 +1278,8 @@ Conn_Close( CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClie NumConnections--; LogDebug("Shutdown of connection %d completed, %ld connection%s left.", Idx, NumConnections, NumConnections != 1 ? "s" : ""); + + idle_t = NumConnections > 0 ? 0 : time(NULL); } /* Conn_Close */ @@ -1638,6 +1651,7 @@ static void Account_Connection(void) { NumConnections++; + idle_t = 0; if (NumConnections > NumConnectionsMax) NumConnectionsMax = NumConnections; LogDebug("Total number of connections now %lu (max %lu).", -- cgit 1.4.1 From a78c7b3898e8f2b037fb42aac599ed8f8ec9bd58 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:18:44 +0100 Subject: Adjust severity levels of some log messages --- src/ngircd/conf.c | 6 +++--- src/ngircd/log.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c index 835b5ea4..e46dcfee 100644 --- a/src/ngircd/conf.c +++ b/src/ngircd/conf.c @@ -832,8 +832,8 @@ Read_TextFile(const char *Filename, const char *Name, array *Destination) fp = fopen(Filename, "r"); if (!fp) { - Config_Error(LOG_WARNING, "Can't read %s file \"%s\": %s", - Name, Filename, strerror(errno)); + Config_Error(LOG_ERR, "Can't read %s file \"%s\": %s", + Name, Filename, strerror(errno)); return false; } @@ -843,7 +843,7 @@ Read_TextFile(const char *Filename, const char *Name, array *Destination) /* add text including \0 */ if (!array_catb(Destination, line, strlen(line) + 1)) { - Log(LOG_WARNING, "Cannot read/add \"%s\", line %d: %s", + Log(LOG_ERR, "Cannot read/add \"%s\", line %d: %s", Filename, line_no, strerror(errno)); break; } diff --git a/src/ngircd/log.c b/src/ngircd/log.c index 375f4bc1..e5bed791 100644 --- a/src/ngircd/log.c +++ b/src/ngircd/log.c @@ -109,7 +109,7 @@ Log_ReInit(void) GLOBAL void Log_Exit( void ) { - Log(LOG_NOTICE, "%s done%s, served %lu connection%s.", PACKAGE_NAME, + Log(LOG_INFO, "%s done%s, served %lu connection%s.", PACKAGE_NAME, NGIRCd_SignalRestart ? " (restarting)" : "", Conn_CountAccepted(), Conn_CountAccepted() == 1 ? "" : "s"); #ifdef SYSLOG -- cgit 1.4.1 From 5c6875d7686e1b4dbf1a82b6d159bd5f18da4a52 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:20:58 +0100 Subject: Check type of sockets passed-in by systemd(8) This patch makes sure that ngIRCd doesn't try to handle sockets of unsupported types, for example of AF_INET6 sockets when ngIRCd isn't compiled with support for IPv6 ... --- src/ngircd/conn.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index cfa67eaf..be306e5f 100644 --- a/src/ngircd/conn.c +++ b/src/ngircd/conn.c @@ -532,8 +532,9 @@ Conn_InitListeners( void ) { /* Initialize ports on which the server should accept connections */ unsigned int created = 0; - char *copy, *listen_addr; - int count, fd, i; + char *af_str, *copy, *listen_addr; + int count, fd, i, addr_len; + ng_ipaddr_t addr; assert(Conf_ListenAddress); @@ -549,6 +550,36 @@ Conn_InitListeners( void ) LogDebug("Initializing %d systemd sockets ...", count); for (i = 0; i < count; i++) { fd = SD_LISTEN_FDS_START + i; + addr_len = (int)sizeof(addr); + getsockname(fd, (struct sockaddr *)&addr, (socklen_t*)&addr_len); +#ifdef WANT_IPV6 + if (addr.sin4.sin_family != AF_INET && addr.sin4.sin_family != AF_INET6) +#else + if (addr.sin4.sin_family != AF_INET) +#endif + { + /* Socket is of unsupported type! For example, systemd passed in + * an IPv6 socket but ngIRCd isn't compiled with IPv6 support. */ + switch (addr.sin4.sin_family) + { + case AF_UNSPEC: af_str = "AF_UNSPEC"; break; + case AF_UNIX: af_str = "AF_UNIX"; break; + case AF_INET: af_str = "AF_INET"; break; +#ifdef AF_INET6 + case AF_INET6: af_str = "AF_INET6"; break; +#endif +#ifdef AF_NETLINK + case AF_NETLINK: af_str = "AF_NETLINK"; break; +#endif + default: af_str = "unknown"; break; + } + Log(LOG_CRIT, + "Socket %d is of unsupported type \"%s\" (%d), have to ignore it!", + fd, af_str, addr.sin4.sin_family); + close(fd); + continue; + } + Init_Socket(fd); if (!io_event_create(fd, IO_WANTREAD, cb_listen)) { Log(LOG_ERR, -- cgit 1.4.1 From 69c3f9699892e55edb82f6e9497389024e61badc Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:21:35 +0100 Subject: Show address and port of sockets passed-in by systemd(8) --- src/ngircd/conn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index be306e5f..3a430428 100644 --- a/src/ngircd/conn.c +++ b/src/ngircd/conn.c @@ -588,7 +588,8 @@ Conn_InitListeners( void ) continue; } Log(LOG_INFO, - "Initialized socket %d from systemd.", fd); + "Initialized socket %d from systemd(8): %s:%d.", fd, + ng_ipaddr_tostr(&addr), ng_ipaddr_getport(&addr)); created++; } return created; -- cgit 1.4.1