BGP新手实战教程

这是一篇写给BGP新手的实战教程,旨在快速让Player们配置好bird并写好正确的过滤器。通常而言,你现在应该有一段IP和一个ASN了。如果你没有,欢迎前往 Skywolf Cloud 申请…

你需要:

  • 一个ASN
  • 一段IP
  • 一台有BGP Session的VPS(或一个BGP Tunnel)
  • 较为熟练的使用Linux的能力
  • 能够自己动手搜索和解决一部分问题的能力
  • 一颗永无止境的心

我们默认使用的系统为Debian 11。

开始之前

在这章,我们将介绍一些BGP跟Bird的相关知识。

何为路由

通俗而言,路由就是“将一个数据包根据路由表进行转发的过程”。

而一条路由,就是一条“这个目标地址该被转发到哪个接口”的记录。

路由表,就是记录了很多个“这个目标地址该被转发到哪个接口”的表。

当然,路由表内还包含很多比如失效日期、MTU之类的附属信息,并通过优化手段进行存储,具体实现可以自行去查询各厂源代码或Google。

比如,如下是一条在Linux内记录的去往1.1.1.1的路由

1
1.1.1.1 via 100.100.0.0 dev eth0 src 2.2.2.2

接下来我们来分段分析

  • 1.1.1.1 目标IP,实际存储肯定是按照IP块来存储
  • via 100.100.0.0 下一跳的IP
  • dev eth0 目标接口
  • src 2.2.2.2 当这跳转发的来源是本机的时候,采用的源地址IP

如果同时有devvia,那么会根据ARP表将目标MAC重写为下一跳的MAC(如果表内没有则在dev指定的接口查询),然后转发到dev指定的接口。

如果仅有dev没有via,那么会根据ARP表将目标MAC重写为目标地址的MAC(如果表内没有则在dev指定的接口查询),然后转发到dev指定的接口。

如果仅有via没有dev,那么会递归查询到至少拥有dev的下一跳,根据原来的via和查询到的dev,按照第一条规则处理。

BGP Session 与 IP Transit

有的时候,我们会看到有商家说:提供BGP Session,也有的商家说:提供 IP Transit,那么它们的区别在哪呢?

区别:BGP Session 指的是一个会话,而IP Transit是在这个会话之内提供的服务。

一般而言,提供BGP Session的商家会给你提供互联网上全表(互联网上所有IP的路由)的服务,这项服务也被称作IP Transit。也就是说,一般商家而言的BGP Session,包括IP Transit。

而当我们直接买带宽的时候,我们会听到IP Transit(有时简称IPT),因为它要与另外一项服务:传输(Transit)区分开,后者指的是一条点对点的隧道,但是有服务保证。

而鉴于目前互联网上通用的不同AS间发送路由的协议只有BGP,所以IP Transit一般隐含了BGP Session,不然路由无法接收,更谈不上广播了。

什么是BGP

以下文段来自维基百科

边界网关协议(英语:Border Gateway Protocol,缩写:BGP)是互联网

上一个核心的去中心化自治路由协议。它通过维护IP路由表或“前缀”表来实现自治系统(AS)之间的可达性,属于矢量路由协议。BGP不使用传统的内部网关协议(IGP)的指标,而使用基于路径、网络策略或规则集来决定路由。因此,它更适合被称为矢量性协议,而不是路由协议。大多数互联网服务提供商必须使用BGP来与其他ISP创建路由连接(尤其是当它们采取多宿主连接时)。因此,即使大多数互联网用户不直接使用它,但是与7号信令系统——即通过PSTN的跨供应商核心响应设置协议相比,BGP仍然是互联网最重要的协议之一。特大型的私有IP网络也可以使用BGP。例如,当需要将若干个大型的OSPF开放最短路径优先)网络进行合并,而OSPF本身又无法提供这种可扩展性时。使用BGP的另一个原因是其能为多宿主的单个或多个ISP网络提供更好的冗余。

简而言之,BGP是在自治系统之间的最主要的路由协议(思科的EIGRP也能提供自治系统之间的路由,但目前我没看到任何实际使用),用来在自治系统间传递路由。通常而言,BGP只会把最优路由传递给对方。

什么是Bird

以下文段来自bird官网

BIRD项目旨在开发一个功能齐全的动态 IP 路由守护程序,主要针对(但不限于)Linux、FreeBSD 和其他类 UNIX系统,并在GNU 通用公共许可证

下分发。

目前它由CZ.NIC Labs

开发和支持。目前BIRD团队成员有:

(OSPF、BSD 端口、发布、打包)

Martin Mareš

(整体架构、核心、转储、BGP)

Ondřej Zajíček

(新的 BGP 功能、OSPFv3、BFD)

Maria Matějka

  • (MPLS、过滤器、多线程)

BIRD团队成员是 Libor Forst

Pavel Machek

如上所写,bird是一个在Linux和FreeBSD平台运行的大部分由C实现的路由程序,支持包括但不限于BGP、RIP、OSPF、Babel等协议。同类产品还有OpenBGPD, FRRouting等。但是目前使用比较广的是Bird,本wiki也是为了bird而写。

Bird的版本变迁

Bird目前有三个大版本:v1, v2, v3

v1

