Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BPF Networking

Introduction

BPF (Berkeley Packet Filter) networking refers to the use of eBPF programs attached to various networking hooks in the Linux kernel to inspect, modify, redirect, and make decisions about network traffic. While XDP handles the earliest ingress point and tc provides traditional traffic control, BPF networking encompasses a broader set of hooks including sock_ops, sk_msg, sk_lookup, and SO_REUSEPORT programs that operate at the socket and transport layers.

These programs enable high-performance, programmable networking without modifying kernel code — powering modern load balancers (Katran, Cilium), service meshes (Cilium, Istio sidecar replacement), DDoS mitigation systems, and network observability platforms.

BPF Networking Hook Points

Overview of All Hooks

graph TB
    subgraph "Ingress Path"
        NIC[NIC] --> XDP_HOOK["XDP<br>(earliest point)"]
        XDP_HOOK --> TC_INGRESS["tc ingress<br>(after sk_buff)"]
        TC_INGRESS --> NETFILTER["netfilter/iptables"]
        NETFILTER --> SOCKET["Socket deliver"]
    end

    subgraph "Socket Layer"
        SOCKET --> SK_LOOKUP["sk_lookup<br>(socket lookup)"]
        SK_LOOKUP --> SK_REUSEPORT["SO_REUSEPORT BPF<br>(socket selection)"]
        SK_REUSEPORT --> SOCK_OPS["sock_ops<br>(TCP state machine)"]
        SOCK_OPS --> SK_MSG["sk_msg<br>(sendmsg redirect)"]
    end

    subgraph "Egress Path"
        APP[Application] --> SENDMSG[sendmsg/sendfile]
        SENDMSG --> SK_MSG
        SK_MSG --> TC_EGRESS["tc egress"]
        TC_EGRESS --> XDP_EGRESS["XDP egress (ND)"]
        TC_EGRESS --> NIC_OUT[NIC]
    end

    style XDP_HOOK fill:#F44336
    style TC_INGRESS fill:#FF9800
    style SK_LOOKUP fill:#9C27B0
    style SK_REUSEPORT fill:#2196F3
    style SOCK_OPS fill:#4CAF50
    style SK_MSG fill:#00BCD4

Hook Summary Table

HookAttach PointDirectionLayerUse Cases
XDPNIC driverIngressL2/L3DDoS, firewall, forwarding
tc BPFqdiscBothL2-L4QoS, NAT, monitoring
sock_opsTCP socketBothL4 (TCP)Congestion control, metrics
sk_msgsocket sendmsgEgressL4Socket redirect, proxy
sk_lookupsocket lookupIngressL4Socket dispatch, multi-listener
SO_REUSEPORTsocket bindIngressL4Load balancing, socket selection

tc BPF Programs

tc (traffic control) BPF programs are attached to the kernel’s qdisc (queueing discipline) layer. They can inspect and modify packets, enforce rate limiting, and redirect traffic.

Architecture

graph LR
    subgraph "tc Ingress"
        IN_SKB[sk_buff arrives] --> CLS_CLSACT[clsact qdisc]
        CLS_CLSACT --> TC_BPF_INGRESS[tc BPF program]
        TC_BPF_INGRESS -->|TC_ACT_OK| PASS[Pass to stack]
        TC_BPF_INGRESS -->|TC_ACT_SHOT| DROP[Drop]
        TC_BPF_INGRESS -->|TC_ACT_REDIRECT| REDIR[Redirect to dev]
        TC_BPF_INGRESS -->|TC_ACT_STOLEN| STOLEN[Consumed]
    end

    subgraph "tc Egress"
        APP_SEND[App sends] --> CLS_CLSACT_E[clsact qdisc]
        CLS_CLSACT_E --> TC_BPF_EGRESS[tc BPF program]
        TC_BPF_EGRESS --> NIC_OUT[Transmit]
    end

    style TC_BPF_INGRESS fill:#FF9800
    style TC_BPF_EGRESS fill:#FF9800

