BGP多线主机抗D实战:ExaBGP + RTBH/FlowSpec 秒级牵引攻击流量,1分钟全自动黑洞

当机房防火墙被打满、家里带宽被抽干之前,你还有一条“逃生通道”——通过BGP协议直接向上游路由器下发黑洞路由或FlowSpec规则。本教程不是理论科普,是一套在BGP多线主机上落地可跑的DDoS防护方案。所有配置都在本人实测环境下通过,核心组件只有ExaBGP和不到100行的Python脚本。

为什么不在服务器上死磕 iptables,而要上升到 BGP 层?

BGP多线主机通常接入多个运营商,单机牵引带宽再大也扛不住多G的SYN Flood。网络层风暴未到网卡就已经被交换机打满,服务器内的任何手段都无效。正确姿势是让流量在“更靠近源头”的路由器上被丢弃。这就是RTBH(远程触发黑洞)和FlowSpec的用武之地。

  • RTBH:把攻击目标IP的下一跳指向黑洞(通常为0.0.0.0/null0),该IP的所有入向流量在边缘被丢弃。优点是兼容性广,缺点是“全盘丢弃”,连正常流量也没了。
  • FlowSpec:下发精确匹配规则,比如丢弃来自某个源的UDP流量或特定DSCP标记的包,比RTBH更细腻。但需要上游路由器开启BGP FlowSpec能力。

我们将在BGP多线主机上同时实现这两者。

环境准备

用一台轻云互联的BGP多线主机作为实验机,几个BGP公网IP,上游有至少1个能接收IPv4 Unicast和IPv4 FlowSpec的对等邻居。安装所需工具:

# CentOS / Ubuntu 均可
apt install -y python3-pip python3-psutil
pip3 install exabgp

第一步:ExaBGP 部署与基本配置

ExaBGP需要用进程守护,不需要root权限,但习惯上跑在`exabgp`用户下。这里是关键配置,我已删掉了不相关的参数。

cat > /etc/exabgp/exabgp.conf <<'EOF'
process blackhole {
    run /opt/ddos/blackhole_handler.py;
    encoder text;
}

neighbor 203.0.113.1 {
    description "Upstream ISP";
    router-id 10.0.0.1;
    local-address 10.0.0.2;
    local-as 65002;
    peer-as 65001;
    family {
        ipv4 unicast;
        ipv4 flowspec;   # 如果上游不支持可以去掉,RTBH仍然可用
    }
    capability {
        ipv4-flowspec;
    }
}
EOF

上述配置中,process blackhole会在ExaBGP启动时拉起我们的管道脚本。关键文件在下一节。

第二步:自动RTBH黑洞,5秒内让你眼不见攻击包

思路是:一个monitor脚本周期采样`/proc/net/dev`,入向PPS超过阈值时,把攻击IP写入FIFO;blackhole_handler作为ExaBGP子进程,从FIFO读到IP后,立即向BGP邻居宣告黑洞路由。

2.1 创建FIFO和监控脚本

mkdir -p /opt/ddos
mkfifo /var/run/blackhole.fifo
chmod 666 /var/run/blackhole.fifo

monitor.py(5秒采样一次,超过80000 PPS触发):

#!/usr/bin/env python3
import time, os, sys

THRESHOLD_PPS = 80000
NET_DEV = '/proc/net/dev'
IF_NAME = 'ens4'   # 换成你的网卡名

def read_rx_packets():
    with open(NET_DEV) as f:
        lines = f.read().strip().split('\n')[2:]
        for line in lines:
            if IF_NAME in line:
                p_data = line.split()
                return int(p_data[1].replace(':', ''))  # Linux中的rxPackets是第1列
    return 0

prev = read_rx_packets()
last_ts = time.time()
pending_ip = None  # 简化:这里演示只记录一个IP。实际可从日志/连接数/abuse报告提取

while True:
    time.sleep(5)
    cur = read_rx_packets()
    now = time.time()
    pps = (cur - prev) / (now - last_ts)
    if pps > THRESHOLD_PPS:
        # 真实场景请从netstat/ss或硬中断数据中找出嫌疑IP
        # 这里仅作为示例,写死一个受害IP
        pending_ip = '198.51.100.10'
        with open('/var/run/blackhole.fifo', 'w') as fifo:
            fifo.write(pending_ip + '\n')
            fifo.flush()
        print(f'{now} Alert! PPS={pps:.0f}, blackhole {pending_ip}', flush=True)
        # 下一次循环前重置
        prev = cur; last_ts = now
    else:
        prev = cur; last_ts = now

2.2 blackhole_handler.py —— ExaBGP子进程,负责宣告路由

#!/usr/bin/env python3
import sys, os, threading, time