Bird v1 是最早的Bird,由两个Daemon分别支持IPv4与IPv6的路由,最晚版本是1.6.8,于2019年9月11日发布。

目前而言我们不推荐使用此版本。

v2

Bird v2是目前Bird的主线版本。与v1相比,v2仅使用一个Daemon来运行v4与v6的路由。最新版本是2.0.9,于2022年3月2日发布。

本Wiki将主要讲述该版本的使用。

v3

Bird v3是Bird的下一代版本,该版本增加了多线程的支持。目前的最新版本为3.0-alpha0,于2022年2月7日发布。

目前v3还处在alpha阶段,我们欢迎有时间的用户去测试这个版本,并给作者提出建议。

Bird的基本语法

以下内容摘自 Soha 的文章

和 Juniper、Cisco 等路由器,或 FRR(Quagga)等路由软件不同,写 BIRD 的配置文件就像是在写程序,如果你是个程序员,那么上手应该会很快。正因如此,它也有着和常见编程语言所类似的语法。下面则是一些基础语法。

杂项

/* */ 包起来的内容是注释,# 至其所在行行末的内容也是注释。

分号 ; 标示着一个选项或语句的结束,而花括号 { } 内则是多个选项或语句。

在 BIRD 的配置文件中,有协议实例(protocol <proto> <name> {}),过滤器(filter <name> [local_variables] {}),函数(function <name> [local_variables] {})可以定义,这些将在下文各部分选择性挑重点介绍。

print 用来输出内容,这些会输出在 BIRD 的日志文件中,在使用 systemd 的系统中,可以使用 journalctl -xeu bird.service 查看。

变量与常量

变量名、常量名、协议实例名、过滤器名、函数名等,都遵循着这样的规则:必须以下划线或字母开头,名称内也只能有字母、数字、下划线。比如 Soha233_my_filterbgp_4842 都是合法的名字。当然在 BIRD 中有例外,如果一个名字用单引号括起来,那么我们还可以用冒号、横线、点,比如 '2.333:what-a-strange-name',只不过不推荐这么用就是了。

使用 define 定义常量,如 define LOCAL_AS = 65550

BIRD 中可以针对很多变量类型定义集合。集合用一对方括号定义,如 [1, 2, 3, 4]。集合可以用范围来快速生成,比如 [1, 2, 10..13] 就会生成为 [1, 2, 10, 11, 12, 13]。范围的写法还可以用在社区属性中,如 [(64512, 100..200)],在社区属性中还可以用通配符,如 [(1, 2, *)]

前缀的集合中的范围写法较为复杂,[prefix{low, high}]prefix 是一个用于匹配的前缀,lowhigh 两个值限制了它的 CIDR 长度。[192.168.1.0/24{16,30}] 表示的是包含或被包含于 192.168.1.0/24 且 CIDR 在 16-30 之间的前缀,例如 192.168.0.0/20192.168.1.0/29 均属于这个集合,而 192.168.233.0/24 不属于。这样子的写法可能过于麻烦,所以 BIRD 中也使用加号和减号提供了两种便捷的写法,如 [2001:db8:10::/44+, 2001:db8:2333::/48-] 则等价于 [2001:db8:10::/44{44,128}, 2001:db8:2333::/48{0,48}]

在 BIRD 中还有一类特殊的变量类型,他们都是列表,bgppath(AS Path,路由的 bgp_path 属性)、clist(BGP Community 列表,路由的 bgp_community 属性)、eclist(BGP Extended Community 列表,路由的 bgp_ext_community 属性)、lclist(BGP Large Community 列表,路由的 bgp_large_community 属性)都是这类变量,他们的操作有非常特殊的用法。下面的代码展示了 bgppath 的用法。clist/eclist/lclist 与之类似,但是它们只能使用其中的 emptylenadddeletefilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function foo()
bgppath P;
bgppath P2; {
print "path 中第一个元素是", P.first, ",最后一个元素是", P.last;
# 第一个元素可以认为是邻居的 ASN,最后一个元素是宣告这条路由的 ASN
# 这两个在 P 中没有元素的时候是 0
print "path 的长度是", P.len;
if P.empty then {
print "path 为空";
}
P.prepend(233); # 在 path 的第一个位置插入元素
P.delete(233); # 删除 path 中所有等于 233 的元素
P.delete([64512..65535]); # 删除 path 中所有属于集合 [64512..65535] 的元素
P.filter([64512..65535]); # 只在 path 中留下集合 [64512..65535] 中出现的元素

# 如果不想改变 P,可以使用下面这样的方法将操作后的结果存入 P2
P2 = delete(P, 233)
P2 = filter(P, [64512..65535])
}

变量只能定义在函数或过滤器的最开头(左花括号外面),关于变量类型的更详细信息,请移步官方文档相关部分

操作符

在 BIRD 中有很多常见的操作符。如 +-*/() 这些基本的算数操作符,有等于 a = b、不等于 a != b、大于 a > b、大于等于 a >= b、小于 a < b、小于等于 a <= b 这些比较符,有与 &&、或 ||、非 ! 这三种逻辑操作符。还有 ~!~ 这两种判断包含或者不包含的操作符。包含操作符的用法写在附录 3 中。

分支

