1
0
mirror of https://github.com/systemd/systemd synced 2026-03-13 16:44:48 +01:00

Compare commits

..

18 Commits

Author SHA1 Message Date
Franck Bui
45b1fc3a88 system-conf: drop reference to ShutdownWatchdogUsec=
Commit 65224c1d0e50667a87c2c4f840c49d4918718f80 renamed ShutdownWatchdogUsec
into RebootWatchdogUsec but left a reference of ShutdownWatchdogUsec in
system.conf.
2021-04-27 12:38:57 +01:00
Zbigniew Jędrzejewski-Szmek
862e01d70f
Merge pull request #17655 from wat-ze-hex/bpf-build-rule
Introduce SocketBind{Allow|Deny}= properties powered by source compiled BPF
2021-04-27 12:52:30 +02:00
Julia Kartseva
7dc1707aab fuzz: add SocketBind{Allow|Deny}= directive 2021-04-26 16:26:28 -07:00
Julia Kartseva
c308025875 readme: update README with requirements for bpf 2021-04-26 16:26:28 -07:00
Julia Kartseva
6359811021 man: add SocketBind{Allow|Deny}= documentation 2021-04-26 16:26:28 -07:00
Julia Kartseva
18ef723ef6 systemctl: show SocketBind{Allow|Deny} properties 2021-04-26 16:26:28 -07:00
Julia Kartseva
dcf4781caf dbus: add dbus-cgroup for SocketBind{Allow|Deny}= 2021-04-26 16:26:28 -07:00
Julia Kartseva
28b76fc82a tests: add test program for SocketBind{Allow|Deny}=
Verify that service exited correctly if valid ports are passed to
SocketBind{Allow|Deny}=
Use `ncat` program starting a listening service binding to a specified
port, e.g.
"timeout --preserve-status -sSIGTERM 1s /bin/nc -l -p ${port} -vv"
2021-04-26 16:26:28 -07:00
Julia Kartseva
3d027d4d60 shared, bpf: add bpf link serialization
core: serialize socket_bind bpf links
2021-04-26 16:26:24 -07:00
Julia Kartseva
8dd210ab66 core: add SocketBind{Allow|Deny} fragment parser 2021-04-26 16:21:59 -07:00
Julia Kartseva
a8e5eb1788 core: add socket-bind cgroup mask harness
Standard cgroup harness for bpf feature.
2021-04-26 16:21:59 -07:00
Julia Kartseva
91ce91c76c core, bpf: add socket-bind feature to unit
Add supported and install unit interface for socket-bind feature.

supported verifies that
- unified cgroup hierarchy (cgroup v2) is used
- BPF_FRAMEWORK (libbpf + clang + llvm + bpftool) was available in
compile time
- kernel supports BPF_PROG_TYPE_CGROUP_SOCK_ADDR
- bpf programs can be loaded into kernel
- bpf link can be used

install:
- load bpf_object from bpf skeleton
- resize rules map to fit socket_bind_allow and socket_bind deny rules
from cgroup context
- populate cgroup-bpf maps with rules
- get bpf programs from bpf skeleton
- attach programs to unit cgroup using bpf link
- save bpf link in the unit
2021-04-26 16:21:59 -07:00
Julia Kartseva
b18e9fc167 cgroup: add socket-bind to cgroup context 2021-04-26 16:21:59 -07:00
Julia Kartseva
43b3f0fb00 shared, bpf: add bpf link helpers
add can_link_bpf_program and bpf_link_free helpers.
2021-04-26 16:21:59 -07:00
Julia Kartseva
09fc220c52 meson, bpf: add build rule for socket-bind program 2021-04-26 16:21:59 -07:00
Julia Kartseva
7d861e1263 meson, bpf: add HAVE_LIBBPF, BPF_FRAMEWORK options
* Add `bpf-framework` feature gate with 'auto', 'true' and 'false' choices
* Add libbpf [0] dependency
* Search for clang llvm-strip and bpftool binaries in compile time to
generate bpf skeleton.

For libbpf [0], make 0.2.0 [1] the minimum required version.
If libbpf is satisfied, set HAVE_LIBBPF config option to 1.

If `bpf-framework` feature gate is set to 'auto', means that whether
bpf feature is enabled or now is defined by the presence of all of
libbpf, clang, llvm and bpftool in build
environment.
With 'auto' all dependencies are optional.
If the gate is set to `true`, make all of the libbpf, clang and llvm
dependencies mandatory.
If it's set to `false`, set `BPF_FRAMEWORK` to false and make libbpf
dependency optional.

libbpf dependency is dynamic followed by the common pattern in systemd.

