core: introduce activation rate limiting for socket units
authorLennart Poettering <lennart@poettering.net>
Tue, 26 Apr 2016 18:26:15 +0000 (20:26 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 29 Apr 2016 14:27:48 +0000 (16:27 +0200)
This adds two new settings TriggerLimitIntervalSec= and TriggerLimitBurst= that
define a rate limit for activation of socket units. When the limit is hit, the
socket is is put into a failure mode. This is an alternative fix for #2467,
since the original fix resulted in issue #2684.

In a later commit the StartLimitInterval=/StartLimitBurst= rate limiter will be
changed to be applied after any start conditions checks are made. This way,
there are two separate rate limiters enforced: one at triggering time, before
any jobs are queued with this patch, as well as the start limit that is moved
again to be run immediately before the unit is activated. Condition checks are
done in between the two, and thus no longer affect the start limit.

man/systemd.socket.xml
src/core/dbus-socket.c
src/core/load-fragment-gperf.gperf.m4
src/core/main.c
src/core/socket.c
src/core/socket.h

index 2d63396..dc3fee5 100644 (file)
         suffix.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>TriggerLimitIntervalSec=</varname></term>
+        <term><varname>TriggerLimitIntervalBurst=</varname></term>
+
+        <listitem><para>Configures a limit on how often this socket unit my be activated within a specific time
+        interval. The <varname>TriggerLimitIntervalSec=</varname> may be used to configure the length of the time
+        interval in the usual time units <literal>us</literal>, <literal>ms</literal>, <literal>s</literal>,
+        <literal>min</literal>, <literal>h</literal>, … and defaults to 5s (See
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details on
+        the various time units available). The <varname>TriggerLimitBurst=</varname> setting takes an integer value and
+        specifies the numer of permitted activations per time interval, and defaults to 2500 (thus by default
+        permitting 2500 activations per 5s). Set either to 0 to disable any form of trigger rate limiting. If the limit
+        is hit, the socket unit is placed into a failure mode, and will not be connectible anymore until
+        restarted. Note that this limit is enforced before the service activation is enqueued.</para></listitem>
+      </varlistentry>
+
     </variablelist>
 
     <para>Check
index d33e494..bb09a51 100644 (file)
@@ -149,6 +149,8 @@ const sd_bus_vtable bus_socket_vtable[] = {
         SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0),
         SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0),
         SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("TriggerLimitIntervalSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
index 5568b46..32bb62f 100644 (file)
@@ -297,6 +297,8 @@ Socket.RemoveOnStop,             config_parse_bool,                  0,
 Socket.Symlinks,                 config_parse_unit_path_strv_printf, 0,                             offsetof(Socket, symlinks)
 Socket.FileDescriptorName,       config_parse_fdname,                0,                             0
 Socket.Service,                  config_parse_socket_service,        0,                             0
+Socket.TriggerLimitIntervalSec,  config_parse_sec,                   0,                             offsetof(Socket, trigger_limit.interval)
+Socket.TriggerLimitBurst,        config_parse_unsigned,              0,                             offsetof(Socket, trigger_limit.burst)
 m4_ifdef(`HAVE_SMACK',
 `Socket.SmackLabel,              config_parse_string,                0,                             offsetof(Socket, smack)
 Socket.SmackLabelIPIn,           config_parse_string,                0,                             offsetof(Socket, smack_ip_in)
index 75c5ff8..9fcd3fe 100644 (file)
@@ -289,6 +289,7 @@ static int parse_crash_chvt(const char *value) {
 }
 
 static int set_machine_id(const char *m) {
+        assert(m);
 
         if (sd_id128_from_string(m, &arg_machine_id) < 0)
                 return -EINVAL;
index a9fff9c..42260d8 100644 (file)
@@ -99,6 +99,8 @@ static void socket_init(Unit *u) {
         s->exec_context.std_error = u->manager->default_std_error;
 
         s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID;
+
+        RATELIMIT_INIT(s->trigger_limit, 5*USEC_PER_SEC, 2500);
 }
 
 static void socket_unwatch_control_pid(Socket *s) {
@@ -1887,6 +1889,9 @@ static void socket_enter_running(Socket *s, int cfd) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         int r;
 
+        /* Note that this call takes possession of the connection fd passed. It either has to assign it somewhere or
+         * close it. */
+
         assert(s);
 
         /* We don't take connections anymore if we are supposed to
@@ -1896,7 +1901,7 @@ static void socket_enter_running(Socket *s, int cfd) {
                 log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled.");
 
                 if (cfd >= 0)
-                        safe_close(cfd);
+                        cfd = safe_close(cfd);
                 else  {
                         /* Flush all sockets by closing and reopening them */
                         socket_close_fds(s);
@@ -1918,6 +1923,13 @@ static void socket_enter_running(Socket *s, int cfd) {
                 return;
         }
 
+        if (!ratelimit_test(&s->trigger_limit)) {
+                safe_close(cfd);
+                log_unit_warning(UNIT(s), "Trigger limit hit, refusing further activation.");
+                socket_enter_stop_pre(s, SOCKET_FAILURE_TRIGGER_LIMIT_HIT);
+                return;
+        }
+
         if (cfd < 0) {
                 Iterator i;
                 Unit *other;
@@ -1949,7 +1961,7 @@ static void socket_enter_running(Socket *s, int cfd) {
                 Service *service;
 
                 if (s->n_connections >= s->max_connections) {
-                        log_unit_warning(UNIT(s), "Too many incoming connections (%u)", s->n_connections);
+                        log_unit_warning(UNIT(s), "Too many incoming connections (%u), refusing connection attempt.", s->n_connections);
                         safe_close(cfd);
                         return;
                 }
@@ -1965,6 +1977,7 @@ static void socket_enter_running(Socket *s, int cfd) {
 
                         /* ENOTCONN is legitimate if TCP RST was received.
                          * This connection is over, but the socket unit lives on. */
+                        log_unit_debug(UNIT(s), "Got ENOTCONN on incoming socket, assuming aborted connection attempt, ignoring.");
                         safe_close(cfd);
                         return;
                 }
@@ -1993,7 +2006,7 @@ static void socket_enter_running(Socket *s, int cfd) {
                 if (r < 0)
                         goto fail;
 
-                cfd = -1;
+                cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */
                 s->n_connections++;
 
                 r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL);
@@ -2806,6 +2819,7 @@ static const char* const socket_result_table[_SOCKET_RESULT_MAX] = {
         [SOCKET_FAILURE_EXIT_CODE] = "exit-code",
         [SOCKET_FAILURE_SIGNAL] = "signal",
         [SOCKET_FAILURE_CORE_DUMP] = "core-dump",
+        [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit",
         [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit"
 };
 
index b537b02..2a4b1bb 100644 (file)
@@ -52,6 +52,7 @@ typedef enum SocketResult {
         SOCKET_FAILURE_EXIT_CODE,
         SOCKET_FAILURE_SIGNAL,
         SOCKET_FAILURE_CORE_DUMP,
+        SOCKET_FAILURE_TRIGGER_LIMIT_HIT,
         SOCKET_FAILURE_SERVICE_START_LIMIT_HIT,
         _SOCKET_RESULT_MAX,
         _SOCKET_RESULT_INVALID = -1
@@ -156,6 +157,8 @@ struct Socket {
         bool reset_cpu_usage:1;
 
         char *fdname;
+
+        RateLimit trigger_limit;
 };
 
 /* Called from the service code when collecting fds */