过滤器和函数中的语句都是顺序执行的。同时支持 caseif 两种分支语句。在 BIRD 中是不支持循环的。

if 的写法如下:

1
2
3
4
5
if 6939 ~ bgp_path then {   # 只要 AS Path 中有 6939
bgp_local_pref = 233; # 就将这条路由的 Local Preference 调为 233
} else {
bgp_local_pref = 2333; # 否则设为 2333
}

case 的写法如下:

1
2
3
4
5
6
case arg1 {
2: print "two"; print "I can do more commands without {}";
# ^ case 不需要花括号就能在一个分支中写下更多语句。
3..5: print "three to five";
else: print "something else";
}

在 BIRD 中,if 和 case 的写法均与常见编程语言略有不同。

阶段1 BGP Player

从这篇文章开始,我们就要安装bird并开始往外广播了。

开始之前

我们假设:

  • 你只拥有IPv6段
  • 你的ASN是 AS114514
  • 你的IPv6段为 2404::/48,你计划能使用 2404::1访问这台机器
  • 该机器的公网IPv4为 1.1.1.1 公网IPv6为 2405::1
  • 你的BGP邻居为AS7720,对方的IPv6为2405::2
  • 你与你的邻居在同一个子网且一跳可达

该设置将通用于本篇文章

安装Bird

目前而言,在bullseye(Debian 11)中,stable源里的bird是2.0.7,backports源里的bird是2.0.8,最新版本是2.0.10。我们 **强烈建议 **使用2.0.10,里面修复了很多2.0.9的bug。如果您想使用2.0.8,将debian添加backports源后用apt安装即可。

下面这段shell脚本直接复制到ssh执行即可快捷安装bird 2.0.10

1
2
3
4
5
6
7
8
9
apt update
apt install -y build-essential autoconf git flex bison m4 libssh-dev libncurses-dev libreadline-dev
cd ~
git clone https://gitlab.nic.cz/labs/bird.git -b 2.0.10 BIRD
cd BIRD
autoreconf
./configure --prefix= --sysconfdir=/etc/bird --runstatedir=/var/run/bird
make
make install

安装完后执行service bird start,后执行 birdc show status

返回内容第一行应该与如下相似:

1
BIRD v2.0.10-6-g4b1aa37f ready.

如果该行相似,并且版本号确认为2.0.10.且最后一行为

1
Daemon is up and running

那么bird就安装成功了。

配置虚拟网卡

如果你需要使用2404::1来访问你的机器,那么你的机器就需要有一个网卡绑定这个地址。由于这个IP并不在任意一个物理接口中可用(或者在多个物理接口中可用),所以你需要使用一个虚拟网卡来绑定它。

dummy网卡的作用,就是绑定一个并不实际关联到物理接口的地址到你的机器。它的工作方式与loopback接口类似,但你可以创建非常多的dummy接口用来绑定非常多的地址(尽管我非常不建议这么做,这样可能会导致bird在扫描网卡的时候占用大量CPU导致BGP断连)。

我们可以用下面的命令创建一个dummy网卡并绑定地址。

1
2
3
ip link add dummy0 type dummy # 新建一个dummy网卡,命名为dummy0(强烈建议使用一个规则的命名方式,比如dummy+数字)
ip link set dummy0 up # 标记网卡状态为UP
ip addr add 2404:f4c0::1/128 dev dummy0 # 向dummy网卡添加地址

通过命令创建的网卡每次重启会消失,建议让它在开机时自行启动(比如rc.local)或写入interfaces文件,具体方法请自行Google

强烈建议dummy只绑定/128(IPv6)或/32(IPv4)地址,否则可能跟接下来在bird内配置的地址导致冲突从而出现错误

撰写配置文件

bird在debian的默认配置文件在/etc/bird/bird.conf

你可以复制一份以备之后详细阅读他的注释(有很多有用的示例),现在我们要做的就是清空它。

运行如下命令:

1
echo > /etc/bird/bird.conf

现在bird.conf已经清空了。

基本配置

我们先写一个基本的配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
log syslog all;
router id 1.1.1.1; # 指定路由ID,通常而言需要全球单播ipv4作为routerid
define ASN=114514; # 定义常量ASN,提升可扩展性
define OWNIPv6s=[2404::/48]; # 定义OWNIPv6s,包括自己的IPv6,提升可扩展性,为后续过滤器做准备
protocol device { # 扫描设备IP,这么写即可
};
protocol kernel {
ipv6 {
export all; # 将所有路由都导入系统路由表
};
};
protocol static static_v6 {
ipv6;
route 2404::/48 reject; #在STATIC中添加路由
};

上面配置文件比较重要的是static。一般,我们在static的第一行用ipv4;ipv6;指定该static的类型,然后在下面用route xxxxx/xx reject;来宣告路由。由于该整段都分配在该设备上,并且更细分的路由(如/128 /64)会比该路由优先,所以这里reject(也可用unreachable,即向请求方返回icmp unreachable信息)起到保底的作用。

过滤器

过滤器非常的重要,做好自己的过滤是对与你建立BGP连接的人的尊敬

所幸,作为一个只有一个接点的BGP Player,我们不需要太过于复杂的过滤器,对于导出,我们只需要下面这些即可