meson, bpf: add build rule for socket_bind program
2021-04-26 16:20:58 -07:00
Julia Kartseva
cf4f9a57f2 bpf: add build script for bpf programs
Add a build script to compile bpf source code. A program in restricted
C is compiled into an object file. Object file is converted to BPF
skeleton [0] header file.
If build with custom meson build rule, the target header will reside in
build/ directory (not in source tree), e.g the path for socket_bind:
`build/src/core/bpf/socket_bind/socket-bind.skel.h`

Script runs the phases:
* clang to generate *.o from restricted C
* llvm-strip to remove useless DWARF info
* bpf skeleton generation with bpftool
These phases are logged to stderr for debug purposes.

To include BTF debug information, -g option is passed to clang.

[0] https://lwn.net/Articles/806911/
2021-04-26 16:07:41 -07:00
Julia Kartseva
58a33faf80 bpf: add socket-bind BPF program code sources
Introduce BPF program compiled from BPF source code in
restricted C - socket-bind.
It addresses feature request [0].

The goal is to allow systemd services to bind(2) only to a predefined set
of ports. This prevents assigning socket address with unallowed port
to a socket and creating servers listening on that port.

This compliments firewalling feature presenting in systemd:
whereas cgroup/{egress|ingress} hooks act on packets, this doesn't
protect from untrusted service or payload hijacking an important port.

While ports in 0-1023 range are restricted to root only, 1024-65535
range is not protected by any mean.

Performance is another aspect of socket_bind feature since per-packet
cost can be eliminated for some port-based filtering policies.

The feature is implemented with cgroup/bind{4|6} hooks [1].
In contrast to the present systemd approach using raw bpf instructions,
this program is compiled from sources. Stretch goal is to
make bpf ecosystem in systemd more friendly for developer and to clear
path for more BPF programs.

[0] https://github.com/systemd/systemd/pull/13496#issuecomment-570573085
[1] https://www.spinics.net/lists/netdev/msg489054.html
2021-04-26 16:07:41 -07:00
38 changed files with 1364 additions and 5 deletions

21
README
View File

@ -35,6 +35,10 @@ LICENSE:
REQUIREMENTS:
Linux kernel >= 3.13
Linux kernel >= 4.2 for unified cgroup hierarchy support
Linux kernel >= 4.10 for cgroup-bpf egress and ingress hooks
Linux kernel >= 4.15 for cgroup-bpf device hook
Linux kernel >= 4.17 for cgroup-bpf socket address hooks
Linux kernel >= 5.3 for bounded-loops in BPF program
Linux kernel >= 5.4 for signed Verity images support
Kernel Config Options:
@ -95,8 +99,20 @@ REQUIREMENTS:
Required for CPUQuota= in resource control unit settings
CONFIG_CFS_BANDWIDTH
Required for IPAddressDeny= and IPAddressAllow= in resource control
Required for IPAddressDeny=, IPAddressAllow=, IPIngressFilterPath=,
IPEgressFilterPath= in resource control unit settings
unit settings
CONFIG_BPF
CONFIG_BPF_SYSCALL
CONFIG_BPF_JIT
CONFIG_HAVE_EBPF_JIT
CONFIG_CGROUP_BPF
Required for SocketBind{Allow|Deny}= in resource control unit settings
CONFIG_BPF
CONFIG_BPF_SYSCALL
CONFIG_BPF_JIT
CONFIG_HAVE_EBPF_JIT
CONFIG_CGROUP_BPF
For UEFI systems:
@ -154,6 +170,7 @@ REQUIREMENTS:
libcryptsetup (optional), >= 2.3.0 required for signed Verity images support
libaudit (optional)
libacl (optional)
libbpf >= 0.2.0 (optional)
libfdisk >= 2.33 (from util-linux) (optional)
libselinux (optional)
liblzma (optional)
@ -178,6 +195,8 @@ REQUIREMENTS:
meson >= 0.46 (>= 0.49 is required to build position-independent executables)
ninja
gcc, awk, sed, grep, m4, and similar tools
clang >= 10.0, llvm >= 10.0 (optional, required to build BPF programs
from source code in C)
During runtime, you need the following additional
dependencies:

View File

