1*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0 2*4882a593Smuzhiyun# Copyright (c) 2020 SUSE LLC. 3*4882a593Smuzhiyun 4*4882a593Smuzhiyunimport collections 5*4882a593Smuzhiyunimport functools 6*4882a593Smuzhiyunimport json 7*4882a593Smuzhiyunimport os 8*4882a593Smuzhiyunimport socket 9*4882a593Smuzhiyunimport subprocess 10*4882a593Smuzhiyunimport unittest 11*4882a593Smuzhiyun 12*4882a593Smuzhiyun 13*4882a593Smuzhiyun# Add the source tree of bpftool and /usr/local/sbin to PATH 14*4882a593Smuzhiyuncur_dir = os.path.dirname(os.path.realpath(__file__)) 15*4882a593Smuzhiyunbpftool_dir = os.path.abspath(os.path.join(cur_dir, "..", "..", "..", "..", 16*4882a593Smuzhiyun "tools", "bpf", "bpftool")) 17*4882a593Smuzhiyunos.environ["PATH"] = bpftool_dir + ":/usr/local/sbin:" + os.environ["PATH"] 18*4882a593Smuzhiyun 19*4882a593Smuzhiyun 20*4882a593Smuzhiyunclass IfaceNotFoundError(Exception): 21*4882a593Smuzhiyun pass 22*4882a593Smuzhiyun 23*4882a593Smuzhiyun 24*4882a593Smuzhiyunclass UnprivilegedUserError(Exception): 25*4882a593Smuzhiyun pass 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun 28*4882a593Smuzhiyundef _bpftool(args, json=True): 29*4882a593Smuzhiyun _args = ["bpftool"] 30*4882a593Smuzhiyun if json: 31*4882a593Smuzhiyun _args.append("-j") 32*4882a593Smuzhiyun _args.extend(args) 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun return subprocess.check_output(_args) 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun 37*4882a593Smuzhiyundef bpftool(args): 38*4882a593Smuzhiyun return _bpftool(args, json=False).decode("utf-8") 39*4882a593Smuzhiyun 40*4882a593Smuzhiyun 41*4882a593Smuzhiyundef bpftool_json(args): 42*4882a593Smuzhiyun res = _bpftool(args) 43*4882a593Smuzhiyun return json.loads(res) 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun 46*4882a593Smuzhiyundef get_default_iface(): 47*4882a593Smuzhiyun for iface in socket.if_nameindex(): 48*4882a593Smuzhiyun if iface[1] != "lo": 49*4882a593Smuzhiyun return iface[1] 50*4882a593Smuzhiyun raise IfaceNotFoundError("Could not find any network interface to probe") 51*4882a593Smuzhiyun 52*4882a593Smuzhiyun 53*4882a593Smuzhiyundef default_iface(f): 54*4882a593Smuzhiyun @functools.wraps(f) 55*4882a593Smuzhiyun def wrapper(*args, **kwargs): 56*4882a593Smuzhiyun iface = get_default_iface() 57*4882a593Smuzhiyun return f(*args, iface, **kwargs) 58*4882a593Smuzhiyun return wrapper 59*4882a593Smuzhiyun 60*4882a593Smuzhiyun 61*4882a593Smuzhiyunclass TestBpftool(unittest.TestCase): 62*4882a593Smuzhiyun @classmethod 63*4882a593Smuzhiyun def setUpClass(cls): 64*4882a593Smuzhiyun if os.getuid() != 0: 65*4882a593Smuzhiyun raise UnprivilegedUserError( 66*4882a593Smuzhiyun "This test suite needs root privileges") 67*4882a593Smuzhiyun 68*4882a593Smuzhiyun @default_iface 69*4882a593Smuzhiyun def test_feature_dev_json(self, iface): 70*4882a593Smuzhiyun unexpected_helpers = [ 71*4882a593Smuzhiyun "bpf_probe_write_user", 72*4882a593Smuzhiyun "bpf_trace_printk", 73*4882a593Smuzhiyun ] 74*4882a593Smuzhiyun expected_keys = [ 75*4882a593Smuzhiyun "syscall_config", 76*4882a593Smuzhiyun "program_types", 77*4882a593Smuzhiyun "map_types", 78*4882a593Smuzhiyun "helpers", 79*4882a593Smuzhiyun "misc", 80*4882a593Smuzhiyun ] 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun res = bpftool_json(["feature", "probe", "dev", iface]) 83*4882a593Smuzhiyun # Check if the result has all expected keys. 84*4882a593Smuzhiyun self.assertCountEqual(res.keys(), expected_keys) 85*4882a593Smuzhiyun # Check if unexpected helpers are not included in helpers probes 86*4882a593Smuzhiyun # result. 87*4882a593Smuzhiyun for helpers in res["helpers"].values(): 88*4882a593Smuzhiyun for unexpected_helper in unexpected_helpers: 89*4882a593Smuzhiyun self.assertNotIn(unexpected_helper, helpers) 90*4882a593Smuzhiyun 91*4882a593Smuzhiyun def test_feature_kernel(self): 92*4882a593Smuzhiyun test_cases = [ 93*4882a593Smuzhiyun bpftool_json(["feature", "probe", "kernel"]), 94*4882a593Smuzhiyun bpftool_json(["feature", "probe"]), 95*4882a593Smuzhiyun bpftool_json(["feature"]), 96*4882a593Smuzhiyun ] 97*4882a593Smuzhiyun unexpected_helpers = [ 98*4882a593Smuzhiyun "bpf_probe_write_user", 99*4882a593Smuzhiyun "bpf_trace_printk", 100*4882a593Smuzhiyun ] 101*4882a593Smuzhiyun expected_keys = [ 102*4882a593Smuzhiyun "syscall_config", 103*4882a593Smuzhiyun "system_config", 104*4882a593Smuzhiyun "program_types", 105*4882a593Smuzhiyun "map_types", 106*4882a593Smuzhiyun "helpers", 107*4882a593Smuzhiyun "misc", 108*4882a593Smuzhiyun ] 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun for tc in test_cases: 111*4882a593Smuzhiyun # Check if the result has all expected keys. 112*4882a593Smuzhiyun self.assertCountEqual(tc.keys(), expected_keys) 113*4882a593Smuzhiyun # Check if unexpected helpers are not included in helpers probes 114*4882a593Smuzhiyun # result. 115*4882a593Smuzhiyun for helpers in tc["helpers"].values(): 116*4882a593Smuzhiyun for unexpected_helper in unexpected_helpers: 117*4882a593Smuzhiyun self.assertNotIn(unexpected_helper, helpers) 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun def test_feature_kernel_full(self): 120*4882a593Smuzhiyun test_cases = [ 121*4882a593Smuzhiyun bpftool_json(["feature", "probe", "kernel", "full"]), 122*4882a593Smuzhiyun bpftool_json(["feature", "probe", "full"]), 123*4882a593Smuzhiyun ] 124*4882a593Smuzhiyun expected_helpers = [ 125*4882a593Smuzhiyun "bpf_probe_write_user", 126*4882a593Smuzhiyun "bpf_trace_printk", 127*4882a593Smuzhiyun ] 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun for tc in test_cases: 130*4882a593Smuzhiyun # Check if expected helpers are included at least once in any 131*4882a593Smuzhiyun # helpers list for any program type. Unfortunately we cannot assume 132*4882a593Smuzhiyun # that they will be included in all program types or a specific 133*4882a593Smuzhiyun # subset of programs. It depends on the kernel version and 134*4882a593Smuzhiyun # configuration. 135*4882a593Smuzhiyun found_helpers = False 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun for helpers in tc["helpers"].values(): 138*4882a593Smuzhiyun if all(expected_helper in helpers 139*4882a593Smuzhiyun for expected_helper in expected_helpers): 140*4882a593Smuzhiyun found_helpers = True 141*4882a593Smuzhiyun break 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun self.assertTrue(found_helpers) 144*4882a593Smuzhiyun 145*4882a593Smuzhiyun def test_feature_kernel_full_vs_not_full(self): 146*4882a593Smuzhiyun full_res = bpftool_json(["feature", "probe", "full"]) 147*4882a593Smuzhiyun not_full_res = bpftool_json(["feature", "probe"]) 148*4882a593Smuzhiyun not_full_set = set() 149*4882a593Smuzhiyun full_set = set() 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun for helpers in full_res["helpers"].values(): 152*4882a593Smuzhiyun for helper in helpers: 153*4882a593Smuzhiyun full_set.add(helper) 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun for helpers in not_full_res["helpers"].values(): 156*4882a593Smuzhiyun for helper in helpers: 157*4882a593Smuzhiyun not_full_set.add(helper) 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun self.assertCountEqual(full_set - not_full_set, 160*4882a593Smuzhiyun {"bpf_probe_write_user", "bpf_trace_printk"}) 161*4882a593Smuzhiyun self.assertCountEqual(not_full_set - full_set, set()) 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun def test_feature_macros(self): 164*4882a593Smuzhiyun expected_patterns = [ 165*4882a593Smuzhiyun r"/\*\*\* System call availability \*\*\*/", 166*4882a593Smuzhiyun r"#define HAVE_BPF_SYSCALL", 167*4882a593Smuzhiyun r"/\*\*\* eBPF program types \*\*\*/", 168*4882a593Smuzhiyun r"#define HAVE.*PROG_TYPE", 169*4882a593Smuzhiyun r"/\*\*\* eBPF map types \*\*\*/", 170*4882a593Smuzhiyun r"#define HAVE.*MAP_TYPE", 171*4882a593Smuzhiyun r"/\*\*\* eBPF helper functions \*\*\*/", 172*4882a593Smuzhiyun r"#define HAVE.*HELPER", 173*4882a593Smuzhiyun r"/\*\*\* eBPF misc features \*\*\*/", 174*4882a593Smuzhiyun ] 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun res = bpftool(["feature", "probe", "macros"]) 177*4882a593Smuzhiyun for pattern in expected_patterns: 178*4882a593Smuzhiyun self.assertRegex(res, pattern) 179