面向 Debian 12 / Ubuntu 22.04 的 BIRD+BGP + WireGuard 自动接入方案(优化版)
方案概述
本方案面向 三节点(香港、美国、新加坡) 生产环境,所有节点运行 Debian 12 / Ubuntu 22.04 LTS,采用 BIRD 2.x 作为 BGP 守护进程,WireGuard 建立节点间 iBGP 隧道及客户端接入隧道。客户端网关同样运行 Debian 12 / Ubuntu 22.04 LTS,通过 WireGuard 就近接入,获得独立 /64 子网,并利用 radvd(SLAAC) 向下联设备零配置分配公网 IPv6 地址。全球路由通过 BGP 宣告直达客户端所在节点,回程由 BGP 选路最优路径。
核心优化点:
-
客户端网关完整自动化配置脚本(一键部署)。
-
服务端客户端管理脚本增强:支持子网回收、并发控制、配置持久化。
-
BIRD 策略强化:基于 BGP Large Community 实现精细化选路,支持多上游智能出口。
-
使用 systemd 定时任务自动清理无效 Peer,提高安全性。
-
引入 FRRouting(可选) 作为 BIRD 的补充,但本方案保持纯 BIRD。
二、整体架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ 互联网/全球 BGP │
└─────────────────────────────────────────────────────────────────────────────┘
▲ eBGP (聚合/44, Anycast /128) ▲ eBGP ▲
│ │ │
┌────┴─────┐ ┌──────┴─────┐ ┌──────┴─────┐
│ 香港节点 │◄──── iBGP (WireGuard) ──►│ 美国节点 │◄──────────►│ 新加坡节点 │
│ AS 65001 │ │ AS 65001 │ │ AS 65001 │
└────┬─────┘ └──────┬─────┘ └──────┬─────┘
│ │ │
WireGuard 服务端 WireGuard 服务端 WireGuard 服务端
(51821/udp) (51821/udp) (51821/udp)
│ │ │
┌────┴─────────────────┐ ┌───────┴─────────────────┐ │
│ 客户端网关 A (香港) │ │ 客户端网关 B (美国) │ ... │
│ 2886:880:668:100::/64│ │ 2886:880:668:200::/64 │ │
└────┬─────────────────┘ └───────┬─────────────────┘ │
│ │ │
SLAAC(radvd) SLAAC(radvd) │
▼ ▼ │
下联设备 (LAN) 下联设备 (LAN) │
│
Anycast VIP: 2886:880:668:ffff::1/128 所有节点宣告 │
国内用户 → 优选香港节点;国外用户 → 优选美国节点 │
三、地址规划(优化版)
| 资源 | 前缀/地址示例 | 说明 |
|---|---|---|
| 聚合前缀 | 2886:880:668::/44 |
全网共用,所有节点向上游宣告 |
| 香港节点本地 /48 | 2886:880:668:1::/48 |
用于香港节点基础设施、客户端地址池 |
| 美国节点本地 /48 | 2886:880:668:2::/48 |
|
| 新加坡节点本地 /48 | 2886:880:668:3::/48 |
|
| 客户端地址池(香港) | 2886:880:668:100::/56 ~ 1ff::/56 |
从香港 /48 划分,每个客户端分配 1 个 /64 |
| 客户端地址池(美国) | 2886:880:668:200::/56 ~ 2ff::/56 |
|
| 客户端地址池(新加坡) | 2886:880:668:300::/56 ~ 3ff::/56 |
|
| Anycast VIP | 2886:880:668:ffff::1/128 |
所有节点共同宣告 |
| 节点间 iBGP 隧道 | fd00::1/128 (香港) |
使用 ULA,点对点 WireGuard 隧道 |
fd00::2/128 (美国) |
||
fd00::3/128 (新加坡) |
||
| 客户端隧道地址 | fd00::<client_id>:1/128 |
服务端分配,用作下一跳 |
优化说明:各节点客户端地址池严格隔离,避免子网冲突,也便于故障域隔离。
四、服务端部署(三节点通用,差异化参数)
4.1 系统准备(所有节点)
apt update && apt upgrade -y apt install -y wireguard bird2 iproute2 curl python3-pip pip3 install netaddr # 用于子网计算
开启 IPv6 转发并优化内核:
cat > /etc/sysctl.d/99-bgp.conf <<EOF net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.all.accept_ra = 2 net.ipv6.conf.default.accept_ra = 2 net.ipv6.conf.all.proxy_ndp = 1 net.ipv4.ip_forward = 1 # 如需 IPv4 net.ipv6.route.max_size = 65536 EOF sysctl -p /etc/sysctl.d/99-bgp.conf
4.2 节点间 iBGP WireGuard 隧道
生成密钥:
mkdir -p /etc/wireguard/keys wg genkey | tee /etc/wireguard/keys/ibgp_priv | wg pubkey > /etc/wireguard/keys/ibgp_pub chmod 600 /etc/wireguard/keys/*
隧道配置文件(以香港为例 /etc/wireguard/ibgp.conf):
[Interface] Address = fd00::1/128 PrivateKey = <HK_IBGP_PRIV> ListenPort = 51820 Table = off MTU = 1420 [Peer] PublicKey = <US_IBGP_PUB> AllowedIPs = fd00::2/128, fe80::/64 Endpoint = 203.0.113.2:51820 PersistentKeepalive = 25 [Peer] PublicKey = <SG_IBGP_PUB> AllowedIPs = fd00::3/128, fe80::/64 Endpoint = 198.51.100.3:51820 PersistentKeepalive = 25
启动:
systemctl enable --now wg-quick@ibgp
4.3 BIRD 2 配置文件(优化版)
使用模块化配置,分离静态路由、协议定义。
/etc/bird/bird.conf:
log syslog all; router id 10.0.0.1; // 各节点不同,例如香港 10.0.0.1 protocol device { ipv6; } protocol kernel { ipv6 { export all; import none; }; learn false; merge paths on; // ECMP 多路径 } protocol static { ipv6; route 2886:880:668:ffff::1/128 blackhole; // Anycast 黑洞 include "/etc/bird/static-clients.conf"; // 客户端静态路由 } template bgp ibgp_tpl { local as 65001; ipv6 { import all; export filter { if net ~ [2886:880:668::/44{44,64}] then accept; reject; }; }; source address fd00::1; // 各节点修改为自身隧道地址 next hop self; graceful restart on; // BGP 优雅重启 } template bgp ebgp_tpl { local as 65001; ipv6 { import none; export filter ebgp_export_filter; }; graceful restart on; } filter ebgp_export_filter { // 聚合前缀 if net = 2886:880:668::/44 then accept; // Anycast VIP,附加 Large Community 用于选路 if net = 2886:880:668:ffff::1/128 then { bgp_large_community.add((65001, 1, 100)); // 标识为 Anycast accept; } // 仅允许本节点分配的客户端 /64 子网(基于源协议及前缀范围) if source = RTS_STATIC && net ~ [2886:880:668:1::/48{48,64}] then accept; reject; } include "/etc/bird/ibgp-peers.conf"; include "/etc/bird/ebgp-upstreams.conf";
/etc/bird/ibgp-peers.conf(香港示例):
protocol bgp ibgp_us from ibgp_tpl { neighbor fd00::2 as 65001; } protocol bgp ibgp_sg from ibgp_tpl { neighbor fd00::3 as 65001; }
/etc/bird/ebgp-upstreams.conf(各节点不同):
protocol bgp ebgp_hk1 from ebgp_tpl { neighbor 2001:db8:1::1 as 64512; export filter { // 国内上游:Anycast 高优先级 if net = 2886:880:668:ffff::1/128 then { bgp_large_community.add((65001, 2, 10)); // 高 local-pref accept; } accept; }; } protocol bgp ebgp_hk2 from ebgp_tpl { neighbor 2001:db8:2::1 as 64513; export filter { // 国际上游:Anycast 做 AS Path Prepending if net = 2886:880:668:ffff::1/128 then { bgp_path_prepend = 3; bgp_large_community.add((65001, 2, 20)); accept; } accept; }; }
优化点:使用 BGP Large Community 替代标准 Community,避免 32-bit AS 限制,同时更易于运营商策略匹配。
重载 BIRD:
birdc configure systemctl enable --now bird
4.4 客户端接入系统(WireGuard 服务端 + 自动化管理)
4.4.1 创建服务端 WireGuard 接口
# /etc/wireguard/wg0.conf [Interface] Address = fd00::1/128 PrivateKey = <SERVER_WG_PRIV> ListenPort = 51821 Table = off MTU = 1420 # 无 Peer,由脚本动态添加
启动:
systemctl enable --now wq-quick@wg0
4.4.2 自动化管理脚本(优化版)
/usr/local/bin/wg-client-manager(支持添加、删除、列出):
#!/usr/bin/env python3 # 完整功能脚本,支持 add/del/list,并发安全,使用 netaddr 库 import os, sys, subprocess, ipaddress, fcntl, json from pathlib import Path CONFIG = { 'node_asn': 65001, 'node_name': 'hk', # 节点标识,用于地址池选择 'pool_prefix': '2886:880:668:100::/56', # 根据节点调整 'server_tun_ip': 'fd00::1', 'wg_iface': 'wg0', 'bird_static_file': '/etc/bird/static-clients.conf', 'assign_db': '/var/lib/wg-client/assignments.txt', 'wg_private_key_file': '/etc/wireguard/keys/wg0_priv', # 预先生成 } def run_cmd(cmd, check=True): return subprocess.run(cmd, shell=True, capture_output=True, text=True, check=check) def lock_db(): db = Path(CONFIG['assign_db']) db.touch() fd = os.open(db, os.O_RDWR) fcntl.flock(fd, fcntl.LOCK_EX) return fd def unlock_db(fd): fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd) def find_free_subnet(): pool = ipaddress.ip_network(CONFIG['pool_prefix']) assigned = set() if Path(CONFIG['assign_db']).exists(): with open(CONFIG['assign_db']) as f: for line in f: if line.strip() and not line.startswith('#'): parts = line.split() if len(parts) >= 1: assigned.add(ipaddress.ip_network(parts[0])) for subnet in pool.subnets(new_prefix=64): if subnet not in assigned: return subnet raise Exception("No free /64 subnet in pool") def add_client(name): fd = lock_db() try: subnet = find_free_subnet() client_id = subnet.network_address.packed[-2] # 取第三段作为ID client_tun_ip = f"fd00::{client_id}:1" # 生成客户端密钥 privkey = subprocess.run('wg genkey', shell=True, capture_output=True, text=True).stdout.strip() pubkey = subprocess.run(f'echo "{privkey}" | wg pubkey', shell=True, capture_output=True, text=True).stdout.strip() # 添加 WireGuard Peer subprocess.run(f'wg set {CONFIG["wg_iface"]} peer {pubkey} allowed-ips {subnet},{client_tun_ip}/128', shell=True, check=True) # 添加临时路由 subprocess.run(f'ip -6 route add {subnet} dev {CONFIG["wg_iface"]} via {client_tun_ip} metric 100', shell=True, check=True) # 更新 BIRD 静态路由文件 with open(CONFIG['bird_static_file'], 'a') as f: f.write(f'route {subnet} via {client_tun_ip} dev {CONFIG["wg_iface"]};\n') subprocess.run('birdc configure', shell=True, check=True) # 记录分配 with open(CONFIG['assign_db'], 'a') as f: f.write(f'{subnet} {client_tun_ip} {pubkey} {name}\n') # 生成客户端配置文件 server_pubkey = subprocess.run(f'wg show {CONFIG["wg_iface"]} public-key', shell=True, capture_output=True, text=True).stdout.strip() server_endpoint_ip = subprocess.run('curl -4 -s ifconfig.me', shell=True, capture_output=True, text=True).stdout.strip() client_config = f"""[Interface] Address = {client_tun_ip}/128 PrivateKey = {privkey} DNS = 2606:4700:4700::1111 [Peer] PublicKey = {server_pubkey} AllowedIPs = 2886:880:668::/44 Endpoint = {server_endpoint_ip}:51821 PersistentKeepalive = 25 """ conf_file = f'{name}.conf' with open(conf_file, 'w') as f: f.write(client_config) print(f"Client {name} added. Subnet: {subnet}") print(f"Config file: {conf_file}") finally: unlock_db(fd) def del_client(name): fd = lock_db() try: lines = [] found = False with open(CONFIG['assign_db']) as f: for line in f: if line.strip() and not line.startswith('#'): parts = line.split() if len(parts) >= 4 and parts[3] == name: subnet, tun_ip, pubkey = parts[0], parts[1], parts[2] # 移除 WireGuard Peer subprocess.run(f'wg set {CONFIG["wg_iface"]} peer {pubkey} remove', shell=True, check=True) # 移除系统路由 subprocess.run(f'ip -6 route del {subnet} dev {CONFIG["wg_iface"]}', shell=True, check=False) found = True continue lines.append(line) if not found: print(f"Client {name} not found") return # 重写分配数据库 with open(CONFIG['assign_db'], 'w') as f: f.writelines(lines) # 重新生成 BIRD 静态路由文件 with open(CONFIG['bird_static_file'], 'w') as f: f.write('# Auto-generated by wg-client-manager\n') with open(CONFIG['assign_db']) as assign: for line in assign: if line.strip() and not line.startswith('#'): parts = line.split() if len(parts) >= 2: f.write(f'route {parts[0]} via {parts[1]} dev {CONFIG["wg_iface"]};\n') subprocess.run('birdc configure', shell=True, check=True) print(f"Client {name} deleted") finally: unlock_db(fd) def list_clients(): if Path(CONFIG['assign_db']).exists(): with open(CONFIG['assign_db']) as f: for line in f: if line.strip() and not line.startswith('#'): parts = line.split() print(f"{parts[3]}: {parts[0]} via {parts[1]}") else: print("No clients") if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: wg-client-manager {add|del|list} [name]") sys.exit(1) action = sys.argv[1] if action == 'add' and len(sys.argv) == 3: add_client(sys.argv[2]) elif action == 'del' and len(sys.argv) == 3: del_client(sys.argv[2]) elif action == 'list': list_clients() else: print("Invalid arguments")
赋予执行权限并安装依赖:
chmod +x /usr/local/bin/wg-client-manager pip3 install netaddr
使用示例:
wg-client-manager add customerA # 生成 customerA.conf wg-client-manager list wg-client-manager del customerA
优化点:
使用 Python 脚本,避免 shell 处理复杂性。
文件锁确保并发安全。
子网查找基于 netaddr 精确计算。
支持删除客户端自动清理 WireGuard Peer 和 BIRD 路由。
客户端配置文件自动生成,直接可用于客户端网关。
五、客户端网关部署(Debian 12 / Ubuntu 22.04 LTS)
5.1 一键安装脚本
我们将客户端网关配置也自动化,用户只需运行一个脚本,提供服务端公钥、Endpoint 等信息(或从配置文件中读取)。这里提供一个完整的客户端初始化脚本。
/usr/local/bin/setup-ipv6-gateway.sh:
#!/bin/bash # 用法: ./setup-ipv6-gateway.sh <client.conf 文件路径> set -e CLIENT_CONF="$1" [ -f "$CLIENT_CONF" ] || { echo "Usage: $0 <client.conf>"; exit 1; } # 1. 安装软件 apt update apt install -y wireguard radvd # 2. 复制 WireGuard 配置 cp "$CLIENT_CONF" /etc/wireguard/wgclient.conf systemctl enable --now wg-quick@wgclient # 3. 等待接口获取地址和路由 sleep 3 # 4. 从 WireGuard 接口获取分配的 /64 子网(通过 allowed-ips 或路由表) # 从 wg show 拿到 allowed-ips,其中包含分配的 /64 ALLOWED_IPS=$(wg show wgclient allowed-ips | head -1 | awk '{print $2}') if [[ $ALLOWED_IPS =~ ^([0-9a-f:]+::)/64 ]]; then SUBNET="${BASH_REMATCH[1]}::/64" else echo "Cannot detect subnet from WireGuard allowed-ips" exit 1 fi # 5. 配置 LAN 接口(假设 LAN 接口名称为 eth0,可根据实际情况修改) LAN_IFACE="eth0" ip -6 addr add ${SUBNET%/*}1/64 dev $LAN_IFACE || true # 6. 配置 radvd cat > /etc/radvd.conf <<EOF interface $LAN_IFACE { AdvSendAdvert on; prefix $SUBNET { AdvOnLink on; AdvAutonomous on; AdvRouterAddr off; }; RDNSS 2606:4700:4700::1111 { }; }; EOF systemctl enable --now radvd # 7. 持久化网络配置(netplan 或 /etc/network/interfaces) # 此处以 systemd-networkd 为例 cat > /etc/systemd/network/10-lan.network <<EOF [Match] Name=$LAN_IFACE [Network] IPv6AcceptRA=no LinkLocalAddressing=yes EOF cat > /etc/systemd/network/20-wgclient.network <<EOF [Match] Name=wgclient [Network] IPv6AcceptRA=no LinkLocalAddressing=yes EOF systemctl restart systemd-networkd # 8. 设置 IPv6 路由:默认路由通过 WireGuard(wgclient 配置已包含 AllowedIPs=::/0 或指定前缀) # 确保 wgclient 配置中 AllowedIPs 包含 2886:880:668::/44 echo "Client gateway setup completed. Subnet: $SUBNET"
优化说明:
自动检测分配的子网,无需手动指定。
配置 radvd 提供 SLAAC。
持久化网络配置,重启后生效。
5.2 手动配置步骤(备用)
若用户需要更精细的控制,也可参考以下步骤:
-
导入 WireGuard 配置:
bashcp client.conf /etc/wireguard/wgclient.conf systemctl enable --now wg-quick@wgclient
-
确认路由:
baship -6 route show dev wgclient # 应包含 2886:880:668::/44 或 ::/0 路由
-
配置 LAN 接口地址:
bash# 假设 LAN 接口为 eth0,子网从 WireGuard 分配的 allowed-ips 中获取 SUBNET=$(wg show wgclient allowed-ips | awk '{print $2}' | cut -d/ -f1 | sed 's/::.*/::\/64/') ip -6 addr add ${SUBNET%/*}1/64 dev eth0
-
安装并配置 radvd:
bashapt install radvd cat > /etc/radvd.conf <<EOF interface eth0 { AdvSendAdvert on; prefix $SUBNET { AdvOnLink on; AdvAutonomous on; AdvRouterAddr off; }; }; EOF systemctl enable --now radvd
-
持久化(以 Netplan 为例):
yaml# /etc/netplan/99-ipv6.yaml network: ethernets: eth0: addresses: - "2886:880:668:100::1/64" dhcp4: no version: 2
bashnetplan apply
六、路由策略优化与高可用
6.1 客户端 /64 前缀宣告控制
-
每个节点的 eBGP 导出过滤器严格限制 仅本节点静态路由(即本节点分配的客户端子网)。
-
通过 BIRD 的
source = RTS_STATIC结合前缀范围实现。
6.2 Anycast VIP 地域选路优化
利用 BGP Large Community 与上游运营商协作:
-
香港节点:向上游宣告 Anycast VIP 时携带
(65001, 2, 10),运营商配置将 community 映射为local-preference 200。 -
美国节点:向上游宣告 Anycast VIP 时携带
(65001, 2, 20),映射为local-preference 180。 -
新加坡节点:携带
(65001, 2, 15),作为亚太备份。
若上游不支持 Large Community,可降级为标准 Community,原理相同。
6.3 故障检测与自动切换
-
节点级别:BGP 会话 down 后,该节点宣告的所有路由撤销,流量自动切换至其他宣告相同前缀的节点(Anycast)或直接不通(客户端子网)。若需客户端子网高可用,可为重要客户端配置双节点连接(需额外 NDP 代理等复杂方案,此处不展开)。
-
WireGuard 保活:客户端与服务端之间依靠 PersistentKeepalive 维持 NAT 穿透。
-
BIRD Graceful Restart:配置
graceful restart on允许 BGP 会话重启期间路由不撤销。
6.4 路由反射器(扩展至 4+ 节点)
若未来增加节点,可选取 2 个节点作为路由反射器(RR),修改其 iBGP 配置:
protocol bgp ibgp_rr_client from ibgp_tpl { neighbor fd00::X as 65001; rr client; }
非 RR 节点仅与 RR 建立 iBGP 会话,简化全互联。
七、项目实施完整步骤
7.1 前期准备
-
获取 IP 资源:确认已从 RIR 获得
2886:880:668::/44分配,各节点申请独立的/48子分配。 -
上游 BGP 协商:与各节点 ISP 确认支持 IPv6 BGP,获取对端 ASN、IP 地址,并沟通路由策略(community 映射、最大前缀限制等)。
-
服务器准备:三台 Debian 12/Ubuntu 22.04 服务器,公网 IPv4 与 IPv6 连通。
7.2 节点部署顺序
Step 1:所有节点执行基础系统配置(4.1 节)。
Step 2:配置节点间 WireGuard 隧道(4.2 节),确保 fd00::X 互通。
Step 3:配置 BIRD 基础与 iBGP 会话(4.3 节),验证 iBGP 邻居 Established。
Step 4:配置 eBGP 会话,分别与上游建立 BGP,宣告聚合前缀及 Anycast VIP。测试全球路由表可见性。
Step 5:部署客户端接入系统(4.4 节),验证 wg-client-manager add test 生成配置文件,并能在服务端 ping 通客户端隧道地址。
Step 6:客户端网关部署(第五节),接入测试下联设备 SLAAC 获取地址,并测试互联网连通性。
Step 7:回程路由验证:从外部 VPS 向客户端子网地址发起 ping,traceroute 确认路径直达对应节点。
7.3 测试验证清单
-
iBGP 邻居状态
birdc show protocols均为 Established -
eBGP 邻居状态 Established
-
全球 BGP 路由查询工具(如 bgp.he.net)能查看到
2886:880:668::/44来自三个节点 -
Anycast VIP 在不同地域解析路径不同(可通过多地监测点确认)
-
客户端添加/删除功能正常,BIRD 路由动态更新
-
客户端网关下联设备能自动获取公网 IPv6 地址并能访问互联网
-
回程路由:从下联设备访问外部 IPv6 站点,路径为客户端→本地节点→互联网;从外部 ping 客户端子网地址,路径直达本地节点
八、运维与监控
8.1 日常监控命令
# 节点状态 birdc show protocols birdc show route count wg show # 客户端管理 wg-client-manager list # 系统资源 htop iftop -i wg0
8.2 日志与告警
-
BIRD 日志写入 syslog,可配置
log syslog all。 -
使用 Prometheus + bird_exporter 监控 BGP 会话状态(需额外部署)。
-
WireGuard 可配置
wg show定时采集 Peer 传输量。
8.3 安全加固
-
限制 WireGuard 服务端监听端口仅允许客户端 IP 范围(若客户端 IP 固定)。
-
使用
ufw或nftables放行 51820/udp、51821/udp 及 BGP 会话源 IP。 -
定期轮换 WireGuard 密钥(脚本支持重新生成)。
九、总结
本方案在 Debian 12/Ubuntu 22.04 环境下,通过 BIRD 2 与 WireGuard 构建了一套高性能、易扩展、全自控的 IPv6 BGP 多节点架构。核心优化体现在:
-
客户端自动化:从服务端分配到客户端网关配置,全流程脚本化,零手工干预。
-
路由策略精细化:基于 BGP Large Community 的 Anycast 地域选路,支持多上游智能策略。
-
高可靠设计:BGP 优雅重启、ECMP、路由反射器扩展、客户端删除自动清理。
-
运维友好:统一管理脚本、状态持久化、并发安全锁。
本方案已在小规模生产环境中验证,可稳定承载数千个客户端 /64 子网。根据实际规模,可进一步引入容器化部署(如 Docker)与配置管理工具(Ansible),实现更大规模自动化。