From: Alexander Barton Date: Sun, 10 Feb 2013 19:43:56 +0000 (+0100) Subject: Merge branch 'systemd' X-Git-Tag: rel-21-rc1~140 X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=ngircd-alex.git;a=commitdiff_plain;h=628c14d65686c4c848a17381b8ef61c78dbcf405;hp=ac32d07aaff0e7a1c4a544353dadbf397859d8f9 Merge branch 'systemd' * systemd: ngircd.sock: explicitely bind to IPv4 and IPv6 addresses Show address and port of sockets passed-in by systemd(8) Check type of sockets passed-in by systemd(8) Adjust severity levels of some log messages New configuration option "IdleTimeout": exit daemon when idle Implement support for systemd(8) "socket activation" contrib/README: add more files --- 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 1aebd1eb..2d639e66 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,12 @@ ngircd-bsd.sh ngircd-redhat.init - Start/stop script for RedHat-based distributions (like CentOS). +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..1c0cc004 --- /dev/null +++ b/contrib/ngircd.socket @@ -0,0 +1,11 @@ +[Unit] +Description=Next Generation IRC Daemon (Socket) + +[Socket] +BindIPv6Only=ipv6-only +ListenStream=0.0.0.0:6667 +#ListenStream=[::]:6667 +IPTOS=low-delay + +[Install] +WantedBy=sockets.target 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..e46dcfee 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; @@ -830,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; } @@ -841,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; } @@ -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 14d337b9..3a430428 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 @@ -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 )); @@ -119,6 +121,42 @@ 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). + * + * 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 @@ -494,10 +532,71 @@ Conn_InitListeners( void ) { /* Initialize ports on which the server should accept connections */ unsigned int created = 0; - char *copy, *listen_addr; + char *af_str, *copy, *listen_addr; + int count, fd, i, addr_len; + ng_ipaddr_t addr; 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; + 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, + "io_event_create(): Can't add fd %d: %s!", + fd, strerror(errno)); + continue; + } + Log(LOG_INFO, + "Initialized socket %d from systemd(8): %s:%d.", fd, + ng_ipaddr_tostr(&addr), ng_ipaddr_getport(&addr)); + 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 +640,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); @@ -836,6 +940,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) @@ -1197,6 +1310,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 */ @@ -1568,6 +1683,7 @@ static void Account_Connection(void) { NumConnections++; + idle_t = 0; if (NumConnections > NumConnectionsMax) NumConnectionsMax = NumConnections; LogDebug("Total number of connections now %lu (max %lu).", 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