diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 8a033c92be..ba7728b614 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2920,6 +2920,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly a(iiss) NFTSet = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -3538,6 +3540,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -4168,6 +4172,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -4957,6 +4963,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly a(iiss) NFTSet = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -5585,6 +5593,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
+
+
@@ -6197,6 +6207,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
+
+
@@ -6860,6 +6872,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly a(iiss) NFTSet = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -7416,6 +7430,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
+
+
@@ -7942,6 +7958,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
+
+
@@ -8728,6 +8746,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly a(iiss) NFTSet = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -9270,6 +9290,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
+
+
@@ -9782,6 +9804,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
+
+
@@ -10427,6 +10451,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly a(iiss) NFTSet = [...];
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
@@ -10597,6 +10623,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
+
+
@@ -10775,6 +10803,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
+
+
@@ -10976,6 +11006,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly a(iiss) NFTSet = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -11166,6 +11198,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
+
+
@@ -11374,6 +11408,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
+
+
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
index 6673f0c3a0..038a58e3dc 100644
--- a/man/systemd.resource-control.xml
+++ b/man/systemd.resource-control.xml
@@ -1500,6 +1500,73 @@ DeviceAllow=/dev/loop-control
+
+
+ NFTSet=family:table:set
+
+ This setting provides a method for integrating dynamic cgroup IDs into firewall rules with
+ NFT sets. The benefit of
+ using this setting is to be able to use the IDs as selectors in firewall rules easily and this in
+ turn allows more fine grained filtering. NFT rules for cgroup matching use numeric cgroup IDs,
+ which change every time a service is restarted, making them hard to use in systemd environment
+ otherwise.
+
+ This option expects a whitespace separated list of NFT set definitions. Each definition
+ consists of a colon-separated tuple of source type (only cgroup), NFT address
+ family (one of arp, bridge, inet,
+ ip, ip6, or netdev), table name and set
+ name. The names of tables and sets must conform to lexical restrictions of NFT table names. The
+ type of the element used in the NFT filter must match the type implied by the directive
+ (cgroup) as shown in the table below. When a control group is realized, the
+ corresponding ID will be appended to the NFT sets and it will be be removed when the control group
+ is removed. systemd only inserts elements to (or removes from) the sets, so the
+ related NFT rules, tables and sets must be prepared elsewhere in advance. Failures to manage the
+ sets will be ignored.
+
+
+ Defined source type values
+
+
+
+
+
+
+ Source type
+ Description
+ Corresponding NFT type name
+
+
+
+
+
+ cgroup
+ control group ID
+ cgroupsv2
+
+
+
+
+
+ If the firewall rules are reinstalled so that the contents of NFT sets are destroyed, command
+ systemctl daemon-reload can be used to refill the sets.
+
+ Example:
+ [Unit]
+NFTSet=cgroup:inet:filter:my_service
+
+ Corresponding NFT rules:
+ table inet filter {
+ set my_service {
+ type cgroupsv2
+ }
+ chain x {
+ socket cgroupv2 level 2 @my_service accept
+ drop
+ }
+}
+
+
+
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index b304b39e8c..32c78a449b 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -20,6 +20,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"
@@ -291,6 +292,8 @@ void cgroup_context_done(CGroupContext *c) {
cpu_set_reset(&c->startup_cpuset_mems);
c->delegate_subgroup = mfree(c->delegate_subgroup);
+
+ nft_set_context_clear(&c->nft_set_context);
}
static int unit_get_kernel_memory_limit(Unit *u, const char *file, uint64_t *ret) {
@@ -664,6 +667,10 @@ 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);
}
+
+ FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets)
+ fprintf(f, "%sNFTSet: %s:%s:%s:%s\n", prefix, nft_set_source_to_string(nft_set->source),
+ nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set);
}
void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) {
@@ -1342,6 +1349,43 @@ static void cgroup_apply_firewall(Unit *u) {
(void) bpf_firewall_install(u);
}
+void cgroup_modify_nft_set(Unit *u, bool add) {
+ int r;
+ CGroupContext *c;
+
+ assert(u);
+
+ if (!MANAGER_IS_SYSTEM(u->manager))
+ return;
+
+ if (cg_all_unified() <= 0)
+ return;
+
+ assert_se(c = unit_get_cgroup_context(u));
+ if (u->cgroup_id == 0)
+ return;
+
+ if (!u->manager->fw_ctx) {
+ r = fw_ctx_new_full(&u->manager->fw_ctx, /* init_tables= */ false);
+ if (r < 0)
+ return;
+
+ assert(u->manager->fw_ctx);
+ }
+
+ FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) {
+ uint64_t element = u->cgroup_id;
+
+ r = nft_set_element_modify_any(u->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element));
+ if (r < 0)
+ log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, cgroup %" PRIu64 ", ignoring: %m",
+ add? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, u->cgroup_id);
+ else
+ log_debug("%s NFT set: family %s, table %s, set %s, cgroup %" PRIu64,
+ add? "Added" : "Deleted", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, u->cgroup_id);
+ }
+}
+
static void cgroup_apply_socket_bind(Unit *u) {
assert(u);
@@ -1781,6 +1825,8 @@ static void cgroup_context_apply(
if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES)
cgroup_apply_restrict_network_interfaces(u);
+
+ cgroup_modify_nft_set(u, /* add = */ true);
}
static bool unit_get_needs_bpf_firewall(Unit *u) {
@@ -2950,6 +2996,8 @@ void unit_prune_cgroup(Unit *u) {
(void) lsm_bpf_cleanup(u); /* Remove cgroup from the global LSM BPF map */
#endif
+ cgroup_modify_nft_set(u, /* add = */ false);
+
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 bbbf9408cc..90159fd84c 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"
@@ -223,6 +224,8 @@ struct CGroupContext {
usec_t memory_pressure_threshold_usec;
/* NB: For now we don't make the period configurable, not the type, nor do we allow multiple
* triggers, nor triggers for non-memory pressure. We might add that later. */
+
+ NFTSetContext nft_set_context;
};
/* Used when querying IP accounting data */
@@ -277,6 +280,8 @@ int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const
void cgroup_oomd_xattr_apply(Unit *u, const char *cgroup_path);
int cgroup_log_xattr_apply(Unit *u, const char *cgroup_path);
+void cgroup_modify_nft_set(Unit *u, bool add);
+
CGroupMask unit_get_own_mask(Unit *u);
CGroupMask unit_get_delegate_mask(Unit *u);
CGroupMask unit_get_members_mask(Unit *u);
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
index 5347525844..edb5dfa13e 100644
--- a/src/core/dbus-cgroup.c
+++ b/src/core/dbus-cgroup.c
@@ -14,8 +14,10 @@
#include "dbus-cgroup.h"
#include "dbus-util.h"
#include "errno-util.h"
+#include "escape.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"
@@ -423,6 +425,34 @@ 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', "(iiss)");
+ if (r < 0)
+ return r;
+
+ FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) {
+ r = sd_bus_message_append(reply, "(iiss)", nft_set->source, nft_set->nfproto, nft_set->table, nft_set->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),
@@ -490,6 +520,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("RestrictNetworkInterfaces", "(bas)", property_get_restrict_network_interfaces, 0, 0),
SD_BUS_PROPERTY("MemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, memory_pressure_watch), 0),
SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, memory_pressure_threshold_usec), 0),
+ SD_BUS_PROPERTY("NFTSet", "a(iiss)", property_get_cgroup_nft_set, 0, 0),
SD_BUS_VTABLE_END
};
@@ -2192,6 +2223,75 @@ int bus_cgroup_set_property(
return 1;
}
+ if (streq(name, "NFTSet")) {
+ int source, nfproto;
+ const char *table, *set;
+ bool empty = true;
+
+ r = sd_bus_message_enter_container(message, 'a', "(iiss)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(message, "(iiss)", &source, &nfproto, &table, &set)) > 0) {
+ const char *source_name, *nfproto_name;
+
+ if (source != NFT_SET_SOURCE_CGROUP)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid source %d.", source);
+
+ source_name = nft_set_source_to_string(source);
+ assert(source_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_valid(table)) {
+ _cleanup_free_ char *esc = NULL;
+
+ esc = cescape(table);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NFT table name %s.", strna(esc));
+ }
+
+ if (!nft_identifier_valid(set)) {
+ _cleanup_free_ char *esc = NULL;
+
+ esc = cescape(set);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NFT set name %s.", strna(esc));
+ }
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ r = nft_set_add(&c->nft_set_context, source, nfproto, table, set);
+ if (r < 0)
+ return r;
+
+ unit_write_settingf(
+ u, flags|UNIT_ESCAPE_SPECIFIERS, name,
+ "%s=%s:%s:%s:%s",
+ name,
+ source_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 && !UNIT_WRITE_FLAGS_NOOP(flags)) {
+ nft_set_context_clear(&c->nft_set_context);
+ unit_write_settingf(u, flags, name, "%s=", name);
+ }
+
+ return 1;
+ }
+
+ /* must be last */
if (streq(name, "DisableControllers") || (u->transient && u->load_state == UNIT_STUB))
return bus_cgroup_set_transient_property(u, c, name, message, flags, error);
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 0d1ee9c231..6cdf131975 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -258,6 +258,7 @@
{{type}}.RestrictNetworkInterfaces, config_parse_restrict_network_interfaces, 0, offsetof({{type}}, cgroup_context)
{{type}}.MemoryPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.memory_pressure_threshold_usec)
{{type}}.MemoryPressureWatch, config_parse_memory_pressure_watch, 0, offsetof({{type}}, cgroup_context.memory_pressure_watch)
+{{type}}.NFTSet, config_parse_cgroup_nft_set, NFT_SET_PARSE_CGROUP, offsetof({{type}}, cgroup_context)
{%- endmacro -%}
%{
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index d89f5ca229..935a4ef35d 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -33,8 +33,10 @@
#include "errno-list.h"
#include "escape.h"
#include "exec-credential.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"
@@ -6696,3 +6698,21 @@ int config_parse_open_file(
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 = ASSERT_PTR(data);
+ Unit *u = ASSERT_PTR(userdata);
+
+ return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->nft_set_context, u);
+}
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 39378b3a3c..69198050ea 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -158,6 +158,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tty_size);
CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns);
CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
+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/core/manager.c b/src/core/manager.c
index 07845af6d1..aed2c988af 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1679,6 +1679,8 @@ Manager* manager_free(Manager *m) {
free(m->watchdog_pretimeout_governor);
free(m->watchdog_pretimeout_governor_overridden);
+ m->fw_ctx = fw_ctx_free(m->fw_ctx);
+
#if BPF_FRAMEWORK
lsm_bpf_destroy(m->restrict_fs);
#endif
diff --git a/src/core/manager.h b/src/core/manager.h
index ee2ace70a8..55543703cd 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -486,6 +486,9 @@ struct Manager {
RateLimit dump_ratelimit;
sd_event_source *memory_pressure_event_source;
+
+ /* For NFTSet= */
+ FirewallContext *fw_ctx;
};
static inline usec_t manager_default_timeout_abort_usec(Manager *m) {
diff --git a/src/core/socket.c b/src/core/socket.c
index 43f412bc89..0c905afddc 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -1489,14 +1489,22 @@ static int socket_address_listen_do(
log_unit_error_errno(u, error, fmt, strna(_t)); \
})
-static int fork_needed(const SocketAddress *address, const ExecContext *context) {
+static int fork_needed(const SocketAddress *address, Socket *s) {
int r;
assert(address);
- assert(context);
+ assert(s);
/* Check if we need to do the cgroup or netns stuff. If not we can do things much simpler. */
+ /* If there are any NFTSet= directives with cgroup source, we need the cgroup */
+ Unit *u = UNIT(s);
+ CGroupContext *c = unit_get_cgroup_context(u);
+ if (c)
+ FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets)
+ if (nft_set->source == NFT_SET_SOURCE_CGROUP)
+ return true;
+
if (IN_SET(address->sockaddr.sa.sa_family, AF_INET, AF_INET6)) {
r = bpf_firewall_supported();
if (r < 0)
@@ -1505,7 +1513,7 @@ static int fork_needed(const SocketAddress *address, const ExecContext *context)
return true;
}
- return exec_needs_network_namespace(context);
+ return exec_needs_network_namespace(&s->exec_context);
}
static int socket_address_listen_in_cgroup(
@@ -1525,7 +1533,7 @@ static int socket_address_listen_in_cgroup(
* the socket is actually properly attached to the unit's cgroup for the purpose of BPF filtering and
* such. */
- r = fork_needed(address, &s->exec_context);
+ r = fork_needed(address, s);
if (r < 0)
return r;
if (r == 0) {
diff --git a/src/core/unit.c b/src/core/unit.c
index 5fd66e7b6e..80ac787022 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -3820,6 +3820,10 @@ int unit_coldplug(Unit *u) {
r = q;
}
+ CGroupContext *c = unit_get_cgroup_context(u);
+ if (c)
+ cgroup_modify_nft_set(u, /* add = */ true);
+
return r;
}
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index bbb87e00bd..ab456efb9e 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -161,7 +161,7 @@ Address.DuplicateAddressDetection, config_parse_duplicate_address_dete
Address.Scope, config_parse_address_scope, 0, 0
Address.RouteMetric, config_parse_address_route_metric, 0, 0
Address.NetLabel, config_parse_address_netlabel, 0, 0
-Address.NFTSet, config_parse_address_ip_nft_set, 0, 0
+Address.NFTSet, config_parse_address_ip_nft_set, NFT_SET_PARSE_NETWORK, 0
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
Neighbor.Address, config_parse_neighbor_address, 0, 0
@@ -258,7 +258,7 @@ DHCPv4.InitialAdvertisedReceiveWindow, config_parse_tcp_window,
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd)
DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel)
-DHCPv4.NFTSet, config_parse_nft_set, 0, offsetof(Network, dhcp_nft_set_context)
+DHCPv4.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_nft_set_context)
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
@@ -280,7 +280,7 @@ DHCPv6.DUIDRawData, config_parse_duid_rawdata,
DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel)
DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release)
-DHCPv6.NFTSet, config_parse_nft_set, 0, offsetof(Network, dhcp6_nft_set_context)
+DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context)
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
@@ -304,7 +304,7 @@ IPv6AcceptRA.RouteAllowList, config_parse_in_addr_prefixes,
IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_route_prefix)
IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens)
IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel)
-IPv6AcceptRA.NFTSet, config_parse_nft_set, 0, offsetof(Network, ndisc_nft_set_context)
+IPv6AcceptRA.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, ndisc_nft_set_context)
DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0
DHCPServer.UplinkInterface, config_parse_uplink, 0, 0
DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target)
@@ -372,7 +372,7 @@ DHCPPrefixDelegation.ManageTemporaryAddress, config_parse_bool,
DHCPPrefixDelegation.Token, config_parse_address_generation_type, 0, offsetof(Network, dhcp_pd_tokens)
DHCPPrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric)
DHCPPrefixDelegation.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_pd_netlabel)
-DHCPPrefixDelegation.NFTSet, config_parse_nft_set, 0, offsetof(Network, dhcp_pd_nft_set_context)
+DHCPPrefixDelegation.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_pd_nft_set_context)
IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec)
IPv6SendRA.RetransmitSec, config_parse_router_retransmit, 0, offsetof(Network, router_retransmit_usec)
IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index e2d6bfebf3..47cfc199d3 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -17,6 +17,7 @@
#include "exec-util.h"
#include "exit-status.h"
#include "fileio.h"
+#include "firewall-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "in-addr-util.h"
@@ -449,6 +450,91 @@ static int bus_append_ip_address_access(sd_bus_message *m, int family, const uni
return sd_bus_message_close_container(m);
}
+static int bus_append_nft_set(sd_bus_message *m, const char *field, const char *eq) {
+ int r;
+
+ assert(m);
+ assert(field);
+ assert(eq);
+
+ if (isempty(eq)) {
+ r = sd_bus_message_append(m, "(sv)", field, "a(iiss)", 0);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+ }
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', "a(iiss)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(iiss)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ for (const char *p = eq;;) {
+ _cleanup_free_ char *tuple = NULL, *source_str = NULL, *nfproto_str = NULL, *table = NULL, *set = NULL;
+ const char *q = NULL;
+ int source, nfproto;
+
+ r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s: %m", field);
+ if (r == 0)
+ break;
+ if (isempty(tuple))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+
+ q = tuple;
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set, NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r != 4 || !isempty(q))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+
+ assert(source_str);
+ assert(nfproto_str);
+ assert(table);
+ assert(set);
+
+ source = nft_set_source_from_string(source_str);
+ if (source != NFT_SET_SOURCE_CGROUP)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+
+ nfproto = nfproto_from_string(nfproto_str);
+ if (nfproto < 0 || !nft_identifier_valid(table) || !nft_identifier_valid(set))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+
+ r = sd_bus_message_append(m, "(iiss)", source, nfproto, table, set);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+}
+
static int bus_append_cgroup_property(sd_bus_message *m, const char *field, const char *eq) {
int r;
@@ -914,6 +1000,9 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
if (streq(field, "MemoryPressureThresholdSec"))
return bus_append_parse_sec_rename(m, field, eq);
+ if (streq(field, "NFTSet"))
+ return bus_append_nft_set(m, field, eq);
+
return 0;
}
diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c
index 39f9188de2..a71ea060e3 100644
--- a/src/shared/firewall-util-nft.c
+++ b/src/shared/firewall-util-nft.c
@@ -1202,6 +1202,7 @@ static const char *const nft_set_source_table[] = {
[NFT_SET_SOURCE_ADDRESS] = "address",
[NFT_SET_SOURCE_PREFIX] = "prefix",
[NFT_SET_SOURCE_IFINDEX] = "ifindex",
+ [NFT_SET_SOURCE_CGROUP] = "cgroup",
};
DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int);
@@ -1218,11 +1219,11 @@ void nft_set_context_clear(NFTSetContext *s) {
s->sets = mfree(s->sets);
}
-static int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
+int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
_cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
assert(s);
- assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX));
+ assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP));
assert(nfproto_is_valid(nfproto));
assert(table);
assert(set);
@@ -1285,6 +1286,7 @@ int config_parse_nft_set(
assert(lvalue);
assert(rvalue);
assert(nft_set_context);
+ assert(IN_SET(ltype, NFT_SET_PARSE_NETWORK, NFT_SET_PARSE_CGROUP));
if (isempty(rvalue)) {
nft_set_context_clear(nft_set_context);
@@ -1328,7 +1330,9 @@ int config_parse_nft_set(
assert(set);
source = nft_set_source_from_string(source_str);
- if (source < 0) {
+ if (source < 0 ||
+ (ltype == NFT_SET_PARSE_NETWORK && !IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX)) ||
+ (ltype == NFT_SET_PARSE_CGROUP && source != NFT_SET_SOURCE_CGROUP)) {
_cleanup_free_ char *esc = NULL;
esc = cescape(source_str);
diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h
index bb35e76f21..e45f51fab4 100644
--- a/src/shared/firewall-util.h
+++ b/src/shared/firewall-util.h
@@ -36,6 +36,7 @@ typedef enum NFTSetSource {
NFT_SET_SOURCE_ADDRESS,
NFT_SET_SOURCE_PREFIX,
NFT_SET_SOURCE_IFINDEX,
+ NFT_SET_SOURCE_CGROUP,
_NFT_SET_SOURCE_MAX,
_NFT_SET_SOURCE_INVALID = -EINVAL,
} NFTSetSource;
@@ -89,4 +90,11 @@ int nft_set_element_modify_any(
const void *element,
size_t element_size);
+int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set);
+
+typedef enum NFTSetParseFlags {
+ NFT_SET_PARSE_NETWORK,
+ NFT_SET_PARSE_CGROUP,
+} NFTSetParseFlags;
+
CONFIG_PARSER_PROTOTYPE(config_parse_nft_set);
diff --git a/src/test/test-nft-set.c b/src/test/test-nft-set.c
index df8e93aaa2..bb0c902493 100644
--- a/src/test/test-nft-set.c
+++ b/src/test/test-nft-set.c
@@ -43,6 +43,14 @@ int main(int argc, char **argv) {
r = safe_atou32(argv[6], &element);
assert_se(r == 0);
+ r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element));
+ assert_se(r == 0);
+ } else if (streq(argv[5], "uint64")) {
+ uint64_t element;
+
+ r = safe_atou64(argv[6], &element);
+ assert_se(r == 0);
+
r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element));
assert_se(r == 0);
} else if (streq(argv[5], "in_addr")) {