core: introduce activation rate limiting for socket units

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.
This commit is contained in:
Lennart Poettering 2016-04-26 20:26:15 +02:00
parent 3282493ad0
commit 8b26cdbd2a
6 changed files with 41 additions and 3 deletions

View File

@ -807,6 +807,22 @@
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

View 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),

View 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)

View 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;

View 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"
};

View 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 */