tc Action Return Codes

CodeValueDescription
TC_ACT_OK0Allow packet to continue
TC_ACT_RECLASSIFY1Reclassify and restart
TC_ACT_SHOT2Drop packet
TC_ACT_PIPE3Pipe to next action
TC_ACT_STOLEN4Consume packet (no stats)
TC_ACT_REDIRECT7Redirect to another interface

Attaching tc BPF Programs

# Create clsact qdisc (supports both ingress and egress)
sudo tc qdisc add dev eth0 clsact

# Attach BPF program to ingress
sudo tc filter add dev eth0 ingress bpf direct-action obj tc_prog.o sec classifier

# Attach BPF program to egress
sudo tc filter add dev eth0 egress bpf direct-action obj tc_prog.o sec classifier

# List attached filters
sudo tc filter show dev eth0 ingress

# Remove filter
sudo tc filter del dev eth0 ingress

tc BPF Program Example (C)

// tc_prog.c — Traffic classifier and packet modifier
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

// Map to store per-IP packet counts
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, __u32);      // source IP
    __type(value, __u64);    // packet count
} pkt_count SEC(".maps");

// Map to store rate limit state
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, __u32);      // source IP
    __type(value, __u64);    // timestamp of last packet
} rate_limit SEC(".maps");

SEC("classifier")
int tc_filter(struct __sk_buff *skb)
{
    void *data     = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    // Parse Ethernet header
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK;

    // Only process IPv4
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return TC_ACT_OK;

    // Parse IP header
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;

    __u32 src_ip = ip->saddr;

    // Update packet count
    __u64 *count = bpf_map_lookup_elem(&pkt_count, &src_ip);
    if (count) {
        __sync_fetch_and_add(count, 1);
    } else {
        __u64 init_val = 1;
        bpf_map_update_elem(&pkt_count, &src_ip, &init_val, BPF_ANY);
    }

    // Simple rate limiting: drop if too frequent
    __u64 now = bpf_ktime_get_ns();
    __u64 *last = bpf_map_lookup_elem(&rate_limit, &src_ip);
    if (last && (now - *last) < 1000000) {  // 1ms interval
        return TC_ACT_SHOT;  // Drop
    }
    bpf_map_update_elem(&rate_limit, &src_ip, &now, BPF_ANY);

    // Parse TCP header for port-based decisions
    if (ip->protocol == IPPROTO_TCP) {
        struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
        if ((void *)(tcp + 1) > data_end)
            return TC_ACT_OK;

        // Mark SSH traffic with a priority (modify DSCP)
        if (tcp->dest == __constant_htons(22)) {
            ip->tos = (ip->tos & 0x03) | 0x20;  // DSCP = 8
        }
    }

    return TC_ACT_OK;
}

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

Using libbpf to Load tc Programs

// loader.c — Load and attach tc BPF program
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <net/if.h>
#include <linux/pkt_cls.h>

int main(int argc, char **argv)
{
    struct bpf_object *obj;
    struct bpf_program *prog;
    int ifindex = if_nametoindex("eth0");

    // Open and load BPF object
    obj = bpf_object__open_file("tc_prog.o", NULL);
    bpf_object__load(obj);

    // Find the classifier program
    prog = bpf_object__find_program_by_name(obj, "tc_filter");

    // Attach to tc ingress using libbpf's tc attach helper
    DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook,
        .ifindex = ifindex,
        .attach_point = BPF_TC_INGRESS,
    );
    bpf_tc_hook_create(&hook);

    DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts,
        .handle = 1,
        .priority = 1,
        .prog_fd = bpf_program__fd(prog),
    );
    bpf_tc_attach(&hook, &opts);

    printf("tc BPF program attached to eth0 ingress\n");
    // ... cleanup
    return 0;
}

sock_ops Programs