1
2
3
4
filter export_filter_v6 {
if net ~ OWNIPv6s then accept; # 如果前缀包括在OWNIPv6s内则放出
reject; # 否则全部拒绝
};

而对于导入,可以直接不过滤,收全表

1
2
3
filter import_filter_v6 {
accept; # 接收所有路由
};

也可以如下所示过滤掉默认路由

1
2
3
4
filter import_filter_v6 {
if net ~ [::/0] then reject; # 如果为默认路由则拒绝
accept; # 接收所有其他路由
};

写好过滤器后,我们就可以开始配置BGP会话了。

配置BGP会话

如下所示

1
2
3
4
5
6
7
8
9
10
protocol bgp bgp_as7720_v6 { # 建议给自己指定一个命名规则
local 2405::1 as ASN; # 指定本端地址与ASN
neighbor 2405::2 as 7720; # 指定对端地址与ASN
ipv6 { # 指定要在该BGP邻居上跑的协议
import filter import_filter_v6; # 指定导入过滤器
export filter export_filter_v6; # 指定导出过滤器
export limit 10; # 限制导出前缀数量,根据需要调整,防止过滤器配糊导致session爆炸需要联系对方NOC手动重启(比如HE)
};
graceful restart; # 平滑重启,建议支持,防止重启bird的时候造成路由撤回导致服务中断
};

在BGP中,是可以在一个会话上传递多种协议的,也就是Multiprotocol extensions for BGP(也简称MP-BGP)(RFC 4760

)。但是,如果没有明确约定,一般都是每种协议起一个会话。

收尾

通常而言,如果一切正常,你的前缀应该已经广播出去,并将在24-72小时内传递至全球互联网,并且你应该能用你所广播的地址(在本篇文章中是2404::1)访问你的机器。

通常而言,如果你只为自己服务,那目前的配置已经足够了。但如果你想为别人提供服务,那目前的配置就力有不逮。下一章,我们将讲解当需要给别人配置的时候该如何配置。

附录:完整配置文件

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
log syslog all;
router id 1.1.1.1; # 指定路由ID,通常而言需要全球单播ipv4作为routerid
define ASN=114514; # 定义常量ASN,提升可扩展性
define OWNIPv6s=[2404::/48]; # 定义OWNIPv6s,包括自己的IPv6,提升可扩展性,为后续过滤器做准备
protocol device { # 扫描设备IP,这么写即可
};
protocol kernel {
ipv6 {
export all; # 将所有路由都导入系统路由表
};
};
protocol static static_v6 {
ipv6;
route 2404::/48 reject; #在STATIC中添加路由
};
filter export_filter_v6 {
if net ~ OWNIPv6s then accept; # 如果前缀包括在OWNIPv6s内则放出
reject; # 否则全部拒绝
};
filter import_filter_v6 {
if net ~ [::/0] then reject; # 如果为默认路由则拒绝
accept; # 接收所有其他路由
};
protocol bgp bgp_as7720_v6 { # 建议给自己指定一个命名规则
local 2405::1 as ASN; # 指定本端地址与ASN
neighbor 2405::2 as 7720; # 指定对端地址与ASN
ipv6 { # 指定要在该BGP邻居上跑的协议
import filter import_filter_v6; # 指定导入过滤器
export filter export_filter_v6; # 指定导出过滤器
export limit 10; # 限制导出前缀数量,根据需要调整,防止过滤器配糊导致session爆炸需要联系对方NOC手动重启(比如HE)
};
graceful restart; # 平滑重启,建议支持,防止重启bird的时候造成路由撤回导致服务中断
};

阶段2 向他人服务

在这篇文章中,我们将讲述在给他人提供服务的时候,应该如何搭建一个更可靠的BGP系统。

前言

我个人认为,这一部分会比较重要。

开始之前

我们假设:

  • 你只拥有IPv6段
  • 你的ASN是 AS114514
  • 你的IPv6段为 2404::/48,你计划能使用 2404::1访问这台机器
  • 该机器的公网IPv4为 1.1.1.1 公网IPv6为 2405::1
  • 你的BGP上游为AS7720,对方的IPv6为2405::2,对方的IPv6前缀为2404:1::/48
  • 你的BGP下游为AS218072,对方的IPv6为2405::3,对方的IPv6前缀为2404:2::/48
  • 你的BGP对等为AS6939,对方的IPv6为2405::4,对方的IPv6前缀为2404:3::/48
  • 你与你的邻居在同一个子网且一跳可达

该设置将通用于本篇文章

概念

IRR

IRR(Internet Routing Registry)是存储互联网路由对象的数据库,里面记录了某个前缀该被路由到哪个ASN。IRR实际上由很多个数据库组成,具体列表请看这里。五大RIR(ARIN,RIPE,AFRINIC,APNIC,LACNIC),比较老的T1(LEVEL3,NTTCOM)都有自己的IRR数据库,同时还有一个商业数据库RADb和一个非营利数据库ALTDB。这9个数据库是目前比较通用的数据库。

RPKI

关于RPKI的介绍我推荐查看这篇文章。简而言之,RPKI也可以用来将ASN与路由关联在一起,但与IRR的区别是RPKI需要使用证书签名,使用它可以有效地防止路由劫持。

上下游和对等

在业务中,我们遇到的需要建立BGP的对象主要可以分为三类:上游,下游和对等。

