前言

本文用以记录自己加入 DN42 网络的历程,本人是初入 BGP 的小白,对于文章中出现的各种不严谨内容和各种低级错误,请大佬们手下留情,可在评论区指出。

欢迎和我 Peer:VCNET DN42

注册

iYoRoy 的博客有详细的过程,我就不再重复了:DN42探究日记 - Ep.1 加入DN42网络

选择 BGP Daemon

参考各位大佬的教程,我决定使用 Bird v3 为我的 BGP Daemon,以 Debian 13 为例:

1
2
3
4
5
apt update
apt -y install apt-transport-https ca-certificates wget
wget -O /usr/share/keyrings/cznic-labs-pkg.gpg https://pkg.labs.nic.cz/gpg
echo "deb [signed-by=/usr/share/keyrings/cznic-labs-pkg.gpg] https://pkg.labs.nic.cz/bird3 trixie main" > /etc/apt/sources.list.d/cznic-labs-bird3.list
apt update && apt -y install bird3 wireguard wireguard-tools

Debian 软件源中的 Bird 版本比较旧,需要安装上面的最新版

其他系统及 Bird v2 软件源可查看:CZ.NIC Labs 📦 Repos Setup Docs

bird.conf

修改 /etc/bird/bird.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
define OWNAS =  4242423322;
define OWNIP = 172.23.100.180;
define OWNIPv6 = fd48:8669:9f9f::8;
define OWNNET = 172.23.100.160/27;
define OWNNETv6 = fd48:8669:9f9f::/48;
define OWNNETSET = [172.23.100.160/27+];
define OWNNETSETv6 = [fd48:8669:9f9f::/48+];

router id OWNIP;

protocol device {
scan time 10;
}

function is_self_net() {
return net ~ OWNNETSET;
}

function is_self_net_v6() {
return net ~ OWNNETSETv6;
}

function is_valid_network() {
return net ~ [
172.20.0.0/14{21,29}, # dn42
172.20.0.0/24{28,32}, # dn42 Anycast
172.21.0.0/24{28,32}, # dn42 Anycast
172.22.0.0/24{28,32}, # dn42 Anycast
172.23.0.0/24{28,32}, # dn42 Anycast
172.31.0.0/16+, # ChaosVPN
10.100.0.0/14+, # ChaosVPN
10.127.0.0/16{16,32}, # neonetwork
10.0.0.0/8{15,24} # Freifunk.net
];
}

function is_valid_network_v6() {
return net ~ [
fd00::/8{44,64} # ULA address space as per RFC 4193
];
}

protocol kernel {
scan time 20;
ipv6 {
import none;
export filter {
if source = RTS_STATIC then reject;
krt_prefsrc = OWNIPv6;
accept;
};
};
};

protocol kernel {
scan time 20;
ipv4 {
import none;
export filter {
if source = RTS_STATIC then reject;
krt_prefsrc = OWNIP;
accept;
};
};
}

protocol static {
route OWNNET reject;
ipv4 {
import all;
export none;
};
}

protocol static {
route OWNNETv6 reject;
ipv6 {
import all;
export none;
};
}

include "rpki.conf";
include "ospf.conf";
include "ibgp.conf";
include "ebgp.conf";

首先修改自己的网络信息:

  • OWNAS 修改为你的 ASN,如:4242423322
  • OWNIP 修改为此节点所分配的 IPv4 地址,如:172.23.100.180
  • OWNIPv6 修改为此节点所分配的 IPv6 地址,如:fd48:8669:9f9f::8
  • OWNNET 修改为你所拥有的 IPv4 网段,如:172.23.100.160/27
  • OWNNETv6 修改为你所拥有的 IPv6 网段,如:fd48:8669:9f9f::/48
  • OWNNETSET 和 OWNNETSETv6 同 上两个,但不要忘记末尾的 +

rpki.conf

修改 /etc/bird/rpki.conf

1
2
3
4
5
6
7
8
9
10
11
roa4 table dn42_roa;
roa6 table dn42_roa_v6;

protocol rpki dn42_rpki {
roa4 { table dn42_roa; };
roa6 { table dn42_roa_v6; };
remote "rpki.akae.re" port 8082;
refresh 30;
retry 5;
expire 600;
}
什么是 ROA

ROA是数字签名对象,将地址绑定到AS号码,并由地址持有者签名。ROA提供了一种验证IP地址块持有者是否已授权特定AS在域间路由环境中为该地址块发起路由的方法。[RFC6482]中描述了ROA。ROA旨在满足为域间路由添加安全性的要求。