sock_ops programs attach to the TCP state machine, providing hooks at every stage of a TCP connection’s lifecycle. They can observe and modify socket options, congestion control parameters, and connection metadata.

sock_ops Hooks

stateDiagram-v2
    [*] --> SOCK_OPS_CREATE: BPF_SOCK_OPS_TCP_CONNECT_CB
    SOCK_OPS_CREATE --> SYN_SENT: BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB
    SYN_SENT --> ESTABLISHED: BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB
    ESTABLISHED --> DATA_TRANSFER: BPF_SOCK_OPS_DATA_CB
    DATA_TRANSFER --> DATA_TRANSFER: BPF_SOCK_OPS_RWND_CB
    DATA_TRANSFER --> DATA_TRANSFER: BPF_SOCK_OPS_RTO_CB
    DATA_TRANSFER --> DATA_TRANSFER: BPF_SOCK_OPS_RETRANS_CB
    ESTABLISHED --> FIN_WAIT: BPF_SOCK_OPS_TCP_CONNECT_CB
    FIN_WAIT --> [*]

sock_ops Callback Types

CallbackWhenUse Case
BPF_SOCK_OPS_TCP_CONNECT_CBActive connectSet CC algorithm per-connection
BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CBSYN-ACK receivedConnection tracking
BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CBPassive openServer-side tracking
BPF_SOCK_OPS_RWND_CBWindow updateFlow control tuning
BPF_SOCK_OPS_RTO_CBRetransmission timeoutCongestion monitoring
BPF_SOCK_OPS_RETRANS_CBRetransmissionLoss detection
BPF_SOCK_OPS_STATE_CBState changeConnection lifecycle

sock_ops Program Example

// sock_ops_monitor.c — TCP connection monitoring
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/bpf.h>

// Store connection info
struct conn_info {
    __u32 saddr;
    __u32 daddr;
    __u16 sport;
    __u16 dport;
    __u32 bytes_sent;
    __u32 bytes_received;
    __u32 retransmissions;
    __u64 established_ns;
};

struct {
    __uint(type, BPF_MAP_TYPE_SK_STORAGE);
    __uint(map_flags, BPF_F_NO_PREALLOC);
    __type(key, int);
    __type(value, struct conn_info);
} conn_map SEC(".maps");

SEC("sockops")
int tcp_monitor(struct bpf_sock_ops *skops)
{
    int op = skops->op;
    struct conn_info *info;

    switch (op) {
    case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:
    case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
        // Initialize connection tracking
        info = bpf_sk_storage_get(&conn_map, skops->sk, 0,
                                  BPF_SK_STORAGE_GET_F_CREATE);
        if (!info)
            break;

        info->saddr = skops->local_ip4;
        info->daddr = skops->remote_ip4;
        info->sport = skops->local_port;
        info->dport = bpf_ntohl(skops->remote_port);
        info->bytes_sent = 0;
        info->bytes_received = 0;
        info->retransmissions = 0;
        info->established_ns = bpf_ktime_get_ns();

        // Set congestion control algorithm for this connection
        bpf_setsockopt(skops, SOL_TCP, TCP_CONGESTION,
                       "bbr", sizeof("bbr"));
        break;

    case BPF_SOCK_OPS_RETRANS_CB:
        info = bpf_sk_storage_get(&conn_map, skops->sk, 0, 0);
        if (info)
            info->retransmissions++;
        break;

    case BPF_SOCK_OPS_STATE_CB:
        if (skops->args[1] == BPF_TCP_CLOSE) {
            // Connection closing — could push to perf event
            bpf_sk_storage_delete(&conn_map, skops->sk);
        }
        break;
    }

    return 1;
}

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

Attaching sock_ops Programs

# Load and attach to a cgroup (all sockets in the cgroup)
bpftool prog load sock_ops_monitor.o /sys/fs/bpf/sock_ops_monitor
bpftool net attach sockops id $(bpftool prog show name tcp_monitor -j | jq .id) \
    pinned /sys/fs/cgroup/unified/

