• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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