/* * Copyright (c) 2019 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include "BPF.h" #include "catch.hpp" #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) TEST_CASE("test hash of maps", "[hash_of_maps]") { { const std::string BPF_PROGRAM = R"( BPF_ARRAY(cntl, int, 1); BPF_ARRAY(ex1, int, 1024); BPF_ARRAY(ex2, int, 1024); BPF_ARRAY(ex3, u64, 1024); BPF_HASH_OF_MAPS(maps_hash, int, "ex1", 10); int syscall__getuid(void *ctx) { int key = 0, data, *val, cntl_val; void *inner_map; val = cntl.lookup(&key); if (!val || *val == 0) return 0; // cntl_val == 1 : lookup and update cntl_val = *val; inner_map = maps_hash.lookup(&key); if (!inner_map) return 0; if (cntl_val == 1) { val = bpf_map_lookup_elem(inner_map, &key); if (val) { data = 1; bpf_map_update_elem(inner_map, &key, &data, 0); } } return 0; } )"; ebpf::BPF bpf; ebpf::StatusTuple res(0); res = bpf.init(BPF_PROGRAM); REQUIRE(res.ok()); auto t = bpf.get_map_in_map_table("maps_hash"); auto ex1_table = bpf.get_array_table("ex1"); auto ex2_table = bpf.get_array_table("ex2"); auto ex3_table = bpf.get_array_table("ex3"); int ex1_fd = ex1_table.get_fd(); int ex2_fd = ex2_table.get_fd(); int ex3_fd = ex3_table.get_fd(); int key = 0, value = 0; res = t.update_value(key, ex1_fd); REQUIRE(res.ok()); // updating already-occupied slot will succeed. res = t.update_value(key, ex2_fd); REQUIRE(res.ok()); res = t.update_value(key, ex1_fd); REQUIRE(res.ok()); // an in-compatible map key = 1; res = t.update_value(key, ex3_fd); REQUIRE(res.code() == -1); // hash table, any valid key should work as long // as hash table is not full. key = 10; res = t.update_value(key, ex2_fd); REQUIRE(res.ok()); res = t.remove_value(key); REQUIRE(res.ok()); // test effectiveness of map-in-map key = 0; std::string getuid_fnname = bpf.get_syscall_fnname("getuid"); res = bpf.attach_kprobe(getuid_fnname, "syscall__getuid"); REQUIRE(res.ok()); auto cntl_table = bpf.get_array_table("cntl"); cntl_table.update_value(0, 1); REQUIRE(getuid() >= 0); res = ex1_table.get_value(key, value); REQUIRE(res.ok()); REQUIRE(value > 0); res = bpf.detach_kprobe(getuid_fnname); REQUIRE(res.ok()); res = t.remove_value(key); REQUIRE(res.ok()); } } TEST_CASE("test hash of maps using custom key", "[hash_of_maps_custom_key]") { { const std::string BPF_PROGRAM = R"( struct custom_key { int value_1; int value_2; }; BPF_ARRAY(cntl, int, 1); BPF_TABLE("hash", int, int, ex1, 1024); BPF_TABLE("hash", int, int, ex2, 1024); BPF_HASH_OF_MAPS(maps_hash, struct custom_key, "ex1", 10); int syscall__getuid(void *ctx) { struct custom_key hash_key = {1, 0}; int key = 0, data, *val, cntl_val; void *inner_map; val = cntl.lookup(&key); if (!val || *val == 0) return 0; hash_key.value_2 = *val; inner_map = maps_hash.lookup(&hash_key); if (!inner_map) return 0; val = bpf_map_lookup_elem(inner_map, &key); if (!val) { data = 1; bpf_map_update_elem(inner_map, &key, &data, 0); } else { data = 1 + *val; bpf_map_update_elem(inner_map, &key, &data, 0); } return 0; } )"; struct custom_key { int value_1; int value_2; }; ebpf::BPF bpf; ebpf::StatusTuple res(0); res = bpf.init(BPF_PROGRAM); REQUIRE(res.ok()); auto t = bpf.get_map_in_map_table("maps_hash"); auto ex1_table = bpf.get_hash_table("ex1"); auto ex2_table = bpf.get_hash_table("ex2"); auto cntl_table = bpf.get_array_table("cntl"); int ex1_fd = ex1_table.get_fd(); int ex2_fd = ex2_table.get_fd(); // test effectiveness of map-in-map std::string getuid_fnname = bpf.get_syscall_fnname("getuid"); res = bpf.attach_kprobe(getuid_fnname, "syscall__getuid"); REQUIRE(res.ok()); struct custom_key hash_key = {1, 1}; res = t.update_value(hash_key, ex1_fd); REQUIRE(res.ok()); struct custom_key hash_key2 = {1, 2}; res = t.update_value(hash_key2, ex2_fd); REQUIRE(res.ok()); int key = 0, value = 0, value2 = 0; // Can't get value when value didn't set. res = ex1_table.get_value(key, value); REQUIRE(!res.ok()); REQUIRE(value == 0); // Call syscall__getuid, then set value to ex1_table res = cntl_table.update_value(key, 1); REQUIRE(res.ok()); REQUIRE(getuid() >= 0); // Now we can get value from ex1_table res = ex1_table.get_value(key, value); REQUIRE(res.ok()); REQUIRE(value >= 1); // Can't get value when value didn't set. res = ex2_table.get_value(key, value2); REQUIRE(!res.ok()); REQUIRE(value2 == 0); // Call syscall__getuid, then set value to ex2_table res = cntl_table.update_value(key, 2); REQUIRE(res.ok()); REQUIRE(getuid() >= 0); // Now we can get value from ex2_table res = ex2_table.get_value(key, value2); REQUIRE(res.ok()); REQUIRE(value > 0); res = bpf.detach_kprobe(getuid_fnname); REQUIRE(res.ok()); res = t.remove_value(hash_key); REQUIRE(res.ok()); res = t.remove_value(hash_key2); REQUIRE(res.ok()); } } TEST_CASE("test array of maps", "[array_of_maps]") { { const std::string BPF_PROGRAM = R"( BPF_ARRAY(cntl, int, 1); BPF_TABLE("hash", int, int, ex1, 1024); BPF_TABLE("hash", int, int, ex2, 1024); BPF_TABLE("hash", u64, u64, ex3, 1024); BPF_ARRAY_OF_MAPS(maps_array, "ex1", 10); int syscall__getuid(void *ctx) { int key = 0, data, *val, cntl_val; void *inner_map; val = cntl.lookup(&key); if (!val || *val == 0) return 0; // cntl_val == 1 : lookup and update // cntl_val == 2 : delete cntl_val = *val; inner_map = maps_array.lookup(&key); if (!inner_map) return 0; if (cntl_val == 1) { val = bpf_map_lookup_elem(inner_map, &key); if (!val) { data = 1; bpf_map_update_elem(inner_map, &key, &data, 0); } } else if (cntl_val == 2) { bpf_map_delete_elem(inner_map, &key); } return 0; } )"; ebpf::BPF bpf; ebpf::StatusTuple res(0); res = bpf.init(BPF_PROGRAM); REQUIRE(res.ok()); auto t = bpf.get_map_in_map_table("maps_array"); auto ex1_table = bpf.get_hash_table("ex1"); auto ex2_table = bpf.get_hash_table("ex2"); auto ex3_table = bpf.get_hash_table("ex3"); int ex1_fd = ex1_table.get_fd(); int ex2_fd = ex2_table.get_fd(); int ex3_fd = ex3_table.get_fd(); int key = 0, value = 0; res = t.update_value(key, ex1_fd); REQUIRE(res.ok()); // updating already-occupied slot will succeed. res = t.update_value(key, ex2_fd); REQUIRE(res.ok()); res = t.update_value(key, ex1_fd); REQUIRE(res.ok()); // an in-compatible map key = 1; res = t.update_value(key, ex3_fd); REQUIRE(res.code() == -1); // array table, out of bound access key = 10; res = t.update_value(key, ex2_fd); REQUIRE(res.code() == -1); // test effectiveness of map-in-map key = 0; std::string getuid_fnname = bpf.get_syscall_fnname("getuid"); res = bpf.attach_kprobe(getuid_fnname, "syscall__getuid"); REQUIRE(res.ok()); auto cntl_table = bpf.get_array_table("cntl"); cntl_table.update_value(0, 1); REQUIRE(getuid() >= 0); res = ex1_table.get_value(key, value); REQUIRE(res.ok()); REQUIRE(value == 1); cntl_table.update_value(0, 2); REQUIRE(getuid() >= 0); res = ex1_table.get_value(key, value); REQUIRE(res.code() == -1); res = bpf.detach_kprobe(getuid_fnname); REQUIRE(res.ok()); res = t.remove_value(key); REQUIRE(res.ok()); } } #endif