# Or with libbpf:
# struct bpf_link *link = bpf_program__attach_sockops(prog, cgroup_fd);

sk_msg Programs

sk_msg programs intercept sendmsg() and sendfile() calls, allowing BPF programs to redirect messages between sockets without passing through the full network stack. This is the foundation of BPF-based socket-level proxies and load balancers.

sk_msg Architecture

graph LR
    subgraph "Without sk_msg"
        APP1[App A] -->|sendmsg| TCP1[TCP stack]
        TCP1 --> NIC1[NIC]
        NIC1 --> NET[Network]
        NET --> NIC2[NIC]
        NIC2 --> TCP2[TCP stack]
        TCP2 --> APP2[App B]
    end

    subgraph "With sk_msg Redirect"
        APP3[App A] -->|sendmsg| SK_MSG[sk_msg BPF]
        SK_MSG -->|bpf_msg_redirect_hash| SK_B[Socket B]
        SK_B --> APP4[App B]
    end

    style SK_MSG fill:#4CAF50

sk_msg Program Example

// sk_msg_redirect.c — Redirect messages between sockets
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/tcp.h>

// Map storing socket references for redirect
struct {
    __uint(type, BPF_MAP_TYPE_SOCKHASH);
    __uint(max_entries, 65535);
    __type(key, __u32);    // cookie or identifier
    __type(value, __u64);  // socket cookie
} sock_map SEC(".maps");

SEC("sk_msg")
int sk_msg_redirect(struct sk_msg_md *msg)
{
    __u32 key = msg->local_port;  // Use local port as key

    // Look up destination socket
    __u64 *dst_cookie = bpf_map_lookup_elem(&sock_map, &key);
    if (!dst_cookie)
        return SK_PASS;  // No redirect, pass normally

    // Redirect message to the target socket
    bpf_msg_redirect_hash(msg, &sock_map, &key, BPF_F_INGRESS);

    return SK_PASS;
}

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

Use Cases for sk_msg

  1. Service mesh sidecar proxy — Redirect app traffic to a proxy socket
  2. TCP splicing — Forward data between connections without copying to userspace
  3. Load balancing — Distribute connections across backend sockets
  4. Protocol-aware routing — Inspect payload to route to different backends
// Protocol-aware routing example
SEC("sk_msg")
int protocol_router(struct sk_msg_md *msg)
{
    // Check first bytes of payload for HTTP method
    char method[4] = {};
    bpf_msg_pull_data(msg, 0, 4, 0);
    if (msg->data_end - msg->data >= 4) {
        __builtin_memcpy(method, (void *)msg->data, 4);

        __u32 key;
        if (method[0] == 'G' && method[1] == 'E' && method[2] == 'T') {
            key = 1;  // GET backend
        } else if (method[0] == 'P' && method[1] == 'O') {
            key = 2;  // POST backend
        } else {
            key = 0;  // Default backend
        }

        bpf_msg_redirect_hash(msg, &sock_map, &key, BPF_F_INGRESS);
    }

    return SK_PASS;
}

sk_lookup Programs

sk_lookup programs (also called BPF_PROG_TYPE_SK_LOOKUP) run when the kernel needs to find a socket to deliver an incoming packet to. They override the default socket lookup, enabling custom dispatch logic for multi-listener servers and socket sharing across processes.

Problem sk_lookup Solves