@ -2482,6 +2482,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly s ManagedOOMPreference = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(ss) BPFProgram = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindAllow = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindDeny = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@ -3018,6 +3022,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property BPFProgram is not documented!-->
<!--property SocketBindAllow is not documented!-->
<!--property SocketBindDeny is not documented!-->
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@ -3578,6 +3586,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindAllow"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindDeny"/>
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@ -4265,6 +4277,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
readonly s ManagedOOMPreference = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(ss) BPFProgram = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindAllow = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindDeny = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@ -4827,6 +4843,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property BPFProgram is not documented!-->
<!--property SocketBindAllow is not documented!-->
<!--property SocketBindDeny is not documented!-->
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@ -5383,6 +5403,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindAllow"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindDeny"/>
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@ -5972,6 +5996,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
readonly s ManagedOOMPreference = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(ss) BPFProgram = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindAllow = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindDeny = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@ -6462,6 +6490,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property BPFProgram is not documented!-->
<!--property SocketBindAllow is not documented!-->
<!--property SocketBindDeny is not documented!-->
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@ -6936,6 +6968,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindAllow"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindDeny"/>
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@ -7646,6 +7682,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
readonly s ManagedOOMPreference = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(ss) BPFProgram = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindAllow = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindDeny = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as Environment = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@ -8122,6 +8162,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property BPFProgram is not documented!-->
<!--property SocketBindAllow is not documented!-->
<!--property SocketBindDeny is not documented!-->
<!--property EnvironmentFiles is not documented!-->
<!--property PassEnvironment is not documented!-->
@ -8582,6 +8626,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindAllow"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindDeny"/>
<variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
<variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
@ -9145,6 +9193,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
readonly s ManagedOOMPreference = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(ss) BPFProgram = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindAllow = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindDeny = [...];
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
@ -9285,6 +9337,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<!--property BPFProgram is not documented!-->
<!--property SocketBindAllow is not documented!-->
<!--property SocketBindDeny is not documented!-->
<!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Unit"/>
@ -9429,6 +9485,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindAllow"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindDeny"/>
<!--End of Autogenerated section-->
<refsect2>
@ -9592,6 +9652,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
readonly s ManagedOOMPreference = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(ss) BPFProgram = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindAllow = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly a(iqq) SocketBindDeny = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@ -9748,6 +9812,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<!--property BPFProgram is not documented!-->
<!--property SocketBindAllow is not documented!-->
<!--property SocketBindDeny is not documented!-->
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
@ -9918,6 +9986,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindAllow"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketBindDeny"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>

View File

@ -762,6 +762,76 @@ BPFProgram=bind6:/sys/fs/bpf/sock-addr-hook
</listitem>
</varlistentry>
<varlistentry>
<term><varname>SocketBindAllow=<replaceable>bind-rule</replaceable></varname></term>
<term><varname>SocketBindDeny=<replaceable>bind-rule</replaceable></varname></term>
<listitem>
<para>Allow or deny binding a socket address to a socket by matching it with the <replaceable>bind-rule</replaceable> and
applying a corresponding action if there is a match.</para>
<para><replaceable>bind-rule</replaceable> describes socket properties such as <replaceable>address-family</replaceable>
and <replaceable>ip-ports</replaceable>.</para>
<para><replaceable>bind-rule</replaceable> := [<replaceable>address-family</replaceable><constant>:</constant>]<replaceable>ip-ports</replaceable></para>
<para><replaceable>address-family</replaceable> := { <constant>IPv4</constant> | <constant>IPv6</constant> }</para>
<para><replaceable>ip-ports</replaceable> := { <replaceable>ip-port</replaceable> | <replaceable>ip-port-range</replaceable> |
<constant>any</constant> }</para>
<para>An optional <replaceable>address-family</replaceable> expects <constant>IPv4</constant> or <constant>IPv6</constant> values.
If not specified, a rule will be matched for both IPv4 and IPv6 addresses and applied depending on other socket fields, e.g.
<replaceable>ip-port</replaceable>.</para>
<para><replaceable>ip-port</replaceable> value must lie within 1…65535 interval inclusively, i.e.
dynamic port <constant>0</constant> is not allowed. A range of sequential ports is described by
<replaceable>ip-port-range</replaceable> := <replaceable>ip-port-low</replaceable><constant>-</constant><replaceable>ip-port-high</replaceable>,
where <replaceable>ip-port-low</replaceable> is smaller than or equal to <replaceable>ip-port-high</replaceable>
and both are within 1…65535 inclusively. A special value <constant>any</constant>
should be used to apply a rule to any port with a positive value.</para>
<para>To allow multiple rules assign <varname>SocketBindAllow=</varname> or <varname>SocketBindDeny=</varname> multiple times.
To clear the existing assignments pass an empty <varname>SocketBindAllow=</varname> or <varname>SocketBindDeny=</varname>
assignment.</para>
<para>For each of <varname>SocketBindAllow=</varname> and <varname>SocketBindDeny=</varname>, maximum allowed number of assignments is
<constant>128</constant>.</para>
<itemizedlist>
<listitem><para>Binding to a socket is allowed when a socket address matches an entry in the
<varname>SocketBindAllow=</varname> list.</para></listitem>
<listitem><para>Otherwise, binding is denied when the socket address matches an entry in the
<varname>SocketBindDeny=</varname> list.</para></listitem>
<listitem><para>Otherwise, binding is allowed.</para></listitem>
</itemizedlist>
<para>The feature is implemented with <constant>cgroup/bind4</constant> and <constant>cgroup/bind6</constant> cgroup-bpf hooks.</para>
<para>Examples:<programlisting>
# Allow binding IPv6 socket addresses with a port greater than or equal to 10000.
[Service]
SocketBindAllow=IPv6:10000-65535
SocketBindDeny=any
# Allow binding IPv4 and IPv6 socket addresses with 1234 and 4321 ports.
[Service]
SocketBindAllow=1234
SocketBindAllow=4321
SocketBindDeny=any
# Deny binding IPv6 socket addresses.
[Service]
SocketBindDeny=IPv6:any
# Deny binding IPv4 and IPv6 socket addresses.
[Service]
SocketBindDeny=any
</programlisting></para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>DeviceAllow=</varname></term>

