bb2f5f0
From df25afd2cf5527fe1bb542bb146fef1be8d9a489 Mon Sep 17 00:00:00 2001
bb2f5f0
From: Lennart Poettering <lennart@poettering.net>
bb2f5f0
Date: Sat, 9 Sep 2023 14:46:32 +0200
bb2f5f0
Subject: [PATCH 1/3] core: add new "PollLimit" settings to .socket units
bb2f5f0
bb2f5f0
This adds a new "PollLimit" pair of settings to .socket units, very
bb2f5f0
similar to existing "TriggerLimit" logic. The differences are:
bb2f5f0
bb2f5f0
* PollLimit focusses on the polling on the sockets, and pauses that
bb2f5f0
  temporarily if a ratelimit on that is reached. TriggerLimit otoh
bb2f5f0
  focusses on the triggering effect of socket units, and stops
bb2f5f0
  triggering once the ratelimit is hit.
bb2f5f0
bb2f5f0
* While the trigger limit being hit is an action that causes the socket
bb2f5f0
  unit to fail the polling limit being reached will just temporarily
bb2f5f0
  disable polling on the socket fd, and it is resumed once the ratelimit
bb2f5f0
  interval is over.
bb2f5f0
bb2f5f0
* When a socket unit operates on multiple socket fds (e,g, ListenStream=
bb2f5f0
  on both some ipv6 and an ipv4 address or so). Then the PollLimit will
bb2f5f0
  be specific to each fd, while the trigger limit is specific to the
bb2f5f0
  whole unit.
bb2f5f0
bb2f5f0
Implementation-wise this is mostly a wrapper around sd-event's
bb2f5f0
sd_event_source_set_ratelimit(), which exposes the desired behaviour
bb2f5f0
directly.
bb2f5f0
bb2f5f0
Usecase for all of this: socket services which when overloaded with
bb2f5f0
connections should just slow down reception of it, but not fail
bb2f5f0
persistently.
bb2f5f0
bb2f5f0
(cherry picked from commit 2bec84e7a5bf3687ae65205753ba3d8067cf2f0e)
bb2f5f0
---
bb2f5f0
 man/org.freedesktop.systemd1.xml      | 12 ++++++++++
bb2f5f0
 src/core/dbus-socket.c                |  8 +++++++
bb2f5f0
 src/core/load-fragment-gperf.gperf.in |  2 ++
bb2f5f0
 src/core/socket.c                     | 32 +++++++++++++++++++--------
bb2f5f0
 src/core/socket.h                     |  2 ++
bb2f5f0
 src/shared/bus-unit-util.c            | 10 +++++----
bb2f5f0
 6 files changed, 53 insertions(+), 13 deletions(-)
bb2f5f0
bb2f5f0
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
bb2f5f0
index 56906e2f3b..0557dc2379 100644
bb2f5f0
--- a/man/org.freedesktop.systemd1.xml
bb2f5f0
+++ b/man/org.freedesktop.systemd1.xml
bb2f5f0
@@ -4727,6 +4727,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
bb2f5f0
       readonly t TriggerLimitIntervalUSec = ...;
bb2f5f0
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
bb2f5f0
       readonly u TriggerLimitBurst = ...;
bb2f5f0
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
bb2f5f0
+      readonly t PollLimitIntervalUSec = ...;
bb2f5f0
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
bb2f5f0
+      readonly u PollLimitBurst = ...;
bb2f5f0
       readonly u UID = ...;
bb2f5f0
       readonly u GID = ...;
bb2f5f0
       @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
bb2f5f0
@@ -5961,6 +5965,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
bb2f5f0
 
bb2f5f0
     <variablelist class="dbus-property" generated="True" extra-ref="TriggerLimitBurst"/>
bb2f5f0
 
bb2f5f0
+    <variablelist class="dbus-property" generated="True" extra-ref="PollLimitIntervalUSec"/>
bb2f5f0
+
bb2f5f0
+    <variablelist class="dbus-property" generated="True" extra-ref="PollLimitBurst"/>
bb2f5f0
+
bb2f5f0
     <variablelist class="dbus-property" generated="True" extra-ref="UID"/>