为 DN42 启用 ROA 检查,能有效防止 BGP 劫持和错误配置导致的路由泄露,而使用 RPKI,为我们省去了手动配置 ROA 表和更新的麻烦.

ebgp.conf

修改 /etc/bird/ebgp.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
template bgp dn42_peers {
local as OWNAS;
path metric 1;

ipv4 {
extended next hop;
#import limit 1000 action block; #限制从他人哪里导入路由的数量
import keep filtered;
import filter {
if is_valid_network() && !is_self_net() then {
if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {
print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
reject;
}
accept;
}
reject;
};
export filter {
if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept;
reject;
};
};

ipv6 {
#import limit 1000 action block;
import keep filtered;
import filter {
if is_valid_network_v6() && !is_self_net_v6() then {
if (roa_check(dn42_roa_v6, net, bgp_path.last) != ROA_VALID) then {
print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
reject;
}
accept;
}
reject;
};
export filter {
if is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP] then accept;
reject;
};
};
}

include "dn42_peers/*";

此文件定义了和他人互 Peer 的模板并启用了 ROA 过滤,和他人互 Peer 的配置保存在 /etc/bird/dn42_peers 内。

系统配置

sysctl

在 DN42 内,每个节点几乎都是其他人的路由器,而系统默认并不允许数据包转发并且有严格的数据包过滤,因此需要如下设置:

1
2
3
4
5
6
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
echo "net.ipv4.conf.default.rp_filter=0" >> /etc/sysctl.conf
echo "net.ipv4.conf.all.rp_filter=0" >> /etc/sysctl.conf
sysctl -p

尤其需要注意 人品过滤器 rp_filter,有时 ping 不通他人很可能就是它导致的。

如果你不放心,可使用下面的 bash 脚本一键将 dn42 开头的网卡的 rp_filter 关闭:

1
2
3
4
#!/bin/bash
for net_dev in /proc/sys/net/ipv4/conf/dn42*/rp_filter; do
echo 0 > "$net_dev"
done

Dummy 网卡

我们需要一个 Dummy 网卡绑定本机的 DN42 网络信息,在此,我选择使用 ifupdown 来统一配置我的所有节点。

编辑 /etc/network/interfaces

1
2
3
4
5
6
7
8
auto dn42
iface dn42 inet static
address 172.23.100.180
netmask 255.255.255.255
pre-up ip link add dn42 type dummy || true
up ip link set dn42 up
iface dn42 inet6 static
address fd48:8669:9f9f::8/128

请记得修改为自己的 IP。

使用 ifup dn42ifdown dn42 启用 & 禁用网卡。

容器

根据我自己的经验,我更推荐 Podman 而非 Docker。我曾花了两天时间解决无法对外 ping 的问题,最后发现是 Docker 的 iptables 规则引起的。如果你一定要用 Docker 且遇到类似问题,可尝试添加下面的 iptables 规则:

1
2
3
4
5
iptables -I DOCKER-USER 1 -i dn42+ -o zt+ -j ACCEPT
iptables -I DOCKER-USER 1 -i zt+ -o dn42+ -j ACCEPT

ip6tables -I DOCKER-USER 1 -i dn42+ -o zt+ -j ACCEPT
ip6tables -I DOCKER-USER 1 -i zt+ -o dn42+ -j ACCEPT

与他人互 Peer

WireGuard

与他人互 Peer 之前,通常需要双方建立 P2P 隧道,其原因可以借用蓝天的原话:

DN42 中几乎每个 Peering 都是建立在隧道软件(即 VPN)之上的,原因如下:

  • DN42 各个用户的节点分布在世界各地,隧道软件可以对数据进行基本的加密和保护;
  • DN42 使用的是私有地址,如果直接在互联网上传输,会被防火墙直接丢弃,甚至可能会被主机商认为你在 IP Spoofing(伪造来源 IP 地址),违反服务条款,造成严重后果。

而 DN42 的参与者们用的最多的就是 WireGuard 和 GRE/IPSec,而前者配置较为简单,也有一定的加密能力。

生成密钥对

1
wg genkey | tee privatekey | wg pubkey > publickey

请保存好自己的公私钥。

互 Peer

