Skip to content

Lesson 12: eBPF TC Egress (Outbound Traffic Control)

📚 What is eBPF TC Egress?

TC (Traffic Control) is the Linux kernel's traffic control subsystem. eBPF TC Egress programs attach to the outbound direction of network interfaces for:

  • Filtering outbound network packets (drop, forward, modify)
  • Implementing outbound network policies (traffic shaping, rate limiting)
  • Outbound traffic monitoring and statistics (traffic analysis, QoS)
  • Outbound packet redirection (container networking, service mesh)

TC Egress Hook Point Location

                                        Egress (outbound)

    ... ──────► Routing ──────► Forwarding ─►│  ──────► Network
                                        TC Filter        Interface
                                   (BPF_PROG_TYPE_SCHED_CLS)

🎯 Learning Objectives

  1. Understand how TC Egress programs work
  2. Learn to write outbound packet filtering programs
  3. Master application scenarios for outbound traffic control
  4. Implement an example that limits outbound ICMP packets

1. Differences Between TC Egress and Ingress

FeatureTC IngressTC Egress
DirectionInbound (receiving)Outbound (sending)
Trigger PointAfter packet arrives at NICBefore packet leaves NIC
Typical UseFirewall, inbound filteringTraffic shaping, rate limiting
Visible DataPackets from external sourcesPackets generated by local host

2. Kernel-Space Program: Filtering Outbound ICMP Packets

2.1 Complete Code

File: tc_egress.bpf.c

c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_endian.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

// TC return value definitions
#define TC_ACT_UNSPEC      -1 // Use default behavior
#define TC_ACT_OK           0 // Allow through
#define TC_ACT_RECLASSIFY   1 // Reclassify
#define TC_ACT_SHOT         2 // Drop packet
#define TC_ACT_PIPE         3 // Pass to next action

#define ICMP_PROTOCOL 1

// Egress traffic filter function
// Note: libbpf < 1.0 uses SEC("classifier"), libbpf >= 1.0 can use SEC("tc")
SEC("classifier")
int tc_egress_filter(struct __sk_buff *skb)
{
    // Step 1: Get packet start and end positions
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    // Step 2: Parse Ethernet header
    struct ethhdr *eth = data;

    // Boundary check: ensure no out-of-bounds access
    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK;  // Packet too small, allow through

    // Step 3: Check if it's IP protocol (EtherType = 0x0800)
    if (eth->h_proto != bpf_htons(0x0800))
        return TC_ACT_OK;  // Not IPv4, allow through

    // Step 4: Parse IP header
    struct iphdr *ip = data + sizeof(struct ethhdr);

    // Boundary check: ensure IP header is within packet bounds
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;

    // Step 5: Filter ICMP protocol
    if (ip->protocol == ICMP_PROTOCOL) {
        // Extract source and destination IP
        __u32 src_ip = ip->saddr;
        __u32 dst_ip = ip->daddr;

        bpf_printk("TC Egress: Dropping ICMP packet: %pI4 -> %pI4\n",
                   &src_ip, &dst_ip);

        // Drop ICMP packet
        return TC_ACT_SHOT;
    }

    // Step 6: Allow other protocols
    return TC_ACT_OK;
}

2.2 Code Explanation

Key Point 1: Egress Processing Timing

c
SEC("classifier")
int tc_egress_filter(struct __sk_buff *skb)
  • TC Egress executes just before a packet leaves the network interface
  • Can intercept all outbound traffic generated by the local host
  • Suitable for implementing traffic shaping, rate limiting, etc.

Key Point 2: Section Name Compatibility

Important Note: Section name must be chosen based on libbpf version:

libbpf VersionRecommended Section NameNotes
< 1.0SEC("classifier") or SEC("tc")Only supports this format
>= 1.0SEC("tc") or SEC("tc/egress")Supports more explicit direction specification

For TC egress programs, both ingress and egress use the same section name in older libbpf versions. The direction is specified when attaching via BPF_TC_EGRESS.

Key Point 3: Packet Source

In the Egress direction:

  • Packets come from local applications (e.g., ping, curl)
  • Source IP (ip->saddr) is usually the local host IP
  • Destination IP (ip->daddr) is the remote host IP

Key Point 4: Application Scenarios

Typical TC Egress applications:

  1. Traffic Shaping: Limit outbound bandwidth for specific applications
  2. Protocol Filtering: Block outbound traffic of certain protocols
  3. Data Leakage Prevention: Monitor and control outbound sensitive data
  4. QoS: Set priorities for different types of traffic

