add support for marking ebpf programs as being only for some kernel versions

Some ebpf code cannot be loaded on too old kernels.

Sometimes we want a different - more advanced - version of an ebpf program
to be loaded on a newer kernel.

Test: build, atest
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: I9e93e7246951916e6d60544575337a7a19c82886
diff --git a/libbpf_android/BpfUtils.cpp b/libbpf_android/BpfUtils.cpp
index f8ed6d5..c7c56d5 100644
--- a/libbpf_android/BpfUtils.cpp
+++ b/libbpf_android/BpfUtils.cpp
@@ -96,6 +96,24 @@
     return res;
 }
 
+#define KVER(a, b, c) ((a)*65536 + (b)*256 + (c))
+
+unsigned kernelVersion() {
+    struct utsname buf;
+    int ret = uname(&buf);
+    if (ret) return 0;
+
+    unsigned kver_major;
+    unsigned kver_minor;
+    unsigned kver_sub;
+    char dummy;
+    ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &dummy);
+    // Check the device kernel version
+    if (ret < 3) return 0;
+
+    return KVER(kver_major, kver_minor, kver_sub);
+}
+
 std::string BpfLevelToString(BpfLevel bpfLevel) {
     switch (bpfLevel) {
         case BpfLevel::NONE:
@@ -123,23 +141,12 @@
     // Check if the device is shipped originally with android P.
     if (api_level < MINIMUM_API_REQUIRED) return BpfLevel::NONE;
 
-    struct utsname buf;
-    int ret = uname(&buf);
-    if (ret) return BpfLevel::NONE;
+    unsigned kver = kernelVersion();
 
-    int kernel_version_major;
-    int kernel_version_minor;
-    char dummy;
-    ret = sscanf(buf.release, "%d.%d%c", &kernel_version_major, &kernel_version_minor, &dummy);
-    // Check the device kernel version
-    if (ret < 2) return BpfLevel::NONE;
-
-    if (kernel_version_major > 5) return BpfLevel::EXTENDED_5_4;
-    if (kernel_version_major == 5 && kernel_version_minor >= 4) return BpfLevel::EXTENDED_5_4;
-    if (kernel_version_major == 5) return BpfLevel::EXTENDED_4_19;
-    if (kernel_version_major == 4 && kernel_version_minor >= 19) return BpfLevel::EXTENDED_4_19;
-    if (kernel_version_major == 4 && kernel_version_minor >= 14) return BpfLevel::EXTENDED_4_14;
-    if (kernel_version_major == 4 && kernel_version_minor >= 9) return BpfLevel::BASIC_4_9;
+    if (kver >= KVER(5, 4, 0)) return BpfLevel::EXTENDED_5_4;
+    if (kver >= KVER(4, 19, 0)) return BpfLevel::EXTENDED_4_19;
+    if (kver >= KVER(4, 14, 0)) return BpfLevel::EXTENDED_4_14;
+    if (kver >= KVER(4, 9, 0)) return BpfLevel::BASIC_4_9;
 
     return BpfLevel::NONE;
 }
diff --git a/libbpf_android/Loader.cpp b/libbpf_android/Loader.cpp
index 8f139a3..ce710a4 100644
--- a/libbpf_android/Loader.cpp
+++ b/libbpf_android/Loader.cpp
@@ -30,6 +30,7 @@
 
 #include "../progs/include/bpf_map_def.h"
 #include "LoaderUtils.h"
+#include "bpf/BpfUtils.h"
 #include "include/libbpf_android.h"
 
 #include <cstdlib>
@@ -531,9 +532,10 @@
 }
 
 static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license) {
-    int ret, fd, kvers;
+    unsigned kvers = kernelVersion();
+    int ret, fd;
 
-    if ((kvers = getMachineKvers()) < 0) return -1;
+    if (!kvers) return -1;
 
     string fname = pathToFilename(string(elfPath), true);
 
@@ -541,6 +543,11 @@
         string progPinLoc;
         bool reuse = false;
 
+        if (cs[i].prog_def.has_value()) {
+            if (kvers < cs[i].prog_def->min_kver) continue;
+            if (kvers >= cs[i].prog_def->max_kver) continue;
+        }
+
         // Format of pin location is
         // /sys/fs/bpf/prog_<filename>_<mapname>
         progPinLoc = string(BPF_FS_PATH) + "prog_" + fname + "_" + cs[i].name;
diff --git a/libbpf_android/LoaderUtils.h b/libbpf_android/LoaderUtils.h
index 9eda833..a5ff7fc 100644
--- a/libbpf_android/LoaderUtils.h
+++ b/libbpf_android/LoaderUtils.h
@@ -32,43 +32,6 @@
     return ret;
 }
 