对于新人,可以通过 potat0 的 Telegram 机器人自动 Peer:@Potat0_DN42_Bot,亦或是 iEdon 的网自动 Peer:iEdon Net,还有 Kioubit 的网页自动 Peer:Kioubit Network.

互 Peer 信息

通过自动机器人或网页,亦或是他人的主页,我们既需要获得对方如下信息,也需要给对方提供自己的如下信息:

  • 公钥
  • 公网地址(Endpoint)
  • DN42 的 ASN
  • IPv6 LLA
  • 是否支持ENH(Extended Next Hop),注意:若使用v6交换路由而不启用ENH则无法交换v4路由

在获得了对方上述信息后,我们需要在 /etc/wireguard 下创建一个文件,我的命名习惯为:dn42-424242XXXX.conf,填入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 424242XXX - @YYY   # 对方的 ASN 及联系方式,方便后期查找
[Interface]
ListenPort = <开放给对方的端口 UDP>
Table = off
MTU = 1420 # MTU,一般不需要修改
#将私钥存放到 /etc/wireguard/dn42-privatekey 不需要每次都复制一遍
PostUp = wg set %i private-key /etc/wireguard/dn42-privatekey
PostUp = ip addr add <你的 LLA 地址>/64 peer <对方的 LLA 地址>/64 dev %i
PostUp = ip addr add <你的 ULA 地址>/128 dev %i
PostUp = ip addr add <你的 IPv4 地址>/32 dev %i
[Peer]
PublicKey = <对方的公钥>
Endpoint = <对方提供给你的 Endpoint>
AllowedIPs = 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8, fe80::/64

关于端口,有一个通常做法:20000 + 对方 ASN 后四位

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 4242422547 - @lantian1998
[Interface]
ListenPort = 22547
Table = off
MTU = 1420
PostUp = wg set %i private-key /etc/wireguard/dn42-privatekey
PostUp = ip addr add fe80::3322/64 peer fe80::2547/64 dev %i
PostUp = ip addr add fd48:8669:9f9f::8/128 dev %i
PostUp = ip addr add 172.23.100.180/32 dev %i
[Peer]
PublicKey = xelzwt1j0aoKjsQnnq8jMjZNLbLucBPwPTvHgFH/czs=
Endpoint = alice.lantian.pub:23322
AllowedIPs = 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8, fe80::/64

保存并运行:

#

通过 wg show <文件名(不需要 .conf 后缀)> 即可查看隧道连接状况:

1
2
3
4
5
6
7
8
9
10
11
root@hkg ~ # wg show dn42-4242422547
interface: dn42-4242422547
public key: AHEJ0dXDDxJF0EapR7Ssx2eQV9ReB/OvkWPu7ypWbkA=
private key: (hidden)
listening port: 22547

peer: xelzwt1j0aoKjsQnnq8jMjZNLbLucBPwPTvHgFH/czs=
endpoint: 5.102.125.26:23322
allowed ips: 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8, fe80::/64
latest handshake: 25 seconds ago
transfer: 96.59 MiB received, 532.45 MiB sent

开机自启动:

#

配置 eBGP

/etc/bird/dn42_peers 下新建 <ASN>.conf

1
2
3
4
protocol bgp <BGP 会话名> from dn42_peers {
neighbor <对方的 LLA 地址> % '<WireGuard 隧道名>' external;
description "<对方联系信息 非必需>";
}

需要注意的是:之前的 eBGP 模板内已经默认启用了 Extended next hop(因为绝大部分 Peer 都是默认启用的),不再需要如下配置:

1
2
3
4
5
6
7
8
protocol bgp <BGP 会话名> from dn42_peers {
neighbor <对方的 LLA 地址> % '<WireGuard 隧道名>' external;
description "<对方联系信息 非必需>";

ipv4{
extended next hop;
};
}

完整示例

1
2
3
4
protocol bgp DN42_4242422547_v6 from dn42_peers {
neighbor fe80::2547 % 'dn42-4242422547' external;
description "@lantian1998";
}

测试 LLA 是否可连通,可使用:

$

如:

1
2
3
4
5
6
7
8
9
10
root@hkg ~# ping6 fe80::2547%dn42-4242422547
PING fe80::2547%dn42-4242422547 (fe80::2547%dn42-4242422547) 56 data bytes
64 bytes from fe80::2547%dn42-4242422547: icmp_seq=1 ttl=64 time=1.80 ms
64 bytes from fe80::2547%dn42-4242422547: icmp_seq=2 ttl=64 time=1.70 ms
64 bytes from fe80::2547%dn42-4242422547: icmp_seq=3 ttl=64 time=1.79 ms
64 bytes from fe80::2547%dn42-4242422547: icmp_seq=4 ttl=64 time=1.81 ms
^C
--- fe80::2547%dn42-4242422547 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 1.701/1.775/1.806/0.043 ms