View File

@ -937,6 +937,25 @@ if not libcap.found()
libcap = cc.find_library('cap')
endif
want_bpf_framework = get_option('bpf-framework')
bpf_framework_required = want_bpf_framework == 'true'
libbpf = dependency('libbpf', required : bpf_framework_required, version : '>= 0.2')
conf.set10('HAVE_LIBBPF', libbpf.found())
if want_bpf_framework == 'false'
conf.set10('BPF_FRAMEWORK', 0)
else
clang = find_program('clang', required : bpf_framework_required)
llvm_strip = find_program('llvm-strip', required : bpf_framework_required)
bpftool = find_program('bpftool', required : bpf_framework_required)
bpf_arches = ['x86_64']
deps_found = libbpf.found() and clang.found() and llvm_strip.found() and bpftool.found()
# Can build BPF program from source code in restricted C
conf.set10('BPF_FRAMEWORK',
bpf_arches.contains(host_machine.cpu_family()) and deps_found)
endif
libmount = dependency('mount',
version : fuzzer_build ? '>= 0' : '>= 2.30')
@ -1604,6 +1623,7 @@ conf.set10('ENABLE_EFI', have)
############################################################
build_bpf_skel_py = find_program('tools/build-bpf-skel.py')
generate_gperfs = find_program('tools/generate-gperfs.py')
make_autosuspend_rules_py = find_program('tools/make-autosuspend-rules.py')
make_directive_index_py = find_program('tools/make-directive-index.py')
@ -1696,6 +1716,7 @@ install_libsystemd_static = static_library(
libxz,
libzstd,
liblz4,
libbpf,
libcap,
libblkid,
libmount,
@ -3766,6 +3787,7 @@ foreach tuple : [
['elfutils'],
['gcrypt'],
['gnutls'],
['libbpf'],
['libcryptsetup'],
['libcurl'],
['libfdisk'],
@ -3792,6 +3814,7 @@ foreach tuple : [
# components
['backlight'],
['binfmt'],
['bpf-framework', conf.get('BPF_FRAMEWORK') == 1],
['coredump'],
['environment.d'],
['efi'],

View File

@ -403,3 +403,6 @@ option('kernel-install', type: 'boolean', value: 'true',
description : 'install kernel-install and associated files')
option('analyze', type: 'boolean', value: 'true',
description : 'install systemd-analyze')
option('bpf-framework', type : 'combo', choices : ['auto', 'true', 'false'],
description: 'build BPF programs from source code in restricted C')

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

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;

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

View File

@ -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

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

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

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

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

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)

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;

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

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
src/core/socket-bind.c Normal file
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
src/core/socket-bind.h Normal file
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);

View File

@ -31,7 +31,6 @@
#NUMAMask=
#RuntimeWatchdogSec=0
#RebootWatchdogSec=10min
#ShutdownWatchdogSec=10min
#KExecWatchdogSec=0
#WatchdogDevice=
#CapabilityBoundingSet=

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;

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

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
src/shared/bpf-link.c Normal file
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
src/shared/bpf-link.h Normal file
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);

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

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,

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

View File

@ -611,3 +611,12 @@ tests += [
[libudev],
[threads]],
]
tests += [
[['src/test/test-socket-bind.c'],
[libcore,
libshared],
[libbpf],
core_includes,
'BPF_FRAMEWORK'],
]

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
src/test/test-socket-bind.c Normal file
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;
}

View File