graph TB
    subgraph "Traditional: Single Listener"
        PKT1["Incoming packet, port 80"] --> BIND1["bind, port 80"]
        BIND1 --> PROC1[Single process]
    end

    subgraph "SO_REUSEPORT: Round-Robin"
        PKT2[Incoming packet :80] --> RR[Round-robin]
        RR --> PROC2[Process 1 :80]
        RR --> PROC3[Process 2 :80]
        RR --> PROC4[Process 3 :80]
    end

    subgraph "sk_lookup: Custom Dispatch"
        PKT3[Incoming packet :80] --> SK_LU[sk_lookup BPF]
        SK_LU -->|by tenant| PROC5[Tenant A socket]
        SK_LU -->|by tenant| PROC6[Tenant B socket]
        SK_LU -->|fallback| PROC7[Default socket]
    end

    style SK_LU fill:#9C27B0

sk_lookup Program Example

// sk_lookup_multi.c — Dispatch connections by source IP
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/in.h>
#include <linux/net.h>
#include <linux/tcp.h>

// Map: tenant subnet → socket
struct {
    __uint(type, BPF_MAP_TYPE_SOCKMAP);
    __uint(max_entries, 256);
    __type(key, __u32);   // tenant ID (derived from source IP)
    __type(value, __u64); // socket cookie
} tenant_sockets SEC(".maps");

// Fallback socket
struct {
    __uint(type, BPF_MAP_TYPE_SOCKMAP);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, __u64);
} default_socket SEC(".maps");

SEC("sk_lookup")
int dispatch_by_tenant(struct bpf_sk_lookup *ctx)
{
    struct bpf_sock *sk;
    __u32 tenant_key;

    // Only handle TCP
    if (ctx->protocol != IPPROTO_TCP)
        return SK_PASS;

    // Derive tenant ID from source IP (simple example)
    // Real world: look up tenant mapping
    tenant_key = ctx->ip4 >> 24;  // First octet as rough grouping

    // Try tenant-specific socket first
    sk = bpf_sk_lookup_tcp(ctx, &tenant_key, sizeof(tenant_key),
                           BPF_F_CURRENT_NETNS, 0);
    if (sk) {
        bpf_sk_assign(ctx, sk, 0);
        bpf_sk_release(sk);
        return SK_PASS;
    }

    // Fallback to default socket
    __u32 def_key = 0;
    sk = bpf_sk_lookup_tcp(ctx, &def_key, sizeof(def_key),
                           BPF_F_CURRENT_NETNS, 0);
    if (sk) {
        bpf_sk_assign(ctx, sk, 0);
        bpf_sk_release(sk);
    }

    return SK_PASS;
}

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

Attaching sk_lookup

# Load program
bpftool prog load sk_lookup_multi.o /sys/fs/bpf/dispatch

# Attach to a network namespace
bpftool net attach sk_lookup pinned /sys/fs/bpf/dispatch tag <tag>

# With libbpf:
# link = bpf_program__attach_sk_lookup(prog, netns_fd);

SO_REUSEPORT BPF Programs

SO_REUSEPORT allows multiple sockets to bind to the same port. The kernel normally distributes incoming connections across them using a hash. With a BPF program attached, you can implement custom load-balancing logic.

BPF_PROG_TYPE_SK_REUSEPORT

graph LR
    PKT[Incoming packet] --> CONN_MAP{Hash-based<br>connection table}
    CONN_MAP --> EXISTING[Existing connection]
    CONN_MAP --> NEW_CONN[New connection]

    NEW_CONN --> REUSE_PROG[BPF reuseport program]
    REUSE_PROG --> SOCK_A[Socket A]
    REUSE_PROG --> SOCK_B[Socket B]
    REUSE_PROG --> SOCK_C[Socket C]

    style REUSE_PROG fill:#2196F3

SO_REUSEPORT BPF Example

// reuseport_lb.c — Application-level load balancer
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>

struct {
    __uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
    __uint(max_entries, 32);
    __type(key, __u32);
    __type(value, __u64);  // socket cookie
} socks SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, __u32);  // round-robin counter
} rr_state SEC(".maps");

