diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2017-02-01 00:59:07 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-02-01 00:59:07 +0000 |
commit | 76abd1552db1fe34e294f84e349fc6996822bb9f (patch) | |
tree | af0dc4b7a3623c544920f698f4492ceaee954ab6 | |
parent | 18b0af430393296d941b3316645aa907b13db0c0 (diff) | |
parent | 5a8f99101a5c45406b0946ec0314335fafade75f (diff) | |
download | tests-76abd1552db1fe34e294f84e349fc6996822bb9f.tar.gz |
Basic tests for eBPF maps am: c43db8c835 am: 231dd8a0d4 am: 807573ede8
am: 5a8f99101a
Change-Id: Id6f65bff764232641c716546a346eab8333adf75
-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" |