3. User-Space Program

3.1 Complete Code

File: tc_egress.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <net/if.h>
#include "tc_egress.skel.h"

static volatile bool exiting = false;

static void sig_handler(int sig)
{
    exiting = true;
}

int main(int argc, char **argv)
{
    struct tc_egress_bpf *skel;
    int err;
    int ifindex;
    LIBBPF_OPTS(bpf_tc_hook, hook);
    LIBBPF_OPTS(bpf_tc_opts, opts_egress);

    // Step 1: Check arguments (need to specify network interface name)
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <ifname>\n", argv[0]);
        fprintf(stderr, "Example: %s eth0\n", argv[0]);
        return 1;
    }

    // Step 2: Get network interface index
    ifindex = if_nametoindex(argv[1]);
    if (ifindex == 0) {
        fprintf(stderr, "Failed to get ifindex for %s: %s\n",
                argv[1], strerror(errno));
        return 1;
    }
    printf("Attaching TC egress filter to interface: %s (ifindex=%d)\n",
           argv[1], ifindex);

    // Step 3: Set signal handlers
    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

    // Step 4: Open and load BPF program
    skel = tc_egress_bpf__open_and_load();
    if (!skel) {
        fprintf(stderr, "Failed to open and load BPF skeleton\n");
        return 1;
    }

    // Step 5: Create TC hook (egress direction)
    hook.ifindex = ifindex;
    hook.attach_point = BPF_TC_EGRESS;  // Outbound direction
    err = bpf_tc_hook_create(&hook);
    if (err && err != -EEXIST) {  // -EEXIST means hook already exists, can ignore
        fprintf(stderr, "Failed to create TC hook: %d\n", err);
        goto cleanup;
    }

    // Step 6: Attach egress program
    opts_egress.prog_fd = bpf_program__fd(skel->progs.tc_egress_filter);
    err = bpf_tc_attach(&hook, &opts_egress);
    if (err) {
        fprintf(stderr, "Failed to attach TC egress program: %d\n", err);
        goto cleanup;
    }
    printf("✓ Attached TC egress filter\n");

    // Step 7: Main loop - wait for exit signal
    printf("\nTC egress filter is running. Press Ctrl+C to exit.\n");
    printf("Try: ping 8.8.8.8 (outgoing ICMP requests will be dropped)\n\n");
    printf("View dropped packets: sudo cat /sys/kernel/debug/tracing/trace_pipe\n\n");

    // Wait for exit signal
    while (!exiting) {
        sleep(1);
    }

    printf("\nDetaching TC egress filter...\n");

    // Step 8: Cleanup egress
    opts_egress.flags = opts_egress.prog_fd = opts_egress.prog_id = 0;
    bpf_tc_detach(&hook, &opts_egress);

cleanup:
    tc_egress_bpf__destroy(skel);
    printf("TC egress filter detached successfully.\n");
    return err != 0;
}

3.2 Code Explanation

Key API Functions

  1. bpf_tc_hook_create() - Create TC hook

    c
    hook.ifindex = ifindex;
    hook.attach_point = BPF_TC_EGRESS;  // Note: set to EGRESS
    bpf_tc_hook_create(&hook);
  2. bpf_tc_attach() - Attach eBPF program

    c
    opts.prog_fd = bpf_program__fd(skel->progs.tc_egress_filter);
    bpf_tc_attach(&hook, &opts);
  3. bpf_tc_detach() - Detach eBPF program

    c
    bpf_tc_detach(&hook, &opts);

4. Compilation and Execution

4.1 Compilation Steps

bash
cd src/tc_egress
make

After successful compilation, the following files will be generated:

  • tc_egress - Executable program
  • ../.output/tc_egress.bpf.o - eBPF bytecode
  • ../.output/tc_egress.skel.h - Skeleton header file

4.2 Running Example

bash
# Check network interface name
ip addr show

# Run TC egress filter (requires root privileges)
sudo ./tc_egress ens33   # Replace with your network interface name (e.g., eth0, ens33)

# Test in another terminal
ping 8.8.8.8            # ICMP request packets will be dropped (ping will fail)
curl https://google.com # TCP traffic passes normally

Expected Behavior:

  • ping command will fail because ICMP request packets are dropped on egress
  • HTTP/HTTPS access works normally because only ICMP protocol is filtered

