|
| 1 | +### Proposed Implementation Plan |
| 2 | +- First: Prepare to implement unit testing in the following order: sendmsg.c → cgroup_skb.c → cgroup_sock.c. |
| 3 | +- Test Framework: All tests will use the unitTests_BUILD_CONTEXT framework. |
| 4 | +- Mock Functions: For each _test.c file, include the necessary mocked BPF helper functions required during testing. |
| 5 | +- Testing Methods: |
| 6 | +- For branches that write to BPF maps, use coll.Maps["..."] on the Go testing side to verify whether the map contents are correct. |
| 7 | +- For branches that do not write to any map, the current approach is to use the BPF_LOG() macro to print debug information for verification. |
| 8 | +### Detailed Implementation Points |
| 9 | +- Mounting and Triggering Details for the Three Programs, and Current Testing Points |
| 10 | +#### For sendmsg.c: |
| 11 | +- Purpose: This file’s function is to prepend TLV (Type-Length-Value) metadata when sending messages. |
| 12 | +- Attachment Point: It uses SEC("sk_msg"), so it needs to be attached at the socket message layer. |
| 13 | +- Difference: Its mounting and triggering method differ from the other two programs. |
| 14 | +- Current Status: We are still experimenting with how to properly attach and trigger this eBPF program. |
| 15 | +#### Testing Points |
| 16 | +- 1:msg->family != AF_INET && msg->family != AF_INET6 |
| 17 | +- Messages that are neither IPv4 nor IPv6 should be skipped directly. |
| 18 | +- 2:Inside the function get_origin_dst(struct sk_msg_md *msg, struct ip_addr *dst_ip, __u16 *dst_port) |
| 19 | +- The condition if (!storage || !storage->via_waypoint || storage->has_encoded) means: |
| 20 | +- If storage->via_waypoint is false, it indicates the message does not pass through a Waypoint, so no TLV needs to be constructed. |
| 21 | +- If storage->has_encoded is true, it means the TLV has already been inserted before and should not be inserted again. |
| 22 | +- 3:alloc_dst_length(msg, tlv_size + TLV_END_SIZE) |
| 23 | +- Tests related to buffer memory extension. |
| 24 | +- 4:SK_MSG_WRITE_BUF(msg, off, &type, TLV_TYPE_SIZE); |
| 25 | +- SK_MSG_WRITE_BUF(msg, off, &addr_size, TLV_LENGTH_SIZE); |
| 26 | +- encode_metadata_end(msg, off); |
| 27 | +- Verify that the TLV data is correctly written into the buffer. |
| 28 | +### For cgroup_skb.c |
| 29 | +- Purpose: This file intercepts network packets and monitors or collects statistics on connections that meet certain conditions. |
| 30 | +- Attachment Point: Can be attached to the cgroup, following the example of the testing method used in sockops.c. |
| 31 | +- Triggering: The program can be triggered during connection initiation (dial) in tests. |
| 32 | +#### Testing Points |
| 33 | +- 1:!is_monitoring_enable() || !is_periodic_report_enable() |
| 34 | +- When monitoring is disabled or periodic reporting is turned off, the program should skip processing. |
| 35 | +- 2:skb->family != AF_INET && skb->family != AF_INET6 |
| 36 | +- Non-IPv4/IPv6 packets should be skipped directly. |
| 37 | +- 3:sock_conn_from_sim(skb) |
| 38 | +- Controls skipping of simulated connections. |
| 39 | +- 4:is_managed_by_kmesh_skb(skb) == false |
| 40 | +- Packets not managed by Kmesh should be skipped directly. |
| 41 | +- 5:Packets not managed by Kmesh should be skipped directly. |
| 42 | +- This function triggers reporting based on timing and performs operations on BPF maps. |
| 43 | +- Corresponding maps can be checked on the Go side for testing purposes. |
| 44 | +### For cgroup_sock.c |
| 45 | +- Purpose:During TCP connection initiation, this function performs traffic control and records/modifies the original destination address based on Kmesh’s management logic, and executes further processing via tail call when necessary. |
| 46 | +- Attachment Point: Can be attached to the cgroup, following the example of the testing method used in sockops.c. |
| 47 | +#### Testing Points |
| 48 | +- 1:handle_kmesh_manage_process(&kmesh_ctx) || !is_kmesh_enabled(ctx) |
| 49 | +- If the connection originates from the control plane, or if the current network namespace is not managed by Kmesh, skip processing. |
| 50 | +- 2:ctx->protocol != IPPROTO_TCP |
| 51 | +- Skip non-TCP protocols. |
| 52 | +- 3:frontend_v = map_lookup_frontend(&frontend_k); |
| 53 | +- If the lookup fails (no frontend found), skip processing. |
| 54 | +- 4:service_v = map_lookup_service(&service_k); |
| 55 | +- Branches depending on whether the service is found. |
| 56 | +- 5:set_original_dst_info() |
| 57 | +- Verify that the original destination address is correctly written into the bpf_sk_storage structure. |
| 58 | +### Both cgroup_skb.c and cgroup_sock.c are attached to the cgroup, so their mounting process is the same and the attachment and triggering have already been implemented as follows: |
| 59 | +``` |
| 60 | + mount_cgroup2(t, cgroupPath) |
| 61 | + defer syscall.Unmount(cgroupPath, 0) |
| 62 | + //load the eBPF program |
| 63 | + coll, lk := load_bpf_2_cgroup(t, objFilePath, "..._prog", cgroupPath) |
| 64 | + defer coll.Close() |
| 65 | + defer lk.Close() |
| 66 | + // Set the BPF configuration |
| 67 | + setBpfConfig(t, coll, &factory.GlobalBpfConfig{ |
| 68 | + BpfLogLevel: constants.BPF_LOG_DEBUG, |
| 69 | + AuthzOffload: constants.DISABLED, |
| 70 | + }) |
| 71 | + startLogReader(coll) |
| 72 | +``` |
| 73 | +#### Triggering cgroup_skb.c |
| 74 | +- To trigger cgroup_skb.c, the following code is needed: |
| 75 | +``` |
| 76 | +conn, err := net.Dial("tcp", "127.0.0.1:8080") |
| 77 | + if err != nil { |
| 78 | + t.Fatalf("连接失败: %v", err) |
| 79 | + } |
| 80 | + defer conn.Close() |
| 81 | +``` |
| 82 | +- The server-side code can be included or omitted. |
| 83 | +- 1: Write it like this — during the TCP three-way handshake, sending packets to the server will trigger the egress program, and the client receiving packets will trigger the ingress program. |
| 84 | +- 2:The server-side code can also be included, for example: |
| 85 | +``` |
| 86 | + // ln, err := net.Listen("tcp", "127.0.0.1:8080") |
| 87 | + // if err != nil { |
| 88 | + // t.Fatalf("监听失败: %v", err) |
| 89 | + // } |
| 90 | + // defer ln.Close() |
| 91 | +
|
| 92 | + // go func() { |
| 93 | + // conn, err := ln.Accept() |
| 94 | + // if err != nil { |
| 95 | + // t.Logf("Accept error: %v", err) |
| 96 | + // return |
| 97 | + // } |
| 98 | + // defer conn.Close() |
| 99 | + // io.Copy(io.Discard, conn) // 丢弃接收数据 |
| 100 | + // }() |
| 101 | +``` |
| 102 | +- Both ingress and egress will be triggered. |
| 103 | +- Currently, we are still considering whether it is necessary to implement triggering only in one of these cases. |
| 104 | +#### Triggering cgroup_sock.c |
| 105 | +``` |
| 106 | +conn, err := net.Dial("tcp", "1.1.1.1:80") // 目标地址不重要 |
| 107 | + if err != nil { |
| 108 | + t.Logf("connect failed (ok, just for trigger): %v", err) |
| 109 | + } else { |
| 110 | + conn.Close() |
| 111 | +``` |
| 112 | +### Question |
| 113 | +- 1 |
| 114 | +- To achieve generality for mounting to cgroup, a proName parameter can be added to this function to make it more generic. |
| 115 | +``` |
| 116 | +func load_bpf_2_cgroup(t *testing.T, objFilename string, cgroupPath string) (*ebpf.Collection, link.Link) { |
| 117 | + if cgroupPath == "" { |
| 118 | + t.Fatal("cgroupPath is empty") |
| 119 | + } |
| 120 | + if objFilename == "" { |
| 121 | + t.Fatal("objFilename is empty") |
| 122 | + } |
| 123 | +
|
| 124 | + // load the eBPF program |
| 125 | + spec := loadAndPrepSpec(t, path.Join(*testPath, objFilename)) |
| 126 | + var ( |
| 127 | + coll *ebpf.Collection |
| 128 | + err error |
| 129 | + ) |
| 130 | + |
| 131 | + // Load the eBPF collection into the kernel |
| 132 | + coll, err = ebpf.NewCollection(spec) |
| 133 | + if err != nil { |
| 134 | + var ve *ebpf.VerifierError |
| 135 | + if errors.As(err, &ve) { |
| 136 | + t.Fatalf("verifier error: %+v", ve) |
| 137 | + } else { |
| 138 | + t.Fatal("loading collection:", err) |
| 139 | + } |
| 140 | + } |
| 141 | + spec.Programs["sockops_prog"].AttachType) |
| 142 | + lk, err := link.AttachCgroup(link.CgroupOptions{ |
| 143 | + Path: constants.Cgroup2Path, |
| 144 | + Attach: spec.Programs["sockops_prog"].AttachType, |
| 145 | + Program: coll.Programs["sockops_prog"], |
| 146 | + }) |
| 147 | + t.Log(spec.Programs["sockops_prog"].AttachType) |
| 148 | + if err != nil { |
| 149 | + coll.Close() |
| 150 | + t.Fatalf("Failed to attach cgroup: %v", err) |
| 151 | + } |
| 152 | + return coll, lk |
| 153 | +} |
| 154 | +``` |
| 155 | +- Turn it into |
| 156 | +``` |
| 157 | +func load_bpf_prog_to_cgroup(t *testing.T, objFilename string, progName string, cgroupPath string) (*ebpf.Collection, link.Link) { |
| 158 | + if cgroupPath == "" { |
| 159 | + t.Fatal("cgroupPath is empty") |
| 160 | + } |
| 161 | + if objFilename == "" { |
| 162 | + t.Fatal("objFilename is empty") |
| 163 | + } |
| 164 | +
|
| 165 | + // load the eBPF program |
| 166 | + spec := loadAndPrepSpec(t, path.Join(*testPath, objFilename)) |
| 167 | + var ( |
| 168 | + coll *ebpf.Collection |
| 169 | + err error |
| 170 | + ) |
| 171 | + // Load the eBPF collection into the kernel |
| 172 | + coll, err = ebpf.NewCollection(spec) |
| 173 | + if err != nil { |
| 174 | + var ve *ebpf.VerifierError |
| 175 | + if errors.As(err, &ve) { |
| 176 | + t.Fatalf("verifier error: %+v", ve) |
| 177 | + } else { |
| 178 | + t.Fatal("loading collection:", err) |
| 179 | + } |
| 180 | + } |
| 181 | + lk, err := link.AttachCgroup(link.CgroupOptions{ |
| 182 | + Path: constants.Cgroup2Path, |
| 183 | + Attach: spec.Programs[progName].AttachType, |
| 184 | + Program: coll.Programs[progName], |
| 185 | + }) |
| 186 | + t.Log(spec.Programs[progName].AttachType) |
| 187 | + if err != nil { |
| 188 | + coll.Close() |
| 189 | + t.Fatalf("Failed to attach cgroup: %v", err) |
| 190 | + } |
| 191 | + return coll, lk |
| 192 | +} |
| 193 | +``` |
| 194 | +- 2 |
| 195 | +- Does this function need to be modified |
| 196 | +``` |
| 197 | +func loadAndPrepSpec(t *testing.T, elfPath string) *ebpf.CollectionSpec { |
| 198 | + spec, err := ebpf.LoadCollectionSpec(elfPath) |
| 199 | + if err != nil { |
| 200 | + t.Fatalf("load spec %s: %v", elfPath, err) |
| 201 | + } |
| 202 | + // Unpin all maps, as we don't want to interfere with other tests |
| 203 | + for _, m := range spec.Maps { |
| 204 | + m.Pinning = ebpf.PinNone |
| 205 | + } |
| 206 | +
|
| 207 | + for n, p := range spec.Programs { |
| 208 | + switch p.Type { |
| 209 | + // https://docs.ebpf.io/linux/syscall/BPF_PROG_TEST_RUN/ |
| 210 | + case ebpf.XDP, ebpf.SchedACT, ebpf.SchedCLS, ebpf.SocketFilter, ebpf.CGroupSKB, ebpf.SockOps: |
| 211 | + continue |
| 212 | + } |
| 213 | + |
| 214 | + t.Logf("Skipping program '%s' of type '%s': BPF_PROG_RUN not supported", p.Name, p.Type) |
| 215 | + delete(spec.Programs, n) |
| 216 | + } |
| 217 | + |
| 218 | + return spec |
| 219 | +} |
| 220 | +- The purpose of this function is to load metadata, so ebpf.SkMsg and ebpf.CGroupSockAddr need to be included to prevent deletion. |
0 commit comments