当一切处理妥当后运行 birdc c 重载配置,使用 birdc s p 查看当前 BGP 会话:

1
2
3
4
5
6
7
8
9
10
11
root@hkg ~# birdc s p
BIRD 3.2.0 ready.
Name Proto Table State Since Info
DN42_4242422547_v6 BGP --- up 2026-03-03 Established
dn42_rpki RPKI --- up 2026-03-03 Established
device1 Device --- up 2026-03-03
kernel1 Kernel master6 up 2026-03-03
kernel2 Kernel master4 up 2026-03-03
static1 Static master4 up 2026-03-03
static2 Static master6 up 2026-03-03
...
不使用 MP-BGP

如果只想交互其中一种路由,则不需要对方的 LLA,而是需要对方的 IPv4 或 IPv6(取决于你要传播哪种路由)

并使用如下 eBGP 配置:

1
2
3
4
protocol bgp <BGP 会话名> from dn42_peers {
neighbor <对方的 IPv4/v6 地址> as <对方ASN>;
description "<对方联系信息 非必需>";
}

至此,已完成一台节点的互 Peer。

内网互联

目前,还只是单个节点和他人 Peer,而当我们有多个节点之后,就需要让节点内部相互联通,我目前的网络架构如下:

graph LR
subgraph zt[ZeroTier Underlay]
	us[LAX, US]
	hk[HKG, CN]
	de[DEU, DE]
	cn1[CNVO, CN]
	cn2[Homelab, CN]
	
	us <--> hk
	us <--> de
	us <--> cn1
	us <--> cn2
	hk <--> de
	hk <--> cn1
	hk <--> cn2
	de <--> cn1
	de <--> cn2
	cn1 <--> cn2
end

我将 ZeroTier 作为 L2 使用并组成 Fullmesh 单纯是我懒得维护 n(n-1)/2 条 wg 隧道

内网 BGP 的办法有两种:OSPF 和 Babel 但我在 ZeroTier 下使用 Babel 发生了一些问题,遂用回了 OSPF

基于上面的信息,我决定暂时使用 OSPF Broadcast 的配置。

IGP

什么是 IGP

内部网关协议(英语:Interior Gateway Protocol,缩写为 IGP)是指在一个自治系统(AS)内部所使用的一种路由协议。
与此相对,外部网关协议用来在自治系统之间确定网络可达性、并通过内部网关协议来解析某个自治系统内部的路由。

ospf.conf

修改 /etc/bird/ospf.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol ospf v3 dn42_vcnet_ospf {
ipv4 {
import where is_self_net() && source != RTS_BGP;
export where is_self_net() && source != RTS_BGP;
};
include "ospf-area.conf";
};

protocol ospf v3 dn42_vcnet_ospf6 {
ipv6 {
import where is_self_net_v6() && source != RTS_BGP;
export where is_self_net_v6() && source != RTS_BGP;
};
include "ospf-area.conf";
};

ospf-area.conf

修改 /etc/bird/ospf-area.conf

1
2
3
4
5
6
7
8
9
area 0.0.0.0 {
# Dummy 网卡名称
interface "dn42" { stub; };
# ZeroTier 网卡名称
interface "ztugawjlkq" {
cost 160;
type broadcast;
};
};

由于 ZeroTier 同一个网络的网卡名是不变的,这里 interface 的类型只能是 broadcast.

如果你使用 WireGuard 来组成 Fullmesh,应该使用 ptp,可以精细控制 cost.

使用 birdc c 重载配置后,可通过 birdc show ospf neighbors 查看 OSPF 邻居信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@hkg ~ # birdc show ospf neighbors
BIRD 3.2.0 ready.
dn42_vcnet_ospf:
Router ID Pri State DTime Interface Router IP
172.23.100.165 1 Full/Other 35.257 ztugawjlkq fe80::3c49:e1ff:fe5a:792d
172.23.100.170 1 Full/BDR 38.259 ztugawjlkq fe80::3c29:c0ff:fefc:66f7
172.23.100.167 1 Full/Other 31.441 ztugawjlkq fe80::3ce2:48ff:febb:b986
172.23.100.166 1 Full/Other 24.565 ztugawjlkq fe80::3cf1:97ff:fee8:b68f

