bpf: beef up bpf detection, check if BPF_F_ALLOW_MULTI is supported
authorLennart Poettering <lennart@poettering.net>
Fri, 16 Feb 2018 10:55:33 +0000 (11:55 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 21 Feb 2018 15:43:36 +0000 (16:43 +0100)
This improves the BPF/cgroup detection logic, and looks whether
BPF_ALLOW_MULTI is supported. This flag allows execution of multiple
BPF filters in a recursive fashion for a whole cgroup tree. It enables
us to properly report IP accounting for slice units, as well as
delegation of BPF support to units without breaking our own IP
accounting.

src/core/bpf-firewall.c
src/core/bpf-firewall.h
src/core/dbus-cgroup.c
src/core/ip-address-access.c
src/core/socket.c
src/test/test-bpf.c

index c5cda83..e717be2 100644 (file)
@@ -493,7 +493,7 @@ int bpf_firewall_compile(Unit *u) {
         r = bpf_firewall_supported();
         if (r < 0)
                 return r;
-        if (r == 0) {
+        if (r == BPF_FIREWALL_UNSUPPORTED) {
                 log_debug("BPF firewalling not supported on this manager, proceeding without.");
                 return -EOPNOTSUPP;
         }
@@ -555,7 +555,7 @@ int bpf_firewall_install(Unit *u) {
         r = bpf_firewall_supported();
         if (r < 0)
                 return r;
-        if (r == 0) {
+        if (r == BPF_FIREWALL_UNSUPPORTED) {
                 log_debug("BPF firewalling not supported on this manager, proceeding without.");
                 return -EOPNOTSUPP;
         }
@@ -667,7 +667,7 @@ int bpf_firewall_supported(void) {
 
         if (geteuid() != 0) {
                 log_debug("Not enough privileges, BPF firewalling is not supported.");
-                return supported = false;
+                return supported = BPF_FIREWALL_UNSUPPORTED;
         }
 
         r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
@@ -675,7 +675,7 @@ int bpf_firewall_supported(void) {
                 return log_error_errno(r, "Can't determine whether the unified hierarchy is used: %m");
         if (r == 0) {
                 log_debug("Not running with unified cgroups, BPF firewalling is not supported.");
-                return supported = false;
+                return supported = BPF_FIREWALL_UNSUPPORTED;
         }
 
         fd = bpf_map_new(BPF_MAP_TYPE_LPM_TRIE,
@@ -685,26 +685,26 @@ int bpf_firewall_supported(void) {
                          BPF_F_NO_PREALLOC);
         if (fd < 0) {
                 log_debug_errno(r, "Can't allocate BPF LPM TRIE map, BPF firewalling is not supported: %m");
-                return supported = false;
+                return supported = BPF_FIREWALL_UNSUPPORTED;
         }
 
         safe_close(fd);
 
         if (bpf_program_new(BPF_PROG_TYPE_CGROUP_SKB, &program) < 0) {
                 log_debug_errno(r, "Can't allocate CGROUP SKB BPF program, BPF firewalling is not supported: %m");
-                return supported = false;
+                return supported = BPF_FIREWALL_UNSUPPORTED;
         }
 
         r = bpf_program_add_instructions(program, trivial, ELEMENTSOF(trivial));
         if (r < 0) {
                 log_debug_errno(r, "Can't add trivial instructions to CGROUP SKB BPF program, BPF firewalling is not supported: %m");
-                return supported = false;
+                return supported = BPF_FIREWALL_UNSUPPORTED;
         }
 
         r = bpf_program_load_kernel(program, NULL, 0);
         if (r < 0) {
                 log_debug_errno(r, "Can't load kernel CGROUP SKB BPF program, BPF firewalling is not supported: %m");
-                return supported = false;
+                return supported = BPF_FIREWALL_UNSUPPORTED;
         }
 
         /* Unfortunately the kernel allows us to create BPF_PROG_TYPE_CGROUP_SKB programs even when CONFIG_CGROUP_BPF
@@ -723,12 +723,44 @@ int bpf_firewall_supported(void) {
 
         r = bpf(BPF_PROG_ATTACH, &attr, sizeof(attr));
         if (r < 0) {
-                if (errno == EBADF) /* YAY! */
-                        return supported = true;
+                if (errno != EBADF) {
+                        log_debug_errno(errno, "Didn't get EBADF from BPF_PROG_ATTACH, BPF firewalling is not supported: %m");
+                        return supported = BPF_FIREWALL_UNSUPPORTED;
+                }
+
+                /* YAY! */
+        } else {
+                log_debug("Wut? Kernel accepted our invalid BPF_PROG_ATTACH call? Something is weird, assuming BPF firewalling is broken and hence not supported.");
+                return supported = BPF_FIREWALL_UNSUPPORTED;
+        }
 
-                log_debug_errno(errno, "Didn't get EBADF from BPF_PROG_ATTACH, BPF firewalling is not supported: %m");
-        } else
-                log_debug("Wut? kernel accepted our invalid BPF_PROG_ATTACH call? Something is weird, assuming BPF firewalling is broken and hence not supported.");
+        /* So now we know that the BPF program is generally available, let's see if BPF_F_ALLOW_MULTI is also supported
+         * (which was added in kernel 4.15). We use a similar logic as before, but this time we use
+         * BPF_F_ALLOW_MULTI. Since the flags are checked early in the system call we'll get EINVAL if it's not
+         * supported, and EBADF as before if it is available. */
 
-        return supported = false;
+        attr = (union bpf_attr) {
+                .attach_type = BPF_CGROUP_INET_EGRESS,
+                .target_fd = -1,
+                .attach_bpf_fd = -1,
+                .attach_flags = BPF_F_ALLOW_MULTI,
+        };
+
+        r = bpf(BPF_PROG_ATTACH, &attr, sizeof(attr));
+        if (r < 0) {
+                if (errno == EBADF) {
+                        log_debug_errno(errno, "Got EBADF when using BPF_F_ALLOW_MULTI, which indicates it is supported. Yay!");
+                        return supported = BPF_FIREWALL_SUPPORTED_WITH_MULTI;
+                }
+
+                if (errno == EINVAL)
+                        log_debug_errno(errno, "Got EINVAL error when using BPF_F_ALLOW_MULTI, which indicates it's not supported.");
+                else
+                        log_debug_errno(errno, "Got unexpected error when using BPF_F_ALLOW_MULTI, assuming it's not supported: %m");
+
+                return supported = BPF_FIREWALL_SUPPORTED;
+        } else {
+                log_debug("Wut? Kernel accepted our invalid BPF_PROG_ATTACH+BPF_F_ALLOW_MULTI call? Something is weird, assuming BPF firewalling is broken and hence not supported.");
+                return supported = BPF_FIREWALL_UNSUPPORTED;
+        }
 }
index 37a1f2e..a0658e3 100644 (file)
 
 #include "unit.h"
 
+enum {
+        BPF_FIREWALL_UNSUPPORTED          = 0,
+        BPF_FIREWALL_SUPPORTED            = 1,
+        BPF_FIREWALL_SUPPORTED_WITH_MULTI = 2,
+};
+
 int bpf_firewall_supported(void);
 
 int bpf_firewall_compile(Unit *u);
index 30456fa..f480664 100644 (file)
@@ -1167,7 +1167,7 @@ int bus_cgroup_set_property(
                                 r = bpf_firewall_supported();
                                 if (r < 0)
                                         return r;
-                                if (r == 0) {
+                                if (r == BPF_FIREWALL_UNSUPPORTED) {
                                         static bool warned = false;
 
                                         log_full(warned ? LOG_DEBUG : LOG_WARNING,
index 08bd4c0..f10138c 100644 (file)
@@ -156,7 +156,7 @@ int config_parse_ip_address_access(
                 r = bpf_firewall_supported();
                 if (r < 0)
                         return r;
-                if (r == 0) {
+                if (r == BPF_FIREWALL_UNSUPPORTED) {
                         static bool warned = false;
 
                         log_full(warned ? LOG_DEBUG : LOG_WARNING,
index 1a57cf0..4198878 100644 (file)
@@ -1521,7 +1521,7 @@ static int socket_address_listen_in_cgroup(
         r = bpf_firewall_supported();
         if (r < 0)
                 return r;
-        if (r == 0) /* If BPF firewalling isn't supported anyway — there's no point in this forking complexity */
+        if (r == BPF_FIREWALL_UNSUPPORTED) /* If BPF firewalling isn't supported anyway — there's no point in this forking complexity */
                 goto shortcut;
 
         if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
@@ -2865,7 +2865,7 @@ static int socket_accept_in_cgroup(Socket *s, SocketPort *p, int fd) {
         r = bpf_firewall_supported();
         if (r < 0)
                 return r;
-        if (r == 0)
+        if (r == BPF_FIREWALL_UNSUPPORTED)
                 goto shortcut;
 
         if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
index 361cf10..6ca2be4 100644 (file)
@@ -71,12 +71,17 @@ int main(int argc, char *argv[]) {
         }
 
         r = bpf_firewall_supported();
-        if (r == 0) {
+        if (r == BPF_FIREWALL_UNSUPPORTED) {
                 log_notice("BPF firewalling not supported, skipping");
                 return EXIT_TEST_SKIP;
         }
         assert_se(r > 0);
 
+        if (r == BPF_FIREWALL_SUPPORTED_WITH_MULTI)
+                log_notice("BPF firewalling with BPF_F_ALLOW_MULTI supported. Yay!");
+        else
+                log_notice("BPF firewalling (though without BPF_F_ALLOW_MULTI) supported. Good.");
+
         r = bpf_program_load_kernel(p, log_buf, ELEMENTSOF(log_buf));
         assert(r >= 0);