@ -162,6 +162,8 @@ SetCredential=
Slice=
SloppyOptions=
SmackProcessLabel=
SocketBindAllow=
SocketBindDeny=
StandardError=
StandardInput=
StandardInputData=

View File

@ -52,6 +52,8 @@ RuntimeMaxSec=
SendSIGHUP=
SendSIGKILL=
Slice=
SocketBindAllow=
SocketBindDeny=
StartupBlockIOWeight=
StartupCPUShares=
StartupCPUWeight=

View File

@ -88,6 +88,8 @@ RequiresMountsFor=
RequiresOverridable=
Requisite=
RequisiteOverridable=
SocketBindAllow=
SocketBindDeny=
SourcePath=
StartLimitAction=
StartLimitBurst=

View File

@ -45,6 +45,8 @@ MemoryMin=
MemorySwapMax=
NetClass=
Slice=
SocketBindAllow=
SocketBindDeny=
StartupBlockIOWeight=
StartupCPUShares=
StartupCPUWeight=

View File

@ -204,6 +204,8 @@ SmackLabel=
SmackLabelIPIn=
SmackLabelIPOut=
SmackProcessLabel=
SocketBindAllow=
SocketBindDeny=
SocketGroup=
SocketMode=
SocketProtocol=

View File

@ -158,6 +158,8 @@ SendSIGKILL=
SetCredential=
Slice=
SmackProcessLabel=
SocketBindAllow=
SocketBindDeny=
StandardError=
StandardInput=
StandardInputData=

123
tools/build-bpf-skel.py Executable file
View File

@ -0,0 +1,123 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1+
import argparse
import logging
import pathlib
import subprocess
import sys
def clang_arch_flag(arch):
return '-D__{}__'.format(arch)
def target_triplet():
gcc_exec = 'gcc'
try:
return subprocess.check_output([gcc_exec, '-dumpmachine'],
universal_newlines=True).strip()
except subprocess.CalledProcessError as e:
logging.error('Failed to get target triplet: {}'.format(e))
except FileNotFoundError:
logging.error('gcc not installed')
return None
def clang_compile(clang_exec, clang_flags, src_c, out_file, target_arch,
target_triplet):
clang_args = [clang_exec, *clang_flags, target_arch, '-I.']
if target_triplet:
clang_args += [
'-isystem',
'/usr/include/{}'.format(target_triplet)]
clang_args += [
'-idirafter',
'/usr/local/include',
'-idirafter',
'/usr/include']
clang_args += [src_c, '-o', out_file]
logging.debug('{}'.format(' '.join(clang_args)))
subprocess.check_call(clang_args)
def llvm_strip(llvm_strip_exec, in_file):
llvm_strip_args = [llvm_strip_exec, '-g', in_file]
logging.debug('Stripping useless DWARF info:')
logging.debug('{}'.format(' '.join(llvm_strip_args)))
subprocess.check_call(llvm_strip_args)
def gen_bpf_skeleton(bpftool_exec, in_file, out_fd):
bpftool_args = [bpftool_exec, 'g', 's', in_file]
logging.debug('Generating BPF skeleton:')
logging.debug('{}'.format(' '.join(bpftool_args)))
subprocess.check_call(bpftool_args, stdout=out_fd)
def bpf_build(args):
clang_flags = [
'-Wno-compare-distinct-pointer-types',
'-O2',
'-target',
'bpf',
'-g',
'-c',
]
clang_out_path = pathlib.Path(args.bpf_src_c).with_suffix('.o')
with clang_out_path.open(mode='w') as clang_out, \
open(args.bpf_skel_h, mode='w') as bpf_skel_h:
clang_compile(clang_exec=args.clang_exec,
clang_flags=clang_flags,
src_c=args.bpf_src_c,
out_file=clang_out.name,
target_arch=clang_arch_flag(args.arch),
target_triplet=target_triplet())
llvm_strip(llvm_strip_exec=args.llvm_strip_exec, in_file=clang_out.name)
gen_bpf_skeleton(bpftool_exec=args.bpftool_exec,
in_file=clang_out.name,
out_fd=bpf_skel_h)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'bpf_src_c',
help='Path to *.c source of BPF program in systemd source tree \
relative to the work directory')
parser.add_argument(
'bpf_skel_h',
help='Path to C header file')
parser.add_argument(
'--clang_exec',
help='Path to clang exec')
parser.add_argument(
'--llvm_strip_exec',
help='Path to llvm-strip exec')
parser.add_argument(
'--bpftool_exec',
help='Path to bpftool exec')
parser.add_argument(
'--arch',
help='Target CPU architecture',
default='x86_64')
args = parser.parse_args();
logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
bpf_build(args)