FIFO = '/var/run/blackhole.fifo'
BLACKHOLE_NH = '192.0.2.254'    # 黑洞下一跳,通常用保留地址
BLACKHOLE_COMMUNITY = '65000:666'

def read_fifo_loop():
    if not os.path.exists(FIFO):
        os.mkfifo(FIFO)
    with open(FIFO, 'r') as f:
        while True:
            ip = f.readline().strip()
            if ip:
                print(f'announce route {ip}/32 next-hop {BLACKHOLE_NH} community [{BLACKHOLE_COMMUNITY}]', flush=True)
                print('DEBUG: sent blackhole for', ip, file=sys.stderr, flush=True)

if __name__ == '__main__':
    # 保持进程存活
    read_fifo_loop()

注意`python3`的`print`到stdout就是ExaBGP的控制命令。启动ExaBGP后,写入FIFO观察日志:

systemctl start exabgp
journalctl -u exabgp -f
echo "198.51.100.10" > /var/run/blackhole.fifo

如果看到`announce route 198.51.100.10/32 ...`并且邻居收到了,RTBH已经生效。可以通过ip route get在网关侧验证。

第三步:FlowSpec 精确拦截,只丢掉攻击流量

RTBH会断了所有服务,因此需要精确攻击特征的场景,Flowspec是更好的选择。ExaBGP可以直接通告这条流规则:

# 丢弃来自198.51.100.0/24的UDP流量(假设攻击是UDP放大)
exabgpcli "announce flow route { match { source 198.51.100.0/24; protocol udp; } then { discard; } }"

同样,你可以把这段逻辑嵌入到监控脚本中,比如检测到目标端口是53且UDP包速率极高,就只对UDP协议下发黑名单:

#!/usr/bin/env python3
# flow_trigger.py - 示例:当UDP入包超过阈值,针对源IP段下发FlowSpec丢弃
import time
from collections import defaultdict

# 简化版伪代码,真实环境可用DPDK/EBPF采集源IP
def build_flowspec(src_prefix):
    return f'flow route {{ match {{ source {src_prefix}; protocol udp; }} then {{ discard; }} }}'

# 调用exabgpcli
def push_flowspec(rule):
    import subprocess
    subprocess.run(['exabgpcli', rule], check=True)

# 检测逻辑略,直接模拟一次
if __name__ == '__main__':
    src = '198.51.100.0/24'
    push_flowspec(build_flowspec(src))

FlowSpec的灵活度很大:可以匹配端口、协议、ICMP type、TCP flags等。具体字段名请参考RFC 5575,ExaBGP支持大部分字段。注意并非所有ISP都开启FlowSpec,需提前确认。

第四步:本地兜底 —— 防火墙与速率限制

无论BGP通道延迟多少,本地千万不要裸奔。推荐给关键端口加`hashlimit`,即使上游没把流量全丢掉,服务器也能靠该规则撑住第一波冲击。

iptables -A INPUT -p udp --dport 53 -m hashlimit --hashlimit-above 20/sec --hashlimit-burst 50 --hashlimit-mode srcip --hashlimit-name dns_flood -j DROP

这个规则对源IP限速20QPS,突发50,超过就丢。UDP 53端口放大攻击立刻见效。

效果验证与避坑

用hping3做压力演练:

# 攻击者机器上
hping3 -S --flood -p 80 -U 你的BGP多线主机IP

# 主机上观察
sar -n DEV 1 5  # 看rxPck/s有没有飙升

触发monitor.py后,几秒内上游邻居收到黑洞路由,攻击流量在源头被丢弃。你可以用`tcpdump -nn -i eth0 'host 攻击IP'`看到包速率急剧下降。

几个血泪坑:

  • 黑洞路由的community值必须和上游运营商确认,最常见的666(黑洞)不一定每家通用。
  • ExaBGP必须保持常驻,否则线路被攻击时你没法临时启动进程。用systemd管理好。
  • 自动封禁一定要留白名单,比如允许你的监控源IP和关键客户IP绕过黑洞。
  • 多线BGP主机的优势在于:当一条线路被攻击占满,你可以通过调整本地优先级或降低本地preference,让攻击流量走清洗线路,同时保活其他线路的业务——这是单线路主机做不到的。

这套方案的真实效能

在轻云互联的BGP多线主机上跑这套方案,我实测从PPS报警到BGP路由收敛,最快1-2秒(取决于邻居数量)。比起人工登录机房防火墙或等云厂商清洗,这个速度已经能保命。别小看RTBH简单,关键时刻一条黑洞路由就能保住其他IP的正常服务——BGP多线主机的包容性就在这里。

最后说一句,脚本代码不是封装好的产品,但比那些“高防服务器”更能看出你在网络层掌控到底有多深。把自动判定和BGP调度练在手,比买任何“抗D神器”都踏实。