summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenzo Colitti <lorenzo@google.com>2016-12-20 11:20:28 +0900
committerChenbo Feng <fengc@google.com>2017-01-31 10:31:18 -0800
commitc43db8c8353f82220e13518be676f2c4b7fcc0c6 (patch)
treeaf0dc4b7a3623c544920f698f4492ceaee954ab6
parent2fcf8feb45f03216f1db0aa8da9448b2173e3c91 (diff)
downloadtests-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-xnet/test/bpf.py296
-rwxr-xr-xnet/test/bpf_test.py152
-rwxr-xr-xnet/test/run_net_test.sh1
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"