
การใช้ eBPF/XDP สำหรับทำ DDos Mitigation แบบละเอียด
eBPF/XDP คือเทคโนโลยีด่านหน้าของ Linux ที่ช่วยป้องกัน DDoS ได้อย่างมีประสิทธิภาพสูงสุด ทำงานที่ระดับ Network Driver ทิ้งแพ็กเก็ตตั้งแต่ต้นทาง รองรับหลายสิบล้าน PPS ต่อ Core ปรับแต่ง Logic ได้ยืดหยุ่น ปลอดภัย และเหมาะกับการใช้งานจริง
การประยุกต์ใช้ eBPF/XDP สำหรับการป้องกันการโจมตีแบบ DDoS
การโจมตีแบบ Distributed Denial of Service (DDoS) เป็นหนึ่งในภัยคุกคามที่สำคัญที่สุดในโลกอินเทอร์เน็ตยุคปัจจุบัน ลักษณะของการโจมตีคือมีการส่ง ปริมาณข้อมูล (Traffic) จำนวนมหาศาล ไปยังเซิร์ฟเวอร์เป้าหมาย ทำให้ระบบไม่สามารถให้บริการผู้ใช้งานตามปกติได้
แนวทางการป้องกันแบบเดิม เช่น iptables หรือ Firewall แม้จะสามารถใช้งานได้ แต่มีข้อจำกัดเมื่อเผชิญกับการโจมตีที่มี Packet หลายสิบล้านต่อวินาที เนื่องจากต้องประมวลผลในระดับที่ลึกเกินไปของ Kernel Network Stack ส่งผลให้ CPU ทำงานหนักจนเกิดคอขวด
eBPF/XDP คืออะไร
eBPF (extended Berkeley Packet Filter)
- เป็นเทคโนโลยีที่ช่วยให้เขียนโปรแกรมขนาดเล็กลงไปทำงานภายใน Kernel ได้โดยตรง
- ไม่จำเป็นต้องแก้ไข Source Code ของ Kernel หรือโหลด Kernel Module
- มี Verifier ตรวจสอบความปลอดภัยก่อนใช้งาน ทำให้มั่นใจว่าโค้ดไม่ทำให้ Kernel ล่ม
XDP (eXpress Data Path)
-
เป็น Hook Point ที่อยู่ระดับ Network Driver
-
ทำให้สามารถตรวจสอบและตัดสินใจทิ้งแพ็กเก็ตได้ตั้งแต่ต้นทาง
-
คำสั่งที่สามารถใช้ได้ เช่น
XDP_DROP
— ทิ้งทันทีXDP_PASS
— ปล่อยเข้าสู่ Kernel ต่อXDP_TX
— ส่งออกกลับพอร์ตเดิมXDP_REDIRECT
— ส่งไปยังอินเทอร์เฟซอื่น
ทำไม eBPF/XDP ถึงเหมาะกับการป้องกัน DDoS
-
ประสิทธิภาพสูง (Extreme Performance)
- ทำงานในระดับ Driver ทำให้สามารถรองรับ Packet หลายสิบล้าน PPS ต่อ Core
-
ความยืดหยุ่น (Programmability)
- สามารถเขียน Logic ที่ซับซ้อนได้เอง เช่น การตรวจสอบ TCP Flag, UDP Payload หรือ Signature ของเกม
-
ความปลอดภัย (Safety)
- eBPF มีระบบตรวจสอบก่อนรันใน Kernel ป้องกันโค้ดที่อาจทำให้ระบบเสียหาย
ภาพรวมสถาปัตยกรรม
NIC → Driver → [XDP/eBPF Hook] → Kernel Stack → iptables/nftables → Application
ตัวอย่างการใช้งานจริง
1. โปรแกรมนับสถิติ Packet ต่อวินาที
// xdp_counter.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
enum { STATS_TOTAL = 0, STATS_TCP, STATS_UDP, STATS_OTHER, STATS_MAX };
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, STATS_MAX);
__type(key, __u32);
__type(value, __u64);
} pkt_stats SEC(".maps");
SEC("xdp")
int xdp_packet_counter(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_PASS;
__u32 key = STATS_TOTAL;
__u64 *v = bpf_map_lookup_elem(&pkt_stats, &key);
if (v) (*v)++;
if (eth->h_proto != bpf_htons(ETH_P_IP)) {
key = STATS_OTHER;
} else {
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return XDP_PASS;
switch (ip->protocol) {
case IPPROTO_TCP: key = STATS_TCP; break;
case IPPROTO_UDP: key = STATS_UDP; break;
default: key = STATS_OTHER; break;
}
}
v = bpf_map_lookup_elem(&pkt_stats, &key);
if (v) (*v)++;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
2. Rate Limiting แบบ Token Bucket
// xdp_token_bucket.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#define FILL_RATE 200u
#define BURST_SIZE 400u
#define NSEC_PER_SEC 1000000000ULL
struct ip_state { __u64 last_ns; __u32 tokens; };
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32);
__type(value, struct ip_state);
} rl_map SEC(".maps");
static __always_inline void refill(struct ip_state *st, __u64 now) {
__u64 delta_ns = now - st->last_ns;
if (delta_ns >= NSEC_PER_SEC) {
__u64 secs = delta_ns / NSEC_PER_SEC;
__u64 add = secs * FILL_RATE;
__u64 t = st->tokens + add;
st->tokens = t > BURST_SIZE ? BURST_SIZE : t;
st->last_ns = now;
}
}
SEC("xdp")
int xdp_ratelimit_tb(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_PASS;
if (eth->h_proto != bpf_htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return XDP_PASS;
__u32 src = ip->saddr;
__u64 now = bpf_ktime_get_ns();
struct ip_state *st = bpf_map_lookup_elem(&rl_map, &src);
if (!st) {
struct ip_state init = { .last_ns = now, .tokens = BURST_SIZE - 1 };
bpf_map_update_elem(&rl_map, &src, &init, BPF_ANY);
return XDP_PASS;
}
refill(st, now);
if (st->tokens == 0) return XDP_DROP;
st->tokens--;
bpf_map_update_elem(&rl_map, &src, st, BPF_ANY);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
3. การกรองการโจมตี Amplification Attack
// xdp_amp_filter.c
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)ip + sizeof(*ip);
if ((void *)(udp + 1) <= data_end) {
__u16 sport = bpf_ntohs(udp->source);
if (sport==53 || sport==123 || sport==1900 || sport==11211) {
return XDP_DROP;
}
}
}
คำสั่งติดตั้งและใช้งาน
เตรียมสภาพแวดล้อม
sudo apt update && sudo apt install -y clang llvm libbpf-dev libbpf-tools bpftool git make
sudo mount bpffs /sys/fs/bpf -t bpf
คอมไพล์โปรแกรม XDP
clang -O2 -g -target bpf -c xdp_counter.c -o xdp_counter.o
clang -O2 -g -target bpf -c xdp_token_bucket.c -o xdp_token_bucket.o
ติดตั้งโปรแกรม XDP บนอินเทอร์เฟซ (เช่น eth0)
# โหมด Native (เร็วที่สุด ถ้าการ์ดรองรับ)
sudo ip link set dev eth0 xdp obj xdp_counter.o sec xdp
# โหมด Generic (ใช้ได้ทุกการ์ด)
sudo ip link set dev eth0 xdpgeneric obj xdp_counter.o sec xdp
# ถอนโปรแกรมออก
sudo ip link set dev eth0 xdp off
ใช้ xdp-tools (แนะนำ)
sudo xdp-loader load -m native -s xdp xdp_counter.o -d eth0
sudo xdp-loader status
sudo xdp-loader unload -d eth0 --all
สรุป
การใช้งาน eBPF/XDP เพื่อป้องกันการโจมตีแบบ DDoS เป็นแนวทางที่มีประสิทธิภาพสูงสุดในปัจจุบัน โดยสามารถ ตัดสินใจที่ระดับ Driver ทำให้ลดภาระ CPU และเพิ่มความทนทานของระบบต่อปริมาณ Packet มหาศาล
นอกจากนี้ eBPF ยังช่วยให้สามารถเขียน Logic เฉพาะเพื่อรับมือกับการโจมตีรูปแบบต่าง ๆ ได้ ทำให้ระบบมีความยืดหยุ่นและพร้อมรับกับภัยคุกคามที่เปลี่ยนแปลงตลอดเวลา