Implementasi proxy TCP layer-4 sering bergantung pada splice(2) untuk memindahkan data antar-socket tanpa menyalinnya ke userspace memory buffer. Kendati demikian, splice() tetap melibatkan overhead context switch antar kernel-userspace dan traversal penuh pada TCP/IP stack kernel lokal. eBPF SOCKMAP (BPF_MAP_TYPE_SOCKMAP atau BPF_MAP_TYPE_SOCKHASH) mengatasi overhead ini dengan memotong jalur packet routing langsung pada layer socket transport (sk_msg / stream parser), menghubungkan socket ingress langsung ke antrean egress socket tujuan.

Artikel ini membahas arsitektur pengujian performa eBPF SOCKMAP dibanding splice() standar serta automasi benchmark-nya dalam pipeline CI untuk mencegah regresi performa jaringan.

Arsitektur: eBPF SOCKMAP vs splice(2)

Sistem proxy berbasis splice() membaca data dari socket A ke pipe kernel, lalu meneruskannya ke socket B. Alur ini memerlukan dua system call terpisah per transaksi dan tetap melalui antrean socket normal. Sebaliknya, eBPF SOCKMAP bekerja pada transport layer. Program verdict eBPF ditempelkan pada map socket dan mengarahkan paket secara langsung di layer struct sock menggunakan helper bpf_msg_redirect_hash atau bpf_sk_redirect_map.

Program Minimal eBPF sk_msg Verdict

Berikut adalah kode C eBPF minimal yang memetakan paket yang masuk pada socket sumber langsung ke socket tujuan:

// sockmap_prog.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_SOCKMAP);
    __uint(max_entries, 2);
    __type(key, __u32);
    __type(value, __u64);
} sock_map SEC(".maps");

SEC("sk_msg")
int bpf_tcp_splice(struct sk_msg_md *msg)
{
    // Mengalihkan traffic dari key 0 ke key 1 (socket egress)
    __u32 target_key = 1;
    return bpf_msg_redirect_map(msg, &sock_map, target_key, 0);
}

char _license[] SEC("license") = "GPL";

Test Fixture: Inisialisasi dan Registrasi Socket

Program userspace bertanggung jawab membuat koneksi TCP, memuat program eBPF via libbpf, dan mendaftarkan file descriptor (FD) socket ke dalam sock_map.

// fixture.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>

int register_sockets_to_map(int map_fd, int ingress_fd, int egress_fd) {
    __u32 key_in = 0;
    __u64 val_in = (__u64)ingress_fd;
    if (bpf_map_update_elem(map_fd, &key_in, &val_in, BPF_ANY) != 0) {
        perror("Gagal update ingress_fd");
        return -1;
    }

    __u32 key_out = 1;
    __u64 val_out = (__u64)egress_fd;
    if (bpf_map_update_elem(map_fd, &key_out, &val_out, BPF_ANY) != 0) {
        perror("Gagal update egress_fd");
        return -1;
    }

    return 0;
}

Setup Environment Kernel pada CI Runner

eBPF SOCKMAP membutuhkan kernel Linux modern dengan opsi CONFIG_BPF_STREAM_PARSER=y dan CONFIG_NET_SOCK_MSG=y aktif. Karena shared runner GitHub Actions standar memiliki keterbatasan pada hak akses kernel dan isolasi resource bare-metal, disarankan menggunakan self-hosted runner atau VM terisolasi berbasis KVM/QEMU dengan hak akses CAP_BPF atau CAP_SYS_ADMIN.

Konfigurasi GitHub Actions Workflow

name: SOCKMAP Benchmark CI

on:
  pull_request:
    branches: [ main ]

