summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTopi Miettinen <toiwoton@gmail.com>2022-05-22 13:21:02 +0200
committerTopi Miettinen <topimiettinen@users.noreply.github.com>2022-06-08 18:12:25 +0200
commitc0548df0a2f78f3422d77c77c2149d8a7f50d8f6 (patch)
tree4e363ae5071b1c60622f3f8e9ffa621bbeff3150
parentnetwork: firewall integration with NFT sets (diff)
downloadsystemd-c0548df0a2f78f3422d77c77c2149d8a7f50d8f6.tar.xz
systemd-c0548df0a2f78f3422d77c77c2149d8a7f50d8f6.zip
core: firewall integration with ControlGroupNFTSet=
New directive `ControlGroupNFTSet=` provides a method for integrating services into firewall rules with NFT sets. Example: ``` table inet filter { ... set timesyncd { type cgroupsv2 } chain ntp_output { socket cgroupv2 != @timesyncd counter drop accept } ... } ``` /etc/systemd/system/systemd-timesyncd.service.d/override.conf ``` [Service] ControlGroupNFTSet=inet:filter:timesyncd ``` ``` $ sudo nft list set inet filter timesyncd table inet filter { set timesyncd { type cgroupsv2 elements = { "system.slice/systemd-timesyncd.service" } } } ```
-rw-r--r--man/org.freedesktop.systemd1.xml36
-rw-r--r--man/systemd.resource-control.xml29
-rw-r--r--src/core/cgroup.c52
-rw-r--r--src/core/cgroup.h4
-rw-r--r--src/core/dbus-cgroup.c85
-rw-r--r--src/core/load-fragment-gperf.gperf.in1
-rw-r--r--src/core/load-fragment.c87
-rw-r--r--src/core/load-fragment.h1
-rw-r--r--src/shared/bus-unit-util.c3
-rw-r--r--test/fuzz/fuzz-unit-file/directives.mount1
-rw-r--r--test/fuzz/fuzz-unit-file/directives.scope1
-rw-r--r--test/fuzz/fuzz-unit-file/directives.service1
-rw-r--r--test/fuzz/fuzz-unit-file/directives.slice1
-rw-r--r--test/fuzz/fuzz-unit-file/directives.socket1
-rw-r--r--test/fuzz/fuzz-unit-file/directives.swap1
15 files changed, 304 insertions, 0 deletions
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 7974833554..6625a74073 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2599,6 +2599,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) ControlGroupNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sb) EnvironmentFiles = [...];
@@ -3170,6 +3172,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property ControlGroupNFTSet is not documented!-->
+
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@@ -3750,6 +3754,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ControlGroupNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@@ -4487,6 +4493,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) ControlGroupNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sb) EnvironmentFiles = [...];
@@ -5082,6 +5090,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property ControlGroupNFTSet is not documented!-->
+
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@@ -5656,6 +5666,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ControlGroupNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@@ -6282,6 +6294,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) ControlGroupNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sb) EnvironmentFiles = [...];
@@ -6805,6 +6819,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property ControlGroupNFTSet is not documented!-->
+
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@@ -7297,6 +7313,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ControlGroupNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@@ -8050,6 +8068,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) ControlGroupNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sb) EnvironmentFiles = [...];
@@ -8559,6 +8579,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property ControlGroupNFTSet is not documented!-->
+
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@@ -9037,6 +9059,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ControlGroupNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@@ -9648,6 +9672,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
readonly a(iiqq) SocketBindDeny = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) ControlGroupNFTSet = [...];
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
@@ -9800,6 +9826,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property ControlGroupNFTSet is not documented!-->
+
<!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Unit"/>
@@ -9958,6 +9986,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ControlGroupNFTSet"/>
+
<!--End of Autogenerated section-->
<refsect2>
@@ -10138,6 +10168,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) ControlGroupNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
@@ -10307,6 +10339,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property ControlGroupNFTSet is not documented!-->
+
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
@@ -10493,6 +10527,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ControlGroupNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
index 1397b886c5..23b2d0f390 100644
--- a/man/systemd.resource-control.xml
+++ b/man/systemd.resource-control.xml
@@ -1173,6 +1173,35 @@ DeviceAllow=/dev/loop-control
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>ControlGroupNFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
+ <listitem>
+ <para>This setting provides a method for integrating dynamic cgroup IDs into firewall rules with
+ NFT sets. This option expects a whitespace separated list of NFT set definitions. Each definition
+ consists of a colon-separated tuple of NFT address family (one of <literal>arp</literal>,
+ <literal>bridge</literal>, <literal>inet</literal>, <literal>ip</literal>, <literal>ip6</literal>,
+ or <literal>netdev</literal>), table name and set name. The names of tables and sets must conform
+ to lexical restrictions of NFT table names. When a control group for a unit is realized, the cgroup
+ ID will be appended to the NFT sets and it will be be removed when the control group is
+ removed. Failures to manage the sets will be ignored.</para>
+
+ <para>Example:
+ <programlisting>[Unit]
+ControlGroupNFTSet=inet:filter:my_service
+</programlisting>
+ Corresponding NFT rules:
+ <programlisting>table inet filter {
+ set my_service {
+ type cgroupsv2
+ }
+ chain x {
+ socket cgroupv2 level 2 @my_service accept
+ drop
+ }
+}</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index 9282b1ff20..9a07a73f02 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -19,6 +19,7 @@
#include "devnum-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "firewall-util.h"
#include "in-addr-prefix-util.h"
#include "inotify-util.h"
#include "io-util.h"
@@ -279,6 +280,8 @@ void cgroup_context_done(CGroupContext *c) {
cpu_set_reset(&c->startup_cpuset_cpus);
cpu_set_reset(&c->cpuset_mems);
cpu_set_reset(&c->startup_cpuset_mems);
+
+ c->nft_set_context = nft_set_context_free_many(c->nft_set_context, &c->n_nft_set_contexts);
}
static int unit_get_kernel_memory_limit(Unit *u, const char *file, uint64_t *ret) {
@@ -617,6 +620,11 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
SET_FOREACH(iface, c->restrict_network_interfaces)
fprintf(f, "%sRestrictNetworkInterfaces: %s\n", prefix, iface);
}
+
+ for (size_t i = 0; i < c->n_nft_set_contexts; i++)
+ fprintf(f, "%sControlGroupNFTSet: %s:%s:%s\n", prefix,
+ nfproto_to_string(c->nft_set_context[i].nfproto),
+ c->nft_set_context[i].table, c->nft_set_context[i].set);
}
void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) {
@@ -1226,6 +1234,46 @@ static void cgroup_apply_firewall(Unit *u) {
(void) bpf_firewall_install(u);
}
+static void cgroup_apply_nft_set(Unit *u) {
+ int r;
+ CGroupContext *c;
+
+ assert(u);
+
+ assert_se(c = unit_get_cgroup_context(u));
+
+ for (size_t i = 0; i < c->n_nft_set_contexts; i++) {
+ NFTSetContext *s = &c->nft_set_context[i];
+ r = nft_set_element_add_uint64(s, u->cgroup_id);
+ if (r < 0)
+ log_warning_errno(r, "Adding NFT family %s table %s set %s cgroup %" PRIu64 " failed, ignoring: %m",
+ nfproto_to_string(s->nfproto),
+ s->table,
+ s->set,
+ u->cgroup_id);
+ }
+}
+
+static void cgroup_delete_nft_set(Unit *u) {
+ int r;
+ CGroupContext *c;
+
+ assert(u);
+
+ assert_se(c = unit_get_cgroup_context(u));
+
+ for (size_t i = 0; i < c->n_nft_set_contexts; i++) {
+ NFTSetContext *s = &c->nft_set_context[i];
+ r = nft_set_element_del_uint64(s, u->cgroup_id);
+ if (r < 0)
+ log_warning_errno(r, "Deleting NFT family %s table %s set %s cgroup %" PRIu64 " failed, ignoring: %m",
+ nfproto_to_string(s->nfproto),
+ s->table,
+ s->set,
+ u->cgroup_id);
+ }
+}
+
static void cgroup_apply_socket_bind(Unit *u) {
assert(u);
@@ -1658,6 +1706,8 @@ static void cgroup_context_apply(
if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES)
cgroup_apply_restrict_network_interfaces(u);
+
+ cgroup_apply_nft_set(u);
}
static bool unit_get_needs_bpf_firewall(Unit *u) {
@@ -2807,6 +2857,8 @@ void unit_prune_cgroup(Unit *u) {
(void) lsm_bpf_cleanup(u); /* Remove cgroup from the global LSM BPF map */
#endif
+ cgroup_delete_nft_set(u);
+
is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE);
r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice);
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
index 4413eeaaa0..6ac28d7ca7 100644
--- a/src/core/cgroup.h
+++ b/src/core/cgroup.h
@@ -6,6 +6,7 @@
#include "bpf-lsm.h"
#include "cgroup-util.h"
#include "cpu-set-util.h"
+#include "firewall-util.h"
#include "list.h"
#include "time-util.h"
@@ -194,6 +195,9 @@ struct CGroupContext {
ManagedOOMMode moom_mem_pressure;
uint32_t moom_mem_pressure_limit; /* Normalized to 2^32-1 == 100% */
ManagedOOMPreference moom_preference;
+
+ NFTSetContext *nft_set_context;
+ size_t n_nft_set_contexts;
};
/* Used when querying IP accounting data */
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
index 9a31355a4d..6070c21c4c 100644
--- a/src/core/dbus-cgroup.c
+++ b/src/core/dbus-cgroup.c
@@ -15,6 +15,7 @@
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "firewall-util.h"
#include "in-addr-prefix-util.h"
#include "ip-protocol-list.h"
#include "limits-util.h"
@@ -443,6 +444,36 @@ static int property_get_restrict_network_interfaces(
return sd_bus_message_close_container(reply);
}
+static int property_get_cgroup_nft_set(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+ int r;
+ CGroupContext *c = userdata;
+
+ assert(bus);
+ assert(reply);
+ assert(c);
+
+ r = sd_bus_message_open_container(reply, 'a', "(iss)");
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < c->n_nft_set_contexts; i++) {
+ NFTSetContext *s = &c->nft_set_context[i];
+
+ r = sd_bus_message_append(reply, "(iss)", s->nfproto, s->table, s->set);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0),
@@ -500,6 +531,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("SocketBindAllow", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0),
SD_BUS_PROPERTY("SocketBindDeny", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0),
SD_BUS_PROPERTY("RestrictNetworkInterfaces", "(bas)", property_get_restrict_network_interfaces, 0, 0),
+ SD_BUS_PROPERTY("ControlGroupNFTSet", "a(iss)", property_get_cgroup_nft_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
@@ -2062,5 +2094,58 @@ int bus_cgroup_set_property(
if (streq(name, "DisableControllers") || (u->transient && u->load_state == UNIT_STUB))
return bus_cgroup_set_transient_property(u, c, name, message, flags, error);
+ if (streq(name, "ControlGroupNFTSet")) {
+ int nfproto;
+ const char *table, *set;
+ bool empty = true;
+
+ r = sd_bus_message_enter_container(message, 'a', "(iss)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(message, "(iss)", &nfproto, &table, &set)) > 0) {
+ const char *nfproto_name;
+
+ nfproto_name = nfproto_to_string(nfproto);
+ if (!nfproto_name)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid protocol %d.", nfproto);
+
+ if (nft_identifier_bad(table))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NFT table name %s.", table);
+
+ if (nft_identifier_bad(set))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NFT set name %s.", set);
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ r = nft_set_context_add(&c->nft_set_context, &c->n_nft_set_contexts, nfproto, table, set);
+ if (r < 0)
+ return r;
+
+ unit_write_settingf(
+ u, flags|UNIT_ESCAPE_SPECIFIERS, name,
+ "%s=%s:%s:%s",
+ name,
+ nfproto_name,
+ table,
+ set);
+ }
+
+ empty = false;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ if (empty) {
+ c->nft_set_context = nft_set_context_free_many(c->nft_set_context, &c->n_nft_set_contexts);
+ unit_write_settingf(u, flags, name, "%s=", name);
+ }
+
+ return 1;
+ }
+
return 0;
}
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 7817c20c0b..0db24268d1 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -241,6 +241,7 @@
{{type}}.SocketBindAllow, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_allow)
{{type}}.SocketBindDeny, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_deny)
{{type}}.RestrictNetworkInterfaces, config_parse_restrict_network_interfaces, 0, offsetof({{type}}, cgroup_context)
+{{type}}.ControlGroupNFTSet, config_parse_cgroup_nft_set, 0, offsetof({{type}}, cgroup_context)
{%- endmacro -%}
%{
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 3ff6eae8fc..12c1af7933 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -35,8 +35,10 @@
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
+#include "execute.h"
#include "fd-util.h"
#include "fileio.h"
+#include "firewall-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "io-util.h"
@@ -6520,3 +6522,88 @@ int config_parse_tty_size(
return config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata);
}
+
+static int config_parse_nft_set(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ NFTSetContext **c,
+ size_t *n,
+ Unit *u) {
+ _cleanup_free_ char *family_str = NULL, *table = NULL, *set = NULL, *table_resolved = NULL, *set_resolved = NULL;
+ int nfproto, r;
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *c = nft_set_context_free_many(*c, n);
+ return 0;
+ }
+
+ for (const char *p = rvalue;;) {
+ r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE, &family_str, &table, &set, NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r == 0)
+ break;
+ if (r != 3) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse NFT set, ignoring: %s", p);
+ return 0;
+ }
+
+ nfproto = nfproto_from_string(family_str);
+ if (nfproto < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family, ignoring: %s", family_str);
+ return 0;
+ }
+
+ r = unit_path_printf(u, table, &table_resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", table);
+ return 0;
+ }
+
+ if (nft_identifier_bad(table_resolved))
+ return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", table);
+
+ r = unit_path_printf(u, set, &set_resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", set);
+ return 0;
+ }
+
+ if (nft_identifier_bad(set_resolved))
+ return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", set);
+
+ r = nft_set_context_add(c, n, nfproto, table_resolved, set_resolved);
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+int config_parse_cgroup_nft_set(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ CGroupContext *c = data;
+ Unit *u = userdata;
+
+ return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->nft_set_context, &c->n_nft_set_contexts, u);
+}
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 26b8de28f7..3632b5b096 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -150,6 +150,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_socket_bind);
CONFIG_PARSER_PROTOTYPE(config_parse_restrict_network_interfaces);
CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec);
CONFIG_PARSER_PROTOTYPE(config_parse_tty_size);
+CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index a326ca30a9..e1584c2e8d 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -891,6 +891,9 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
return 1;
}
+ if (streq(field, "ControlGroupNFTSet"))
+ return bus_append_nft_set(m, field, eq);
+
return 0;
}
diff --git a/test/fuzz/fuzz-unit-file/directives.mount b/test/fuzz/fuzz-unit-file/directives.mount
index 0a44328e5c..2b3331a411 100644
--- a/test/fuzz/fuzz-unit-file/directives.mount
+++ b/test/fuzz/fuzz-unit-file/directives.mount
@@ -28,6 +28,7 @@ Capabilities=
CapabilityBoundingSet=
ConfigurationDirectory=
ConfigurationDirectoryMode=
+ControlGroupNFTSet=
CoredumpFilter=
DefaultMemoryLow=
DefaultMemoryMin=
diff --git a/test/fuzz/fuzz-unit-file/directives.scope b/test/fuzz/fuzz-unit-file/directives.scope
index 4552d0b403..c4d579065a 100644
--- a/test/fuzz/fuzz-unit-file/directives.scope
+++ b/test/fuzz/fuzz-unit-file/directives.scope
@@ -8,6 +8,7 @@ BlockIODeviceWeight=
BlockIOReadBandwidth=
BlockIOWeight=
BlockIOWriteBandwidth=
+ControlGroupNFTSet=
CPUAccounting=
CPUQuota=
CPUQuotaPeriodSec=
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index 3c33d947fe..30e6936d12 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -72,6 +72,7 @@ ConditionSecurity=
ConditionUser=
ConditionVirtualization=
Conflicts=
+ControlGroupNFTSet=
DefaultDependencies=
Description=
Documentation=
diff --git a/test/fuzz/fuzz-unit-file/directives.slice b/test/fuzz/fuzz-unit-file/directives.slice
index ab77070c5e..749f1795e3 100644
--- a/test/fuzz/fuzz-unit-file/directives.slice
+++ b/test/fuzz/fuzz-unit-file/directives.slice
@@ -8,6 +8,7 @@ BlockIODeviceWeight=
BlockIOReadBandwidth=
BlockIOWeight=
BlockIOWriteBandwidth=
+ControlGroupNFTSet=
CPUAccounting=
CPUQuota=
CPUQuotaPeriodSec=
diff --git a/test/fuzz/fuzz-unit-file/directives.socket b/test/fuzz/fuzz-unit-file/directives.socket
index 90358fc11a..1b1ddf8c9c 100644
--- a/test/fuzz/fuzz-unit-file/directives.socket
+++ b/test/fuzz/fuzz-unit-file/directives.socket
@@ -33,6 +33,7 @@ Capabilities=
CapabilityBoundingSet=
ConfigurationDirectory=
ConfigurationDirectoryMode=
+ControlGroupNFTSet=
CoredumpFilter=
DefaultMemoryLow=
DefaultMemoryMin=
diff --git a/test/fuzz/fuzz-unit-file/directives.swap b/test/fuzz/fuzz-unit-file/directives.swap
index 5d057fa630..186dedbf3e 100644
--- a/test/fuzz/fuzz-unit-file/directives.swap
+++ b/test/fuzz/fuzz-unit-file/directives.swap
@@ -28,6 +28,7 @@ Capabilities=
CapabilityBoundingSet=
ConfigurationDirectory=
ConfigurationDirectoryMode=
+ControlGroupNFTSet=
CoredumpFilter=
DefaultMemoryLow=
DefaultMemoryMin=