You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
9.5 KiB
365 lines
9.5 KiB
4 months ago
|
#!/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.
|
||
|
|
||
|
"""kernel net test library for bpf testing."""
|
||
|
|
||
|
import ctypes
|
||
|
import os
|
||
|
import platform
|
||
|
import resource
|
||
|
import socket
|
||
|
|
||
|
import csocket
|
||
|
import cstruct
|
||
|
import net_test
|
||
|
|
||
|
# __NR_bpf syscall numbers for various architectures.
|
||
|
# NOTE: If python inherited COMPAT_UTS_MACHINE, uname's 'machine' field will
|
||
|
# return the 32-bit architecture name, even if python itself is 64-bit. To work
|
||
|
# 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.
|
||
|
#
|
||
|
# 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,
|
||
|
"armv8l-32bit": 386,
|
||
|
"armv8l-64bit": 280,
|
||
|
"i686-32bit": 357,
|
||
|
"i686-64bit": 321,
|
||
|
"x86_64-32bit": 357,
|
||
|
"x86_64-64bit": 321,
|
||
|
}[os.uname()[4] + "-" + platform.architecture()[0]]
|
||
|
|
||
|
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
|
||
|
BPF_PROG_ATTACH = 8
|
||
|
BPF_PROG_DETACH = 9
|
||
|
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_PROG_TYPE_TRACEPOINT = 5
|
||
|
BPF_PROG_TYPE_XDP = 6
|
||
|
BPF_PROG_TYPE_PERF_EVENT = 7
|
||
|
BPF_PROG_TYPE_CGROUP_SKB = 8
|
||
|
BPF_PROG_TYPE_CGROUP_SOCK = 9
|
||
|
|
||
|
# BPF program attach type.
|
||
|
BPF_CGROUP_INET_INGRESS = 0
|
||
|
BPF_CGROUP_INET_EGRESS = 1
|
||
|
BPF_CGROUP_INET_SOCK_CREATE = 2
|
||
|
|
||
|
# 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
|
||
|
# 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_skb_change_head = 43
|
||
|
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
|
||
|
|
||
|
# 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.
|
||
|
# 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)
|
||
|
|
||
|
|
||
|
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))
|
||
|
BpfSyscall(BPF_MAP_UPDATE_ELEM, attr)
|
||
|
|
||
|
|
||
|
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))
|
||
|
BpfSyscall(BPF_MAP_LOOKUP_ELEM, attr)
|
||
|
return c_value
|
||
|
|
||
|
|
||
|
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
|
||
|
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, prog_license=b"GPL"):
|
||
|
bpf_prog = "".join(instructions)
|
||
|
insn_buff = ctypes.create_string_buffer(bpf_prog)
|
||
|
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),
|
||
|
ctypes.addressof(gpl_license), LOG_LEVEL,
|
||
|
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)
|
||
|
ret = libc.setsockopt(sock_fd, socket.SOL_SOCKET, SO_ATTACH_BPF,
|
||
|
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))
|
||
|
return BpfSyscall(BPF_PROG_DETACH, attr)
|
||
|
|
||
|
|
||
|
# 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 BpfFuncCall(func):
|
||
|
code = BPF_JMP | BPF_CALL
|
||
|
dst_src = 0
|
||
|
ret = BpfInsn((code, dst_src, 0, func))
|
||
|
return ret.Pack()
|