jobs:
  sockmap-benchmark:
    runs-on: [self-hosted, linux, bare-metal]
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Install Toolchain dan Dependensi Kernel
        run: |
          sudo apt-get update
          sudo apt-get install -y clang llvm libbpf-dev linux-tools-common \
            linux-tools-$(uname -r) iperf3 jq

      - name: Compile eBPF dan Test Fixture
        run: |
          clang -O2 -g -target bpf -c sockmap_prog.c -o sockmap_prog.o
          gcc -O2 fixture.c -lbpf -o fixture

      - name: Jalankan Benchmark Latensi dan Throughput
        id: bench
        run: |
          sudo ./run_benchmark.sh

      - name: Verifikasi Ambang Batas Regresi
        run: |
          python3 verify_regression.py \
            --baseline baseline.json \
            --current result.json \
            --max-drop-pct 5.0

Otomatisasi Pengujian dan Deteksi Regresi

Pengujian dilakukan dalam dua isolasi network namespace (netns) lokal untuk mengukur throughput murni tanpa intervensi fisik kabel jaringan. Skrip run_benchmark.sh mengeksekusi tes perbandingan antara backend proxy splice() konvensional dan eBPF SOCKMAP menggunakan iperf3.

#!/usr/bin/env bash
# run_benchmark.sh
set -euo pipefail

# Inisialisasi namespace jaringan terisolasi
ip netns add ns_src
ip netns add ns_dst
ip link add veth_src type veth peer name veth_dst
ip link set veth_src netns ns_src
ip link set veth_dst netns ns_dst

ip netns exec ns_src ip addr add 10.10.1.1/24 dev veth_src
ip netns exec ns_src ip link set veth_src up
ip netns exec ns_src ip link set lo up

ip netns exec ns_dst ip addr add 10.10.1.2/24 dev veth_dst
ip netns exec ns_dst ip link set veth_dst up
ip netns exec ns_dst ip link set lo up

# Jalankan server iperf3 di ns_dst
ip netns exec ns_dst iperf3 -s -D --json --logfile dst_iperf.json

# Jalankan proxy fixture (SOCKMAP mode) di root namespace
./fixture --mode sockmap &
FIXTURE_PID=$!
sleep 1

# Jalankan trafik iperf3 client via ns_src
ip netns exec ns_src iperf3 -c 10.10.1.2 -t 10 -J > result.json

kill $FIXTURE_PID
ip netns del ns_src
ip netns del ns_dst

Verifikasi Regresi Otomatis

Pipeline CI memverifikasi metrik transfer data untuk memastikan perubahan kode pada hook kernel tidak menurunkan throughput melebihi ambang batas yang ditentukan (threshold):

# verify_regression.py
import json
import sys
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--baseline", required=True)
parser.add_argument("--current", required=True)
parser.add_argument("--max-drop-pct", type=float, default=5.0)
args = parser.parse_args()

with open(args.baseline) as f:
    base_data = json.load(f)
with open(args.current) as f:
    curr_data = json.load(f)

base_bps = base_data["end"]["sum_sent"]["bits_per_second"]
curr_bps = curr_data["end"]["sum_sent"]["bits_per_second"]

drop_pct = ((base_bps - curr_bps) / base_bps) * 100.0

print(f"Baseline Throughput: {base_bps / 1e9:.2f} Gbps")
print(f"Current Throughput:  {curr_bps / 1e9:.2f} Gbps")
print(f"Perubahan: {drop_pct:.2f}%")

if drop_pct > args.max_drop_pct:
    sys.exit(f"ERROR: Terjadi regresi performa sebesar {drop_pct:.2f}% (> {args.max_drop_pct}%)")

print("Verifikasi performa berhasil: Berada dalam batas toleransi.")

Troubleshooting dan Pertimbangan Operasional

  • Noise dan Kestabilan Benchmark: Pada lingkungan VM/bare-metal CI, pinning CPU core menggunakan taskset -c atau cpuset sangat dianjurkan untuk iperf3 client, fixture proxy, dan core yang menangani softirq networking agar hasil benchmark deterministik.
  • Keterbatasan Kernel: eBPF SOCKMAP beroperasi pada TCP stream terkonfirmasi (state ESTABLISHED). Map update akan gagal dengan EINVAL jika file descriptor socket masih berada dalam state listen atau syn-sent.
  • Verifikasi BPF Map: Periksa isi map pada masa runtime pengujian menggunakan CLI standar kernel:
    bpftool map dump id <map_id>