1. eBPF คืออะไร
eBPF (extensed Berkeley Packet Filter) เป็นระบบย่อยเคอร์เนล Linux ที่ให้คุณรันโปรแกรมแบบแซนด์บ็อกซ์ภายในเคอร์เนลโดยไม่ต้องแก้ไขซอร์สโค้ดเคอร์เนลหรือโหลดโมดูลเคอร์เนล โปรแกรมได้รับการตรวจสอบโดยตัวตรวจสอบรหัสไบต์ของเคอร์เนลก่อนดำเนินการ จึงมั่นใจในความปลอดภัย
สำหรับระบบเครือข่าย โปรแกรม eBPF จะแนบไปกับจุดเกี่ยวในสแต็กเครือข่ายของเคอร์เนล และสามารถตรวจสอบ แก้ไข เปลี่ยนเส้นทาง หรือปล่อยแพ็กเก็ตได้ ข้อได้เปรียบที่สำคัญมากกว่าiptablesหรือโมดูลเคอร์เนลคือประสิทธิภาพและความสามารถในการตั้งโปรแกรม: โปรแกรม eBPF ได้รับการคอมไพล์ด้วย JIT เป็นโค้ดเนทีฟและสามารถแชร์สถานะผ่านแผนที่(ร้านค้าคีย์-ค่าที่ใช้ร่วมกันระหว่างเคอร์เนลและพื้นที่ผู้ใช้)
| ตะขอ | ที่ตั้ง | เวลาแฝง | ใช้กรณี |
|---|---|---|---|
| เอ็กซ์ดีพี | ไดรเวอร์ NIC ก่อนการจัดสรร sk_buff | ต่ำสุด | DDoS ลดลง ปรับสมดุลโหลด |
| tc ทางเข้า/ออก | หลังจากการจัดสรร sk_buff | ต่ำ | การกำหนดลักษณะการจราจร การทำเครื่องหมาย การเปลี่ยนเส้นทาง |
| ตัวกรองซ็อกเก็ต | เส้นทางรับซ็อกเก็ต | ปานกลาง | การกรองสไตล์ tcpdump |
| kprobe/จุดติดตาม | การเข้า/ออกฟังก์ชันเคอร์เนล | แตกต่างกันไป | การสังเกตการติดตาม |
2. คะแนนตะขอ XDP
โปรแกรม XDP (eXpress Data Path) จะทำงานที่จุดแรกสุดที่เป็นไปได้ในสแต็กเครือข่าย — ภายในไดรเวอร์ NIC ก่อนที่เคอร์เนลจะจัดสรรsk_buff. ซึ่งหมายความว่า:
- XDP ดั้งเดิม: ไดรเวอร์รองรับ XDP โดยกำเนิด (Intel i40e, Mellanox mlx5 ฯลฯ ) เร็วที่สุด — ทำงานในบริบทของไดรเวอร์
- XDP ทั่วไป: ทางเลือกสำรองสำหรับไดรเวอร์ที่ไม่มีการสนับสนุนแบบเนทิฟ วิ่งตาม
sk_buffการจัดสรร — ยังเร็วกว่า iptables แต่ไม่เร็วเท่าเนทิฟ - XDP ที่ออฟโหลดแล้ว: โปรแกรมทำงานบน NIC ASIC นั่นเอง ต้องใช้ฮาร์ดแวร์ SmartNIC (เช่น Netronome) ต้นทุน CPU เป็นศูนย์
โปรแกรม XDP ส่งคืนหนึ่งในห้าคำตัดสิน:
| รหัสส่งคืน | การกระทำ |
|---|---|
XDP_DROP | วางแพ็กเก็ตทันที — ละทิ้งเวลาแฝงต่ำสุด |
XDP_PASS | ส่งผ่านไปยังสแต็กเครือข่ายปกติ |
XDP_TX | ส่งกลับอินเทอร์เฟซเดียวกัน (เด้ง) |
XDP_REDIRECT | เปลี่ยนเส้นทางไปยังอินเทอร์เฟซอื่นหรือซ็อกเก็ต AF_XDP |
XDP_ABORTED | เส้นทางข้อผิดพลาด — ปล่อยพร้อมกับเหตุการณ์การติดตาม |
3. ตัวอย่างการวางแพ็คเก็ต XDP
โปรแกรมต่อไปนี้ปล่อยแพ็กเก็ต UDP ทั้งหมดจาก IP ต้นทางที่จัดเก็บไว้ในแมป eBPF ทำให้ส่วนควบคุมพื้นที่ผู้ใช้อัปเดตรายการบล็อกขณะรันไทม์
// xdp_drop_udp.c — Drop UDP from IPs in a BPF map
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
// BPF map: src IP → drop flag (1 = drop)
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32); // source IPv4 address
__type(value, __u32); // 1 = block
} blocklist SEC(".maps");
SEC("xdp")
int xdp_drop_udp(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
// Parse Ethernet header
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS;
// Parse IPv4 header
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return XDP_PASS;
if (ip->protocol != IPPROTO_UDP) return XDP_PASS;
// Check blocklist map
__u32 src = ip->saddr;
__u32 *val = bpf_map_lookup_elem(&blocklist, &src);
if (val && *val == 1) return XDP_DROP;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
data_end. การดำเนินการทางคณิตศาสตร์ของพอยน์เตอร์ทุกครั้งจะต้องตามด้วยการตรวจสอบขอบเขต ไม่เช่นนั้นโปรแกรมจะไม่โหลดโหลดแล้วแนบไปกับip:
# Compile
clang -O2 -target bpf -c xdp_drop_udp.c -o xdp_drop_udp.o
# Attach to interface (native XDP)
ip link set eth0 xdp obj xdp_drop_udp.o sec xdp
# Add an IP to the blocklist via bpftool
bpftool map update name blocklist key 0x01 0x02 0x03 0x04 value 0x01 0x00 0x00 0x00
# Remove XDP program
ip link set eth0 xdp off
4. AF_XDP: เคอร์เนล-บายพาส
AF_XDPเป็นตระกูลซ็อกเก็ตที่รวมกับ XDPXDP_REDIRECTคำตัดสิน ส่งแพ็กเก็ตโดยตรงไปยังพื้นที่หน่วยความจำผู้ใช้ (UMEM) โดยไม่เกี่ยวข้องกับเคอร์เนลต่อแพ็กเก็ต นี่คือคำตอบของระบบนิเวศ eBPF สำหรับโมเดลเคอร์เนลบายพาสของ DPDK
ส่วนประกอบสำคัญ:
- อูเมม: ขอบเขตหน่วยความจำที่ลงทะเบียนโดยผู้ใช้แบ่งออกเป็นเฟรม แชร์ระหว่างเคอร์เนลและพื้นที่ผู้ใช้ผ่านหน่วยความจำที่ใช้ร่วมกัน
- แหวน: วงแหวนที่ไม่มีการล็อคสี่วงต่อซ็อกเก็ต: เติม (พื้นที่ผู้ใช้ → เคอร์เนลพร้อมเฟรมอิสระ), การเสร็จสมบูรณ์ (เคอร์เนล → พื้นที่ผู้ใช้พร้อมเฟรมที่ทำ TX), วงแหวน RX (เคอร์เนล → พื้นที่ผู้ใช้พร้อมเฟรมที่ได้รับ), วงแหวน TX (พื้นที่ผู้ใช้ → เคอร์เนลพร้อมเฟรมที่จะส่ง)
- โหมดการทำสำเนาเป็นศูนย์: หากไดรเวอร์รองรับ เฟรมจะถูกถ่ายโอนโดยไม่มีการคัดลอกใดๆ — เพียงแค่ส่งต่อพอยน์เตอร์เท่านั้น
AF_XDP เหมาะอย่างยิ่งสำหรับการประมวลผลแพ็กเก็ตแบบกำหนดเองในอัตราบรรทัดโดยไม่มีความซับซ้อนในการดำเนินงานของ DPDK (ไม่มีเพจขนาดใหญ่ ไม่จำเป็นต้องปักหมุด CPU สำหรับการใช้งานขั้นพื้นฐาน)
5. tc BPF: การกำหนดรูปแบบและการกรองการรับส่งข้อมูล
tc(ควบคุมการจราจร) โปรแกรม BPF แนบอยู่ที่clsactqdisc และสามารถรันบนทางเข้าหรือทางออกได้ ต่างจาก XDP พวกเขาเห็นอย่างครบถ้วนsk_buffและสามารถเข้าถึงข้อมูลเมตาของซ็อกเก็ต, VLAN และส่วนหัวของช่องสัญญาณได้
// tc_mark.c — Mark packets with DSCP EF (46) for VoIP traffic on port 5060
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
SEC("classifier")
int tc_mark_voip(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return TC_ACT_OK;
if (eth->h_proto != __constant_htons(ETH_P_IP)) return TC_ACT_OK;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return TC_ACT_OK;
if (ip->protocol != IPPROTO_UDP) return TC_ACT_OK;
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return TC_ACT_OK;
// Mark SIP traffic (port 5060) with DSCP EF (46 = 0xB8 in TOS byte)
if (udp->dest == __constant_htons(5060) || udp->source == __constant_htons(5060)) {
// DSCP EF = 46, shifted left 2 bits in TOS field = 184 (0xB8)
bpf_skb_store_bytes(skb, offsetof(struct iphdr, tos) + sizeof(struct ethhdr),
&((__u8){184}), 1, BPF_F_RECOMPUTE_CSUM);
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
# Attach tc BPF program
tc qdisc add dev eth0 clsact
tc filter add dev eth0 egress bpf da obj tc_mark.o sec classifier
6. การจำกัดอัตราด้วย eBPF Maps
แผนที่ eBPF ช่วยให้สามารถประมวลผลแบบมีสถานะได้ รูปแบบต่อไปนี้ใช้การจำกัดอัตรา IP ต่อแหล่งที่มาโดยใช้ที่เก็บข้อมูลโทเค็นที่จัดเก็บไว้ในBPF_MAP_TYPE_LRU_HASH:
// Conceptual token bucket per source IP — checks tokens, drops if exceeded
struct ratelimit_entry {
__u64 tokens; // current token count
__u64 last_update; // nanoseconds timestamp
};
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32); // source IP
__type(value, struct ratelimit_entry);
} rate_map SEC(".maps");
// In XDP program:
// 1. bpf_ktime_get_ns() — get current time
// 2. Lookup entry for src IP
// 3. Refill tokens: tokens += (elapsed_ns / 1e9) * rate_pps
// 4. If tokens >= 1: decrement and XDP_PASS
// 5. Else: XDP_DROP
7. bpftool & วิปัสสนา bpftrace
เครื่องมือสำคัญสองประการสำหรับการทำงานกับโปรแกรม eBPF แบบสด:
# bpftool — inspect loaded programs and maps
bpftool prog list # list all loaded eBPF programs
bpftool prog show id 42 # details for program ID 42
bpftool prog dump xlated id 42 # disassemble to eBPF bytecode
bpftool prog dump jited id 42 # dump JIT-compiled native code
bpftool map list # list all BPF maps
bpftool map dump name blocklist # dump all entries in map "blocklist"
bpftool map update name blocklist \
key 192 168 1 100 value 1 0 0 0 # add entry (network byte order)
# bpftrace — DTrace-style one-liners for kernel tracing
# Count XDP drops per second
bpftrace -e 'tracepoint:xdp:xdp_exception { @drops[args->action] = count(); } interval:s:1 { print(@drops); clear(@drops); }'
# Trace tcp_retransmit_skb — show retransmit events with comm name
bpftrace -e 'kprobe:tcp_retransmit_skb { printf("%s retransmit\n", comm); }'
# Histogram of packet sizes on eth0
bpftrace -e 'tracepoint:net:netif_receive_skb /args->name == "eth0"/ { @size = hist(args->len); }'
8. การเปรียบเทียบ: eBPF/XDP กับ DPDK กับ RDMA
| คุณสมบัติ | eBPF/XDP | กปปส | อาร์ดีเอ็มเอ |
|---|---|---|---|
| การมีส่วนร่วมของเคอร์เนล | ขั้นต่ำ (XDP ในไดรเวอร์) | ไม่มี (บายพาสเต็ม) | ไม่มี (RDMA NIC) |
| โมเดลหน่วยความจำ | มาตรฐาน + AF_XDP UMEM | จำเป็นต้องมีหน้าขนาดใหญ่ | ขอบเขตหน่วยความจำที่ลงทะเบียน |
| ปริมาณงานสูงสุด | ~100 Gbps XDP เนทิฟ | >100 กิกะบิตต่อวินาที | 200+ Gbps (อินฟินิแบนด์) |
| การใช้งานซีพียู | ต่ำ (ขับเคลื่อนด้วยเหตุการณ์) | สูง (แกนสำรวจไม่ว่าง) | ใกล้ศูนย์ (ออฟโหลด) |
| ความซับซ้อนของปฏิบัติการ | ต่ำ — เครื่องมือมาตรฐาน | สูง — คอร์เฉพาะ, หน้าใหญ่มาก | สูง — การจัดการผ้า |
| กรณีการใช้งาน | การบรรเทา DDoS, LB, ความสามารถในการสังเกต | เราเตอร์เสมือน, NFV, แพ็กเก็ตเจน | อุปกรณ์จัดเก็บข้อมูล (NVMe-oF), HPC MPI |
| ภาษา | จำกัด C / สนิม | C / สนิม | กริยา API (C) |