上游

上游(Upstream),即向你提供互联网服务(IP Transit)的对象,比如各类ISP。一般而言,上游一般会发送互联网的全部路由,即全表。对于某些业务,上游可能仅会发送一部分路由(例如中国优化路由)。

对于上游,我们应发送从自身和下游收到的路由。

下游

下游(Downstream),即你向其提供互联网服务(IP Transit)的对象。

对于下游,我们应发送从自身、上游、下游、对等收到的表,即全表。对于特殊下游(例如路由优化业务),我们仅发送所需的路由。对于有IP段白名单的特殊上游,我们应仅向拥有白名单内IP段的下游发相关路由。

对等

对等(Peer),通常而言是你/对方需要直接访问对方/你服务的对象,例如Cloudflare和Google。一般对等的目的是节省结算费用(通常楼内线的费用远小于同等带宽的IP Transit费用)。

对于对等,我们应发送从自身和下游收到的路由。

发表

总而言之,发表可以总结为如下规则:

  • 上游和对等:仅发送从自身和下游收到的路由。
  • 下游:一般发全表。对于特殊下游,根据所购买的服务发路由。对于有IP段白名单的特殊上游,我们应仅向拥有白名单内IP段的下游发相关路由。
  • 路由优先级:下游>对等>上游

AS-SET

AS-SET,顾名思义,一个AS的集合。一个AS-SET内通常要包含自己以及自己的下游的ASN。AS-SET允许嵌套。

BGP Community

BGP Community(也称BGP社区) 类似于给路由的标签,对等方可以根据标签内容来做出自己的选择。

BGP Community 有如下三种类型:

  • BGP Community (BGP社区),为一个4字节的值,在Bird内显示为(2byte ASN,2byte value),前二字节为ASN,后二字节由AS自由分配。由于使用它需要一个2byte的ASN(目前申请比较困难),所以一般在大ISP中能见到,或者使用保留ASN。
  • BGP Extended Community (BGP扩展社区),在鸟内一般表示为(type,administrator,value),为一个八字节的值,前二字节为类型,后六字节为管理员和分配的编号,由AS自行分配。它比较著名的应用位于MPLS-VPN内。
  • BGP Large Community (BGP大型社区),在鸟内一般表示为(4byte ASN,4byte value,4byte value),为一个12字节的值,前四字节为ASN,中间四字节与后四字节由AS自由分配。它被开发的主要原因是因为4字节ASN不能用于普通的BGP社区。

目前,在公网上应用比较广的主要为BGP CommunityBGP Large Community

RFC要求所有支持BGP社区的路由器必须处理知名的BGP社区,也就是NO_EXPORT, NO_ADVERTISENO_EXPORT_SUBCONFED。在默认情况下,bird会自动处理这些BGP社区。

过滤器

过滤器非常的重要,做好自己的过滤是对与你建立BGP连接的人的尊敬,如果要为下游服务更是如此

规则

过滤器过滤要基于一套规则。下面我们来看一下HE的规则:

过滤器过滤要基于一套规则。下面我们来看一下HE的规则:

Hurricane Electric Route Filtering Algorithm

This is the route filtering algorithm for customers and peers that have explicit filtering:

  1. Attempt to find an as-set to use for this network.

1.1 In peeringdb, for this ASN, check for an IRR as-set name. Validate the as-set name by retrieving it. If it exists, use it.

1.2 In IRR, query for an aut-num for this ASN. If it exists, inspect the aut-num for this ASN to see if we can extract from their IRR policy an as-set for what they will announce to Hurricane by finding export or mp-export to AS6939, ANY, or AS-ANY. Precedence is as follows: The first match is used, “export” is checked before “mp-export”, and “export: to AS6939” is checked before “export: to ANY” or “export: to AS-ANY”. Validate the as-set name by retrieving it. If it exists, use it.

1.3 Check various internal lists maintained by Hurricane Electric’s NOC that map ASNs to as-set names where we discovered or were told of them. Validate the as-set name by retrieving it. If it exists, use it.

1.4 If no as-set name is found by the previous steps use the ASN.

  1. Collect the received routes for all BGP sessions with this ASN. This details both accepted and filtered routes.
  2. For each route, perform the following rejection tests:

3.1 Reject default routes 0.0.0.0/0 and ::/0.

3.2 Reject AS paths that use BGP AS_SET notation (i.e. {1} or {1 2}, etc). See draft-ietf-idr-deprecate-as-set-confed-set.

3.3 Reject prefix lengths less than minimum and greater than maximum. For IPv4 this is 8 and 24. For IPv6 this is 16 and 48.

3.4 Reject bogons (RFC1918, documentation prefix, etc).

3.5 Reject exchange prefixes for all exchanges Hurricane Electric is connected to.

3.6 Reject AS paths that exceed 50 hops in length. Excessive BGP AS Path Prepending is a Self-Inflicted Vulnerability.

3.7 Reject AS paths that use unallocated 32-bit ASNs between 1000000 and 4199999999. https://www.iana.org/assignments/as-numbers/as-numbers.xhtml

3.8 Reject AS paths that use AS 23456. AS 23456 should not be encountered in the AS paths of BGP speakers that support 32-bit ASNs.