4.3 View Kernel Logs

bash
# View bpf_printk output
sudo cat /sys/kernel/debug/tracing/trace_pipe

Expected output:

tc_egress-12345 [001] .... 123456.789: TC Egress: Dropping ICMP packet: 192.168.1.100 -> 8.8.8.8

5. Practical Exercises

Exercise 1: Basic - Count Outbound Traffic

Task: Use a BPF Map to count the number of outbound packets for each protocol (TCP, UDP, ICMP).

Hint:

c
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 256);  // 256 protocol numbers
    __type(key, __u32);
    __type(value, __u64);
} protocol_stats SEC(".maps");

// In tc_egress_filter:
__u32 proto = ip->protocol;
__u64 *count = bpf_map_lookup_elem(&protocol_stats, &proto);
if (count) {
    __sync_fetch_and_add(count, 1);
}

Exercise 2: Intermediate - Limit Outbound HTTP Traffic

Task: Drop all outbound TCP packets with destination port 80 or 443.

Hint:

c
struct tcphdr *tcp = (void *)ip + sizeof(*ip);
if ((void *)(tcp + 1) > data_end)
    return TC_ACT_OK;

if (ip->protocol == 6) {  // TCP
    __u16 dport = bpf_ntohs(tcp->dest);
    if (dport == 80 || dport == 443) {
        bpf_printk("TC Egress: Blocking HTTP(S) to port %d\n", dport);
        return TC_ACT_SHOT;
    }
}

Exercise 3: Advanced - Implement Simple Rate Limiting

Task: Use timestamps and counters to limit outbound traffic to a maximum of 100 packets per second.

Hint:

c
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, struct rate_limit_info);
} rate_limit SEC(".maps");

struct rate_limit_info {
    __u64 last_time;
    __u64 count;
};

// Check rate in the program

6. Common Questions

Q1: How to view attached TC egress programs?

bash
# View TC filters
sudo tc filter show dev eth0 egress

# Using bpftool
sudo bpftool prog list
sudo bpftool net list

Q2: Does the Egress filter impact performance?

  • TC Egress executes in the kernel network stack with some performance overhead
  • But compared to userspace firewalls (like iptables), performance is better
  • For high-performance needs, consider XDP (but XDP only supports ingress)

Q3: Can I use Ingress and Egress simultaneously?

Yes! You can attach both ingress and egress programs to the same interface:

c
// Create ingress hook
hook.attach_point = BPF_TC_INGRESS;
bpf_tc_hook_create(&hook);
bpf_tc_attach(&hook, &opts_ingress);

// Create egress hook
hook.attach_point = BPF_TC_EGRESS;
bpf_tc_hook_create(&hook);
bpf_tc_attach(&hook, &opts_egress);

Q4: Can TC Egress modify packets?

Yes! Using helper functions like bpf_skb_store_bytes() you can modify packet contents:

c
// Modify destination IP
__u32 new_ip = bpf_htonl(0x08080808);  // 8.8.8.8
bpf_skb_store_bytes(skb, offset, &new_ip, sizeof(new_ip), 0);

7. Advanced Egress Application Scenarios

7.1 Traffic Shaping

Control outbound bandwidth for specific applications:

c
// Using token bucket algorithm
if (!has_tokens()) {
    return TC_ACT_SHOT;  // Drop packets exceeding rate limit
}
consume_token();
return TC_ACT_OK;

7.2 Data Leakage Prevention (DLP)

Monitor and block outbound sensitive data:

c
// Check packet contents
if (contains_sensitive_data(skb)) {
    bpf_printk("TC Egress: Blocked sensitive data leak\n");
    return TC_ACT_SHOT;
}

7.3 Service Mesh

Redirect outbound traffic to a proxy:

c
// Redirect to sidecar proxy
return bpf_redirect(proxy_ifindex, 0);

8. Reference Resources


Summary

Through this lesson, you should have mastered:

✅ Basic concepts and working principles of TC Egress programs ✅ How to write outbound packet filtering logic ✅ Advanced application scenarios for TC Egress ✅ How to use TC Egress APIs ✅ How to debug and test TC Egress programs

Next Steps:

  • Learn XDP (eXpress Data Path) for even higher performance packet processing
  • Explore combined use of TC and XDP
  • Deep dive into eBPF applications in container networking (e.g., Cilium)

Released under the MIT License.