Calico BGP功能介绍:BIRD简介

Calico作为一种常用的Kubernetes网络插件,使用BGP协议对各节点的容器网络进行路由交换。本文是《Calico BGP功能介绍》系列的第一篇,介绍Calico所使用的BGP软件路由器——BIRD。

关于BGP协议,网上资料众多,在这里不再做介绍。另外,推荐《BGP in the datacenter》作为BGP应用的进阶阅读,另有中文翻译版本

BIRD

BIRD实际上是BIRD Internet Routing Daemon的缩写(禁止套娃),是一款可运行在Linux和其他类Unix系统上的路由软件,它实现了多种路由协议,比如BGP、OSPF、RIP等。

概念

BIRD会在内存中维护许多路由表,路由表根据不同的协议,通过与各种“其他事物”交换路由信息,来更新路由规则。这里说的“其他事物”可能是其他的路由表,也可能是外部的路由器,还可以是内核的某些API。

路由表

路由表(Routing tables)是BIRD的核心,一个路由表是内存中一组路由规则的集合,BIRD根据网络类型的不同会有多种路由表。默认情况下,BIRD有master4master6两个默认的路由表,分别保存IPv4和IPv6路由规则。除此外,你也可以创建其他的路由表,比如在配置文件bird.conf中添加如下配置,创建一个IPv4的路由表my_table

ipv4 table my_table;

要注意的是,BIRD的路由表仅仅是一个表,并没有转发的功能,真正的转发控制,是内核的FIB(Forwarding Information Base)。而BIRD的kernel协议,可以将BIRD路由表与FIB进行同步,后面会介绍。

路由规则中包含了各种路由属性(Route attributes),网络类型不同的路由表,其路由属性也不太一样,比如常见的IPv4和IPv6的路由表,会包括两个路由属性:

  • 路由目的地
  • 路由下一跳

而VPN路由表还会包含路由属性:路由标识符(Route distinguisher)。

BIRD的每种表都会将一个或一组路由属性作为主键,类似于SQL数据库。当多个来源都提供了相同主键的路由条目时,BIRD会根据一定的规则选择最优路由。例如IPv4和IPv6类型的路由表,将“路由目的地”作为主键。

协议与通道

协议(Protocols)将路由表和“其他事物”连接起来。“其他事物”可以是一个Socket对象,连接了外部的路由器,例如BGP路由协议;也可以是修改FIB的内核API,例如kernel协议;也可以是空,比如静态路由static协议。一个协议可以实例化为多个对象,例如创建多个BGP协议的实例,以表示多个BGP邻居。

协议也会提供一些路由属性,根据协议的不同路由属性也不同,比如使用BGP协议时,会有bgp_path属性。

协议可能包含一些通道(Channels),通道是在协议和路由表之间,配置了路由规则在导入(import))、导出(export)两个方向上的行为,导入导出是针对路由表来说的,路由规则经过通道后,或是被接收(Accept),或是被拒绝(Reject),或是被修改。不同的协议可拥有的通道也不一样,例如BGP协议可以同时拥有IPv4IPv6通道,RIP只能拥有IPv4或IPv6一种协议,而BFD则没有通道。

下面是根据官网样例修改而来的配置,实例化了一个名为peer_one的BGP协议,并且设置了ipv4ipv6两个通道,两个通道都未指明连接的路由表,则使用默认的master4master6路由表。其中在ipv4 通道中,导入方向配置为全部接收,导出方向上只导出静态路由,同时还会对路由规则的BGP信息进行修改:修改bgp community,修改bgp path;在ipv6通道上,则直接使用默认配置。

protocol bgp peer_one {
        local 198.51.100.14 as 65000;        # Use a private AS number
        neighbor 198.51.100.130 as 64496;    # Our neighbor ...
        ipv4 {
                export filter {                      # We use non-trivial export rules
                        if source = RTS_STATIC then { # Export only static routes
                                # Assign our community
                                bgp_community.add((65000,64501));
                                # Artificially increase path length
                                # by advertising local AS number twice
                                if bgp_path ~ [= 65000 =] then
                                        bgp_path.prepend(65000);
                                accept;
                        }
                        reject;
                };
                import all;
        };
        ipv6;
}

主要功能

模板

在BIRD中,可以定义模板(template),通过模板来创建一个协议的多个实例。模板在使用BGP协议时非常好用,因为BGP通常都会设置多个BGP Peer。例如下面配置,通过模板提取出共用的配置,然后利用模板创建多个BGP邻居。

template bgp foo {
        local 198.51.100.14 as 65000;

        ipv4 {
                table mytable4;
                import filter { ... };
                export none;
        };
        ipv6 {
                table mytable6;
                import filter { ... };
                export none;
        };
}

protocol bgp bgp1 from foo {
        neighbor 198.51.100.130 as 64496;
}

protocol bgp bgp2 from foo {
        neighbor 198.51.100.131 as 64496;
}

过滤器

