1#!/usr/bin/python3 2# 3# Copyright 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""kernel net test library for bpf testing.""" 18 19import ctypes 20import errno 21import os 22import resource 23import socket 24import sys 25 26import csocket 27import cstruct 28import net_test 29 30# __NR_bpf syscall numbers for various architectures. 31# NOTE: If python inherited COMPAT_UTS_MACHINE, uname's 'machine' field will 32# return the 32-bit architecture name, even if python itself is 64-bit. To work 33# around this problem and pick the right syscall nr, we can additionally check 34# the bitness of the python interpreter. Assume that the 64-bit architectures 35# are not running with COMPAT_UTS_MACHINE and must be 64-bit at all times. 36__NR_bpf = { # pylint: disable=invalid-name 37 "aarch64-32bit": 386, 38 "aarch64-64bit": 280, 39 "armv7l-32bit": 386, 40 "armv8l-32bit": 386, 41 "armv8l-64bit": 280, 42 "i686-32bit": 357, 43 "i686-64bit": 321, 44 "x86_64-32bit": 357, 45 "x86_64-64bit": 321, 46 "riscv64-64bit": 280, 47}[os.uname()[4] + "-" + ("64" if sys.maxsize > 0x7FFFFFFF else "32") + "bit"] 48 49# After ACK merge of 5.10.168 is when support for this was backported from 50# upstream Linux 5.14 and was merged into ACK android{12,13}-5.10 branches. 51# ACK android12-5.10 was >= 5.10.168 without this support only for ~4.5 hours 52# ACK android13-4.10 was >= 5.10.168 without this support only for ~25 hours 53# as such we can >= 5.10.168 instead of > 5.10.168 54# Additionally require support to be backported to any 5.10+ non-GKI/GSI kernel. 55HAVE_SO_NETNS_COOKIE = net_test.LINUX_VERSION >= (5, 10, 168) or net_test.NonGXI(5, 10) 56 57# Note: This is *not* correct for parisc & sparc architectures 58SO_NETNS_COOKIE = 71 59 60LOG_LEVEL = 1 61LOG_SIZE = 65536 62 63# BPF syscall commands constants. 64BPF_MAP_CREATE = 0 65BPF_MAP_LOOKUP_ELEM = 1 66BPF_MAP_UPDATE_ELEM = 2 67BPF_MAP_DELETE_ELEM = 3 68BPF_MAP_GET_NEXT_KEY = 4 69BPF_PROG_LOAD = 5 70BPF_OBJ_PIN = 6 71BPF_OBJ_GET = 7 72BPF_PROG_ATTACH = 8 73BPF_PROG_DETACH = 9 74BPF_PROG_TEST_RUN = 10 75BPF_PROG_GET_NEXT_ID = 11 76BPF_MAP_GET_NEXT_ID = 12 77BPF_PROG_GET_FD_BY_ID = 13 78BPF_MAP_GET_FD_BY_ID = 14 79BPF_OBJ_GET_INFO_BY_FD = 15 80BPF_PROG_QUERY = 16 81 82# setsockopt SOL_SOCKET constants 83SO_ATTACH_BPF = 50 84 85# BPF map type constant. 86BPF_MAP_TYPE_UNSPEC = 0 87BPF_MAP_TYPE_HASH = 1 88BPF_MAP_TYPE_ARRAY = 2 89BPF_MAP_TYPE_PROG_ARRAY = 3 90BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4 91 92# BPF program type constant. 93BPF_PROG_TYPE_UNSPEC = 0 94BPF_PROG_TYPE_SOCKET_FILTER = 1 95BPF_PROG_TYPE_KPROBE = 2 96BPF_PROG_TYPE_SCHED_CLS = 3 97BPF_PROG_TYPE_SCHED_ACT = 4 98BPF_PROG_TYPE_TRACEPOINT = 5 99BPF_PROG_TYPE_XDP = 6 100BPF_PROG_TYPE_PERF_EVENT = 7 101BPF_PROG_TYPE_CGROUP_SKB = 8 102BPF_PROG_TYPE_CGROUP_SOCK = 9 103 104# BPF program attach type. 105BPF_CGROUP_INET_INGRESS = 0 106BPF_CGROUP_INET_EGRESS = 1 107BPF_CGROUP_INET_SOCK_CREATE = 2 108 109# BPF register constant 110BPF_REG_0 = 0 111BPF_REG_1 = 1 112BPF_REG_2 = 2 113BPF_REG_3 = 3 114BPF_REG_4 = 4 115BPF_REG_5 = 5 116BPF_REG_6 = 6 117BPF_REG_7 = 7 118BPF_REG_8 = 8 119BPF_REG_9 = 9 120BPF_REG_10 = 10 121 122# BPF instruction constants 123BPF_PSEUDO_MAP_FD = 1 124BPF_LD = 0x00 125BPF_LDX = 0x01 126BPF_ST = 0x02 127BPF_STX = 0x03 128BPF_ALU = 0x04 129BPF_JMP = 0x05 130BPF_RET = 0x06 131BPF_MISC = 0x07 132BPF_W = 0x00 133BPF_H = 0x08 134BPF_B = 0x10 135BPF_IMM = 0x00 136BPF_ABS = 0x20 137BPF_IND = 0x40 138BPF_MEM = 0x60 139BPF_LEN = 0x80 140BPF_MSH = 0xa0 141BPF_ADD = 0x00 142BPF_SUB = 0x10 143BPF_MUL = 0x20 144BPF_DIV = 0x30 145BPF_OR = 0x40 146BPF_AND = 0x50 147BPF_LSH = 0x60 148BPF_RSH = 0x70 149BPF_NEG = 0x80 150BPF_MOD = 0x90 151BPF_XOR = 0xa0 152BPF_JA = 0x00 153BPF_JEQ = 0x10 154BPF_JGT = 0x20 155BPF_JGE = 0x30 156BPF_JSET = 0x40 157BPF_K = 0x00 158BPF_X = 0x08 159BPF_ALU64 = 0x07 160BPF_DW = 0x18 161BPF_XADD = 0xc0 162BPF_MOV = 0xb0 163 164BPF_ARSH = 0xc0 165BPF_END = 0xd0 166BPF_TO_LE = 0x00 167BPF_TO_BE = 0x08 168 169BPF_JNE = 0x50 170BPF_JSGT = 0x60 171 172BPF_JSGE = 0x70 173BPF_CALL = 0x80 174BPF_EXIT = 0x90 175 176# BPF helper function constants 177# pylint: disable=invalid-name 178BPF_FUNC_unspec = 0 179BPF_FUNC_map_lookup_elem = 1 180BPF_FUNC_map_update_elem = 2 181BPF_FUNC_map_delete_elem = 3 182BPF_FUNC_ktime_get_ns = 5 183BPF_FUNC_get_current_uid_gid = 15 184BPF_FUNC_skb_change_head = 43 185BPF_FUNC_get_socket_cookie = 46 186BPF_FUNC_get_socket_uid = 47 187BPF_FUNC_ktime_get_boot_ns = 125 188# pylint: enable=invalid-name 189 190BPF_F_RDONLY = 1 << 3 191BPF_F_WRONLY = 1 << 4 192 193# These object below belongs to the same kernel union and the types below 194# (e.g., bpf_attr_create) aren't kernel struct names but just different 195# variants of the union. 196# pylint: disable=invalid-name 197BpfAttrCreate = cstruct.Struct( 198 "bpf_attr_create", "=IIIII", 199 "map_type key_size value_size max_entries, map_flags") 200BpfAttrOps = cstruct.Struct( 201 "bpf_attr_ops", "=QQQQ", 202 "map_fd key_ptr value_ptr flags") 203BpfAttrProgLoad = cstruct.Struct( 204 "bpf_attr_prog_load", "=IIQQIIQI", "prog_type insn_cnt insns" 205 " license log_level log_size log_buf kern_version") 206BpfAttrProgAttach = cstruct.Struct( 207 "bpf_attr_prog_attach", "=III", "target_fd attach_bpf_fd attach_type") 208BpfAttrGetFdById = cstruct.Struct( 209 "bpf_attr_get_fd_by_id", "=III", "id next_id open_flags") 210BpfAttrProgQuery = cstruct.Struct( 211 "bpf_attr_prog_query", "=IIIIQIQ", "target_fd attach_type query_flags attach_flags prog_ids_ptr prog_cnt prog_attach_flags") 212BpfInsn = cstruct.Struct("bpf_insn", "=BBhi", "code dst_src_reg off imm") 213# pylint: enable=invalid-name 214 215libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) 216HAVE_EBPF_5_4 = net_test.LINUX_VERSION >= (5, 4, 0) 217 218# set memlock resource 1 GiB 219resource.setrlimit(resource.RLIMIT_MEMLOCK, (1073741824, 1073741824)) 220 221 222# BPF program syscalls 223def BpfSyscall(op, attr): 224 ret = libc.syscall(__NR_bpf, op, csocket.VoidPointer(attr), len(attr)) 225 csocket.MaybeRaiseSocketError(ret) 226 return ret 227 228 229def CreateMap(map_type, key_size, value_size, max_entries, map_flags=0): 230 attr = BpfAttrCreate((map_type, key_size, value_size, max_entries, map_flags)) 231 return BpfSyscall(BPF_MAP_CREATE, attr) 232 233 234def UpdateMap(map_fd, key, value, flags=0): 235 c_value = ctypes.c_uint32(value) 236 c_key = ctypes.c_uint32(key) 237 value_ptr = ctypes.addressof(c_value) 238 key_ptr = ctypes.addressof(c_key) 239 attr = BpfAttrOps((map_fd, key_ptr, value_ptr, flags)) 240 BpfSyscall(BPF_MAP_UPDATE_ELEM, attr) 241 242 243def LookupMap(map_fd, key): 244 c_value = ctypes.c_uint32(0) 245 c_key = ctypes.c_uint32(key) 246 attr = BpfAttrOps( 247 (map_fd, ctypes.addressof(c_key), ctypes.addressof(c_value), 0)) 248 BpfSyscall(BPF_MAP_LOOKUP_ELEM, attr) 249 return c_value 250 251 252def GetNextKey(map_fd, key): 253 """Get the next key in the map after the specified key.""" 254 if key is not None: 255 c_key = ctypes.c_uint32(key) 256 c_next_key = ctypes.c_uint32(0) 257 key_ptr = ctypes.addressof(c_key) 258 else: 259 key_ptr = 0 260 c_next_key = ctypes.c_uint32(0) 261 attr = BpfAttrOps( 262 (map_fd, key_ptr, ctypes.addressof(c_next_key), 0)) 263 BpfSyscall(BPF_MAP_GET_NEXT_KEY, attr) 264 return c_next_key 265 266 267def GetFirstKey(map_fd): 268 return GetNextKey(map_fd, None) 269 270 271def DeleteMap(map_fd, key): 272 c_key = ctypes.c_uint32(key) 273 attr = BpfAttrOps((map_fd, ctypes.addressof(c_key), 0, 0)) 274 BpfSyscall(BPF_MAP_DELETE_ELEM, attr) 275 276 277def BpfProgLoad(prog_type, instructions, prog_license=b"GPL"): 278 bpf_prog = b"".join(instructions) 279 insn_buff = ctypes.create_string_buffer(bpf_prog) 280 gpl_license = ctypes.create_string_buffer(prog_license) 281 log_buf = ctypes.create_string_buffer(b"", LOG_SIZE) 282 attr = BpfAttrProgLoad((prog_type, len(insn_buff) // len(BpfInsn), 283 ctypes.addressof(insn_buff), 284 ctypes.addressof(gpl_license), LOG_LEVEL, 285 LOG_SIZE, ctypes.addressof(log_buf), 0)) 286 return BpfSyscall(BPF_PROG_LOAD, attr) 287 288 289# Attach a socket eBPF filter to a target socket 290def BpfProgAttachSocket(sock_fd, prog_fd): 291 uint_fd = ctypes.c_uint32(prog_fd) 292 ret = libc.setsockopt(sock_fd, socket.SOL_SOCKET, SO_ATTACH_BPF, 293 ctypes.pointer(uint_fd), ctypes.sizeof(uint_fd)) 294 csocket.MaybeRaiseSocketError(ret) 295 296 297# Attach a eBPF filter to a cgroup 298def BpfProgAttach(prog_fd, target_fd, prog_type): 299 attr = BpfAttrProgAttach((target_fd, prog_fd, prog_type)) 300 return BpfSyscall(BPF_PROG_ATTACH, attr) 301 302 303# Detach a eBPF filter from a cgroup 304def BpfProgDetach(target_fd, prog_type): 305 attr = BpfAttrProgAttach((target_fd, 0, prog_type)) 306 try: 307 return BpfSyscall(BPF_PROG_DETACH, attr) 308 except socket.error as e: 309 if e.errno != errno.ENOENT: 310 raise 311 312 313# Convert a BPF program ID into an open file descriptor 314def BpfProgGetFdById(prog_id): 315 if prog_id is None: 316 return None 317 attr = BpfAttrGetFdById((prog_id, 0, 0)) 318 return BpfSyscall(BPF_PROG_GET_FD_BY_ID, attr) 319 320 321# Convert a BPF map ID into an open file descriptor 322def BpfMapGetFdById(map_id): 323 if map_id is None: 324 return None 325 attr = BpfAttrGetFdById((map_id, 0, 0)) 326 return BpfSyscall(BPF_MAP_GET_FD_BY_ID, attr) 327 328 329# Return BPF program id attached to a given cgroup & attach point 330# Note: as written this only supports a *single* program per attach point 331def BpfProgQuery(target_fd, attach_type, query_flags, attach_flags): 332 prog_id = ctypes.c_uint32(-1) 333 minus_one = prog_id.value # but unsigned, so really 4294967295 334 attr = BpfAttrProgQuery((target_fd, attach_type, query_flags, attach_flags, ctypes.addressof(prog_id), 1, 0)) 335 if BpfSyscall(BPF_PROG_QUERY, attr) == 0: 336 # to see kernel updates we have to convert back from the buffer that actually went to the kernel... 337 attr._Parse(attr._buffer) 338 assert attr.prog_cnt >= 0, "prog_cnt is %s" % attr.prog_cnt 339 assert attr.prog_cnt <= 1, "prog_cnt is %s" % attr.prog_cnt # we don't support more atm 340 if attr.prog_cnt == 0: 341 assert prog_id.value == minus_one, "prog_id is %s" % prog_id 342 return None 343 else: 344 assert prog_id.value != minus_one, "prog_id is %s" % prog_id 345 return prog_id.value 346 else: 347 return None 348 349 350# BPF program command constructors 351def BpfMov64Reg(dst, src): 352 code = BPF_ALU64 | BPF_MOV | BPF_X 353 dst_src = src << 4 | dst 354 ret = BpfInsn((code, dst_src, 0, 0)) 355 return ret.Pack() 356 357 358def BpfLdxMem(size, dst, src, off): 359 code = BPF_LDX | (size & 0x18) | BPF_MEM 360 dst_src = src << 4 | dst 361 ret = BpfInsn((code, dst_src, off, 0)) 362 return ret.Pack() 363 364 365def BpfStxMem(size, dst, src, off): 366 code = BPF_STX | (size & 0x18) | BPF_MEM 367 dst_src = src << 4 | dst 368 ret = BpfInsn((code, dst_src, off, 0)) 369 return ret.Pack() 370 371 372def BpfStMem(size, dst, off, imm): 373 code = BPF_ST | (size & 0x18) | BPF_MEM 374 dst_src = dst 375 ret = BpfInsn((code, dst_src, off, imm)) 376 return ret.Pack() 377 378 379def BpfAlu64Imm(op, dst, imm): 380 code = BPF_ALU64 | (op & 0xf0) | BPF_K 381 dst_src = dst 382 ret = BpfInsn((code, dst_src, 0, imm)) 383 return ret.Pack() 384 385 386def BpfJumpImm(op, dst, imm, off): 387 code = BPF_JMP | (op & 0xf0) | BPF_K 388 dst_src = dst 389 ret = BpfInsn((code, dst_src, off, imm)) 390 return ret.Pack() 391 392 393def BpfRawInsn(code, dst, src, off, imm): 394 ret = BpfInsn((code, (src << 4 | dst), off, imm)) 395 return ret.Pack() 396 397 398def BpfMov64Imm(dst, imm): 399 code = BPF_ALU64 | BPF_MOV | BPF_K 400 dst_src = dst 401 ret = BpfInsn((code, dst_src, 0, imm)) 402 return ret.Pack() 403 404 405def BpfExitInsn(): 406 code = BPF_JMP | BPF_EXIT 407 ret = BpfInsn((code, 0, 0, 0)) 408 return ret.Pack() 409 410 411def BpfLoadMapFd(map_fd, dst): 412 code = BPF_LD | BPF_DW | BPF_IMM 413 dst_src = BPF_PSEUDO_MAP_FD << 4 | dst 414 insn1 = BpfInsn((code, dst_src, 0, map_fd)) 415 insn2 = BpfInsn((0, 0, 0, map_fd >> 32)) 416 return insn1.Pack() + insn2.Pack() 417 418 419def BpfFuncCall(func): 420 code = BPF_JMP | BPF_CALL 421 dst_src = 0 422 ret = BpfInsn((code, dst_src, 0, func)) 423 return ret.Pack() 424