亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

EBPF tc編程入門與踩坑記錄

2025-06-06 08:26:33
19
0

什么是eBPF和tc?

eBPF簡介

eBPF是一種革命性的內核技術,它允許用戶在不修改內核源代碼或load內核模塊的情況下,安全高效地擴展內核功能。eBPF程序運行在內核的沙盒環境中,通過驗證器確保其安全性。

tc簡介

tc(Traffic Control)是Linux內核提供的網絡流量控制工具,用于實現QoS(服務質量)功能,包括流量整形、調度和過濾等。tc提供了豐富的分類器和動作,而eBPF可以作為一種強大的分類器。

為什么選擇eBPF+tc?

  1. 高性能:eBPF程序在內核空間運行,avoid了用戶空間和內核空間之間的上下文切換

  2. 安全性:所有eBPF程序都必須通過驗證器的嚴格檢查

  3. 靈活性:可以動態load和unload,無需重啟系統

  4. 可編程性:使用C等高級語言編寫,比傳統tc命令更強大

開發環境準備

在開始編寫eBPF tc程序前,我們需要準備以下環境:

# 安裝必要的工具
sudo apt-get update
sudo apt-get install -y clang llvm libbpf-dev libelf-dev build-essential linux-tools-common linux-tools-generic

# 檢查內核eBPF支持
sudo grep -i ebpf /boot/config-$(uname -r)

第一個eBPF tc程序

讓我們從一個簡單的例子開始:統計通過某個網絡接口的TCP SYN包數量。

1. 編寫eBPF程序

創建文件syn_counter.c

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <bpf/bpf_helpers.h>

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

SEC("classifier")
int count_syn(struct __sk_buff *skb) {
    void *data_end = (void *)(long)skb->data_end;
    void *data = (void *)(long)skb->data;
    
    struct ethhdr *eth = data;
    if ((void *)eth + sizeof(*eth) > data_end)
        return TC_ACT_OK;
    
    if (eth->h_proto != htons(ETH_P_IP))
        return TC_ACT_OK;
    
    struct iphdr *ip = data + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return TC_ACT_OK;
    
    if (ip->protocol != IPPROTO_TCP)
        return TC_ACT_OK;
    
    struct tcphdr *tcp = (void *)ip + sizeof(*ip);
    if ((void *)tcp + sizeof(*tcp) > data_end)
        return TC_ACT_OK;
    
    if (tcp->syn && !tcp->ack) {
        __u32 key = 0;
        __u64 *count = bpf_map_lookup_elem(&syn_counter, &key);
        if (count) {
            (*count)++;
        }
    }
    
    return TC_ACT_OK;
}

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

2. 編譯eBPF程序

使用以下命令編譯:

clang -O2 -target bpf -c syn_counter.c -o syn_counter.o

3. load eBPF程序到tc

假設我們要監控eth0接口:

# 創建clsact qdisc(如果不存在)
sudo tc qdisc add dev eth0 clsact

# load eBPF程序到ingress方向
sudo tc filter add dev eth0 ingress bpf obj syn_counter.o sec classifier direct-action

# 查看load的filter
sudo tc filter show dev eth0 ingress

4. 查看統計結果

我們可以使用bpftool查看統計結果:

# 首先找到map的id
sudo bpftool map list

# 然后查看map內容(替換上面的map id)
sudo bpftool map dump id <map_id>

eBPF tc編程踩坑點

一般來講tc編程所需要用的helper函數都能在bpf.h里找到相應的注釋說明,但實際代碼過程中筆者依然遇到幾個較坑的點

1、解析數據包時,skb->data_end-skb->data要小于skb->len,這是由于數據包skb是非線性的,也就是data_end-data其實是線性區的大小,而skb->len是線性區加非線性區的大小,前者其實是skb->data_len,但是BPF程序中無法從__sk_buff中直接拿到這個值,我們可以使用bpf_skb_pull_data(skb,len)直接訪問非線性數據,這個helper函數的官方解釋如下

  • 如果 skb 是非線性的并且len 由線性和非線性部分組成,則pull入非線性數據,使得 skb 中的 len 字節可讀可寫,如果為 len 傳遞了一個零值,則拉取整個 skb 長度。
  • 此 helper 僅用于通過 direct packet access 進行讀取和寫入。
  • 對于direct packet access,如果偏移量無效,或者如果請求的數據在 skb 的非線性部分中,則訪問偏移量在數據包邊界內的測試(在 skb->data_end 上測試)很容易失敗。失敗時,程序可以退出,或者在非線性緩沖區的情況下,使用helper程序使數據可用。 bpf_skb_load_bytes() helper是訪問數據的第一個解決方案。另一種方法是使用 bpf_skb_pull_data 拉入一次非線性部分,然后重新測試并最終訪問數據。
  • 同時,這也保證了skb是未克隆的,這是direct write的必要條件。由于這僅需要是寫入部分的不變量,因此驗證程序檢測寫入并添加一個調用 bpf_skb_pull_data 的prologue??,以從一開始就有效地取消克隆 skb,以防它確實被克隆。
  • 對這個helper的調用很容易改變底層的數據包緩沖區。因此,在load時,如果helper與direct packet access結合使用,則verifier先前對指針所做的所有檢查都將無效并且必須再次執行。

2、tc程序對數據包頭部進行剪裁,并load到網卡的ingress方向,使用tcpdump抓包發現報文并未進行相應的修改,原因是入方向tcpdump抓包點要早于tc程序,在tc程序中使用重定向將報文發到另一張網卡再使用tcp dump抓包,可看到報文符合代碼處理邏輯。

0條評論
0 / 1000
c****4
4文章數
0粉絲數
c****4
4 文章 | 0 粉絲
原創

