From 84e24afd2f6607a2345c4df2b2f9ad81e9dd4bbc Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Mon, 4 Feb 2013 21:31:42 +0100 Subject: [PATCH 1/7] contrib/README: add more files --- contrib/README | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contrib/README b/contrib/README index 1aebd1eb..f3730a4e 100644 --- a/contrib/README +++ b/contrib/README @@ -2,18 +2,21 @@ ngIRCd - Next Generation IRC Server http://ngircd.barton.de/ - (c)2001-2011 Alexander Barton and Contributors. + (c)2001-2013 Alexander Barton and Contributors. ngIRCd is free software and published under the terms of the GNU General Public License. - -- Contributions -- + -- Contributions -- Debian/ - Various files for building Debian GNU/Linux packages (".deb's"). + - ngircd.init; ngircd.default: init script for Debian-based systems. + - ngircd.pam: example PAM configuraton. MacOSX/ - Project files for XCode, the "project builder" of Apple Mac OS X. + - de.barton.ngircd.plist[.tmpl]: launchd(8) property list. ngindent - Script to indent the code of ngIRCd in the "standard way". @@ -24,6 +27,9 @@ ngircd-bsd.sh ngircd-redhat.init - Start/stop script for RedHat-based distributions (like CentOS). +ngircd.service + - systemd(8) service unit configuration file. + ngircd.spec - RPM "spec" file. From 8ab097afb743061c6c9b865bdb401ba51285c347 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Mon, 4 Feb 2013 21:46:20 +0100 Subject: [PATCH 2/7] 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 - - - --- contrib/Makefile.am | 3 +- contrib/README | 3 ++ contrib/ngircd.socket | 10 +++++++ src/ngircd/conn.c | 70 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 contrib/ngircd.socket diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 09b43a68..6d16f7ca 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -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 @@ -17,6 +17,7 @@ EXTRA_DIST = README \ ngIRCd-Logo.gif \ ngircd-redhat.init \ ngircd.service \ + ngircd.socket \ ngircd.spec \ platformtest.sh \ systrace.policy diff --git a/contrib/README b/contrib/README index f3730a4e..2d639e66 100644 --- a/contrib/README +++ b/contrib/README @@ -30,6 +30,9 @@ ngircd-redhat.init ngircd.service - systemd(8) service unit configuration file. +ngircd.socket + - systemd(8) socket unit configuration file for "socket activation". + ngircd.spec - RPM "spec" file. diff --git a/contrib/ngircd.socket b/contrib/ngircd.socket new file mode 100644 index 00000000..3838efcc --- /dev/null +++ b/contrib/ngircd.socket @@ -0,0 +1,10 @@ +[Unit] +Description=Next Generation IRC Daemon (Socket) + +[Socket] +ListenStream=6667 +#ListenStream=6668 +IPTOS=low-delay + +[Install] +WantedBy=sockets.target 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); From f295117fba615333908e707a656b6cd0fb0493ed Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Mon, 4 Feb 2013 23:15:53 +0100 Subject: [PATCH 3/7] 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(-) 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).", From a78c7b3898e8f2b037fb42aac599ed8f8ec9bd58 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:18:44 +0100 Subject: [PATCH 4/7] Adjust severity levels of some log messages --- src/ngircd/conf.c | 6 +++--- src/ngircd/log.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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 From 5c6875d7686e1b4dbf1a82b6d159bd5f18da4a52 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:20:58 +0100 Subject: [PATCH 5/7] 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(-) 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, From 69c3f9699892e55edb82f6e9497389024e61badc Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:21:35 +0100 Subject: [PATCH 6/7] Show address and port of sockets passed-in by systemd(8) --- src/ngircd/conn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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; From 2cb7023e2835e9c29bec5a6d3a911894c847005d Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sun, 10 Feb 2013 20:37:55 +0100 Subject: [PATCH 7/7] ngircd.sock: explicitely bind to IPv4 and IPv6 addresses --- contrib/ngircd.socket | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/ngircd.socket b/contrib/ngircd.socket index 3838efcc..1c0cc004 100644 --- a/contrib/ngircd.socket +++ b/contrib/ngircd.socket @@ -2,8 +2,9 @@ Description=Next Generation IRC Daemon (Socket) [Socket] -ListenStream=6667 -#ListenStream=6668 +BindIPv6Only=ipv6-only +ListenStream=0.0.0.0:6667 +#ListenStream=[::]:6667 IPTOS=low-delay [Install]