diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2016-12-20 11:20:28 +0900 |
---|---|---|
committer | Chenbo Feng <fengc@google.com> | 2017-01-31 10:31:18 -0800 |
commit | c43db8c8353f82220e13518be676f2c4b7fcc0c6 (patch) | |
tree | af0dc4b7a3623c544920f698f4492ceaee954ab6 | |
parent | 2fcf8feb45f03216f1db0aa8da9448b2173e3c91 (diff) | |
download | tests-c43db8c8353f82220e13518be676f2c4b7fcc0c6.tar.gz |
Basic tests for eBPF maps
Add some kernel net tests for evaluating eBPF maps. The tests verified
the basic eBPF map syscalls such as create map, update element, lookup
element and delete elements. It is prepared for further testcase
development related to xt_eBPF modules.
Add another three more test for bpf program running. Test features with
socket filter.
Test: all_tests.sh passes on android-4.4 or above
Change-Id: I25b8877c58998e712569d7922b0531852357c6b8
-rwxr-xr-x | net/test/bpf.py | 296 | ||||
-rwxr-xr-x | net/test/bpf_test.py | 152 | ||||
-rwxr-xr-x | net/test/run_net_test.sh | 1 |
3 files changed, 449 insertions, 0 deletions
diff --git a/net/test/bpf.py b/net/test/bpf.py new file mode 100755 index 0000000..50add04 --- /dev/null +++ b/net/test/bpf.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# +# Copyright 2016 The Android Open Source Project +# +# 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. + +import ctypes + +import csocket +import cstruct +import net_test +import socket + +# TODO: figure out how to make this arch-dependent if we run these tests +# on non-X86 +__NR_bpf = 321 +LOG_LEVEL = 1 +LOG_SIZE = 65536 + +# BPF syscall commands constants. +BPF_MAP_CREATE = 0 +BPF_MAP_LOOKUP_ELEM = 1 +BPF_MAP_UPDATE_ELEM = 2 +BPF_MAP_DELETE_ELEM = 3 +BPF_MAP_GET_NEXT_KEY = 4 +BPF_PROG_LOAD = 5 +BPF_OBJ_PIN = 6 +BPF_OBJ_GET = 7 +SO_ATTACH_BPF = 50 + +# BPF map type constant. +BPF_MAP_TYPE_UNSPEC = 0 +BPF_MAP_TYPE_HASH = 1 +BPF_MAP_TYPE_ARRAY = 2 +BPF_MAP_TYPE_PROG_ARRAY = 3 +BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4 + +# BPF program type constant. +BPF_PROG_TYPE_UNSPEC = 0 +BPF_PROG_TYPE_SOCKET_FILTER = 1 +BPF_PROG_TYPE_KPROBE = 2 +BPF_PROG_TYPE_SCHED_CLS = 3 +BPF_PROG_TYPE_SCHED_ACT = 4 + +# BPF register constant +BPF_REG_0 = 0 +BPF_REG_1 = 1 +BPF_REG_2 = 2 +BPF_REG_3 = 3 +BPF_REG_4 = 4 +BPF_REG_5 = 5 +BPF_REG_6 = 6 +BPF_REG_7 = 7 +BPF_REG_8 = 8 +BPF_REG_9 = 9 +BPF_REG_10 = 10 + +# BPF instruction constants +BPF_PSEUDO_MAP_FD = 1 +BPF_LD = 0x00 +BPF_LDX = 0x01 +BPF_ST = 0x02 +BPF_STX = 0x03 +BPF_ALU = 0x04 +BPF_JMP = 0x05 +BPF_RET = 0x06 +BPF_MISC = 0x07 +BPF_W = 0x00 +BPF_H = 0x08 +BPF_B = 0x10 +BPF_IMM = 0x00 +BPF_ABS = 0x20 +BPF_IND = 0x40 +BPF_MEM = 0x60 +BPF_LEN = 0x80 +BPF_MSH = 0xa0 +BPF_ADD = 0x00 +BPF_SUB = 0x10 +BPF_MUL = 0x20 +BPF_DIV = 0x30 +BPF_OR = 0x40 +BPF_AND = 0x50 +BPF_LSH = 0x60 +BPF_RSH = 0x70 +BPF_NEG = 0x80 +BPF_MOD = 0x90 +BPF_XOR = 0xa0 +BPF_JA = 0x00 +BPF_JEQ = 0x10 +BPF_JGT = 0x20 +BPF_JGE = 0x30 +BPF_JSET = 0x40 +BPF_K = 0x00 +BPF_X = 0x08 +BPF_ALU64 = 0x07 +BPF_DW = 0x18 +BPF_XADD = 0xc0 +BPF_MOV = 0xb0 + +BPF_ARSH = 0xc0 +BPF_END = 0xd0 +BPF_TO_LE = 0x00 +BPF_TO_BE = 0x08 + +BPF_JNE = 0x50 +BPF_JSGT = 0x60 + +BPF_JSGE = 0x70 +BPF_CALL = 0x80 +BPF_EXIT = 0x90 + +# BPF helper function constants +BPF_FUNC_unspec = 0 +BPF_FUNC_map_lookup_elem = 1 +BPF_FUNC_map_update_elem = 2 +BPF_FUNC_map_delete_elem = 3 + +# BPF attr struct +BpfAttrCreate = cstruct.Struct("bpf_attr_create", "=IIII", + "map_type key_size value_size max_entries") +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") +BpfInsn = cstruct.Struct("bpf_insn", "=BBhi", "code dst_src_reg off imm") + +libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) +HAVE_EBPF_SUPPORT = net_test.LINUX_VERSION >= (4, 4, 0) + + +# BPF program syscalls +def CreateMap(map_type, key_size, value_size, max_entries): + attr = BpfAttrCreate((map_type, key_size, value_size, max_entries)) + ret = libc.syscall(__NR_bpf, BPF_MAP_CREATE, attr.CPointer(), len(attr)) + csocket.MaybeRaiseSocketError(ret) + return ret + + +def UpdateMap(map_fd, key, value, flags=0): + c_value = ctypes.c_uint32(value) + c_key = ctypes.c_uint32(key) + value_ptr = ctypes.addressof(c_value) + key_ptr = ctypes.addressof(c_key) + attr = BpfAttrOps((map_fd, key_ptr, value_ptr, flags)) + ret = libc.syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, + attr.CPointer(), len(attr)) + csocket.MaybeRaiseSocketError(ret) + + +def LookupMap(map_fd, key): + c_value = ctypes.c_uint32(0) + c_key = ctypes.c_uint32(key) + attr = BpfAttrOps( + (map_fd, ctypes.addressof(c_key), ctypes.addressof(c_value), 0)) + ret = libc.syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, + attr.CPointer(), len(attr)) + csocket.MaybeRaiseSocketError(ret) + return c_value + + +def GetNextKey(map_fd, key): + c_key = ctypes.c_uint32(key) + c_next_key = ctypes.c_uint32(0) + attr = BpfAttrOps( + (map_fd, ctypes.addressof(c_key), ctypes.addressof(c_next_key), 0)) + ret = libc.syscall(__NR_bpf, BPF_MAP_GET_NEXT_KEY, + attr.CPointer(), len(attr)) + csocket.MaybeRaiseSocketError(ret) + return c_next_key + + +def DeleteMap(map_fd, key): + c_key = ctypes.c_uint32(key) + attr = BpfAttrOps((map_fd, ctypes.addressof(c_key), 0, 0)) + ret = libc.syscall(__NR_bpf, BPF_MAP_DELETE_ELEM, + attr.CPointer(), len(attr)) + csocket.MaybeRaiseSocketError(ret) + + +def BpfProgLoad(prog_type, insn_ptr, prog_len, insn_len): + gpl_license = ctypes.create_string_buffer(b"GPL") + log_buf = ctypes.create_string_buffer(b"", LOG_SIZE) + attr = BpfAttrProgLoad( + (prog_type, prog_len / insn_len, insn_ptr, ctypes.addressof(gpl_license), + LOG_LEVEL, LOG_SIZE, ctypes.addressof(log_buf), 0)) + ret = libc.syscall(__NR_bpf, BPF_PROG_LOAD, attr.CPointer(), len(attr)) + csocket.MaybeRaiseSocketError(ret) + return ret + + +def BpfProgAttach(sock_fd, prog_fd): + prog_ptr = ctypes.c_uint32(prog_fd) + ret = libc.setsockopt(sock_fd, socket.SOL_SOCKET, SO_ATTACH_BPF, + ctypes.addressof(prog_ptr), ctypes.sizeof(prog_ptr)) + csocket.MaybeRaiseSocketError(ret) + + +# BPF program command constructors +def BpfMov64Reg(dst, src): + code = BPF_ALU64 | BPF_MOV | BPF_X + dst_src = src << 4 | dst + ret = BpfInsn((code, dst_src, 0, 0)) + return ret.Pack() + + +def BpfLdxMem(size, dst, src, off): + code = BPF_LDX | (size & 0x18) | BPF_MEM + dst_src = src << 4 | dst + ret = BpfInsn((code, dst_src, off, 0)) + return ret.Pack() + + +def BpfStxMem(size, dst, src, off): + code = BPF_STX | (size & 0x18) | BPF_MEM + dst_src = src << 4 | dst + ret = BpfInsn((code, dst_src, off, 0)) + return ret.Pack() + + +def BpfStMem(size, dst, off, imm): + code = BPF_ST | (size & 0x18) | BPF_MEM + dst_src = dst + ret = BpfInsn((code, dst_src, off, imm)) + return ret.Pack() + + +def BpfAlu64Imm(op, dst, imm): + code = BPF_ALU64 | (op & 0xf0) | BPF_K + dst_src = dst + ret = BpfInsn((code, dst_src, 0, imm)) + return ret.Pack() + + +def BpfJumpImm(op, dst, imm, off): + code = BPF_JMP | (op & 0xf0) | BPF_K + dst_src = dst + ret = BpfInsn((code, dst_src, off, imm)) + return ret.Pack() + + +def BpfRawInsn(code, dst, src, off, imm): + ret = BpfInsn((code, (src << 4 | dst), off, imm)) + return ret.Pack() + + +def BpfMov64Imm(dst, imm): + code = BPF_ALU64 | BPF_MOV | BPF_K + dst_src = dst + ret = BpfInsn((code, dst_src, 0, imm)) + return ret.Pack() + + +def BpfExitInsn(): + code = BPF_JMP | BPF_EXIT + ret = BpfInsn((code, 0, 0, 0)) + return ret.Pack() + + +def BpfLoadMapFd(map_fd, dst): + code = BPF_LD | BPF_DW | BPF_IMM + dst_src = BPF_PSEUDO_MAP_FD << 4 | dst + insn1 = BpfInsn((code, dst_src, 0, map_fd)) + insn2 = BpfInsn((0, 0, 0, map_fd >> 32)) + return insn1.Pack() + insn2.Pack() + + +def BpfFuncLookupMap(): + code = BPF_JMP | BPF_CALL + dst_src = 0 + ret = BpfInsn((code, dst_src, 0, BPF_FUNC_map_lookup_elem)) + return ret.Pack() + + +def BpfFuncUpdateMap(): + code = BPF_JMP | BPF_CALL + dst_src = 0 + ret = BpfInsn((code, dst_src, 0, BPF_FUNC_map_update_elem)) + return ret.Pack() + + +def BpfFuncDeleteMap(): + code = BPF_JMP | BPF_CALL + dst_src = 0 + ret = BpfInsn((code, dst_src, 0, BPF_FUNC_map_delete_elem)) + return ret.Pack() diff --git a/net/test/bpf_test.py b/net/test/bpf_test.py new file mode 100755 index 0000000..1e294c9 --- /dev/null +++ b/net/test/bpf_test.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +# +# Copyright 2016 The Android Open Source Project +# +# 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. + +import ctypes +import errno +import socket +import unittest + +from bpf import * # pylint: disable=wildcard-import +import csocket +import net_test + +libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) +HAVE_EBPF_SUPPORT = net_test.LINUX_VERSION >= (4, 4, 0) + +@unittest.skipUnless(HAVE_EBPF_SUPPORT, + "eBPF function not fully supported") +class BpfTest(net_test.NetworkTest): + + def testCreateMap(self): + key, value = 1, 1 + map_fd = CreateMap(BPF_MAP_TYPE_HASH, 4, 4, 100) + UpdateMap(map_fd, key, value) + self.assertEquals(LookupMap(map_fd, key).value, value) + DeleteMap(map_fd, key) + self.assertRaisesErrno(errno.ENOENT, LookupMap, map_fd, key) + + def testIterateMap(self): + map_fd = CreateMap(BPF_MAP_TYPE_HASH, 4, 4, 100) + value = 1024 + for key in xrange(1, 100): + UpdateMap(map_fd, key, value) + for key in xrange(1, 100): + self.assertEquals(LookupMap(map_fd, key).value, value) + self.assertRaisesErrno(errno.ENOENT, LookupMap, map_fd, 101) + key = 0 + count = 0 + while 1: + if count == 99: + self.assertRaisesErrno(errno.ENOENT, GetNextKey, map_fd, key) + break + else: + result = GetNextKey(map_fd, key) + key = result.value + self.assertGreater(key, 0) + self.assertEquals(LookupMap(map_fd, key).value, value) + count += 1 + + def testProgLoad(self): + bpf_prog = BpfMov64Reg(BPF_REG_6, BPF_REG_1) + bpf_prog += BpfLdxMem(BPF_W, BPF_REG_0, BPF_REG_6, 0) + bpf_prog += BpfExitInsn() + insn_buff = ctypes.create_string_buffer(bpf_prog) + # Load a program that does nothing except pass every packet it receives + # It should not block the packet transmission otherwise the test fails. + prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, + ctypes.addressof(insn_buff), + len(insn_buff), BpfInsn._length) + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + sock.settimeout(1) + BpfProgAttach(sock.fileno(), prog_fd) + addr = "127.0.0.1" + sock.bind((addr, 0)) + addr = sock.getsockname() + sockaddr = csocket.Sockaddr(addr) + sock.sendto("foo", addr) + data, addr = csocket.Recvfrom(sock, 4096, 0) + self.assertEqual("foo", data) + self.assertEqual(sockaddr, addr) + + def testPacketBlock(self): + bpf_prog = BpfMov64Reg(BPF_REG_6, BPF_REG_1) + bpf_prog += BpfMov64Imm(BPF_REG_0, 0) + bpf_prog += BpfExitInsn() + insn_buff = ctypes.create_string_buffer(bpf_prog) + prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, + ctypes.addressof(insn_buff), + len(insn_buff), BpfInsn._length) + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + sock.settimeout(1) + BpfProgAttach(sock.fileno(), prog_fd) + addr = "127.0.0.1" + sock.bind((addr, 0)) + addr = sock.getsockname() + sock.sendto("foo", addr) + self.assertRaisesErrno(errno.EAGAIN, csocket.Recvfrom, sock, 4096, 0) + + def testPacketCount(self): + map_fd = CreateMap(BPF_MAP_TYPE_HASH, 4, 4, 100) + key = 0xf0f0 + bpf_prog = BpfMov64Reg(BPF_REG_6, BPF_REG_1) + bpf_prog += BpfLoadMapFd(map_fd, BPF_REG_1) + bpf_prog += BpfMov64Imm(BPF_REG_7, key) + bpf_prog += BpfStxMem(BPF_W, BPF_REG_10, BPF_REG_7, -4) + bpf_prog += BpfMov64Reg(BPF_REG_8, BPF_REG_10) + bpf_prog += BpfAlu64Imm(BPF_ADD, BPF_REG_8, -4) + bpf_prog += BpfMov64Reg(BPF_REG_2, BPF_REG_8) + bpf_prog += BpfFuncLookupMap() + bpf_prog += BpfJumpImm(BPF_AND, BPF_REG_0, 0, 10) + bpf_prog += BpfLoadMapFd(map_fd, BPF_REG_1) + bpf_prog += BpfMov64Reg(BPF_REG_2, BPF_REG_8) + bpf_prog += BpfStMem(BPF_W, BPF_REG_10, -8, 1) + bpf_prog += BpfMov64Reg(BPF_REG_3, BPF_REG_10) + bpf_prog += BpfAlu64Imm(BPF_ADD, BPF_REG_3, -8) + bpf_prog += BpfMov64Imm(BPF_REG_4, 0) + bpf_prog += BpfFuncUpdateMap() + bpf_prog += BpfLdxMem(BPF_W, BPF_REG_0, BPF_REG_6, 0) + bpf_prog += BpfExitInsn() + bpf_prog += BpfMov64Reg(BPF_REG_2, BPF_REG_0) + bpf_prog += BpfMov64Imm(BPF_REG_1, 1) + bpf_prog += BpfRawInsn(BPF_STX | BPF_XADD | BPF_W, BPF_REG_2, BPF_REG_1, + 0, 0) + bpf_prog += BpfLdxMem(BPF_W, BPF_REG_0, BPF_REG_6, 0) + bpf_prog += BpfExitInsn() + insn_buff = ctypes.create_string_buffer(bpf_prog) + # this program loaded is used to counting the packet transmitted through + # a target socket. It will store the packet count into the eBPF map and we + # will verify if the counting result is correct. + prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, + ctypes.addressof(insn_buff), + len(insn_buff), BpfInsn._length) + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + sock.settimeout(1) + BpfProgAttach(sock.fileno(), prog_fd) + addr = "127.0.0.1" + sock.bind((addr, 0)) + addr = sock.getsockname() + sockaddr = csocket.Sockaddr(addr) + packet_count = 100 + for i in xrange(packet_count): + sock.sendto("foo", addr) + data, retaddr = csocket.Recvfrom(sock, 4096, 0) + self.assertEqual("foo", data) + self.assertEqual(sockaddr, retaddr) + self.assertEquals(LookupMap(map_fd, key).value, packet_count) + + +if __name__ == "__main__": + unittest.main() diff --git a/net/test/run_net_test.sh b/net/test/run_net_test.sh index 366ad3e..a802205 100755 --- a/net/test/run_net_test.sh +++ b/net/test/run_net_test.sh @@ -16,6 +16,7 @@ OPTIONS="$OPTIONS CONFIG_INET_UDP_DIAG CONFIG_INET_DIAG_DESTROY" OPTIONS="$OPTIONS IP_SCTP INET_SCTP_DIAG" OPTIONS="$OPTIONS CONFIG_IP_NF_TARGET_REJECT CONFIG_IP_NF_TARGET_REJECT_SKERR" OPTIONS="$OPTIONS CONFIG_IP6_NF_TARGET_REJECT CONFIG_IP6_NF_TARGET_REJECT_SKERR" +OPTIONS="$OPTIONS BPF_SYSCALL" # For 3.1 kernels, where devtmpfs is not on by default. OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT" |