diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-02-17 03:18:42 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-02-17 03:18:42 +0000 |
commit | a9b705628e6cfceeb2a017930d10c1c7e3ac35a8 (patch) | |
tree | cca98857a0dee6694371e16d0e9254e7a5eb5146 | |
parent | 4446d4efa09ca67e194d1cd0cb09fbb339030f9c (diff) | |
parent | e382c3dc249d48f19399d0e065702879677f662b (diff) | |
download | tests-a9b705628e6cfceeb2a017930d10c1c7e3ac35a8.tar.gz |
Snap for 7149879 from e382c3dc249d48f19399d0e065702879677f662b to sc-v2-release
Change-Id: I8fe7b06fe2a58890463e68173021ccf8ab412a3f
-rwxr-xr-x | net/test/bpf.py | 49 | ||||
-rwxr-xr-x | net/test/bpf_test.py | 194 |
2 files changed, 198 insertions, 45 deletions
diff --git a/net/test/bpf.py b/net/test/bpf.py index 9e8f6c8..be4c72f 100755 --- a/net/test/bpf.py +++ b/net/test/bpf.py @@ -14,15 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""kernel net test library for bpf testing.""" + import ctypes import os +import platform +import resource +import socket import csocket import cstruct import net_test -import socket -import platform -import resource # __NR_bpf syscall numbers for various architectures. # NOTE: If python inherited COMPAT_UTS_MACHINE, uname's 'machine' field will @@ -30,8 +32,11 @@ import resource # around this problem and pick the right syscall nr, we can additionally check # the bitness of the python interpreter. Assume that the 64-bit architectures # are not running with COMPAT_UTS_MACHINE and must be 64-bit at all times. -# TODO: is there a better way of doing this? -__NR_bpf = { +# +# Is there a better way of doing this? +# Is it correct to use os.uname()[4] instead of platform.machine() ? +# Should we use 'sys.maxsize > 2**32' instead of platform.architecture()[0] ? +__NR_bpf = { # pylint: disable=invalid-name "aarch64-32bit": 386, "aarch64-64bit": 280, "armv7l-32bit": 386, @@ -151,13 +156,17 @@ BPF_CALL = 0x80 BPF_EXIT = 0x90 # BPF helper function constants +# pylint: disable=invalid-name BPF_FUNC_unspec = 0 BPF_FUNC_map_lookup_elem = 1 BPF_FUNC_map_update_elem = 2 BPF_FUNC_map_delete_elem = 3 +BPF_FUNC_ktime_get_ns = 5 BPF_FUNC_get_current_uid_gid = 15 BPF_FUNC_get_socket_cookie = 46 BPF_FUNC_get_socket_uid = 47 +BPF_FUNC_ktime_get_boot_ns = 125 +# pylint: enable=invalid-name BPF_F_RDONLY = 1 << 3 BPF_F_WRONLY = 1 << 4 @@ -165,29 +174,39 @@ BPF_F_WRONLY = 1 << 4 # These object below belongs to the same kernel union and the types below # (e.g., bpf_attr_create) aren't kernel struct names but just different # variants of the union. -BpfAttrCreate = cstruct.Struct("bpf_attr_create", "=IIIII", - "map_type key_size value_size max_entries, map_flags") -BpfAttrOps = cstruct.Struct("bpf_attr_ops", "=QQQQ", - "map_fd key_ptr value_ptr flags") +# pylint: disable=invalid-name +BpfAttrCreate = cstruct.Struct( + "bpf_attr_create", "=IIIII", + "map_type key_size value_size max_entries, map_flags") +BpfAttrOps = cstruct.Struct( + "bpf_attr_ops", "=QQQQ", + "map_fd key_ptr value_ptr flags") BpfAttrProgLoad = cstruct.Struct( "bpf_attr_prog_load", "=IIQQIIQI", "prog_type insn_cnt insns" " license log_level log_size log_buf kern_version") BpfAttrProgAttach = cstruct.Struct( "bpf_attr_prog_attach", "=III", "target_fd attach_bpf_fd attach_type") BpfInsn = cstruct.Struct("bpf_insn", "=BBhi", "code dst_src_reg off imm") +# pylint: enable=invalid-name libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) HAVE_EBPF_SUPPORT = net_test.LINUX_VERSION >= (4, 4, 0) +HAVE_EBPF_4_9 = net_test.LINUX_VERSION >= (4, 9, 0) +HAVE_EBPF_4_14 = net_test.LINUX_VERSION >= (4, 14, 0) +HAVE_EBPF_4_19 = net_test.LINUX_VERSION >= (4, 19, 0) +HAVE_EBPF_5_4 = net_test.LINUX_VERSION >= (5, 4, 0) # set memlock resource 1 GiB resource.setrlimit(resource.RLIMIT_MEMLOCK, (1073741824, 1073741824)) + # BPF program syscalls def BpfSyscall(op, attr): ret = libc.syscall(__NR_bpf, op, csocket.VoidPointer(attr), len(attr)) csocket.MaybeRaiseSocketError(ret) return ret + def CreateMap(map_type, key_size, value_size, max_entries, map_flags=0): attr = BpfAttrCreate((map_type, key_size, value_size, max_entries, map_flags)) return BpfSyscall(BPF_MAP_CREATE, attr) @@ -212,31 +231,34 @@ def LookupMap(map_fd, key): def GetNextKey(map_fd, key): + """Get the next key in the map after the specified key.""" if key is not None: c_key = ctypes.c_uint32(key) c_next_key = ctypes.c_uint32(0) key_ptr = ctypes.addressof(c_key) else: - key_ptr = 0; + key_ptr = 0 c_next_key = ctypes.c_uint32(0) attr = BpfAttrOps( (map_fd, key_ptr, ctypes.addressof(c_next_key), 0)) BpfSyscall(BPF_MAP_GET_NEXT_KEY, attr) return c_next_key + def GetFirstKey(map_fd): return GetNextKey(map_fd, None) + def DeleteMap(map_fd, key): c_key = ctypes.c_uint32(key) attr = BpfAttrOps((map_fd, ctypes.addressof(c_key), 0, 0)) BpfSyscall(BPF_MAP_DELETE_ELEM, attr) -def BpfProgLoad(prog_type, instructions): +def BpfProgLoad(prog_type, instructions, prog_license=b"GPL"): bpf_prog = "".join(instructions) insn_buff = ctypes.create_string_buffer(bpf_prog) - gpl_license = ctypes.create_string_buffer(b"GPL") + gpl_license = ctypes.create_string_buffer(prog_license) log_buf = ctypes.create_string_buffer(b"", LOG_SIZE) attr = BpfAttrProgLoad((prog_type, len(insn_buff) / len(BpfInsn), ctypes.addressof(insn_buff), @@ -244,6 +266,7 @@ def BpfProgLoad(prog_type, instructions): LOG_SIZE, ctypes.addressof(log_buf), 0)) return BpfSyscall(BPF_PROG_LOAD, attr) + # Attach a socket eBPF filter to a target socket def BpfProgAttachSocket(sock_fd, prog_fd): uint_fd = ctypes.c_uint32(prog_fd) @@ -251,11 +274,13 @@ def BpfProgAttachSocket(sock_fd, prog_fd): ctypes.pointer(uint_fd), ctypes.sizeof(uint_fd)) csocket.MaybeRaiseSocketError(ret) + # Attach a eBPF filter to a cgroup def BpfProgAttach(prog_fd, target_fd, prog_type): attr = BpfAttrProgAttach((target_fd, prog_fd, prog_type)) return BpfSyscall(BPF_PROG_ATTACH, attr) + # Detach a eBPF filter from a cgroup def BpfProgDetach(target_fd, prog_type): attr = BpfAttrProgAttach((target_fd, 0, prog_type)) diff --git a/net/test/bpf_test.py b/net/test/bpf_test.py index 30e8e49..92bbe50 100755 --- a/net/test/bpf_test.py +++ b/net/test/bpf_test.py @@ -18,19 +18,86 @@ import ctypes import errno import os import socket -import struct import subprocess import tempfile import unittest -from bpf import * # pylint: disable=wildcard-import +import bpf +from bpf import BPF_ADD +from bpf import BPF_AND +from bpf import BPF_CGROUP_INET_EGRESS +from bpf import BPF_CGROUP_INET_INGRESS +from bpf import BPF_CGROUP_INET_SOCK_CREATE +from bpf import BPF_DW +from bpf import BPF_F_RDONLY +from bpf import BPF_F_WRONLY +from bpf import BPF_FUNC_get_current_uid_gid +from bpf import BPF_FUNC_get_socket_cookie +from bpf import BPF_FUNC_get_socket_uid +from bpf import BPF_FUNC_ktime_get_boot_ns +from bpf import BPF_FUNC_ktime_get_ns +from bpf import BPF_FUNC_map_lookup_elem +from bpf import BPF_FUNC_map_update_elem +from bpf import BPF_JNE +from bpf import BPF_MAP_TYPE_HASH +from bpf import BPF_PROG_TYPE_CGROUP_SKB +from bpf import BPF_PROG_TYPE_CGROUP_SOCK +from bpf import BPF_PROG_TYPE_SCHED_CLS +from bpf import BPF_PROG_TYPE_SOCKET_FILTER +from bpf import BPF_REG_0 +from bpf import BPF_REG_1 +from bpf import BPF_REG_10 +from bpf import BPF_REG_2 +from bpf import BPF_REG_3 +from bpf import BPF_REG_4 +from bpf import BPF_REG_6 +from bpf import BPF_REG_7 +from bpf import BPF_STX +from bpf import BPF_W +from bpf import BPF_XADD +from bpf import BpfAlu64Imm +from bpf import BpfExitInsn +from bpf import BpfFuncCall +from bpf import BpfJumpImm +from bpf import BpfLdxMem +from bpf import BpfLoadMapFd +from bpf import BpfMov64Imm +from bpf import BpfMov64Reg +from bpf import BpfProgAttach +from bpf import BpfProgAttachSocket +from bpf import BpfProgDetach +from bpf import BpfProgLoad +from bpf import BpfRawInsn +from bpf import BpfStMem +from bpf import BpfStxMem +from bpf import CreateMap +from bpf import DeleteMap +from bpf import GetFirstKey +from bpf import GetNextKey +from bpf import LookupMap +from bpf import UpdateMap import csocket import net_test +from net_test import LINUX_VERSION import sock_diag libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) -HAVE_EBPF_ACCOUNTING = net_test.LINUX_VERSION >= (4, 9, 0) -HAVE_EBPF_SOCKET = net_test.LINUX_VERSION >= (4, 14, 0) + +HAVE_EBPF_ACCOUNTING = bpf.HAVE_EBPF_4_9 +HAVE_EBPF_SOCKET = bpf.HAVE_EBPF_4_14 + +# bpf_ktime_get_ns() was made non-GPL requiring in 5.8 and at the same time +# bpf_ktime_get_boot_ns() was added, both of these changes were backported to +# Android Common Kernel in 4.14.221, 4.19.175, 5.4.97. +# As such we require 4.14.222+ 4.19.176+ 5.4.98+ 5.8.0+, +# but since we only really care about LTS releases: +HAVE_EBPF_KTIME_GET_NS_APACHE2 = ( + ((LINUX_VERSION > (4, 14, 221)) and (LINUX_VERSION < (4, 19, 0))) or + ((LINUX_VERSION > (4, 19, 175)) and (LINUX_VERSION < (5, 4, 0))) or + (LINUX_VERSION > (5, 4, 97)) +) +HAVE_EBPF_KTIME_GET_BOOT_NS = HAVE_EBPF_KTIME_GET_NS_APACHE2 + KEY_SIZE = 8 VALUE_SIZE = 4 TOTAL_ENTRIES = 20 @@ -41,18 +108,19 @@ key_offset = -8 # Offset to store the map value in stack register REG10 value_offset = -16 + # Debug usage only. def PrintMapInfo(map_fd): # A random key that the map does not contain. key = 10086 while 1: try: - nextKey = GetNextKey(map_fd, key).value - value = LookupMap(map_fd, nextKey) - print(repr(nextKey) + " : " + repr(value.value)) - key = nextKey - except: - print("no value") + next_key = GetNextKey(map_fd, key).value + value = LookupMap(map_fd, next_key) + print(repr(next_key) + " : " + repr(value.value)) # pylint: disable=superfluous-parens + key = next_key + except socket.error: + print("no value") # pylint: disable=superfluous-parens break @@ -67,7 +135,7 @@ def SocketUDPLoopBack(packet_count, version, prog_fd): sock.bind((addr, 0)) addr = sock.getsockname() sockaddr = csocket.Sockaddr(addr) - for i in range(packet_count): + for _ in range(packet_count): sock.sendto("foo", addr) data, retaddr = csocket.Recvfrom(sock, 4096, 0) assert "foo" == data @@ -91,7 +159,7 @@ def SocketUDPLoopBack(packet_count, version, prog_fd): # the stack. def BpfFuncCountPacketInit(map_fd): key_pos = BPF_REG_7 - insPackCountStart = [ + return [ # Get a preloaded key from BPF_REG_0 and store it at BPF_REG_7 BpfMov64Reg(key_pos, BPF_REG_10), BpfAlu64Imm(BPF_ADD, key_pos, key_offset), @@ -111,7 +179,6 @@ def BpfFuncCountPacketInit(map_fd): BpfMov64Imm(BPF_REG_4, 0), BpfFuncCall(BPF_FUNC_map_update_elem) ] - return insPackCountStart INS_BPF_EXIT_BLOCK = [ @@ -148,11 +215,13 @@ INS_BPF_PARAM_STORE = [ BpfStxMem(BPF_DW, BPF_REG_10, BPF_REG_0, key_offset), ] + @unittest.skipUnless(HAVE_EBPF_ACCOUNTING, "BPF helper function is not fully supported") class BpfTest(net_test.NetworkTest): def setUp(self): + super(BpfTest, self).setUp() self.map_fd = -1 self.prog_fd = -1 self.sock = None @@ -164,6 +233,7 @@ class BpfTest(net_test.NetworkTest): os.close(self.map_fd) if self.sock: self.sock.close() + super(BpfTest, self).tearDown() def testCreateMap(self): key, value = 1, 1 @@ -174,11 +244,11 @@ class BpfTest(net_test.NetworkTest): DeleteMap(self.map_fd, key) self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, key) - def CheckAllMapEntry(self, nonexistent_key, totalEntries, value): + def CheckAllMapEntry(self, nonexistent_key, total_entries, value): count = 0 key = nonexistent_key while True: - if count == totalEntries: + if count == total_entries: self.assertRaisesErrno(errno.ENOENT, GetNextKey, self.map_fd, key) break else: @@ -206,11 +276,10 @@ class BpfTest(net_test.NetworkTest): value = 1024 for key in range(0, TOTAL_ENTRIES): UpdateMap(self.map_fd, key, value) - firstKey = GetFirstKey(self.map_fd) - key = firstKey.value + first_key = GetFirstKey(self.map_fd) + key = first_key.value self.CheckAllMapEntry(key, TOTAL_ENTRIES - 1, value) - def testRdOnlyMap(self): self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE, TOTAL_ENTRIES, map_flags=BPF_F_RDONLY) @@ -261,6 +330,60 @@ class BpfTest(net_test.NetworkTest): SocketUDPLoopBack(packet_count, 6, self.prog_fd) self.assertEqual(packet_count * 2, LookupMap(self.map_fd, key).value) + def testKtimeGetNsGPL(self): + instructions = [BpfFuncCall(BPF_FUNC_ktime_get_ns)] + instructions += INS_BPF_EXIT_BLOCK + self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions) + # No exceptions? Good. + + ############################################################################## + # + # Test for presence of kernel patch: + # + # UPSTREAM: net: bpf: Make bpf_ktime_get_ns() available to non GPL programs + # + # 4.14: https://android-review.googlesource.com/c/kernel/common/+/1585269 + # commit cbb4c73f9eab8f3c8ac29175d45c99ccba382e15 + # + # 4.19: https://android-review.googlesource.com/c/kernel/common/+/1355243 + # commit 272e21ccc9a92feeee80aff0587410a314b73c5b + # + # 5.4: https://android-review.googlesource.com/c/kernel/common/+/1355422 + # commit 45217b91eaaa3a563247c4f470f4cb785de6b1c6 + # + @unittest.skipUnless(HAVE_EBPF_KTIME_GET_NS_APACHE2, + "no bpf_ktime_get_ns() support for non-GPL programs") + def testKtimeGetNsApache2(self): + instructions = [BpfFuncCall(BPF_FUNC_ktime_get_ns)] + instructions += INS_BPF_EXIT_BLOCK + self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions, + b"Apache 2.0") + # No exceptions? Good. + + ############################################################################## + # + # Test for presence of kernel patch: + # + # BACKPORT: bpf: add bpf_ktime_get_boot_ns() + # + # 4.14: https://android-review.googlesource.com/c/kernel/common/+/1585587 + # commit 34073d7a8ee47ca908b56e9a1d14ca0615fdfc09 + # + # 4.19: https://android-review.googlesource.com/c/kernel/common/+/1585606 + # commit 4812ec50935dfe59ba9f48a572e278dd0b02af68 + # + # 5.4: https://android-review.googlesource.com/c/kernel/common/+/1585252 + # commit 57b3f4830fb66a6038c4c1c66ca2e138fe8be231 + # + @unittest.skipUnless(HAVE_EBPF_KTIME_GET_BOOT_NS, + "no bpf_ktime_get_boot_ns() support") + def testKtimeGetBootNs(self): + instructions = [BpfFuncCall(BPF_FUNC_ktime_get_boot_ns)] + instructions += INS_BPF_EXIT_BLOCK + self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions, + b"Apache 2.0") + # No exceptions? Good. + def testGetSocketCookie(self): self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE, TOTAL_ENTRIES) @@ -302,16 +425,18 @@ class BpfTest(net_test.NetworkTest): self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, uid) SocketUDPLoopBack(packet_count, 4, self.prog_fd) self.assertEqual(packet_count, LookupMap(self.map_fd, uid).value) - DeleteMap(self.map_fd, uid); + DeleteMap(self.map_fd, uid) SocketUDPLoopBack(packet_count, 6, self.prog_fd) self.assertEqual(packet_count, LookupMap(self.map_fd, uid).value) + @unittest.skipUnless(HAVE_EBPF_ACCOUNTING, "Cgroup BPF is not fully supported") class BpfCgroupTest(net_test.NetworkTest): @classmethod def setUpClass(cls): + super(BpfCgroupTest, cls).setUpClass() cls._cg_dir = tempfile.mkdtemp(prefix="cg_bpf-") cmd = "mount -t cgroup2 cg_bpf %s" % cls._cg_dir try: @@ -326,10 +451,12 @@ class BpfCgroupTest(net_test.NetworkTest): @classmethod def tearDownClass(cls): os.close(cls._cg_fd) - subprocess.call(('umount %s' % cls._cg_dir).split()) + subprocess.call(("umount %s" % cls._cg_dir).split()) os.rmdir(cls._cg_dir) + super(BpfCgroupTest, cls).tearDownClass() def setUp(self): + super(BpfCgroupTest, self).setUp() self.prog_fd = -1 self.map_fd = -1 @@ -350,6 +477,7 @@ class BpfCgroupTest(net_test.NetworkTest): BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE) except socket.error: pass + super(BpfCgroupTest, self).tearDown() def testCgroupBpfAttach(self): self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, INS_BPF_EXIT_BLOCK) @@ -371,8 +499,8 @@ class BpfCgroupTest(net_test.NetworkTest): self.assertRaisesErrno(errno.EPERM, SocketUDPLoopBack, 1, 4, None) self.assertRaisesErrno(errno.EPERM, SocketUDPLoopBack, 1, 6, None) BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_EGRESS) - SocketUDPLoopBack( 1, 4, None) - SocketUDPLoopBack( 1, 6, None) + SocketUDPLoopBack(1, 4, None) + SocketUDPLoopBack(1, 6, None) def testCgroupBpfUid(self): self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE, @@ -383,7 +511,8 @@ class BpfCgroupTest(net_test.NetworkTest): BpfFuncCall(BPF_FUNC_get_socket_uid) ] instructions += (INS_BPF_PARAM_STORE + BpfFuncCountPacketInit(self.map_fd) - + INS_CGROUP_ACCEPT + INS_PACK_COUNT_UPDATE + INS_CGROUP_ACCEPT) + + INS_CGROUP_ACCEPT + INS_PACK_COUNT_UPDATE + + INS_CGROUP_ACCEPT) self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, instructions) BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_INGRESS) packet_count = 20 @@ -405,31 +534,30 @@ class BpfCgroupTest(net_test.NetworkTest): if success: self.fail("Failed to create socket family=%d type=%d err=%s" % (family, socktype, os.strerror(e.errno))) - return; + return if not success: - self.fail("unexpected socket family=%d type=%d created, should be blocked" % - (family, socktype)) - + self.fail("unexpected socket family=%d type=%d created, should be blocked" + % (family, socktype)) def trySocketCreate(self, success): - for family in [socket.AF_INET, socket.AF_INET6]: - for socktype in [socket.SOCK_DGRAM, socket.SOCK_STREAM]: - self.checkSocketCreate(family, socktype, success) + for family in [socket.AF_INET, socket.AF_INET6]: + for socktype in [socket.SOCK_DGRAM, socket.SOCK_STREAM]: + self.checkSocketCreate(family, socktype, success) @unittest.skipUnless(HAVE_EBPF_SOCKET, - "Cgroup BPF socket is not supported") + "Cgroup BPF socket is not supported") def testCgroupSocketCreateBlock(self): instructions = [ BpfFuncCall(BPF_FUNC_get_current_uid_gid), BpfAlu64Imm(BPF_AND, BPF_REG_0, 0xfffffff), BpfJumpImm(BPF_JNE, BPF_REG_0, TEST_UID, 2), ] - instructions += INS_BPF_EXIT_BLOCK + INS_CGROUP_ACCEPT; + instructions += INS_BPF_EXIT_BLOCK + INS_CGROUP_ACCEPT self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SOCK, instructions) BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE) with net_test.RunAsUid(TEST_UID): # Socket creation with target uid should fail - self.trySocketCreate(False); + self.trySocketCreate(False) # Socket create with different uid should success self.trySocketCreate(True) BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE) |