]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
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 2d6339680b41e04cc24fe4fc2117be766737f010..dc3fee5dfbbcb206cccbe62f636921ef6b35ae9f 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 d33e494f6bab066e67d94edb8dbd38cbf460042a..bb09a515f88c8afd1c20163585a769d2d39d805c 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 5568b4696fe0f8a78a3d100b9dc70e8897b17ab5..32bb62fa63c498d5143936b6aaa4c04a0504fb1c 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 75c5ff81f23f453771ad65ea501bf3d0188c7090..9fcd3fe1bdcc284b5655cf0997adc92690b3fd34 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 a9fff9c2593dab772b849d7bfe25dda7b7635d27..42260d872921a9d333dca31d335a7d7ff9572268 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 b537b026a7b41e3d73c281677e62800d031e5004..2a4b1bb6741a6117a3b0dda7db4c88cb41bc253c 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 */