bb2f5f0
 
bb2f5f0
     <variablelist class="dbus-property" generated="True" extra-ref="GID"/>
bb2f5f0
@@ -6497,6 +6505,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
bb2f5f0
 
bb2f5f0
     
bb2f5f0
 
bb2f5f0
+    <para><varname>PollLimitIntervalUSec</varname>/<varname>PollLimitBurst</varname> properties configure the
bb2f5f0
+    polling limit for the socket unit. Expects a time in ┬Ás, resp. an unsigned integer. If either is set to
bb2f5f0
+    zero the limiting feature is turned off.</para>
bb2f5f0
+
bb2f5f0
     <refsect2>
bb2f5f0
       <title>Properties</title>
bb2f5f0
 
bb2f5f0
diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
bb2f5f0
index 09a3a9502b..04552b7c60 100644
bb2f5f0
--- a/src/core/dbus-socket.c
bb2f5f0
+++ b/src/core/dbus-socket.c
bb2f5f0
@@ -129,6 +129,8 @@ const sd_bus_vtable bus_socket_vtable[] = {
bb2f5f0
         SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST),
bb2f5f0
         SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
bb2f5f0
         SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
bb2f5f0
+        SD_BUS_PROPERTY("PollLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, poll_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST),
bb2f5f0
+        SD_BUS_PROPERTY("PollLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, poll_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST),
bb2f5f0
         SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
bb2f5f0
         SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
bb2f5f0
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
bb2f5f0
@@ -248,6 +250,9 @@ static int bus_socket_set_transient_property(
bb2f5f0
         if (streq(name, "TriggerLimitBurst"))
bb2f5f0
                 return bus_set_transient_unsigned(u, name, &s->trigger_limit.burst, message, flags, error);
bb2f5f0
 
bb2f5f0
+        if (streq(name, "PollLimitBurst"))
bb2f5f0
+                return bus_set_transient_unsigned(u, name, &s->poll_limit_burst, message, flags, error);
bb2f5f0
+
bb2f5f0
         if (streq(name, "SocketMode"))
bb2f5f0
                 return bus_set_transient_mode_t(u, name, &s->socket_mode, message, flags, error);
bb2f5f0
 
bb2f5f0
@@ -275,6 +280,9 @@ static int bus_socket_set_transient_property(
bb2f5f0
         if (streq(name, "TriggerLimitIntervalUSec"))
bb2f5f0
                 return bus_set_transient_usec(u, name, &s->trigger_limit.interval, message, flags, error);
bb2f5f0
 
bb2f5f0
+        if (streq(name, "PollLimitIntervalUSec"))
bb2f5f0
+                return bus_set_transient_usec(u, name, &s->poll_limit_interval, message, flags, error);
bb2f5f0
+
bb2f5f0
         if (streq(name, "SmackLabel"))
bb2f5f0
                 return bus_set_transient_string(u, name, &s->smack, message, flags, error);
bb2f5f0
 
bb2f5f0
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
bb2f5f0
index b66adf2811..0d1ee9c231 100644
bb2f5f0
--- a/src/core/load-fragment-gperf.gperf.in
bb2f5f0
+++ b/src/core/load-fragment-gperf.gperf.in
bb2f5f0
@@ -507,6 +507,8 @@ Socket.FileDescriptorName,               config_parse_fdname,
bb2f5f0
 Socket.Service,                          config_parse_socket_service,                 0,                                  0
bb2f5f0
 Socket.TriggerLimitIntervalSec,          config_parse_sec,                            0,                                  offsetof(Socket, trigger_limit.interval)
bb2f5f0
 Socket.TriggerLimitBurst,                config_parse_unsigned,                       0,                                  offsetof(Socket, trigger_limit.burst)
bb2f5f0
+Socket.PollLimitIntervalSec,             config_parse_sec,                            0,                                  offsetof(Socket, poll_limit_interval)
bb2f5f0
+Socket.PollLimitBurst,                   config_parse_unsigned,                       0,                                  offsetof(Socket, poll_limit_burst)
bb2f5f0
 {% if ENABLE_SMACK %}
bb2f5f0
 Socket.SmackLabel,                       config_parse_unit_string_printf,             0,                                  offsetof(Socket, smack)
bb2f5f0
 Socket.SmackLabelIPIn,                   config_parse_unit_string_printf,             0,                                  offsetof(Socket, smack_ip_in)
bb2f5f0
diff --git a/src/core/socket.c b/src/core/socket.c
bb2f5f0
index 75034ac357..dc18744f54 100644
bb2f5f0
--- a/src/core/socket.c
bb2f5f0
+++ b/src/core/socket.c
bb2f5f0
@@ -101,6 +101,9 @@ static void socket_init(Unit *u) {
bb2f5f0
 
bb2f5f0
         s->trigger_limit.interval = USEC_INFINITY;
bb2f5f0
         s->trigger_limit.burst = UINT_MAX;
bb2f5f0
+
bb2f5f0
+        s->poll_limit_interval = USEC_INFINITY;
bb2f5f0
+        s->poll_limit_burst = UINT_MAX;
bb2f5f0
 }
bb2f5f0
 
bb2f5f0
 static void socket_unwatch_control_pid(Socket *s) {
bb2f5f0
@@ -310,17 +313,20 @@ static int socket_add_extras(Socket *s) {
bb2f5f0
          * off the queues, which it might not necessarily do. Moreover, while Accept=no services are supposed to
bb2f5f0
          * process whatever is queued in one go, and thus should normally never have to be started frequently. This is
bb2f5f0
          * different for Accept=yes where each connection is processed by a new service instance, and thus frequent
bb2f5f0
-         * service starts are typical. */
bb2f5f0
+         * service starts are typical.
bb2f5f0
+         *
bb2f5f0
+         * For the poll limit we follow a similar rule, but use 3/4th of the trigger limit parameters, to
bb2f5f0
+         * trigger this earlier. */
bb2f5f0
 
bb2f5f0
         if (s->trigger_limit.interval == USEC_INFINITY)
bb2f5f0
                 s->trigger_limit.interval = 2 * USEC_PER_SEC;
bb2f5f0
+        if (s->trigger_limit.burst == UINT_MAX)
bb2f5f0
+                s->trigger_limit.burst = s->accept ? 200 : 20;
bb2f5f0
 
bb2f5f0
-        if (s->trigger_limit.burst == UINT_MAX) {
bb2f5f0
-                if (s->accept)
bb2f5f0
-                        s->trigger_limit.burst = 200;
bb2f5f0
-                else
bb2f5f0
-                        s->trigger_limit.burst = 20;
bb2f5f0
-        }
bb2f5f0
+        if (s->poll_limit_interval == USEC_INFINITY)
bb2f5f0
+                s->poll_limit_interval = 2 * USEC_PER_SEC;
bb2f5f0
+        if (s->poll_limit_burst == UINT_MAX)
bb2f5f0
+                s->poll_limit_burst = s->accept ? 150 : 15;
bb2f5f0
 
bb2f5f0
         if (have_non_accept_socket(s)) {
bb2f5f0
 
bb2f5f0
@@ -770,9 +776,13 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
bb2f5f0
 
bb2f5f0
         fprintf(f,
bb2f5f0
                 "%sTriggerLimitIntervalSec: %s\n"
bb2f5f0
-                "%sTriggerLimitBurst: %u\n",
bb2f5f0
+                "%sTriggerLimitBurst: %u\n"
bb2f5f0
+                "%sPollLimitIntervalSec: %s\n"
bb2f5f0
+                "%sPollLimitBurst: %u\n",
bb2f5f0
                 prefix, FORMAT_TIMESPAN(s->trigger_limit.interval, USEC_PER_SEC),
bb2f5f0
-                prefix, s->trigger_limit.burst);
bb2f5f0
+                prefix, s->trigger_limit.burst,
bb2f5f0
+                prefix, FORMAT_TIMESPAN(s->poll_limit_interval, USEC_PER_SEC),
bb2f5f0
+                prefix, s->poll_limit_burst);
bb2f5f0
 
bb2f5f0
         str = ip_protocol_to_name(s->socket_protocol);
bb2f5f0
         if (str)
bb2f5f0
@@ -1765,6 +1775,10 @@ static int socket_watch_fds(Socket *s) {
bb2f5f0
 
bb2f5f0
                         (void) sd_event_source_set_description(p->event_source, "socket-port-io");
bb2f5f0
                 }
bb2f5f0
+
bb2f5f0
+                r = sd_event_source_set_ratelimit(p->event_source, s->poll_limit_interval, s->poll_limit_burst);
bb2f5f0
+                if (r < 0)
bb2f5f0
+                        log_unit_debug_errno(UNIT(s), r, "Failed to set poll limit on I/O event source, ignoring: %m");
bb2f5f0
         }
bb2f5f0
 
bb2f5f0
         return 0;
bb2f5f0
diff --git a/src/core/socket.h b/src/core/socket.h
bb2f5f0
index 191d27f46d..b03a291e4a 100644
bb2f5f0
--- a/src/core/socket.h
bb2f5f0
+++ b/src/core/socket.h
bb2f5f0
@@ -158,6 +158,8 @@ struct Socket {
bb2f5f0
         char *fdname;
bb2f5f0
 
bb2f5f0
         RateLimit trigger_limit;
bb2f5f0
+        usec_t poll_limit_interval;
bb2f5f0
+        unsigned poll_limit_burst;
bb2f5f0
 };
bb2f5f0
 
bb2f5f0
 SocketPeer *socket_peer_ref(SocketPeer *p);
bb2f5f0
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
bb2f5f0
index e7b44cc39b..9f0f37488d 100644
bb2f5f0
--- a/src/shared/bus-unit-util.c
bb2f5f0
+++ b/src/shared/bus-unit-util.c
bb2f5f0
@@ -2170,10 +2170,10 @@ static int bus_append_path_property(sd_bus_message *m, const char *field, const
bb2f5f0
                 return 1;
bb2f5f0
         }
bb2f5f0
 
bb2f5f0
-        if (streq(field, "TriggerLimitBurst"))
bb2f5f0
+        if (STR_IN_SET(field, "TriggerLimitBurst", "PollLimitBurst"))
bb2f5f0
                 return bus_append_safe_atou(m, field, eq);
bb2f5f0
 
bb2f5f0
-        if (streq(field, "TriggerLimitIntervalSec"))
bb2f5f0
+        if (STR_IN_SET(field, "TriggerLimitIntervalSec", "PollLimitIntervalSec"))
bb2f5f0
                 return bus_append_parse_sec_rename(m, field, eq);
bb2f5f0
 
bb2f5f0
         return 0;
bb2f5f0
@@ -2382,7 +2382,8 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
bb2f5f0
                               "MaxConnections",
bb2f5f0
                               "MaxConnectionsPerSource",
bb2f5f0
                               "KeepAliveProbes",
bb2f5f0
-                              "TriggerLimitBurst"))
bb2f5f0
+                              "TriggerLimitBurst",
bb2f5f0
+                              "PollLimitBurst"))
bb2f5f0
                 return bus_append_safe_atou(m, field, eq);
bb2f5f0
 
bb2f5f0
         if (STR_IN_SET(field, "SocketMode",
bb2f5f0
@@ -2397,7 +2398,8 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
bb2f5f0
                               "KeepAliveTimeSec",
bb2f5f0
                               "KeepAliveIntervalSec",
bb2f5f0
                               "DeferAcceptSec",
bb2f5f0
-                              "TriggerLimitIntervalSec"))
bb2f5f0
+                              "TriggerLimitIntervalSec",
bb2f5f0
+                              "PollLimitIntervalSec"))
bb2f5f0
                 return bus_append_parse_sec_rename(m, field, eq);
bb2f5f0
 
bb2f5f0
         if (STR_IN_SET(field, "ReceiveBuffer",