From 5934161d62eaed3a0aa3109371cc5ccea81cc579 Mon Sep 17 00:00:00 2001 From: zcy Date: Mon, 14 Jun 2021 13:59:22 +0800 Subject: [PATCH] bcc-python: support attach_func() and detach_func() (#3479) - support attach_func() and detach_func(). - add an sockmap issue to demonstrate using these two functions. --- docs/reference_guide.md | 40 +++++++++- examples/networking/sockmap.py | 130 +++++++++++++++++++++++++++++++++ src/python/bcc/__init__.py | 57 +++++++++++++++ src/python/bcc/libbcc.py | 4 + 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100755 examples/networking/sockmap.py diff --git a/docs/reference_guide.md b/docs/reference_guide.md index 63c82d22..92be49e9 100644 --- a/docs/reference_guide.md +++ b/docs/reference_guide.md @@ -97,6 +97,8 @@ This guide is incomplete. If something feels missing, check the bcc and kernel s - [7. attach_raw_tracepoint()](#7-attach_raw_tracepoint) - [8. attach_raw_socket()](#8-attach_raw_socket) - [9. attach_xdp()](#9-attach_xdp) + - [10. attach_func()](#10-attach_func) + - [11. detach_func()](#11-detach_func) - [Debug Output](#debug-output) - [1. trace_print()](#1-trace_print) - [2. trace_fields()](#2-trace_fields) @@ -1777,7 +1779,7 @@ Examples in situ: Syntax: ```BPF.attach_raw_socket(fn, dev)``` -Attache a BPF function to the specified network interface. +Attaches a BPF function to the specified network interface. The ```fn``` must be the type of ```BPF.function``` and the bpf_prog type needs to be ```BPF_PROG_TYPE_SOCKET_FILTER``` (```fn=BPF.load_func(func_name, BPF.SOCKET_FILTER)```) @@ -1847,6 +1849,42 @@ Examples in situ: [search /examples](https://github.com/iovisor/bcc/search?q=attach_xdp+path%3Aexamples+language%3Apython&type=Code), [search /tools](https://github.com/iovisor/bcc/search?q=attach_xdp+path%3Atools+language%3Apython&type=Code) +### 10. attach_func() + +Syntax: ```BPF.attach_func(fn, attachable_fd, attach_type [, flags])``` + +Attaches a BPF function of the specified type to a particular ```attachable_fd```. if the ```attach_type``` is ```BPF_FLOW_DISSECTOR```, the function is expected to attach to current net namespace and ```attachable_fd``` must be 0. + +For example: + +```Python +b.attach_func(fn, cgroup_fd, b.CGROUP_SOCK_OPS) +b.attach_func(fn, map_fd, b.SK_MSG_VERDICT) +``` + +Note. When attached to "global" hooks (xdp, tc, lwt, cgroup). If the "BPF function" is no longer needed after the program terminates, be sure to call `detach_func` when the program exits. + +Examples in situ: + +[search /examples](https://github.com/iovisor/bcc/search?q=attach_func+path%3Aexamples+language%3Apython&type=Code), + +### 11. detach_func() + +Syntax: ```BPF.detach_func(fn, attachable_fd, attach_type)``` + +Detaches a BPF function of the specified type. + +For example: + +```Python +b.detach_func(fn, cgroup_fd, b.CGROUP_SOCK_OPS) +b.detach_func(fn, map_fd, b.SK_MSG_VERDICT) +``` + +Examples in situ: + +[search /examples](https://github.com/iovisor/bcc/search?q=detach_func+path%3Aexamples+language%3Apython&type=Code), + ## Debug Output ### 1. trace_print() diff --git a/examples/networking/sockmap.py b/examples/networking/sockmap.py new file mode 100755 index 00000000..cba0b5f8 --- /dev/null +++ b/examples/networking/sockmap.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# @lint-avoid-python-3-compatibility-imports +# +# Copyright (c) 2021 Chenyue Zhou + +from __future__ import print_function +import os +import sys +import time +import atexit +import argparse + +from bcc import BPF, lib + + +examples = """examples: + ./sockmap.py -c /root/cgroup # attach to /root/cgroup +""" +parser = argparse.ArgumentParser( + description="pipe data across multiple sockets", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=examples) +parser.add_argument("-c", "--cgroup", required=True, + help="Specify the cgroup address. Note. must be cgroup2") + +bpf_text = ''' +#include + +#define MAX_SOCK_OPS_MAP_ENTRIES 65535 + +struct sock_key { + u32 remote_ip4; + u32 local_ip4; + u32 remote_port; + u32 local_port; + u32 family; +}; + +BPF_SOCKHASH(sock_hash, struct sock_key, MAX_SOCK_OPS_MAP_ENTRIES); + +static __always_inline void bpf_sock_ops_ipv4(struct bpf_sock_ops *skops) { + struct sock_key skk = { + .remote_ip4 = skops->remote_ip4, + .local_ip4 = skops->local_ip4, + .local_port = skops->local_port, + .remote_port = bpf_ntohl(skops->remote_port), + .family = skops->family, + }; + int ret; + + bpf_trace_printk("remote-port: %d, local-port: %d\\n", skk.remote_port, + skk.local_port); + ret = sock_hash.sock_hash_update(skops, &skk, BPF_NOEXIST); + if (ret) { + bpf_trace_printk("bpf_sock_hash_update() failed. %d\\n", -ret); + return; + } + + bpf_trace_printk("Sockhash op: %d, port %d --> %d\\n", skops->op, + skk.local_port, skk.remote_port); +} + +int bpf_sockhash(struct bpf_sock_ops *skops) { + u32 op = skops->op; + + /* ipv4 only */ + if (skops->family != AF_INET) + return 0; + + switch (op) { + case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: + case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: + bpf_sock_ops_ipv4(skops); + break; + default: + break; + } + + return 0; +} + +int bpf_redir(struct sk_msg_md *msg) { + if (msg->family != AF_INET) + return SK_PASS; + + if (msg->remote_ip4 != msg->local_ip4) + return SK_PASS; + + struct sock_key skk = { + .remote_ip4 = msg->local_ip4, + .local_ip4 = msg->remote_ip4, + .local_port = bpf_ntohl(msg->remote_port), + .remote_port = msg->local_port, + .family = msg->family, + }; + int ret = 0; + + ret = sock_hash.msg_redirect_hash(msg, &skk, BPF_F_INGRESS); + bpf_trace_printk("try redirect port %d --> %d\\n", msg->local_port, + bpf_ntohl(msg->remote_port)); + if (ret != SK_PASS) + bpf_trace_printk("redirect port %d --> %d failed\\n", msg->local_port, + bpf_ntohl(msg->remote_port)); + + return ret; +} +''' +args = parser.parse_args() +bpf = BPF(text=bpf_text) +func_sock_ops = bpf.load_func("bpf_sockhash", bpf.SOCK_OPS) +func_sock_redir = bpf.load_func("bpf_redir", bpf.SK_MSG) +# raise if error +fd = os.open(args.cgroup, os.O_RDONLY) +map_fd = lib.bpf_table_fd(bpf.module, b"sock_hash") +bpf.attach_func(func_sock_ops, fd, bpf.CGROUP_SOCK_OPS) +bpf.attach_func(func_sock_redir, map_fd, bpf.SK_MSG_VERDICT) + +def detach_all(): + bpf.detach_func(func_sock_ops, fd, bpf.CGROUP_SOCK_OPS) + bpf.detach_func(func_sock_redir, map_fd, bpf.SK_MSG_VERDICT) + print("Detaching...") + +atexit.register(detach_all) + +while True: + try: + bpf.trace_print() + sleep(1) + except KeyboardInterrupt: + sys.exit(0) diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index 40543dd7..c5bcc5d0 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -179,8 +179,45 @@ class BPF(object): XDP_FLAGS_REPLACE = (1 << 4) # from bpf_attach_type uapi/linux/bpf.h + CGROUP_INET_INGRESS = 0 + CGROUP_INET_EGRESS = 1 + CGROUP_INET_SOCK_CREATE = 2 + CGROUP_SOCK_OPS = 3 + SK_SKB_STREAM_PARSER = 4 + SK_SKB_STREAM_VERDICT = 5 + CGROUP_DEVICE = 6 + SK_MSG_VERDICT = 7 + CGROUP_INET4_BIND = 8 + CGROUP_INET6_BIND = 9 + CGROUP_INET4_CONNECT = 10 + CGROUP_INET6_CONNECT = 11 + CGROUP_INET4_POST_BIND = 12 + CGROUP_INET6_POST_BIND = 13 + CGROUP_UDP4_SENDMSG = 14 + CGROUP_UDP6_SENDMSG = 15 + LIRC_MODE2 = 16 + FLOW_DISSECTOR = 17 + CGROUP_SYSCTL = 18 + CGROUP_UDP4_RECVMSG = 19 + CGROUP_UDP6_RECVMSG = 20 + CGROUP_GETSOCKOPT = 21 + CGROUP_SETSOCKOPT = 22 + TRACE_RAW_TP = 23 TRACE_FENTRY = 24 TRACE_FEXIT = 25 + MODIFY_RETURN = 26 + LSM_MAC = 27 + TRACE_ITER = 28 + CGROUP_INET4_GETPEERNAME = 29 + CGROUP_INET6_GETPEERNAME = 30 + CGROUP_INET4_GETSOCKNAME = 31 + CGROUP_INET6_GETSOCKNAME = 32 + XDP_DEVMAP = 33 + CGROUP_INET_SOCK_RELEASE = 34 + XDP_CPUMAP = 35 + SK_LOOKUP = 36 + XDP = 37 + SK_SKB_VERDICT = 38 _probe_repl = re.compile(b"[^a-zA-Z0-9_]") _sym_caches = {} @@ -544,6 +581,26 @@ class BPF(object): def __iter__(self): return self.tables.__iter__() + @staticmethod + def attach_func(fn, attachable_fd, attach_type, flags=0): + if not isinstance(fn, BPF.Function): + raise Exception("arg 1 must be of type BPF.Function") + + res = lib.bpf_prog_attach(fn.fd, attachable_fd, attach_type, flags) + if res < 0: + raise Exception("Failed to attach BPF function with attach_type "\ + "{0}: {1}".format(attach_type, os.strerror(-res))) + + @staticmethod + def detach_func(fn, attachable_fd, attach_type): + if not isinstance(fn, BPF.Function): + raise Exception("arg 1 must be of type BPF.Function") + + res = lib.bpf_prog_detach2(fn.fd, attachable_fd, attach_type) + if res < 0: + raise Exception("Failed to detach BPF function with attach_type "\ + "{0}: {1}".format(attach_type, os.strerror(-res))) + @staticmethod def attach_raw_socket(fn, dev): dev = _assert_is_bytes(dev) diff --git a/src/python/bcc/libbcc.py b/src/python/bcc/libbcc.py index cdf1a6d2..959296e3 100644 --- a/src/python/bcc/libbcc.py +++ b/src/python/bcc/libbcc.py @@ -123,6 +123,10 @@ lib.bpf_attach_kfunc.restype = ct.c_int lib.bpf_attach_kfunc.argtypes = [ct.c_int] lib.bpf_attach_lsm.restype = ct.c_int lib.bpf_attach_lsm.argtypes = [ct.c_int] +lib.bpf_prog_attach.restype = ct.c_int +lib.bpf_prog_attach.argtype = [ct.c_int, ct.c_int, ct.c_int, ct.c_uint] +lib.bpf_prog_detach2.restype = ct.c_int +lib.bpf_prog_detach2.argtype = [ct.c_int, ct.c_int, ct.c_int] lib.bpf_has_kernel_btf.restype = ct.c_bool lib.bpf_has_kernel_btf.argtypes = None lib.bpf_open_perf_buffer.restype = ct.c_void_p -- 2.34.1