dn42_vcnet_ospf6:
Router ID Pri State DTime Interface Router IP
172.23.100.165 1 Full/Other 35.257 ztugawjlkq fe80::3c49:e1ff:fe5a:792d
172.23.100.170 1 Full/BDR 38.263 ztugawjlkq fe80::3c29:c0ff:fefc:66f7
172.23.100.167 1 Full/Other 31.441 ztugawjlkq fe80::3ce2:48ff:febb:b986
172.23.100.166 1 Full/Other 34.562 ztugawjlkq fe80::3cf1:97ff:fee8:b68f

iBGP

通常情况下,建立 iBGP 需要各个节点组成 Fullmesh,修改 /etc/bird/ibgp.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template bgp ibgpeers {
local as OWNAS;
ipv4 {
import filter {
import where source = RTS_BGP && is_valid_network() && !is_self_net();
export where source = RTS_BGP && is_valid_network() && !is_self_net();
next hop self;
extended next hop;
};
ipv6 {
import where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6();
export where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6();
next hop self;
};
};

include "ibgp/*";

详细解释来自 iYoRoy:

导入和导出规则确保iBGP仅处理BGP协议学到的路由,并且过滤掉IGP的路由防止环回

next hop self是必须的,指示 BIRD 在向 iBGP 邻居导出路由时,将下一跳重写为边界路由器自身的IP地址(而非原始的外部下一跳)。因为内部路由器无法直接访问外部邻居地址,若不重写则会被认定为地址不可达。重写后,内部路由器只需通过 IGP 路由将流量送至边界路由器,由边界路由器完成最终的外部转发。

因为我希望使用IPv6地址建立MP-BGP,通过IPv6路由IPv4,因此在IPv4中启用了extended next hop

接下来,需要给每台节点设置一个 iBGP Peer 配置,在 /etc/bird/ibgp/ 下创建文件:

1
2
3
protocol bgp '<BGP 会话名>' from ibgpeers {
neighbor <其他节点的 IPv6 ULA 地址> as OWNAS;
}

完整示例

1
2
3
protocol bgp 'dn42_ibgp_us' from ibgpeers{
neighbor fd48:8669:9f9f::5 as OWNAS;
}

运行 birdc c 重载配置即可。

优化路由

在完成上面的基础配置后,我们已经可以连上 DN42 的网络了,但此时的选路并非最优,尤其是当你的节点和非本地节点互 Peer 的时候。

BGP Community

Community属性介绍

团体属性是一组有相同特征的目的地址的集合。团体属性用来简化路由策略的应用和降低维护管理的难度,利用团体可以使多个AS中的一组BGP设备共享相同的策略。团体是一个路由属性,在BGP对等体之间传播,且不受AS的限制。BGP设备在将带有团体属性的路由发布给其它对等体之前,可以先改变此路由原有的团体属性。

简单来说,BGP Community 就是给路由打标签,不同的标签有助于我们对路由进行区分和管理。

来自 iYoRoy 的介绍:

…(本文)仅针对地理位置信息添加BGP Communities并进行优选。一般来说这样就足够了。(还有个原因是其他的我还没太搞明白)
注意: 我们应该只对自己的AS添加地理位置信息相关的BGP Communities,不应当对邻居传递来的路由添加相关条目。若对邻居的路由加上自己的地区Communities则会造成伪造路由起源,引发路由劫持。下游可能会误判流量路径,将本应直连的流量绕道至你的网络,增加延迟的同时大量消耗你的网络的流量。(Large Communities除外,Large Community有一套验证机制可以防止此类事情发生,但是不在本文讨论范围内)

我们需要首先找到自己节点所在的国家、地区,按照 BGP-communities #Route Origin 小节所列出的代码,在 /etc/bird/bird.conf 里面添加下面两行:

1
2
define DN42_REGION = 52;   	# 52代表亚洲东部地区
define DN42_COUNTRY= 1344; # 1344代表香港

之后,修改 /etc/bird/ebgp.confdn42_peer 模板:

1
2
3
4
5
6
7
8
9
10
11
         export filter {
- if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept;
+ if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then{
+ if (is_self_net()) then { # 检查是否是自己的路由
+ bgp_community.add((64511, DN42_REGION)); # 打上大洲级别的区域信息
+ bgp_community.add((64511, DN42_COUNTRY)); # 打上国家/地区信息
+ }
+ accept;
+ }
reject;
};

上面是 IPv4 块里面的,修改 IPv6 块的 export filter 时,记得将 is_valid_network()is_self_net() 修改为 is_valid_network_v6()is_self_net_v6().

至此,我们成功给自己的路由打上了标签。

local_pref

local_pref

当一条BGP路由器中存在多条去往同一目标网络的 BGP 路由时,BGP 协议会对这些 BGP 路由属性进行比较,从而筛选出最佳到达目标网络的通达路径;本地优先属性,只在IBGP对等体之间进行交换,即:同一AS内进行,不会通告给AS 域外;用于判断流量离开AS时选择的最佳路由;

现在,我们要利用他人的 Communities 标签进行路由优选。

下面是我暂时使用的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function ebgp_calculate_pref() {
# 基础优先级
int pref = 1000;

# 同区域 +100
if bgp_community ~ [(64511, DN42_REGION)] then
pref = pref + 100;

# 同国家 +50
if bgp_community ~ [(64511, DN42_COUNTRY)] then
pref = pref + 50;

# eBGP邻居 +200
if bgp_path.len = 1 then
pref = pref + 200;

return pref;
}

可保存在 /etc/bird/ebgp.conf 的开头,同时修改 eBGP 的 Peer 模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ipv4 {
extended next hop;
import keep filtered;
import filter {
if is_valid_network() && !is_self_net() then {
if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {
print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
reject;
}
+ bgp_local_pref = ebgp_calculate_pref();
accept;
}
reject;
};

IPv6 同样,只需要在相同位置添加即可。

iBGP 的 local_pref

这部分是我在配置好 IGP 与 iBGP 后出现的一个问题:内部的某个节点(Node 1)有一条最优路由,而其他节点(Node 2,3)会绕道前面的节点(Node 1),即使其他节点有更合适的路由可选。

下面的配置有待观察。

修改 /etc/bird/ibgp.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
template bgp ibgpeers {
local as OWNAS;
ipv4 {
- import where source = RTS_BGP && is_valid_network() && !is_self_net();
+ import filter {
+ if source = RTS_BGP && is_valid_network() && !is_self_net() then {
+ bgp_local_pref = 200;
+ accept;
+ }
+ };
export where source = RTS_BGP && is_valid_network() && !is_self_net();
next hop self;
extended next hop;
};
ipv6 {
- import where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6();
+ import filter {
+ if source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6() then {
+ bgp_local_pref = 200;
+ accept;
+ }
+ };
export where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6();
next hop self;
};
};

上面的配置是将来自 iBGP 的外部路由降低优先级,让本地节点优先使用 eBGP 的路由。

BGP Dampening

什么是 BGP Flapping

BGP Flapping 指的是同一条路由的路径在短时间内发生大量变化,一般源于一个网络反复广播、撤销广播这一条路由。每次广播或撤销路由时,这个网络会把这条路由传递给所有与它相连的 Peer,这些 Peer 会根据这条路由计算出新的最佳路径,然后把新路径传递给它们的 Peer,与此类推。

你能想象吗:

Prefix Duration Changes Rate
fd75:7775::/48 7d 20:13:20 18.314 million 52/s

最近这个由网段引发的路由更改竟然高达 1800 万!

为了抑制这种现象,我们会用上 Kioubit 开发的 FlapAlerted.

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
services:
flapalerted:
image: ghcr.io/kioubit/flapalerted
network_mode: host
command:
- "--asn"
- "4242422547" # 修改成你自己的 ASN
- "--bgpListenAddress"
- "127.0.0.1:1790" # BGP 会话监听端口,稍后你的 BGP 软件需要连接到这个端口
- "--httpAPIListenAddress"
- "127.0.0.1:8080" # HTTP API 监听端口,稍后 StayRTR 需要连接到这个端口
- "-routeChangeCounter"
- "120" # 路由路径在一分钟内需要变更的次数才会被列入前缀列表。默认值 600
- "-overThresholdTarget"
- "5" # 连续多少分钟速率达到或超过 routeChangeCounter 才会触发事件。默认 10
- "-underThresholdTarget"
- "30" # 连续多少分钟速率低于 routeChangeCounter 才会移除事件。默认 15
restart: unless-stopped

stayrtr:
image: rpki/stayrtr
network_mode: host
command:
- "--bind"
- "127.0.0.1:8083" # RPKI-to-Router 协议的监听地址
- "--metrics.addr"
- "127.0.0.1:8084" # Prometheus 格式统计信息 API 的监听地址
- "--cache"
- "http://127.0.0.1:8080/flaps/active/roa" # 修改成你的 FlapAlerted 服务器地址
- "--rtr.expire"
- "3600" # 如果 FlapAlerted 服务器离线,保留现有的信息多长时间
- "--rtr.refresh"
- "300" # 多长时间从 FlapAlerted 服务器刷新一次信息
- "--rtr.retry"
- "300" # 如果 FlapAlerted 服务器离线,多长时间后重试
restart: unless-stopped
depends_on:
- flapalerted

启动 FlapAlerted 和 StayRTR 后,新建 /etc/bird/flap.conf 将路由信息传给 FlapAlerted:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
protocol bgp dn42_flapalerted {
local as OWNAS;

# 修改成 FlapAlerted 设置的 ASN 和 BGP IP/端口。
# 这里我们使用和自己网络相同的 ASN,是为了利用 BGP 协议不会把来自 iBGP 的路由(即自己其它节点的路由)转发给 iBGP Peer 的特点。
# 除非你开启了 add paths 选项,否则来自自己其它节点的路由只会包含最优的路由,如果 Flapping 发生在次优路由就会被隐藏。
# 因此建议有多个节点的用户在每个节点上都单独和 FlapAlerted 建立连接。

neighbor <FlapAlerted 的地址> as OWNAS port 1790;

ipv4 {
# 开启 add paths 选项,把非最优路由也发给 FlapAlerted,让次优路由 Flapping 也可见。
add paths on;
export all;
import none; # 不需要从 FlapAlerted 接收任何路由
};

ipv6 {
add paths on;
export all;
import none;
};
}

# 新建专用于 FlapAlerted 的 ROA 表
roa4 table roa_flap_v4;
roa6 table roa_flap_v6;

protocol rpki dn42_rpki_flapalerted {
roa4 { table roa_flap_v4; };
roa6 { table roa_flap_v6; };
remote 10.22.44.5 port 8083; # 修改成 StayRTR 监听的端口
max version 1;
retry keep 15; # 如果连接中断,每 10 秒重连一次
};

在前文,我们已经配置了 ROA 过滤,因此需要新建一个新 ROA 表,记得在 /etc/bird/bird.conf 里引入此配置文件。

现在,我们需要修改 eBGP 中模板的导入过滤规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  ipv4 {
extended next hop;
import keep filtered;
import filter {
if is_valid_network() && !is_self_net() then {
if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {
print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last;
reject;
}
+ if (roa_check(roa_flap_v4, net, bgp_path.last) = ROA_INVALID) then {
+ # 路由频繁变更,被 FlapAlerted 劫持去了 AS0,Bird 认为路由来自错误的 ASN
+ reject;
+ }
bgp_local_pref = ebgp_calculate_pref();
accept;
}
reject;
};

IPv6 类似,只需要将 roa_flap_v4 修改为 roa_flap_v6 即可。

结语

最后,加入 DN42 的过程还是挺有乐趣的.

其他

Thanks:

感谢 Aluy 的提供的 AMS, NL 节点。
感谢 DN42 群友的帮助和指导。

推荐:

在这里介绍一下 Bird 中文社区的作品(转发自 Telegram):

BIRD-LSP 是一个专为 BIRD2 配置文件打造的现代化工具链项目,提供 Language Server Protocol (LSP) 支持、代码格式化 (Formatter & Parser) 与静态分析 (Linter) 能力。

VSCode 安装 | OpenVSX 安装

目前支持的特性 (v0.3.0):

  • 🎨 语法高亮 | 基于 Tree-sitter 的高精度语法解析

  • 🔍 实时诊断 | 内置 32+ 条 Lint 规则 + 跨文件分析

  • 📝 代码格式化 | 基于 🦀 Rust + dprint 插件实现的高性能格式化库

  • 🔎 悬停提示 | 对 conf 关键词提供 用法示例/类型提示/文档说明

  • 🏗 符号导航 | 跳转到定义、查找引用(支持跨文件)

*目前 BIRD LSP 还处于 Beta 阶段,部署在生产环节之前请谨慎评估

👩‍💻 GitHub 开源地址, 欢迎 Star: https://github.com/bird-chinese-community/BIRD-LSP