过滤器(Filters)在上面的用例中已经出现了多次,通过在通道中添加过滤器,可以灵活地控制路由规则的交换。在过滤器中,你可以像访问变量一样直接使用各种路由属性,来编写各种判断条件,以决定对路由规则是ACCEPT还是REJECT,或是直接对路由属性进行修改。

为了便于复用,还可以以函数的形式定义一个过滤器,使用时在相应的通道中直接调用。需要注意的是,编写过滤器需要使用BIRD提供的专门的编程语言,它提供了一些例如ifswitch的简单控制结构,但不允许有循环出现。同时,除了intstring这些基础的数据结构外,它还提供了例如bgppatchbgpmask等这种表示路由规则中某些信息的数据结构。例如下面定义了一个名为not_too_far的过滤器,丢弃掉rip_metric大于10的路由规则,可以通过import filter not_too_far直接调用此函数。

filter not_too_far
int var;
{
        if defined( rip_metric ) then
                var = rip_metric;
        else {
                var = 1;
                rip_metric = 1;
        }
        if rip_metric > 10 then
                reject "RIP metric is too big";
        else
                accept "ok";
}

常见协议

这里只介绍Calico中使用到的几种协议,以及用到的协议属性。

device

准确来说,device并不算是一个协议,它不产生任何路由,也不支持通道,而是被用来从内核中获取网卡设备的信息。每个bird.conf的配置文件中,都应定义一个device

protocol device {
        scan time 10;           # Scan the interfaces often
        interface "eth0" {
                preferred 192.168.1.1;
                preferred 2001:db8:1:10::1;
        };
}

上面配置定义了BIRD每10s扫描一遍eth0网卡,同时定义了首选的IP地址。

kernel

kernel也不算真正的协议,它负责同步路由表与内核。如果内核支持多个内核路由表,那么可以创建多个kernel实例,否则只需要创建一个kernel实例。kernel协议有两个限制:

  • 不能将多个kernel实例都连接到同一个路由表上
  • 不能修改导出(export)路由规则的目标地址

一些主要的参数包括:

  • learn switch,开启后路由表可以从内核中学习到非内核生成(其他方式添加)的路由。“内核生成的路由”指的是由于本机网络的配置而产生的路由,比如eth0在被分配192.168.1.2/24后,会自动产生一条目的地为192.168.1.0/24,下一跳为eth0的路由。需要注意的是,即使是开启learnkernel也不会将这些路由从内核导入(import)路由表,这种路由的传递需要使用到direct协议。(switch表示onoff两种值,下面相同)
  • persist switch,BIRD退出时,在内核保留同步的路由(即不会进行clean up操作)。
  • scan time number,同步间隔,单位秒
# 同步master4、master6路由表与主FIB,并在退出后保持同步的路由
protocol kernel {
    learn;
    persist;
}

direct

如上面所述,direct用于将内核生成的路由规则从内核导入到BIRD路由表中,可用的参数包括:

  • interface pattern [, ...],用于指定传递由哪些网卡生成的路由规则,默认是全部网卡
  • check link switch,开启后会考虑link的状态,当link状态为up时,传递路由,否则,撤销传递的路由
# 同步除了eth0以外的其他网卡
protocol direct {
  interface -"eth0", "*"; 
}

BGP

每一个BGP协议的实例,代表了一个BGP Peer连接。需要注意的是,部分参数的默认值对IBGP与EBGP并不相同,例如aigp默认在IBGP中是开启的,默认在EBGP是关闭的。协议的主要参数包括:

  • local [ip] [port number] [as number],可以用来指定BGP的源IP地址以及本地的AS。
  • multihop [number],表示多跳的BGP,后面的number可以用来设置TTL的值,IBGP默认开启;相反的,还有个参数为direct,表示与BGP邻居直连,EBGP默认开启。
  • source address ip,用来指定本端使用的BGP源地址。
  • add paths switch|rx|tx,开启时,会将 BGP 配置为向同一目标通告多个路径,否则 BGP 仅通告活动路径。
  • password string ,使用设置的密码进行BGP的身份验证。
  • rr client,开启RR模式(Route Reflector)。
  • rr cluster id IPv4 address,设置RR的cluster id,以防止路由环路。默认情况下,会直接使用BGP的router id(一般是ipv4地址)作为cluster id,当有多个RR时,需要使用此参数设置相同的cluster id
  • bfd switch|graceful,使用BFD作为BGP协议心跳机制。
  • passive switch,被动模式,不主动初始化连接,而是等待其他BGP邻居发起连接。

以上是协议一层的配置参数,在BGP协议中,通道也会有额外的参数,例如:

  • gateway direct|recursive,用来控制如何计算路由的gw属性。当设置为direct时,如果路由中的bgp_next_hop是和本机中的某个地址同一子网(同一个二层),则gw直接为bgp_next_hop,否则为对端BGP Peer的IP地址;当设置为recursive时,会从IGP路由表中查询bgp_next_hop来作为gw。
  • next hop keep switch|ibgp|ebgp,开启后,BGP不再将next hop属性修改为自身,而是直接通告原始的next hop,这个参数在多跳的EBGP场景或BGP路由反射中会使用到。

参考文档

BIRD官方手册