3.9 Reject AS paths that use AS 0. As per RFC 7606, “A BGP speaker MUST NOT originate or propagate a route with an AS number of zero”.

3.10 Reject routes that have RPKI status INVALID_ASN or INVALID_LENGTH based on the origin AS and prefix.

  1. For each route, perform the following acceptance tests:

4.1 If the origin is the neighbor AS, accept routes that have RPKI status VALID based on the origin AS and prefix.

4.2 If the prefix is an announced downstream route that is a subnet of an accepted originated prefix that was accepted due to either RPKI or an RIR handle match, accept the prefix.

4.3 If RIR handles match for the prefix and the peer AS, accept the prefix.

4.4 If this prefix exactly matches a prefix allowed by the IRR policy of this peer, accept the prefix.

4.5 If the first AS in the path matches the peer and path is two hops long and the origin AS is in the expanded as-set for the peer AS and either the RPKI status is VALID or there is an RIR handle match for the origin AS and the prefix, accept the prefix.

  1. Reject all prefixes not explicitly accepted

翻译

Hurricane Electric 的路由过滤算法

这是具有显式过滤的客户和对等方的路由过滤列表:

1.尝试查找该网络的AS-SET

1.1 使用在PeeringDB中匹配该ASN所属IRR策略中的AS-SET如果它存在

1.2 在 IRR 中,查询此 ASN 的 aut-num。如果存在,请检查此 ASN 的 aut-num 以查看我们是否可以从他们的 IRR 策略中提取他们将通过查找到 AS6939、ANY 或 AS-ANY 的 export 或 mp-export 向 HE 宣布的内容的资产。 优先顺序如下:使用第一个匹配项,在“mp-export”之前检查“export”,在“export: to ANY”或“export: to AS-ANY”之前检查“export: to AS6939”。 如果存在,请使用查询来验证AS-SET。

1.3 检查由 HE 的 NOC 维护的各种内部列表,这些列表将 ASN 映射到我们发现或被告知它们的AS-SET。 如果存在,请使用通过查询来验证AS-SET。

1.4 如果前面的步骤没有找到AS-SET,则使用 ASN。

  1. 收集与此 ASN的所有 BGP 会话接收的路由。这个结果同时接受并进行过滤。
  2. 对于每条路由,执行以下拒绝测试:

3.1 拒绝默认路由 0.0.0.0/0 和 ::/0。

3.2 拒绝使用 BGP AS_SET 表示法的 AS 路径(即 {1} 或 {1 2} 等)。请参阅draft-ietf-idr-deprecate-as-set-confed-set。

3.3 拒绝前缀长度小于最小值和大于最大值。IPV4中为 8 和 24,IPV6为16 和 48。

3.4 拒绝 bogons(RFC1918、文档前缀等)。

3.5 拒绝 HE 连接到的所有来自IXP的前缀。

3.6 拒绝长度超过 50 跳的 AS 路径。过多的 BGP AS 路径预置是一个自我造成的漏洞。

3.7 拒绝使用 1000000 到 4199999999 之间未分配的 32 位 ASN 中的 AS 路径。 请参阅 https://www.iana.org/assignments/as-numbers/as-numbers.xhtml

3.8 拒绝使用 AS23456 的 AS 路径。 在支持 32 位 ASN 的 BGP 广播的 AS 路径中不应遇到 AS23456。

3.9 拒绝使用 AS 0 的 AS 路径。根据 RFC 7606,“BGP 广播者不得发起或传播 AS 编号为零的路由”。

3.10 拒绝在源ASN或IP前缀的RPKI中含有INVALID_ASN 或 INVALID_LENGTH状态的路由

  1. 对于每条路由,执行以下接受测试:

4.1 据源 AS 和前缀接受 RPKI 状态为 VALID 的路由。

4.2 如果前缀是一个宣布的下游路由并是一个已接受的起源前缀的子网,该前缀由于 RPKI 或 RIR 句柄匹配而被接受,则接受该前缀。

4.3 如果 RIR 处理前缀和对等 AS 匹配,则接受前缀。

4.4 如果此前缀与该对等网络的 IRR 策略允许的前缀完全匹配,则接受该前缀。

4.5 如果路径中的第一个 AS 与对等网络匹配,并且路径为两跳,并且源 AS 在对等 AS 的扩展AS-SET中,并且 RPKI 状态为 VALID 或存在与源 AS 的 RIR 句柄匹配和前缀,接受前缀。

  1. 拒绝所有未明确接受的前缀

IDC Skywolf 的规则:

  1. 若路由为默认路由,或长度小于最小值/大于最大值,则拒绝。(对于 IPv4,这是 8 和 24。对于 IPv6 来说,这是 16 和 48。)
  2. 若路由的起源ASN为保留ASN,或BGP Path中包含保留ASN,则拒绝。
  3. 若路由为保留前缀,则拒绝。
  4. 若路由的IRR与ASN匹配,且RPKI不为INVALID,则接受。

总结而言,对于对等与下游,我们只应该接受路由长度大于最小值小于最大值,path内不包含保留ASN,不为保留前缀,且IRR匹配,RPKI不为INVALID的路由。

而对于上游,我们仅验证RPKI不为INVALID,路由长度大于最小值小于最大值,path内不包含保留ASN,不为保留前缀即可,不必验证IRR。

