Merge pull request #17655 from wat-ze-hex/bpf-build-rule

Introduce SocketBind{Allow|Deny}= properties powered by source compiled BPF
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2021-04-27 12:52:30 +02:00
committed by GitHub
37 changed files with 1364 additions and 4 deletions
+1
View File
@@ -2164,6 +2164,7 @@ static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
[CGROUP_CONTROLLER_BPF_FIREWALL] = "bpf-firewall",
[CGROUP_CONTROLLER_BPF_DEVICES] = "bpf-devices",
[CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
[CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind",
};
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
+3 -1
View File
@@ -31,6 +31,7 @@ typedef enum CGroupController {
CGROUP_CONTROLLER_BPF_FIREWALL,
CGROUP_CONTROLLER_BPF_DEVICES,
CGROUP_CONTROLLER_BPF_FOREIGN,
CGROUP_CONTROLLER_BPF_SOCKET_BIND,
_CGROUP_CONTROLLER_MAX,
_CGROUP_CONTROLLER_INVALID = -EINVAL,
@@ -51,6 +52,7 @@ typedef enum CGroupMask {
CGROUP_MASK_BPF_FIREWALL = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FIREWALL),
CGROUP_MASK_BPF_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_DEVICES),
CGROUP_MASK_BPF_FOREIGN = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FOREIGN),
CGROUP_MASK_BPF_SOCKET_BIND = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_SOCKET_BIND),
/* All real cgroup v1 controllers */
CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS,
@@ -59,7 +61,7 @@ typedef enum CGroupMask {
CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS,
/* All cgroup v2 BPF pseudo-controllers */
CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN,
CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND,
_CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1
} CGroupMask;
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1+
if conf.get('BPF_FRAMEWORK') == 1
socket_bind_skel_h = custom_target(
'socket-bind.skel.h',
input : 'socket-bind.bpf.c',
output : 'socket-bind.skel.h',
command : [build_bpf_skel_py,
'--clang_exec', clang.path(),
'--llvm_strip_exec', llvm_strip.path(),
'--bpftool_exec', bpftool.path(),
'--arch', host_machine.cpu_family(),
'@INPUT@', '@OUTPUT@'])
endif
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* The SPDX header above is actually correct in claiming this was
* LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
* compatible with GPL we will claim this to be GPL however, which should be
* fine given that LGPL-2.1-or-later downgrades to GPL if needed.
*/
#include <linux/types.h>
/*
* Bind rule is matched with socket fields accessible to cgroup/bind{4,6} hook
* through bpf_sock_addr struct.
* address_family is expected to be one of AF_UNSPEC, AF_INET or AF_INET6.
* Matching by family is bypassed for rules with AF_UNSPEC set, which makes the
* rest of a rule applicable for both IPv4 and IPv6 addresses.
* If matching by family is either successful or bypassed, a rule and a socket
* are matched by ports.
* nr_ports and port_min fields specify a set of ports to match a user port
* with.
* If nr_ports is 0, maching by port is bypassed, making that rule applicable
* for all possible ports, e.g. [1, 65535] range. Thus a rule with
* address_family and nr_ports equal to AF_UNSPEC and 0 correspondingly forms
* 'allow any' or 'deny any' cases.
* For positive nr_ports, a user_port lying in a range from port_min to
* port_min + nr_ports exclusively is considered to be a match. nr_ports
* equalling to 1 forms a rule for a single port.
* Ports are in host order.
*
* Examples:
* AF_UNSPEC, 1, 7777: match IPv4 and IPv6 addresses with 7777 user port;
*
* AF_INET, 1023, 1: match IPv4 addresses with user port in [1, 1023]
* range inclusively;
*
* AF_INET6, 0, 0: match IPv6 addresses;
*
* AF_UNSPEC, 0, 0: match IPv4 and IPv6 addresses.
*/
struct socket_bind_rule {
__u32 address_family;
__u16 nr_ports;
__u16 port_min;
};
#define SOCKET_BIND_MAX_RULES 128
+103
View File
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* The SPDX header above is actually correct in claiming this was
* LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
* compatible with GPL we will claim this to be GPL however, which should be
* fine given that LGPL-2.1-or-later downgrades to GPL if needed.
*/
#include "socket-bind-api.bpf.h"
/* <linux/types.h> must precede <bpf/bpf_helpers.h> due to
* <bpf/bpf_helpers.h> does not depend from type header by design.
*/
#include <linux/types.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <linux/bpf.h>
#include <netinet/in.h>
#include <stdbool.h>
/*
* max_entries is set from user space with bpf_map__resize helper.
*/
struct socket_bind_map_t {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, struct socket_bind_rule);
};
enum socket_bind_action {
SOCKET_BIND_DENY = 0,
SOCKET_BIND_ALLOW = 1,
};
struct socket_bind_map_t sd_bind_allow SEC(".maps");
struct socket_bind_map_t sd_bind_deny SEC(".maps");
static __always_inline bool match_af(
__u8 address_family, const struct socket_bind_rule *r) {
return r->address_family == AF_UNSPEC || address_family == r->address_family;
}
static __always_inline bool match_user_port(
__u16 port, const struct socket_bind_rule *r) {
return r->nr_ports == 0 ||
(port >= r->port_min && port < r->port_min + (__u32) r->nr_ports);
}
static __always_inline bool match(
__u8 address_family,
__u16 port,
const struct socket_bind_rule *r) {
return match_af(address_family, r) && match_user_port(port, r);
}
static __always_inline bool match_rules(
struct bpf_sock_addr *ctx,
struct socket_bind_map_t *rules) {
volatile __u32 user_port = ctx->user_port;
__u16 port = (__u16)bpf_ntohs(user_port);
for (__u32 i = 0; i < SOCKET_BIND_MAX_RULES; ++i) {
const __u32 key = i;
const struct socket_bind_rule *rule = bpf_map_lookup_elem(rules, &key);
/* Lookup returns NULL if iterator is advanced past the last
* element put in the map. */
if (!rule)
break;
if (match(ctx->user_family, port, rule))
return true;
}
return false;
}
static __always_inline int bind_socket(struct bpf_sock_addr *ctx) {
if (match_rules(ctx, &sd_bind_allow))
return SOCKET_BIND_ALLOW;
if (match_rules(ctx, &sd_bind_deny))
return SOCKET_BIND_DENY;
return SOCKET_BIND_ALLOW;
}
SEC("cgroup/bind4")
int sd_bind4(struct bpf_sock_addr *ctx) {
if (ctx->user_family != AF_INET || ctx->family != AF_INET)
return SOCKET_BIND_ALLOW;
return bind_socket(ctx);
}
SEC("cgroup/bind6")
int sd_bind6(struct bpf_sock_addr *ctx) {
if (ctx->user_family != AF_INET6 || ctx->family != AF_INET6)
return SOCKET_BIND_ALLOW;
return bind_socket(ctx);
}
char _license[] SEC("license") = "GPL";
+73
View File
@@ -25,6 +25,7 @@
#include "percent-util.h"
#include "process-util.h"
#include "procfs-util.h"
#include "socket-bind.h"
#include "special.h"
#include "stat-util.h"
#include "stdio-util.h"
@@ -200,6 +201,18 @@ void cgroup_context_remove_bpf_foreign_program(CGroupContext *c, CGroupBPFForeig
free(p);
}
void cgroup_context_remove_socket_bind(CGroupSocketBindItem **head) {
CGroupSocketBindItem *h;
assert(head);
while (*head) {
h = *head;
LIST_REMOVE(socket_bind_items, *head, h);
free(h);
}
}
void cgroup_context_done(CGroupContext *c) {
assert(c);
@@ -221,6 +234,9 @@ void cgroup_context_done(CGroupContext *c) {
while (c->device_allow)
cgroup_context_free_device_allow(c, c->device_allow);
cgroup_context_remove_socket_bind(&c->socket_bind_allow);
cgroup_context_remove_socket_bind(&c->socket_bind_deny);
c->ip_address_allow = ip_address_access_free_all(c->ip_address_allow);
c->ip_address_deny = ip_address_access_free_all(c->ip_address_deny);
@@ -376,6 +392,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
CGroupBPFForeignProgram *p;
CGroupDeviceAllow *a;
CGroupContext *c;
CGroupSocketBindItem *bi;
IPAddressAccessItem *iaai;
char **path;
char q[FORMAT_TIMESPAN_MAX];
@@ -562,6 +579,34 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
LIST_FOREACH(programs, p, c->bpf_foreign_programs)
fprintf(f, "%sBPFProgram: %s:%s",
prefix, bpf_cgroup_attach_type_to_string(p->attach_type), p->bpffs_path);
if (c->socket_bind_allow) {
fprintf(f, "%sSocketBindAllow:", prefix);
LIST_FOREACH(socket_bind_items, bi, c->socket_bind_allow)
cgroup_context_dump_socket_bind_item(bi, f);
fputc('\n', f);
}
if (c->socket_bind_deny) {
fprintf(f, "%sSocketBindDeny:", prefix);
LIST_FOREACH(socket_bind_items, bi, c->socket_bind_deny)
cgroup_context_dump_socket_bind_item(bi, f);
fputc('\n', f);
}
}
void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) {
const char *family = item->address_family == AF_INET ? "IPv4:" :
item->address_family == AF_INET6 ? "IPv6:" : "";
if (item->nr_ports == 0)
fprintf(f, " %sany", family);
else if (item->nr_ports == 1)
fprintf(f, " %s%" PRIu16, family, item->port_min);
else {
uint16_t port_max = item->port_min + item->nr_ports - 1;
fprintf(f, " %s%" PRIu16 "-%" PRIu16, family, item->port_min, port_max);
}
}
int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) {
@@ -1055,6 +1100,12 @@ static void cgroup_apply_firewall(Unit *u) {
(void) bpf_firewall_install(u);
}
static void cgroup_apply_socket_bind(Unit *u) {
assert(u);
(void) socket_bind_install(u);
}
static int cgroup_apply_devices(Unit *u) {
_cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL;
const char *path;
@@ -1483,6 +1534,9 @@ static void cgroup_context_apply(
if (apply_mask & CGROUP_MASK_BPF_FOREIGN)
cgroup_apply_bpf_foreign_program(u);
if (apply_mask & CGROUP_MASK_BPF_SOCKET_BIND)
cgroup_apply_socket_bind(u);
}
static bool unit_get_needs_bpf_firewall(Unit *u) {
@@ -1526,6 +1580,17 @@ static bool unit_get_needs_bpf_foreign_program(Unit *u) {
return !LIST_IS_EMPTY(c->bpf_foreign_programs);
}
static bool unit_get_needs_socket_bind(Unit *u) {
CGroupContext *c;
assert(u);
c = unit_get_cgroup_context(u);
if (!c)
return false;
return c->socket_bind_allow != NULL || c->socket_bind_deny != NULL;
}
static CGroupMask unit_get_cgroup_mask(Unit *u) {
CGroupMask mask = 0;
CGroupContext *c;
@@ -1580,6 +1645,9 @@ static CGroupMask unit_get_bpf_mask(Unit *u) {
if (unit_get_needs_bpf_foreign_program(u))
mask |= CGROUP_MASK_BPF_FOREIGN;
if (unit_get_needs_socket_bind(u))
mask |= CGROUP_MASK_BPF_SOCKET_BIND;
return mask;
}
@@ -3063,6 +3131,11 @@ static int cg_bpf_mask_supported(CGroupMask *ret) {
if (r > 0)
mask |= CGROUP_MASK_BPF_FOREIGN;
/* BPF-based bind{4|6} hooks */
r = socket_bind_supported();
if (r > 0)
mask |= CGROUP_MASK_BPF_SOCKET_BIND;
*ret = mask;
return 0;
}
+13
View File
@@ -32,6 +32,7 @@ typedef struct CGroupIODeviceLatency CGroupIODeviceLatency;
typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight;
typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth;
typedef struct CGroupBPFForeignProgram CGroupBPFForeignProgram;
typedef struct CGroupSocketBindItem CGroupSocketBindItem;
typedef enum CGroupDevicePolicy {
/* When devices listed, will allow those, plus built-in ones, if none are listed will allow
@@ -101,6 +102,13 @@ struct CGroupBPFForeignProgram {
char *bpffs_path;
};
struct CGroupSocketBindItem {
LIST_FIELDS(CGroupSocketBindItem, socket_bind_items);
int address_family;
uint16_t nr_ports;
uint16_t port_min;
};
struct CGroupContext {
bool cpu_accounting;
bool io_accounting;
@@ -165,6 +173,9 @@ struct CGroupContext {
CGroupDevicePolicy device_policy;
LIST_HEAD(CGroupDeviceAllow, device_allow);
LIST_HEAD(CGroupSocketBindItem, socket_bind_allow);
LIST_HEAD(CGroupSocketBindItem, socket_bind_deny);
/* Common */
TasksMax tasks_max;
@@ -203,6 +214,7 @@ usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution,
void cgroup_context_init(CGroupContext *c);
void cgroup_context_done(CGroupContext *c);
void cgroup_context_dump(Unit *u, FILE* f, const char *prefix);
void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f);
void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a);
void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w);
@@ -211,6 +223,7 @@ void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLaten
void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w);
void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b);
void cgroup_context_remove_bpf_foreign_program(CGroupContext *c, CGroupBPFForeignProgram *p);
void cgroup_context_remove_socket_bind(CGroupSocketBindItem **head);
int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode);
int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path);
+112
View File
@@ -16,6 +16,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "limits-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "percent-util.h"
@@ -375,6 +376,32 @@ static int property_get_bpf_foreign_program(
return sd_bus_message_close_container(reply);
}
static int property_get_socket_bind(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
CGroupSocketBindItem **items = userdata, *i;
int r;
assert(items);
r = sd_bus_message_open_container(reply, 'a', "(iqq)");
if (r < 0)
return r;
LIST_FOREACH(socket_bind_items, i, *items) {
r = sd_bus_message_append(reply, "(iqq)", i->address_family, i->nr_ports, i->port_min);
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),
@@ -427,6 +454,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("ManagedOOMMemoryPressureLimit", "u", NULL, offsetof(CGroupContext, moom_mem_pressure_limit), 0),
SD_BUS_PROPERTY("ManagedOOMPreference", "s", property_get_managed_oom_preference, offsetof(CGroupContext, moom_preference), 0),
SD_BUS_PROPERTY("BPFProgram", "a(ss)", property_get_bpf_foreign_program, 0, 0),
SD_BUS_PROPERTY("SocketBindAllow", "a(iqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0),
SD_BUS_PROPERTY("SocketBindDeny", "a(iqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0),
SD_BUS_VTABLE_END
};
@@ -1849,6 +1878,89 @@ int bus_cgroup_set_property(
return 1;
}
if (STR_IN_SET(name, "SocketBindAllow", "SocketBindDeny")) {
CGroupSocketBindItem **list;
uint16_t nr_ports, port_min;
size_t n = 0;
int family;
list = streq(name, "SocketBindAllow") ? &c->socket_bind_allow : &c->socket_bind_deny;
r = sd_bus_message_enter_container(message, 'a', "(iqq)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(iqq)", &family, &nr_ports, &port_min)) > 0) {
if (!IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects INET or INET6 family, if specified.", name);
if (port_min + (uint32_t) nr_ports > (1 << 16))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects maximum port value lesser than 65536.", name);
if (port_min == 0 && nr_ports != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects port range starting with positive value.", name);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ CGroupSocketBindItem *item = NULL;
item = new(CGroupSocketBindItem, 1);
if (!item)
return log_oom();
*item = (CGroupSocketBindItem) {
.address_family = family,
.nr_ports = nr_ports,
.port_min = port_min
};
LIST_PREPEND(socket_bind_items, *list, TAKE_PTR(item));
}
n++;
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *buf = NULL;
_cleanup_fclose_ FILE *f = NULL;
CGroupSocketBindItem *item;
size_t size = 0;
if (n == 0)
cgroup_context_remove_socket_bind(list);
else {
if ((u->manager->cgroup_supported & CGROUP_MASK_BPF_SOCKET_BIND) == 0)
log_full(LOG_DEBUG,
"Unit %s configures source compiled BPF programs "
"but the local system does not support that.\n"
"Starting this unit will fail!", u->id);
}
f = open_memstream_unlocked(&buf, &size);
if (!f)
return -ENOMEM;
fprintf(f, "%s:", name);
LIST_FOREACH(socket_bind_items, item, *list)
cgroup_context_dump_socket_bind_item(item, f);
fputc('\n', f);
r = fflush_and_check(f);
if (r < 0)
return r;
unit_write_setting(u, flags, name, buf);
}
return 1;
}
if (streq(name, "DisableControllers") || (u->transient && u->load_state == UNIT_STUB))
return bus_cgroup_set_transient_property(u, c, name, message, flags, error);
+3 -1
View File
@@ -235,7 +235,9 @@ $1.ManagedOOMMemoryPressure, config_parse_managed_oom_mode,
$1.ManagedOOMMemoryPressureLimit, config_parse_managed_oom_mem_pressure_limit, 0, offsetof($1, cgroup_context.moom_mem_pressure_limit)
$1.ManagedOOMPreference, config_parse_managed_oom_preference, 0, offsetof($1, cgroup_context.moom_preference)
$1.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0
$1.BPFProgram, config_parse_bpf_foreign_program, 0, offsetof($1, cgroup_context)'
$1.BPFProgram, config_parse_bpf_foreign_program, 0, offsetof($1, cgroup_context)
$1.SocketBindAllow, config_parse_cgroup_socket_bind, 0, offsetof($1, cgroup_context.socket_bind_allow)
$1.SocketBindDeny, config_parse_cgroup_socket_bind, 0, offsetof($1, cgroup_context.socket_bind_deny)'
)m4_dnl
Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description)
Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation)
+68
View File
@@ -55,6 +55,7 @@
#endif
#include "securebits-util.h"
#include "signal-util.h"
#include "socket-bind.h"
#include "socket-netlink.h"
#include "stat-util.h"
#include "string-util.h"
@@ -5657,6 +5658,73 @@ int config_parse_bpf_foreign_program(
return 0;
}
int config_parse_cgroup_socket_bind(
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) {
_cleanup_free_ CGroupSocketBindItem *item = NULL;
const char *address_family = NULL, *user_port;
uint16_t nr_ports = 0, port_min = 0;
CGroupSocketBindItem **head = data;
_cleanup_free_ char *word = NULL;
int af = AF_UNSPEC, r;
if (isempty(rvalue)) {
cgroup_context_remove_socket_bind(head);
return 0;
}
r = extract_first_word(&rvalue, &word, ":", 0);
if (r == -ENOMEM)
return log_oom();
if (rvalue)
address_family = word;
if (address_family) {
if (streq(address_family, "IPv4"))
af = AF_INET;
else if (streq(address_family, "IPv6"))
af = AF_INET6;
else
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
"Only IPv4 or IPv6 protocols are supported, ignoring");
}
user_port = rvalue ?: word;
if (!streq(user_port, "any")) {
uint16_t port_max;
r = parse_ip_port_range(user_port, &port_min, &port_max);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_warning_errno(r, "Invalid port or port range, ignoring: %m");
nr_ports = 1 + port_max - port_min;
}
item = new(CGroupSocketBindItem, 1);
if (!item)
return log_oom();
*item = (CGroupSocketBindItem) {
.address_family = af,
.nr_ports = nr_ports,
.port_min = port_min,
};
LIST_PREPEND(socket_bind_items, *head, TAKE_PTR(item));
return 0;
}
static int merge_by_names(Unit **u, Set *names, const char *id) {
char *k;
int r;
+1
View File
@@ -141,6 +141,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_mount_images);
CONFIG_PARSER_PROTOTYPE(config_parse_socket_timestamping);
CONFIG_PARSER_PROTOTYPE(config_parse_extension_images);
CONFIG_PARSER_PROTOTYPE(config_parse_bpf_foreign_program);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_socket_bind);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+8
View File
@@ -105,6 +105,8 @@ libcore_sources = '''
slice.h
smack-setup.c
smack-setup.h
socket-bind.c
socket-bind.h
socket.c
socket.h
swap.c
@@ -123,6 +125,11 @@ libcore_sources = '''
unit.h
'''.split()
subdir('bpf/socket_bind')
if conf.get('BPF_FRAMEWORK') == 1
libcore_sources += [socket_bind_skel_h]
endif
load_fragment_gperf_gperf = custom_target(
'load-fragment-gperf.gperf',
input : 'load-fragment-gperf.gperf.m4',
@@ -152,6 +159,7 @@ libcore = static_library(
include_directories : includes,
dependencies : [versiondep,
threads,
libbpf,
librt,
libseccomp,
libpam,
+238
View File
@@ -0,0 +1,238 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#if BPF_FRAMEWORK
#include <bpf/bpf.h>
#endif
#include "fd-util.h"
#include "socket-bind.h"
#if BPF_FRAMEWORK
/* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */
#include "bpf-link.h"
#include "bpf/socket_bind/socket-bind.skel.h"
#include "bpf/socket_bind/socket-bind-api.bpf.h"
static struct socket_bind_bpf *socket_bind_bpf_free(struct socket_bind_bpf *obj) {
/* socket_bind_bpf__destroy handles object == NULL case */
(void) socket_bind_bpf__destroy(obj);
return NULL;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(struct socket_bind_bpf *, socket_bind_bpf_free);
static int update_rules_map(
int map_fd, CGroupSocketBindItem *head) {
CGroupSocketBindItem *item;
uint32_t i = 0;
assert(map_fd >= 0);
LIST_FOREACH(socket_bind_items, item, head) {
const uint32_t key = i++;
struct socket_bind_rule val = {
.address_family = (uint32_t) item->address_family,
.nr_ports = item->nr_ports,
.port_min = item->port_min,
};
if (bpf_map_update_elem(map_fd, &key, &val, BPF_ANY) != 0)
return -errno;
}
return 0;
}
static int prepare_socket_bind_bpf(
Unit *u, CGroupSocketBindItem *allow, CGroupSocketBindItem *deny, struct socket_bind_bpf **ret_obj) {
_cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = 0;
uint32_t allow_count = 0, deny_count = 0;
int allow_map_fd, deny_map_fd, r;
CGroupSocketBindItem *item;
assert(ret_obj);
LIST_FOREACH(socket_bind_items, item, allow)
allow_count++;
LIST_FOREACH(socket_bind_items, item, deny)
deny_count++;
if (allow_count > SOCKET_BIND_MAX_RULES)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL),
"Maximum number of socket bind rules=%u is exceeded", SOCKET_BIND_MAX_RULES);
if (deny_count > SOCKET_BIND_MAX_RULES)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL),
"Maximum number of socket bind rules=%u is exceeded", SOCKET_BIND_MAX_RULES);
obj = socket_bind_bpf__open();
if (!obj)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOMEM), "Failed to open BPF object");
if (bpf_map__resize(obj->maps.sd_bind_allow, MAX(allow_count, 1u)) != 0)
return log_unit_error_errno(u, errno,
"Failed to resize BPF map '%s': %m", bpf_map__name(obj->maps.sd_bind_allow));
if (bpf_map__resize(obj->maps.sd_bind_deny, MAX(deny_count, 1u)) != 0)
return log_unit_error_errno(u, errno,
"Failed to resize BPF map '%s': %m", bpf_map__name(obj->maps.sd_bind_deny));
if (socket_bind_bpf__load(obj) != 0)
return log_unit_error_errno(u, errno, "Failed to load BPF object");
allow_map_fd = bpf_map__fd(obj->maps.sd_bind_allow);
assert(allow_map_fd >= 0);
r = update_rules_map(allow_map_fd, allow);
if (r < 0)
return log_unit_error_errno(
u, r, "Failed to put socket bind allow rules into BPF map '%s'",
bpf_map__name(obj->maps.sd_bind_allow));
deny_map_fd = bpf_map__fd(obj->maps.sd_bind_deny);
assert(deny_map_fd >= 0);
r = update_rules_map(deny_map_fd, deny);
if (r < 0)
return log_unit_error_errno(
u, r, "Failed to put socket bind deny rules into BPF map '%s'",
bpf_map__name(obj->maps.sd_bind_deny));
*ret_obj = TAKE_PTR(obj);
return 0;
}
int socket_bind_supported(void) {
_cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL;
int r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
if (r < 0)
return log_error_errno(r, "Can't determine whether the unified hierarchy is used: %m");
if (r == 0) {
log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Not running with unified cgroup hierarchy, BPF is not supported");
return 0;
}
if (!bpf_probe_prog_type(BPF_PROG_TYPE_CGROUP_SOCK_ADDR, /*ifindex=*/0)) {
log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"BPF program type cgroup_sock_addr is not supported");
return 0;
}
r = prepare_socket_bind_bpf(/*unit=*/NULL, /*allow_rules=*/NULL, /*deny_rules=*/NULL, &obj);
if (r < 0) {
log_debug_errno(r, "BPF based socket_bind is not supported: %m");
return 0;
}
return can_link_bpf_program(obj->progs.sd_bind4);
}
int socket_bind_add_initial_link_fd(Unit *u, int fd) {
int r;
assert(u);
if (!u->initial_socket_bind_link_fds) {
u->initial_socket_bind_link_fds = fdset_new();
if (!u->initial_socket_bind_link_fds)
return log_oom();
}
r = fdset_put(u->initial_socket_bind_link_fds, fd);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to put socket-bind BPF link fd %d to initial fdset", fd);
return 0;
}
static int socket_bind_install_impl(Unit *u) {
_cleanup_(bpf_link_freep) struct bpf_link *ipv4 = NULL, *ipv6 = NULL;
_cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL;
_cleanup_free_ char *cgroup_path = NULL;
_cleanup_close_ int cgroup_fd = -1;
CGroupContext *cc;
int r;
cc = unit_get_cgroup_context(u);
if (!cc)
return 0;
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to get cgroup path: %m");
if (!cc->socket_bind_allow && !cc->socket_bind_deny)
return 0;
r = prepare_socket_bind_bpf(u, cc->socket_bind_allow, cc->socket_bind_deny, &obj);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to load BPF object: %m");
cgroup_fd = open(cgroup_path, O_RDONLY | O_CLOEXEC, 0);
if (cgroup_fd < 0)
return log_unit_error_errno(
u, errno, "Failed to open cgroup=%s for reading", cgroup_path);
ipv4 = bpf_program__attach_cgroup(obj->progs.sd_bind4, cgroup_fd);
r = libbpf_get_error(ipv4);
if (r != 0)
return log_unit_error_errno(u, r, "Failed to link '%s' cgroup-bpf program",
bpf_program__name(obj->progs.sd_bind4));
ipv6 = bpf_program__attach_cgroup(obj->progs.sd_bind6, cgroup_fd);
r = libbpf_get_error(ipv6);
if (r != 0)
return log_unit_error_errno(u, r, "Failed to link '%s' cgroup-bpf program",
bpf_program__name(obj->progs.sd_bind6));
u->ipv4_socket_bind_link = TAKE_PTR(ipv4);
u->ipv6_socket_bind_link = TAKE_PTR(ipv6);
return 0;
}
int socket_bind_install(Unit *u) {
int r = socket_bind_install_impl(u);
if (r == -ENOMEM)
return r;
fdset_close(u->initial_socket_bind_link_fds);
return r;
}
int serialize_socket_bind(Unit *u, FILE *f, FDSet *fds) {
int r;
assert(u);
r = serialize_bpf_link(f, fds, "ipv4-socket-bind-bpf-link", u->ipv4_socket_bind_link);
if (r < 0)
return r;
return serialize_bpf_link(f, fds, "ipv6-socket-bind-bpf-link", u->ipv6_socket_bind_link);
}
#else /* ! BPF_FRAMEWORK */
int socket_bind_supported(void) {
return 0;
}
int socket_bind_add_initial_link_fd(Unit *u, int fd) {
return 0;
}
int socket_bind_install(Unit *u) {
log_unit_debug(u, "Failed to install socket bind: BPF framework is not supported");
return 0;
}
int serialize_socket_bind(Unit *u, FILE *f, FDSet *fds) {
return 0;
}
#endif
+15
View File
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "fdset.h"
#include "unit.h"
int socket_bind_supported(void);
/* Add BPF link fd created before daemon-reload or daemon-reexec.
* FDs will be closed at the end of socket_bind_install. */
int socket_bind_add_initial_link_fd(Unit *u, int fd);
int socket_bind_install(Unit *u);
int serialize_socket_bind(Unit *u, FILE *f, FDSet *fds);
+20
View File
@@ -7,6 +7,7 @@
#include "format-util.h"
#include "parse-util.h"
#include "serialize.h"
#include "socket-bind.h"
#include "string-table.h"
#include "unit-serialize.h"
#include "user-util.h"
@@ -151,6 +152,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
(void) serialize_cgroup_mask(f, "cgroup-enabled-mask", u->cgroup_enabled_mask);
(void) serialize_cgroup_mask(f, "cgroup-invalidated-mask", u->cgroup_invalidated_mask);
(void) serialize_socket_bind(u, f, fds);
if (uid_is_valid(u->ref_uid))
(void) serialize_item_format(f, "ref-uid", UID_FMT, u->ref_uid);
if (gid_is_valid(u->ref_gid))
@@ -362,6 +365,23 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
else if (MATCH_DESERIALIZE_IMMEDIATE("cgroup-invalidated-mask", l, v, cg_mask_from_string, u->cgroup_invalidated_mask))
continue;
else if (STR_IN_SET(l, "ipv4-socket-bind-bpf-link-fd", "ipv6-socket-bind-bpf-link-fd")) {
int fd;
if (safe_atoi(v, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse %s value: %s, ignoring.", l, v);
else {
if (fdset_remove(fds, fd) < 0) {
log_unit_debug(u, "Failed to remove %s value=%d from fdset", l, fd);
continue;
}
(void) socket_bind_add_initial_link_fd(u, fd);
}
continue;
}
else if (streq(l, "ref-uid")) {
uid_t uid;
+11
View File
@@ -41,6 +41,7 @@
#include "rm-rf.h"
#include "set.h"
#include "signal-util.h"
#include "socket-bind.h"
#include "sparse-endian.h"
#include "special.h"
#include "specifier.h"
@@ -56,6 +57,9 @@
#include "unit.h"
#include "user-util.h"
#include "virt.h"
#if BPF_FRAMEWORK
#include "bpf-link.h"
#endif
/* Thresholds for logging at INFO level about resource consumption */
#define MENTIONWORTHY_CPU_NSEC (1 * NSEC_PER_SEC)
@@ -663,6 +667,13 @@ Unit* unit_free(Unit *u) {
if (u->on_console)
manager_unref_console(u->manager);
fdset_free(u->initial_socket_bind_link_fds);
#if BPF_FRAMEWORK
bpf_link_free(u->ipv4_socket_bind_link);
bpf_link_free(u->ipv6_socket_bind_link);
#endif
unit_release_cgroup(u);
if (!MANAGER_IS_RELOADING(u->manager))
+9
View File
@@ -309,6 +309,15 @@ typedef struct Unit {
* attached to unit cgroup by provided program fd and attach type. */
Hashmap *bpf_foreign_by_key;
FDSet *initial_socket_bind_link_fds;
#if BPF_FRAMEWORK
/* BPF links to BPF programs attached to cgroup/bind{4|6} hooks and
* responsible for allowing or denying a unit to bind(2) to a socket
* address. */
struct bpf_link *ipv4_socket_bind_link;
struct bpf_link *ipv6_socket_bind_link;
#endif
uint64_t ip_accounting_extra[_CGROUP_IP_ACCOUNTING_METRIC_MAX];
/* Low-priority event source which is used to remove watched PIDs that have gone away, and subscribe to any new
+38
View File
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bpf-link.h"
#include "serialize.h"
bool can_link_bpf_program(struct bpf_program *prog) {
_cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
assert(prog);
/* Pass invalid cgroup fd intentionally. */
link = bpf_program__attach_cgroup(prog, /*cgroup_fd=*/-1);
/* EBADF indicates that bpf_link is supported by kernel. */
return libbpf_get_error(link) == -EBADF;
}
int serialize_bpf_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link) {
int fd;
assert(key);
if (!link)
return -ENOENT;
if (libbpf_get_error(link) != 0)
return -EINVAL;
fd = bpf_link__fd(link);
return serialize_fd(f, fds, key, fd);
}
struct bpf_link *bpf_link_free(struct bpf_link *link) {
/* bpf_link__destroy handles link == NULL case */
(void) bpf_link__destroy(link);
return NULL;
}
+16
View File
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <bpf/libbpf.h>
#include <stdio.h>
#include "fdset.h"
#include "macro.h"
bool can_link_bpf_program(struct bpf_program *prog);
int serialize_bpf_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link);
struct bpf_link *bpf_link_free(struct bpf_link *p);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct bpf_link *, bpf_link_free);
+51
View File
@@ -862,6 +862,57 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
return 1;
}
if (STR_IN_SET(field, "SocketBindAllow",
"SocketBindDeny")) {
if (isempty(eq))
r = sd_bus_message_append(m, "(sv)", field, "a(iqq)", 0);
else {
const char *address_family, *user_port;
_cleanup_free_ char *word = NULL;
int family = AF_UNSPEC;
r = extract_first_word(&eq, &word, ":", 0);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to parse %s: %m", field);
address_family = eq ? word : NULL;
if (address_family) {
if (!STR_IN_SET(address_family, "IPv4", "IPv6"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Only IPv4 and IPv6 protocols are supported");
if (streq(address_family, "IPv4"))
family = AF_INET;
else
family = AF_INET6;
}
user_port = eq ? eq : word;
if (streq(user_port, "any")) {
r = sd_bus_message_append(m, "(sv)", field, "a(iqq)", 1, family, 0, 0);
if (r < 0)
return bus_log_create_error(r);
} else {
uint16_t port_min, port_max;
r = parse_ip_port_range(user_port, &port_min, &port_max);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Invalid port or port range: %s", user_port);
r = sd_bus_message_append(
m, "(sv)", field, "a(iqq)", 1, family, port_max - port_min + 1, port_min);
}
}
if (r < 0)
return bus_log_create_error(r);
return 1;
}
return 0;
}
+8
View File
@@ -319,6 +319,13 @@ if conf.get('HAVE_LIBIPTC') == 1
shared_sources += files('firewall-util-iptables.c')
endif
if conf.get('HAVE_LIBBPF') == 1
shared_sources += files('''
bpf-link.c
bpf-link.h
'''.split())
endif
if conf.get('HAVE_KMOD') == 1
shared_sources += files('module-util.c')
endif
@@ -378,6 +385,7 @@ libshared_name = 'systemd-shared-@0@'.format(meson.project_version())
libshared_deps = [threads,
libacl,
libblkid,
libbpf,
libcap,
libcrypt,
libgcrypt,
+28
View File
@@ -1712,6 +1712,34 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
if (r < 0)
return bus_log_parse_error(r);
return 1;
} else if (STR_IN_SET(name, "SocketBindAllow", "SocketBindDeny")) {
uint16_t nr_ports, port_min;
const char *family;
int af;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(iqq)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(m, "(iqq)", &af, &nr_ports, &port_min)) > 0) {
family = af == AF_INET ? "IPv4:" : af == AF_INET6 ? "IPv6:" : "";
if (nr_ports == 0)
bus_print_property_valuef(name, expected_value, value, "%sany", family);
else if (nr_ports == 1)
bus_print_property_valuef(
name, expected_value, value, "%s%hu", family, port_min);
else
bus_print_property_valuef(
name, expected_value, value, "%s%hu-%hu", family, port_min,
(uint16_t) (port_min + nr_ports - 1));
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return bus_log_parse_error(r);
return 1;
}
+9
View File
@@ -611,3 +611,12 @@ tests += [
[libudev],
[threads]],
]
tests += [
[['src/test/test-socket-bind.c'],
[libcore,
libshared],
[libbpf],
core_includes,
'BPF_FRAMEWORK'],
]
+1 -1
View File
@@ -140,7 +140,7 @@ static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) {
static void test_cg_mask_to_string(void) {
test_cg_mask_to_string_one(0, NULL);
test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices bpf-foreign");
test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices bpf-foreign bpf-socket-bind");
test_cg_mask_to_string_one(CGROUP_MASK_CPU, "cpu");
test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct");
test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset");
+151
View File
@@ -0,0 +1,151 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "load-fragment.h"
#include "manager.h"
#include "process-util.h"
#include "rlimit-util.h"
#include "rm-rf.h"
#include "service.h"
#include "socket-bind.h"
#include "strv.h"
#include "tests.h"
#include "unit.h"
#include "virt.h"
static int find_netcat_executable(char **ret_path) {
char **candidates = STRV_MAKE("ncat", "nc", "netcat"), **c;
int r = 0;
STRV_FOREACH(c, candidates) {
r = find_executable(*c, ret_path);
if (r == 0)
break;
}
return r;
}
static int test_socket_bind(
Manager *m,
const char *unit_name,
const char *netcat_path,
const char *port,
char **allow_rules,
char **deny_rules) {
_cleanup_free_ char *exec_start = NULL;
_cleanup_(unit_freep) Unit *u = NULL;
CGroupSocketBindItem *bi;
CGroupContext *cc = NULL;
char **rule;
int cld_code, r;
assert_se(u = unit_new(m, sizeof(Service)));
assert_se(unit_add_name(u, unit_name) == 0);
assert_se(cc = unit_get_cgroup_context(u));
STRV_FOREACH(rule, allow_rules) {
r = config_parse_cgroup_socket_bind(
u->id, "filename", 1, "Service", 1, "SocketBindAllow", 0,
*rule, &cc->socket_bind_allow, u);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to parse SocketBindAllow: %m");
}
fprintf(stderr, "SocketBindAllow:");
LIST_FOREACH(socket_bind_items, bi, cc->socket_bind_allow)
cgroup_context_dump_socket_bind_item(bi, stderr);
fputc('\n', stderr);
STRV_FOREACH(rule, deny_rules) {
r = config_parse_cgroup_socket_bind(
u->id, "filename", 1, "Service", 1, "SocketBindDeny", 0,
*rule, &cc->socket_bind_deny, u);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to parse SocketBindDeny: %m");
}
fprintf(stderr, "SocketBindDeny:");
LIST_FOREACH(socket_bind_items, bi, cc->socket_bind_deny)
cgroup_context_dump_socket_bind_item(bi, stderr);
fputc('\n', stderr);
exec_start = strjoin("-timeout --preserve-status -sSIGTERM 1s ", netcat_path, " -l ", port, " -vv");
assert_se(exec_start != NULL);
r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart",
SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u);
if (r < 0)
return log_error_errno(r, "Failed to parse ExecStart");
SERVICE(u)->type = SERVICE_ONESHOT;
u->load_state = UNIT_LOADED;
r = unit_start(u);
if (r < 0)
return log_error_errno(r, "Unit start failed %m");
while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) {
r = sd_event_run(m->event, UINT64_MAX);
if (r < 0)
return log_error_errno(errno, "Event run failed %m");
}
cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code;
if (cld_code != CLD_EXITED)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code));
if (SERVICE(u)->state != SERVICE_DEAD)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead");
return 0;
}
int main(int argc, char *argv[]) {
_cleanup_free_ char *unit_dir = NULL, *netcat_path = NULL;
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
_cleanup_(manager_freep) Manager *m = NULL;
struct rlimit rl;
int r;
test_setup_logging(LOG_DEBUG);
if (detect_container() > 0)
return log_tests_skipped("test-bpf fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666");
if (getuid() != 0)
return log_tests_skipped("not running as root");
assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0);
rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE);
(void) setrlimit_closest(RLIMIT_MEMLOCK, &rl);
if (!can_memlock())
return log_tests_skipped("Can't use mlock(), skipping.");
r = socket_bind_supported();
if (r <= 0)
return log_tests_skipped("socket-bind is not supported, skipping.");
if (find_netcat_executable(&netcat_path) != 0)
return log_tests_skipped("Can not find netcat executable, skipping.");
r = enter_cgroup_subroot(NULL);
if (r == -ENOMEDIUM)
return log_tests_skipped("cgroupfs not available");
assert_se(get_testdata_dir("units", &unit_dir) >= 0);
assert_se(set_unit_path(unit_dir) >= 0);
assert_se(runtime_dir = setup_fake_runtime_dir());
assert_se(manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0);
assert_se(manager_startup(m, NULL, NULL) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("2000"), STRV_MAKE("any")) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("IPv6:2001-2002"), STRV_MAKE("any")) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("IPv4:6666", "6667"), STRV_MAKE("any")) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("6667", "6668", ""), STRV_MAKE("any")) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "7777", STRV_MAKE_EMPTY, STRV_MAKE_EMPTY) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "8888", STRV_MAKE("any"), STRV_MAKE("any")) >= 0);
return 0;
}