summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenzo Colitti <lorenzo@google.com>2017-02-01 00:59:07 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-02-01 00:59:07 +0000
commit76abd1552db1fe34e294f84e349fc6996822bb9f (patch)
treeaf0dc4b7a3623c544920f698f4492ceaee954ab6
parent18b0af430393296d941b3316645aa907b13db0c0 (diff)
parent5a8f99101a5c45406b0946ec0314335fafade75f (diff)
downloadtests-76abd1552db1fe34e294f84e349fc6996822bb9f.tar.gz
Basic tests for eBPF maps am: c43db8c835 am: 231dd8a0d4 am: 807573ede8
am: 5a8f99101a Change-Id: Id6f65bff764232641c716546a346eab8333adf75
-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"