EBPF tc編程入門與踩坑記錄

2025-06-06 08:26:33
19
0

什么是eBPF和tc?

eBPF簡介

eBPF是一種革命性的內核技術,它允許用戶在不修改內核源代碼或load內核模塊的情況下,安全高效地擴展內核功能。eBPF程序運行在內核的沙盒環境中,通過驗證器確保其安全性。

tc簡介

tc(Traffic Control)是Linux內核提供的網絡流量控制工具,用于實現QoS(服務質量)功能,包括流量整形、調度和過濾等。tc提供了豐富的分類器和動作,而eBPF可以作為一種強大的分類器。

為什么選擇eBPF+tc?

  1. 高性能:eBPF程序在內核空間運行,avoid了用戶空間和內核空間之間的上下文切換

  2. 安全性:所有eBPF程序都必須通過驗證器的嚴格檢查

  3. 靈活性:可以動態load和unload,無需重啟系統

  4. 可編程性:使用C等高級語言編寫,比傳統tc命令更強大

開發環境準備

在開始編寫eBPF tc程序前,我們需要準備以下環境:

# 安裝必要的工具
sudo apt-get update
sudo apt-get install -y clang llvm libbpf-dev libelf-dev build-essential linux-tools-common linux-tools-generic

# 檢查內核eBPF支持
sudo grep -i ebpf /boot/config-$(uname -r)

第一個eBPF tc程序

讓我們從一個簡單的例子開始:統計通過某個網絡接口的TCP SYN包數量。

1. 編寫eBPF程序

創建文件syn_counter.c

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <bpf/bpf_helpers.h>

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

SEC("classifier")
int count_syn(struct __sk_buff *skb) {
    void *data_end = (void *)(long)skb->data_end;
    void *data = (void *)(long)skb->data;
    
    struct ethhdr *eth = data;
    if ((void *)eth + sizeof(*eth) > data_end)
        return TC_ACT_OK;
    
    if (eth->h_proto != htons(ETH_P_IP))
        return TC_ACT_OK;
    
    struct iphdr *ip = data + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return TC_ACT_OK;
    
    if (ip->protocol != IPPROTO_TCP)
        return TC_ACT_OK;
    
    struct tcphdr *tcp = (void *)ip + sizeof(*ip);
    if ((void *)tcp + sizeof(*tcp) > data_end)
        return TC_ACT_OK;
    
    if (tcp->syn && !tcp->ack) {
        __u32 key = 0;
        __u64 *count = bpf_map_lookup_elem(&syn_counter, &key);
        if (count) {
            (*count)++;
        }
    }
    
    return TC_ACT_OK;
}

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

2. 編譯eBPF程序

使用以下命令編譯:

clang -O2 -target bpf -c syn_counter.c -o syn_counter.o

3. load eBPF程序到tc

假設我們要監控eth0接口:

# 創建clsact qdisc(如果不存在)
sudo tc qdisc add dev eth0 clsact

# load eBPF程序到ingress方向
sudo tc filter add dev eth0 ingress bpf obj syn_counter.o sec classifier direct-action

# 查看load的filter
sudo tc filter show dev eth0 ingress

4. 查看統計結果

我們可以使用bpftool查看統計結果:

# 首先找到map的id
sudo bpftool map list

# 然后查看map內容(替換上面的map id)
sudo bpftool map dump id <map_id>

eBPF tc編程踩坑點

一般來講tc編程所需要用的helper函數都能在bpf.h里找到相應的注釋說明,但實際代碼過程中筆者依然遇到幾個較坑的點

1、解析數據包時,skb->data_end-skb->data要小于skb->len,這是由于數據包skb是非線性的,也就是data_end-data其實是線性區的大小,而skb->len是線性區加非線性區的大小,前者其實是skb->data_len,但是BPF程序中無法從__sk_buff中直接拿到這個值,我們可以使用bpf_skb_pull_data(skb,len)直接訪問非線性數據,這個helper函數的官方解釋如下

  • 如果 skb 是非線性的并且len 由線性和非線性部分組成,則pull入非線性數據,使得 skb 中的 len 字節可讀可寫,如果為 len 傳遞了一個零值,則拉取整個 skb 長度。
  • 此 helper 僅用于通過 direct packet access 進行讀取和寫入。
  • 對于direct packet access,如果偏移量無效,或者如果請求的數據在 skb 的非線性部分中,則訪問偏移量在數據包邊界內的測試(在 skb->data_end 上測試)很容易失敗。失敗時,程序可以退出,或者在非線性緩沖區的情況下,使用helper程序使數據可用。 bpf_skb_load_bytes() helper是訪問數據的第一個解決方案。另一種方法是使用 bpf_skb_pull_data 拉入一次非線性部分,然后重新測試并最終訪問數據。
  • 同時,這也保證了skb是未克隆的,這是direct write的必要條件。由于這僅需要是寫入部分的不變量,因此驗證程序檢測寫入并添加一個調用 bpf_skb_pull_data 的prologue??,以從一開始就有效地取消克隆 skb,以防它確實被克隆。
  • 對這個helper的調用很容易改變底層的數據包緩沖區。因此,在load時,如果helper與direct packet access結合使用,則verifier先前對指針所做的所有檢查都將無效并且必須再次執行。

2、tc程序對數據包頭部進行剪裁,并load到網卡的ingress方向,使用tcpdump抓包發現報文并未進行相應的修改,原因是入方向tcpdump抓包點要早于tc程序,在tc程序中使用重定向將報文發到另一張網卡再使用tcp dump抓包,可看到報文符合代碼處理邏輯。

文章來自個人專欄
文章 | 訂閱
0條評論
0 / 1000
請輸入你的評論
0
0