-static int getMachineKvers(void) {
-    struct utsname un;
-    char* unameOut;
-    int nums[3];  // maj, min, sub
-
-    if (uname(&un)) return -1;
-    unameOut = un.release;
-
-    std::string s = unameOut;
-    std::string token;
-    size_t pos = 0;
-    int cur_num = 0;
-
-    while ((pos = s.find('.')) != std::string::npos && cur_num < 3) {
-        token = s.substr(0, pos);
-        s.erase(0, pos + 1);
-
-        if ((pos = token.find('-')) != std::string::npos) token = token.substr(0, pos);
-
-        nums[cur_num++] = stoi(token);
-    }
-
-    if ((pos = s.find('-')) != std::string::npos)
-        token = s.substr(0, pos);
-    else
-        token = s;
-
-    if (token.length() > 0 && cur_num < 3) {
-        nums[cur_num++] = stoi(token);
-    }
-
-    if (cur_num != 3)
-        return -1;
-    else
-        return (65536 * nums[0] + 256 * nums[1] + nums[2]);
-}
-
 static void deslash(std::string& s) {
     std::replace(s.begin(), s.end(), '/', '_');
 }
diff --git a/libbpf_android/include/bpf/BpfUtils.h b/libbpf_android/include/bpf/BpfUtils.h
index 06dcc47..f724f3b 100644
--- a/libbpf_android/include/bpf/BpfUtils.h
+++ b/libbpf_android/include/bpf/BpfUtils.h
@@ -148,6 +148,7 @@
 uint64_t getSocketCookie(int sockFd);
 int synchronizeKernelRCU();
 int setrlimitForTest();
+unsigned kernelVersion();
 std::string BpfLevelToString(BpfLevel BpfLevel);
 BpfLevel getBpfSupportLevel();
 
diff --git a/progs/include/bpf_helpers.h b/progs/include/bpf_helpers.h
index fbdcb2f..09c3373 100644
--- a/progs/include/bpf_helpers.h
+++ b/progs/include/bpf_helpers.h
@@ -98,10 +98,25 @@
 static unsigned long long (*bpf_get_current_uid_gid)(void) = (void*) BPF_FUNC_get_current_uid_gid;
 static unsigned long long (*bpf_get_smp_processor_id)(void) = (void*) BPF_FUNC_get_smp_processor_id;
 
-#define DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
-    const struct bpf_prog_def SEC("progs") the_prog##_def = {       \
-            .uid = (prog_uid),                                      \
-            .gid = (prog_gid),                                      \
-    };                                                              \
-    SEC(SECTION_NAME)                                               \
+#define KVER_NONE 0
+#define KVER(a, b, c) ((a)*65536 + (b)*256 + (c))
+#define KVER_INF 0xFFFFFFFF
+
+// programs requiring a kernel version >= min_kv && < max_kv
+#define DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv) \
+    const struct bpf_prog_def SEC("progs") the_prog##_def = {                                  \
+            .uid = (prog_uid),                                                                 \
+            .gid = (prog_gid),                                                                 \
+            .min_kver = (min_kv),                                                              \
+            .max_kver = (max_kv),                                                              \
+    };                                                                                         \
+    SEC(SECTION_NAME)                                                                          \
     int the_prog
+
+// programs requiring a kernel version >= min_kv
+#define DEFINE_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \
+    DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF)
+
+// programs with no kernel version requirements
+#define DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+    DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF)
diff --git a/progs/include/bpf_map_def.h b/progs/include/bpf_map_def.h
index 03ca73b..01d9b8e 100644
--- a/progs/include/bpf_map_def.h
+++ b/progs/include/bpf_map_def.h
@@ -57,4 +57,7 @@
 struct bpf_prog_def {
     unsigned int uid;
     unsigned int gid;
+
+    unsigned int min_kver;  // KERNEL_MAJOR * 65536 + KERNEL_MINOR * 256 + KERNEL_SUB
+    unsigned int max_kver;  // ie. 0x40900 for Linux 4.9 - but beware of hexadecimal for >= 10
 };