上下游对等与自我路由的区分

因为如上规则,所以我们需要将上下游对等与自身需要BGP广播出去的路由加以区分。

我们有两种区分方式:

  1. 通过BGP Community来实现
  2. 通过分表来实现

在这里,我们仅讲述第一种,有需要可以自己去研究第二种。

实现

下面我们将分为常规过滤,RPKI过滤,IRR过滤跟过滤器撰写来一一实现

常规过滤

这一部分都是些固定的杂活,直接照抄即可。

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
define BOGON_ASNS = [ # 定义保留ASN
0, # RFC 7607
23456, # RFC 4893 AS_TRANS
64496..64511, # RFC 5398 and documentation/example ASNs
64512..65534, # RFC 6996 Private ASNs
65535, # RFC 7300 Last 16 bit ASN
65536..65551, # RFC 5398 and documentation/example ASNs
65552..131071, # RFC IANA reserved ASNs
4200000000..4294967294, # RFC 6996 Private ASNs
4294967295 # RFC 7300 Last 32 bit ASN
];
define BOGON_PREFIXES_V4 = [ # 定义保留IPv4前缀
0.0.0.0/8+, # RFC 1122 'this' network
10.0.0.0/8+, # RFC 1918 private space
100.64.0.0/10+, # RFC 6598 Carrier grade nat space
127.0.0.0/8+, # RFC 1122 localhost
169.254.0.0/16+, # RFC 3927 link local
172.16.0.0/12+, # RFC 1918 private space
192.0.2.0/24+, # RFC 5737 TEST-NET-1
192.88.99.0/24{25,32}, # RFC 7526 deprecated 6to4 relay anycast. If you wish to allow this, change `24+` to `24{25,32}`(no more specific)
192.168.0.0/16+, # RFC 1918 private space
198.18.0.0/15+, # RFC 2544 benchmarking
198.51.100.0/24+, # RFC 5737 TEST-NET-2
203.0.113.0/24+, # RFC 5737 TEST-NET-3
224.0.0.0/4+, # multicast
240.0.0.0/4+ # reserved
];
define BOGON_PREFIXES_V6 = [ # 定义保留IPv6前缀
::/8+, # RFC 4291 IPv4-compatible, loopback, et al
0064:ff9b::/96+, # RFC 6052 IPv4/IPv6 Translation
0064:ff9b:1::/48+, # RFC 8215 Local-Use IPv4/IPv6 Translation
0100::/64+, # RFC 6666 Discard-Only
2001::/32{33,128}, # RFC 4380 Teredo, no more specific
2001:2::/48+, # RFC 5180 BMWG
2001:10::/28+, # RFC 4843 ORCHID
2001:db8::/32+, # RFC 3849 documentation
2002::/16{17,128}, # RFC 7526 deprecated 6to4 relay anycast. If you wish to allow this, change `16+` to `16{17,128}`(no more specific)
3ffe::/16+, 5f00::/8+, # RFC 3701 old 6bone
fc00::/7+, # RFC 4193 unique local unicast
fe80::/10+, # RFC 4291 link local unicast
fec0::/10+, # RFC 3879 old site local unicast
ff00::/8+ # RFC 4291 multicast
];
function general_check(){ # 返回true则拒绝,返回false则允许
if bgp_path ~ BOGON_ASNS then return true; # 如果路径包含保留ASN则返回true
case net.type {
NET_IP4: return net.len > 24 || net ~ BOGON_PREFIXES_V4; # IPv4 CIDR 大于 /24 为太长
NET_IP6: return net.len > 48 || net ~ BOGON_PREFIXES_V6; # IPv6 CIDR 大于 /48 为太长
else: print "unexpected net.type ", net.type, " ", net; return false; # 保底,一般不应该出现非IP4/IP6的前缀。
}
};

RPKI

配置RPKI最简单的方法是使用RTR连接上现有的RPKI验证服务器。在这里,我们会使用Cloudflare的服务器。

目前而言,公网有大量的前缀没有签署RPKI,所以我们仅拒绝INVALID的路由,允许UNKNOWN与VALID的路由。

如下所示:

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
roa4 table rpki4; # 定义两个ROA表,用来接收RPKI。
roa6 table rpki6; # 定义两个ROA表,用来接收RPKI。

protocol rpki rpki_cloudflare { # 配置RPKI协议示例,取名为rpki_cloudflare
roa4 {
table rpki4; # 指定roa表
};
roa6 {
table rpki6; # 指定roa表
};
remote "rtr.rpki.cloudflare.com" port 8282; # 指定RTR服务器
retry keep 5;
refresh keep 30;
expire 600;
transport tcp;
}
function rpki_check() { # 定义rpki检查函数,如果验证为INVALID则为 true
if ( net.type = NET_IP4 && roa_check(rpki4, net, bgp_path.last) = ROA_INVALID ) then {
return true;
}
else if ( net.type = NET_IP6 && roa_check(rpki6, net, bgp_path.last) = ROA_INVALID ) then {
return true;
}
else {
return false;
}
}

IRR