SEC("sk_reuseport")
int reuseport_select(struct sk_reuseport_md *ctx)
{
    // Get number of available sockets
    __u32 num_socks = ctx->max_sockets;
    if (num_socks == 0)
        return SK_PASS;

    // Round-robin selection
    __u32 key = 0;
    __u32 *counter = bpf_map_lookup_elem(&rr_state, &key);
    if (!counter)
        return SK_PASS;

    __u32 idx = __sync_fetch_and_add(counter, 1) % num_socks;

    // Select the socket at index idx
    int *selected = bpf_map_lookup_elem(&socks, &idx);
    if (selected) {
        return SK_PASS;  // Kernel uses this socket
    }

    return SK_PASS;  // Fallback to default
}

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

Setting Up SO_REUSEPORT with BPF

// Server-side setup
int main(void)
{
    int num_workers = 4;

    // Create reuseport socket group
    for (int i = 0; i < num_workers; i++) {
        int fd = socket(AF_INET, SOCK_STREAM, 0);

        int opt = 1;
        setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

        struct sockaddr_in addr = {
            .sin_family = AF_INET,
            .sin_port = htons(8080),
            .sin_addr.s_addr = INADDR_ANY,
        };
        bind(fd, (struct sockaddr *)&addr, sizeof(addr));

        // Add socket to BPF map
        __u32 key = i;
        __u64 cookie;
        bpf_getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, sizeof(cookie));
        bpf_map_update_elem(sock_map_fd, &key, &cookie, BPF_ANY);

        // Attach reuseport BPF program
        setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF,
                   &prog_fd, sizeof(prog_fd));

        listen(fd, 128);

        // Fork worker process
        if (fork() == 0) {
            // Worker: accept and handle connections
            while (1) {
                int conn = accept(fd, NULL, NULL);
                handle_connection(conn);
            }
        }
    }

    // Parent: wait for workers
    while (1) wait(NULL);
}

Combined BPF Networking Pipeline

Real-world BPF networking solutions combine multiple hook types:

graph TB
    subgraph "Full BPF Pipeline"
        NIC_IN[NIC RX] --> XDP_PROG["XDP<br>(DDoS filter)"]
        XDP_PROG -->|XDP_PASS| TC_IN["tc ingress<br>(L4 load balancer)"]
        TC_IN -->|redirect| TC_OUT["tc egress<br>(other NIC)"]
        TC_IN -->|pass| SK_LOOKUP_PROG["sk_lookup<br>(socket dispatch)"]
        SK_LOOKUP_PROG --> REUSE_PROG["SO_REUSEPORT BPF<br>(worker selection)"]
        REUSE_PROG --> WORKER[Worker process]
        WORKER --> SOCK_OPS_PROG["sock_ops<br>(CC tuning)"]
        SOCK_OPS_PROG --> SK_MSG_PROG["sk_msg<br>(connection proxy)"]
        SK_MSG_PROG --> TC_OUT2["tc egress<br>(NAT/modification)"]
        TC_OUT2 --> NIC_OUT[NIC TX]
    end

    style XDP_PROG fill:#F44336
    style TC_IN fill:#FF9800
    style SK_LOOKUP_PROG fill:#9C27B0
    style REUSE_PROG fill:#2196F3
    style SOCK_OPS_PROG fill:#4CAF50
    style SK_MSG_PROG fill:#00BCD4

Cilium’s BPF Pipeline (Example)

Cilium uses all these hooks for its service mesh:

# Cilium's BPF programs (simplified)
bpftool prog show | grep cilium

# XDP: DDoS protection and fast-path forwarding
# tc ingress: Service load balancing (L3/L4), NAT
# sock_ops: Connection tracking, socket-level load balancing
# sk_msg: Transparent proxy (socket-level redirect)
# sk_lookup: Socket dispatch for multi-listener
# cgroup/connect4/6: Intercept connect() for service resolution
# cgroup/sendmsg4/6: Intercept UDP for service resolution

Performance Considerations

BPF vs. iptables/nftables

