bcc-python: support attach_func() and detach_func() (#3479)
authorzcy <zcy.chenyue.zhou@gmail.com>
Mon, 14 Jun 2021 05:59:22 +0000 (13:59 +0800)
committerGitHub <noreply@github.com>
Mon, 14 Jun 2021 05:59:22 +0000 (22:59 -0700)
 - support attach_func() and detach_func().
 - add an sockmap issue to demonstrate using these two functions.

docs/reference_guide.md
examples/networking/sockmap.py [new file with mode: 0755]
src/python/bcc/__init__.py
src/python/bcc/libbcc.py

index 63c82d22aaffd0548b9271f4351d241f7ce9d958..92be49e9bc1ada966a9052344e04301baa6c43b0 100644 (file)
@@ -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 (executable)
index 0000000..cba0b5f
--- /dev/null
@@ -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 <net/sock.h>
+
+#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)
index 40543dd7bfc25602b112258aa7c3f73e51ce2caa..c5bcc5d08cec72432df33a77c556783b51562921 100644 (file)
@@ -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)
index cdf1a6d2221e76e2a60db0ded2b2482e526e9972..959296e393b562fd2cbd8b38c397696317fd2052 100644 (file)
@@ -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