对于IRR,由于IRR没有一个协议来进行分发,所以我们需要使用如下步骤来生成前缀列表,从而过滤IRR:

  1. 使用比如bgpq4等软件获取ASN/ASSET的IP前缀列表。
  2. 用工具将前缀拼合进如下函数
  3. 与前后拼合,重载bird
1
2
3
4
5
6
7
8
9
10
11
function irr_check(string as_set) { # 定义IRR检查函数,如果验证正确则返回true
if net.type = NET_IP4 then { # IPv4检查
if as_set = "<AS-SET>" then {if net ~ [<前缀IP段>/<前缀长度>{<前缀长度>,24}, <前缀IP段>/<前缀长度>{<前缀长度>,24} ...] then return true;else return false;};
if as_set = "<AS-SET>" then {if net ~ [<前缀IP段>/<前缀长度>{<前缀长度>,24} ...] then return true;else return false;};
if as_set = "<AS-SET>" then {if net ~ [<前缀IP段>/<前缀长度>{<前缀长度>,24} ...] then return true;else return false;};
} else if net.type = NET_IP6 then { # IPv6检查
if as_set = "<AS-SET>" then {if net ~ [<前缀IP段>/<前缀长度>{<前缀长度>,48} ...] then return true;else return false;};
if as_set = "<AS-SET>" then {if net ~ [<前缀IP段>/<前缀长度>{<前缀长度>,48} ...] then return true;else return false;};
if as_set = "<AS-SET>" then {if net ~ [<前缀IP段>/<前缀长度>{<前缀长度>,48} ...] then return true;else return false;};
} else
};

其中<>为参数,格式为:

  • : AS114514, AS114514:AS-ALL, AS-SKYWOLF
  • <前缀IP段>: 192.168.0.02404::
  • <前缀长度>: 24

至于工具,就交给你们自己去写吧(逃

过滤器撰写

现在,我们就需要将上面的东西都拼合到一起,从而写出最终的过滤器。

由于我们的过滤器需要携带参数,所以我们不使用filter,转而使用function,并在编写会话的时候使用where语句来调用。

如下所示:

1
2
3
function import_filter_upstream() {

}

阶段3 多地部署

在这篇文章中,我们将讲解多机器时的内网搭建方法和iBGP的配置。

开始之前

我们假设:

  • 你的ASN是 AS114514

  • 你有七台机器

    1. 10.0.0.1 2404::1 fe80::1 边界节点 广播2404:1::/48
    2. 10.0.0.2 2404::2 fe80::2 内网节点
    3. 10.0.0.3 2404::3 fe80::3 内网节点
    4. 10.0.0.4 2404::4 fe80::4 边界节点 广播2404:2::/48
    5. 10.0.0.5 2404::5 fe80::5 内网节点
    6. 10.0.0.6 2404::6 fe80::6 内网节点
    7. 10.0.0.7 2404::7 fe80::7 边界节点 广播2404:3::/48

    fe80开头的为Link Local IPv6

    如下所示,其中黑色直线代表有链路存在,蓝色椭圆代表AS114514这个自治系统

该设置将通用于本篇教程

介绍

经过了一段时间的发展,你现在已经有七个节点,现在你想为他们做一个内网。

在内网中,我们需要使用IGP跟iBGP协议。

基础概念介绍

  • IGP(全称Interior Gateway Protocol),即在AS内部使用的路由协议。用来管理内网路由。

    常见的IGP协议有:OSPFbabelISIS。其中我们将主要讲解OSPF

  • iBGP(全称Interior BGP),是将路由在边界路由器之间传递路由的方式,用来管理多地的公网路由。

  • 链路,通俗来说,指的是一条二层可达的连接,比如使用网线连接。隧道是一种特殊的链路。

  • Link Local IPv6,缩写LL IPv6IPv6 LL,用于链路之上的IPv6,仅在同一链路内有效。在本篇文章中,我们使用LL IPv6来简化链路之间的连接。

IGP跟iBGP的区别和应用

当年我看完这两套协议后,十分不解:这两个协议的区别在哪?该如何应用?所以,我们来单独讲一下它们的区别。

IGP的主要应用在于自治系统内的内网路由的传递。如上图中示例,则IGP是用来管理10.0.0.1-10.0.0.7这一段IP的传递。

iBGP的主要应用在于自治系统的边界路由器之间外部路由传递。如上图中示例,则iBGP用来在10.0.0.1 10.0.0.4 10.0.0.7之间传递各自的广播段和外部BGP收到的段。

full mesh

full mesh是一种连接形式,即所有节点之间全部两两直接连接。

在内网搭建中,有不少人会搞混BGP full mesh与tunnel full mesh。

iBGP为了防止环路,所以每个路由仅会传递给他的直接邻居,不会传递给邻居的邻居,也就是说边界路由器之间需要两两连接以保证路由可以传递到每一个边界路由器,这就是BGP full mesh。

而让边界路由器之间能够直接连接,最直接而最简单的方法就是让网络full mesh,也就是tunnel full mesh。但当网络内节点变得多的时候,这种连接方式就会变得费时费力,难以维护,易出错。为了简化连接,我们就需要使用IGP来代替tunnel full mesh并达到full mesh的效果,即网络内所有设备都能联通。

IGP配置

OSPF

Babel

iBGP 配置

iBGP Full mesh

iBGP RR

iBGP Confederation