graph LR
    subgraph "iptables Path"
        IP_PKT1[Packet] --> IP_TABLES[iptables rules]
        IP_TABLES --> IP_MATCH{Linear match}
        IP_MATCH --> IP_VERDICT[Drop/Accept]
    end

    subgraph "BPF Path"
        BPF_PKT1[Packet] --> BPF_PROG[JIT-compiled BPF]
        BPF_PROG --> BPF_MAP["Map lookup O(1)"]
        BPF_MAP --> BPF_VERDICT[Drop/Accept/Redirect]
    end

    style IP_MATCH fill:#F44336
    style BPF_MAP fill:#4CAF50
Aspectiptables/nftablesBPF
MatchingLinear chain traversalO(1) map lookup
ProgrammabilityFixed rule syntaxFull C/Rust programs
JITNoYes, native code
RedirectComplex (ip rule, etc.)bpf_redirect()
Per-packet overhead~100ns+~10-50ns
State trackingconntrack (kernel)Custom BPF maps

Map Performance Tips

// Use per-CPU maps for high-frequency counters
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_HASH);
    __uint(max_entries, 100000);
    __type(key, __u32);
    __type(value, struct stats);
} percpu_stats SEC(".maps");

// Use LRU maps for automatic eviction
struct {
    __uint(type, BPF_MAP_TYPE_LRU_HASH);
    __uint(max_entries, 1000000);
    __type(key, __u32);
    __type(value, struct flow_data);
} flow_table SEC(".maps");

// Use array maps for fixed-index fast lookup
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 256);
    __type(key, __u32);
    __type(value, __u64);
} counters SEC(".maps");

BPF CO-RE for Networking

BPF Compile Once — Run Everywhere (CO-RE) allows networking programs to work across kernel versions:

// CO-RE: Access kernel struct fields portably
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

SEC("sockops")
int co_re_sockops(struct bpf_sock_ops *skops)
{
    // Access kernel struct tcp_sock fields via CO-RE
    struct tcp_sock *tcp = (void *)skops->sk;

    // BPF_CORE_READ handles field offsets across kernel versions
    __u32 snd_cwnd = BPF_CORE_READ(tcp, snd_cwnd);
    __u32 srtt = BPF_CORE_READ(tcp, srtt_us);

    // Store metrics
    struct tcp_metrics metrics = {
        .cwnd = snd_cwnd,
        .rtt = srtt >> 3,  // srtt is in 1/8 microsecond units
    };

    return 1;
}

Tools for BPF Networking

bpftool

# List all BPF programs
bpftool prog show

# List all maps
bpftool map show

# Dump map contents
bpftool map dump name pkt_count

# Show program attached to network
bpftool net show

# Pin a program for persistence
bpftool prog pin id 42 /sys/fs/bpf/my_prog

# Attach to tc
bpftool net attach tc pinned /sys/fs/bpf/my_prog dev eth0 ingress

bpftrace One-Liners

# Trace all TCP connections
bpftrace -e 'kprobe:tcp_connect { printf("%s → %s\n", comm, ntop(((struct sock *)arg0)->__sk_common.skc_daddr)); }'

# Count packets per protocol
bpftrace -e 'tracepoint:net:netif_receive_skb { @proto[args->protocol] = count(); }'

# Trace socket-level events
bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%s %s → %s\n", comm, args->oldstate, args->newstate); }'

# Measure TCP retransmissions
bpftrace -e 'kprobe:tcp_retransmit_skb { @retrans[comm] = count(); }'

Kernel Source References

FileDescription
net/core/filter.ctc BPF, socket BPF program types
net/core/sock_map.csock_ops, sk_msg, sock map
net/core/skmsg.csk_msg infrastructure
net/ipv4/tcp_bpf.cTCP-specific BPF hooks
net/core/bpf_sk_lookup.csk_lookup program type
tools/testing/selftests/bpf/BPF selftests (many networking examples)

Further Reading