GoBGP 是使用 Go 语言开发的,运行在 Linux 系統上的开源工具,可以提供 BGP 协议的控制平面功能。与 Quagga/FRRouting 相比,GoBGP 的性能更好,收敛时间更短,可以适用于更大规模的网络,比如充当 IXP 路由器。可以使用 Python、C++ 等多种语言,通过 gRPC API 对 GoBGP 进行配置,当然也支持 CLI。GoBGP 还支持 OpenConfig,其 YANG 模型符合 draft-ietf-idr-bgp-model-03 。因为 GoBGP 可以很方便地人工干涉路由,参与感更强,是一个很好的实验工具。本文将介绍 gobgp 的主要功能与实践。
背景介绍 安装与组成 GoBGP 的安装非常简单,從 https://github.com/osrg/gobgp/releases 下载 tar.gz 文件,解压即可。此处选择的是 v2.27.0。
1 $ tar -xzf gobgp_2.27.0_linux_amd64.tar.gz
gobgpd
Gobgp 的 daemon 程序,完整的实现了 BGP 协议
可以通过 gRPC API 与 gobgpd 交互
也可以通过配置文件来配置 bgp
gobgp
Full-featured CLI
可以查看 BGP 相关信息,也可以配置 BGP
配置文件:支持多种格式 toml/yaml/json 等等
支持特性
Full-featured CLI
Multiprotocol Support
IPv4/Pv6
Labeled IPv4/IPv6
Labeled IPv4/IPv6
EVPN
Flowspec IPv4/IPv6/L2
Flexible Policy
Graceful Restart
Both restarting/helper speak role
Route Reflector
Route Server
MRT Dumping
BMP
RPKI Validation
FIB manipulation
gRPC API
Standard configuration format
性能测试 与 Quagga/FRRouting 相比,GoBGP 的性能更好,收敛时间更短,可以适用于更大规模的网络,比如充当 IXP 路由器。更多关于 BGP 的性能测试,可以参考 Comparing Open Source BGP stacks with internet routes
与 Quagga/Zebra 等集成 GoBGP 仅支持 BGP 这一种路由协议,但是它可以和 Zebra 集成,通过 API 的方式与 Quagga/FRR 协同工作,以支持多种路由协议。
GoBGP 可以集成到 Quagga/Zebra 体系中:
使用 GoBGP Basic operation 我们可以启动 gobgpd
作为一个 bgp server,和交换机建立 BGP 连接。
此处有网络拓扑图
比如针对
1 2 3 4 5 6 7 8 9 [global.config] as = 1002 router-id = "172.25.0.136" [[neighbors]] [neighbors.config] peer-as = 1002 neighbor-address = "172.25.0.129" auth-password: "xxxxx"
启动 gobgpd:
1 2 3 4 5 6 / {"level" :"info" ,"msg" :"gobgpd started" ,"time" :"2022-03-03T07:28:56Z" } {"Topic" :"Config" ,"level" :"info" ,"msg" :"Finished reading the config file" ,"time" :"2022-03-03T07:28:56Z" } {"level" :"info" ,"msg" :"Peer 172.25.0.129 is added" ,"time" :"2022-03-03T07:28:56Z" } {"Topic" :"Peer" ,"level" :"info" ,"msg" :"Add a peer configuration for:172.25.0.129" ,"time" :"2022-03-03T07:28:56Z" } {"Key" :"172.25.0.129" ,"State" :"BGP_FSM_OPENCONFIRM" ,"Topic" :"Peer" ,"level" :"info" ,"msg" :"Peer Up" ,"time" :"2022-03-03T07:29:01Z" }
通过 gobgp 查看 peer 信息,这里 State 的 Establ
才表示连接已经建立,如果 State 是 Active
则需要查看交换机配置是否正确。
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 / Peer AS Up/Down State | 172.25.0.129 1002 00:01:29 Establ | 8 8 / BGP neighbor is 172.25.0.129, remote AS 1002 BGP version 4, remote router ID 172.25.100.4 BGP state = ESTABLISHED, up for 00:01:34 BGP OutQ = 0, Flops = 0 Hold time is 90, keepalive interval is 30 seconds Configured hold time is 90, keepalive interval is 30 seconds Neighbor capabilities: multiprotocol: ipv4-unicast: advertised and received route-refresh: advertised and received extended-nexthop: advertised Local: nlri: ipv4-unicast, nexthop: ipv6 4-octet-as: advertised and received Message statistics: Sent Rcvd Opens: 1 1 Notifications: 0 0 Updates: 0 7 Keepalives: 4 4 Route Refresh: 0 0 Discarded: 0 0 Total: 5 12 Route statistics: Advertised: 0 Received: 8 Accepted: 8
查看 global table
:
1 2 3 4 5 6 7 8 9 10 / Network Next Hop AS_PATH Age Attrs *> 10.0.0.0/24 172.25.0.129 801 45090 45090 00:04:16 [{Origin: ?} {LocalPref: 100}] *> 10.0.2.0/24 172.25.0.129 801 45090 45090 00:04:16 [{Origin: ?} {LocalPref: 100}] *> 172.25.0.0/25 172.25.0.129 801 1001 00:04:16 [{Origin: i} {LocalPref: 100}] *> 172.25.0.128/25 172.25.0.129 00:04:16 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 172.25.100.1/32 172.25.0.129 801 00:04:16 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 172.25.100.2/32 172.25.0.129 801 00:04:16 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 172.25.100.3/32 172.25.0.129 801 1001 00:04:16 [{Origin: i} {LocalPref: 100}] *> 172.25.100.4/32 172.25.0.129 00:04:16 [{Origin: i} {Med: 0} {LocalPref: 100}]
查看 adjacent rib-in and rib-out
:
1 2 3 4 5 6 7 8 9 10 11 12 / ID Network Next Hop AS_PATH Age Attrs 0 10.0.0.0/24 172.25.0.129 801 45090 45090 00:07:18 [{Origin: ?} {LocalPref: 100}] 0 10.0.2.0/24 172.25.0.129 801 45090 45090 00:07:18 [{Origin: ?} {LocalPref: 100}] 0 172.25.0.0/25 172.25.0.129 801 1001 00:07:18 [{Origin: i} {LocalPref: 100}] 0 172.25.0.128/25 172.25.0.129 00:07:18 [{Origin: i} {Med: 0} {LocalPref: 100}] 0 172.25.100.1/32 172.25.0.129 801 00:07:18 [{Origin: i} {Med: 0} {LocalPref: 100}] 0 172.25.100.2/32 172.25.0.129 801 00:07:18 [{Origin: i} {Med: 0} {LocalPref: 100}] 0 172.25.100.3/32 172.25.0.129 801 1001 00:07:18 [{Origin: i} {LocalPref: 100}] 0 172.25.100.4/32 172.25.0.129 00:07:18 [{Origin: i} {Med: 0} {LocalPref: 100}] / Network not in table
可以通过以下命令 宣告路由
1 gobgp global rib -a ipv4 add 192.168.1.0/24
Route Reflector 为保证 iBGP 对等体之间的连通性,需要在IBGP对等体之间建立全连接关系 。随着集群规模扩大,Full Mesh 模式效率将急剧降低,Route Reflection 模式是一种成熟的替代方案。RR 方案下允许一个 BGP Speaker (也即是 Route Reflector)向其他 BGP Peer 广播学习到的路由信息,大大减少了 BGP Peer 连接数量。
对于 gobgpd,可以通过修改配置文件,添加 RouteReflector.RouteReflectorConfig
配置来支持 BGP Server 作为 Route Reflector。如下所示:
节点 172.25.0.7 作为 RR 节点,与交换机 172.25.0.1
建立 bgp peer
节点 172.25.0.6 作为 RR client 节点,与 RR 节点 172.25.0.7 建立 bgp peer
节点 172.25.0.8 作为 RR client 节点,与 RR 节点 172.25.0.7 建立 bgp peer
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 [global.config] as = 1001 router-id = "172.25.0.7" [[neighbors]] [neighbors.config] neighbor-address = "172.25.0.1" peer-as = 1001 auth-password = "xxxxxx" [[neighbors]] [neighbors.config] neighbor-address = "172.25.0.6" peer-as = 1001 auth-password = "xxxxxx" [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "172.25.0.137" [[neighbors]] [neighbors.config] neighbor-address = "172.25.0.8" peer-as = 1001 auth-password = "xxxxxx" [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "172.25.0.137"
Route Server 现网中存在一些场景,为了达到网络流量互通的目的,通常需要通过 eBGP 方式进行全连接 。边界设备之间的全连接,对于经费消耗、设备性能要求都是比较高的,并且不利于网络拓扑和设备数量的扩张。Route Server 类似于IBGP全连接使用路由反射器 ,是一台(或多台)用于进行路由服务的设备,其主要的功能是,向各个客户端(边界设备)传播路由,且向客户端发布的路由不修改AS_PATH、Nexthop、MED等路径属性,从而减轻边界路由器全连接的消耗。
如下图所示,一个 IX (Internet eXchange) 中,包含多个独立的 SP (service provider),这些网络想要实现流量互通。每个 SP 都有一个边界路由器连接到公共的交换网络。每个 SP 都有自己的 AS 号,BGP Router 的地址从 10.0.0.1 到 10.0.0.8。
这种情况下,要求这 8 个 BGP Peer 建立全连接,和 iBGP 一样,这种 full mesh 连接对于经费消耗、设备性能要求都是比较高的,并且不利于网络拓扑和设备数量的扩张。
BGP Route Server 可以简化SP的连接,如下所示:
下图展示了 route server 实现的透明路由传播:
更多关于 route server 的信息,可以参考 Route Server 。
对于 GoBGP 同样支持 Route Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 auth-password = "hoge1" [neighbors.transport.config] passive-mode = true [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.2" peer-as = 65002 auth-password = "hoge2" [neighbors.transport.config] passive-mode = true [neighbors.route-server.config] route-server-client = true
BGP Policy Policy 是一种控制 BGP路由如何插入到 RIB 或者广播给 BGP Peer 的方法,分为两个部分 Condition
和 Action
。当 Policy 配置完成后,触发 Condition 条件后,会执行 Action 操作来修改路由。
Condition 包括 prefix
,neighbor
(source/destination of the route) 和 aspath
等
Action 包括 accept
, reject
, MED/aspath/community manipulation
等
Policy Model Policy model 包括有 Import Policy
和 Export Policy
:
Import policy is invoked before best path calculation and pushing routes to RIB.
Export policy is invoked after that.
可以通过以下命令查看 policy
1 2 $ gobgp global policy import $ gobgp global policy export
Route Server Policy Model 对于 Route Server 模式,Import and Export policies 都是针对于一个 Peer 而言的:
The Import policy defines what routes will be imported into the master RIB.
The Export policy defines what routes will be exported from the master RIB.
1 2 $ gobgp neighbor <neighbor-addr> policy import $ gobgp neighbor <neighbor-addr> policy export
Policy Structure 一个 Policy 包含多个 Statement,每个 Statement 都有自己的 Condtions 和 Actions
Conditions 包括:
prefix
neighbor
aspath
aspath length
community
extended community
rpki validation result
route type (internal/external/local)
large community
afi-safi in
Actions 包括:
accept or reject
add/replace/remove community or remove all communities
add/subtract or replace MED value
set next-hop (specific address/own local address/don’t modify)
set local-pref
prepend AS number in the AS_PATH attribute
可以通过以下命令查看 Policy 配置
1 2 3 4 5 6 7 8 $ gobgp policy $ gobgp policy statement $ gobgp policy prefix $ gobgp policy neighbor $ gobgp policy as-path $ gobgp policy community $ gobgp policy ext-community $ gobgp policy large-community
Policy Configuration Policy 配置比较复杂,以下是配置的步骤,具体可以参考 这里 :
define defined-sets
define prefix-sets
define neighbor-sets
define bgp-defined-sets
define community-sets
define ext-community-sets
define as-path-setList
define large-community-sets
define policy-definitions
attach policies to global rib (or neighbor local rib when neighbor is route-server-client ).
Graceful Restart 1 2 3 4 5 6 7 8 9 10 [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true
BMP GoBGP 支持 BGP Monitoring Protocol (RFC 7854) 对 BGP 会话的运行状态进行实时监控,包括对等体关系的建立与关闭、路由信息等。
1 2 3 4 5 6 7 8 [global.config] as = 64512 router-id = "192.168.255.1" [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port=11019
Dynamic Neighbors 在BGP网络中,当多个对等体经常发生变动时,如果采用静态配置对等体的方式,则需频繁地在本端进行增加或删除对等体的配置,维护工作量很大。此时可以配置BGP动态对等体功能,使BGP侦听指定网段的BGP连接请求并动态建立BGP对等体,同时将这些对等体加入到同一个对等体组中。这样当对等体发生变动时,无需在本端进行增加或删除BGP对等体的配置,减少网络维护的工作量。
交换机都一般都支持配置 Dynamic Neighbors,比如 这里是华为交换机配置Dynamic Neighbors 方法 ,对于 gobgp 同样也支持 Dynamic Neighbors。
如下所示,主要需要配置两个部分:
创建一个 peer group,描述这个 peer group 的基本信息
配置 peer group 监听在 172.40.0.0/16
网段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [global.config] as = 65001 router-id = "172.40.1.2" [[peer-groups]] [peer-groups.config] peer-group-name = "sample-group" peer-as = 65002 [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-unicast" [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-flowspec" [[dynamic-neighbors]] [dynamic-neighbors.config] prefix = "172.40.0.0/16" peer-group = "sample-group"
Others 在 GitHub 中还有很多其他关于 MRT/BMP/EVPN 等特性的说明,此处不再赘述,如有需要可以直接查看文档。
GoBGP 编程 Basic Server 参考 gobgp 库 提供的文档 ,我们可以实现一个简单的 go bgp server,如下所示:
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 package mainimport ( "context" "time" "github.com/sirupsen/logrus" apb "google.golang.org/protobuf/types/known/anypb" api "github.com/osrg/gobgp/v3/api" "github.com/osrg/gobgp/v3/pkg/log" "github.com/osrg/gobgp/v3/pkg/server" ) func main () { log := logrus.New() s := server.NewBgpServer(server.LoggerOption(&myLogger{logger: log})) go s.Serve() if err := s.StartBgp(context.Background(), &api.StartBgpRequest{ Global: &api.Global{ Asn: 65003 , RouterId: "10.0.255.254" , ListenPort: -1 , }, }); err != nil { log.Fatal(err) } if err := s.MonitorPeer(ctx, &api.MonitorPeerRequest{}, func (p *api.Peer) { log.Print(p) }); err != nil { log.Fatal(err) } n := &api.Peer{ Conf: &api.PeerConf{ NeighborAddress: "172.17.0.2" , PeerAsn: 65002 , }, } if err := s.AddPeer(context.Background(), &api.AddPeerRequest{ Peer: n, }); err != nil { log.Fatal(err) } nlri, _ := apb.New(&api.IPAddressPrefix{ Prefix: "10.0.0.0" , PrefixLen: 24 , }) a1, _ := apb.New(&api.OriginAttribute{ Origin: 0 , }) a2, _ := apb.New(&api.NextHopAttribute{ NextHop: "10.0.0.1" , }) a3, _ := apb.New(&api.AsPathAttribute{ Segments: []*api.AsSegment{ { Type: 2 , Numbers: []uint32 {6762 , 39919 , 65000 , 35753 , 65000 }, }, }, }) attrs := []*apb.Any{a1, a2, a3} _, err := s.AddPath(context.Background(), &api.AddPathRequest{ Path: &api.Path{ Family: &api.Family{Afi: api.Family_AFI_IP, Safi: api.Family_SAFI_UNICAST}, Nlri: nlri, Pattrs: attrs, }, }) if err != nil { log.Fatal(err) } v6Family := &api.Family{ Afi: api.Family_AFI_IP6, Safi: api.Family_SAFI_UNICAST, } nlri, _ = apb.New(&api.IPAddressPrefix{ PrefixLen: 64 , Prefix: "2001:db8:1::" , }) v6Attrs, _ := apb.New(&api.MpReachNLRIAttribute{ Family: v6Family, NextHops: []string {"2001:db8::1" }, Nlris: []*apb.Any{nlri}, }) c, _ := apb.New(&api.CommunitiesAttribute{ Communities: []uint32 {100 , 200 }, }) _, err = s.AddPath(context.Background(), &api.AddPathRequest{ Path: &api.Path{ Family: v6Family, Nlri: nlri, Pattrs: []*apb.Any{a1, v6Attrs, c}, }, }) if err != nil { log.Fatal(err) } s.ListPath(context.Background(), &api.ListPathRequest{Family: v6Family}, func (p *api.Destination) { log.Info(p) }) time.Sleep(time.Minute * 3 ) }
可以看到,示例代码相对比较简单,主要使用了以下的 API:
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 func NewBgpServer (opt ...ServerOption) *BgpServer func (s *BgpServer) Serve () api.Global{ Asn: 65003 , RouterId: "10.0.255.254" , ListenPort: -1 , } func (s *BgpServer) StartBgp (ctx context.Context, r *api.StartBgpRequest) error func (s *BgpServer) MonitorPeer (ctx context.Context, r *api.MonitorPeerRequest, fn func (*api.Peer) ) error api.Peer{ Conf: &api.PeerConf{ NeighborAddress: "172.17.0.2" , PeerAsn: 65002 , }, } func (s *BgpServer) AddPeer (ctx context.Context, r *api.AddPeerRequest) error func (s *BgpServer) AddPath (ctx context.Context, r *api.AddPathRequest) (*api.AddPathResponse, error)
Route Reflector 可以通过对 api.Peer
这个结构进行更详细的配置,使得加入的 BGP Peer 是作为 RR client:
1 2 3 4 5 6 7 8 9 10 n := &api.Peer{ Conf: &api.PeerConf{ NeighborAddress: "172.25.0.6" , PeerAsn: 1001 , }, RouteReflector: &api.RouteReflector{ RouteReflectorClient: true , RouteReflectorClusterId: "172.25.0.7" , } }
BMP 这里列出了几种常见的 BMP Message:
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 type BMPMessage struct { Header BMPHeader PeerHeader BMPPeerHeader Body BMPBody } type BMPRouteMonitoring struct { BGPUpdate *bgp.BGPMessage BGPUpdatePayload []byte } type BMPPeerDownNotification struct { Reason uint8 BGPNotification *bgp.BGPMessage Data []byte } type BMPPeerUpNotification struct { LocalAddress net.IP LocalPort uint16 RemotePort uint16 SentOpenMsg *bgp.BGPMessage ReceivedOpenMsg *bgp.BGPMessage }
参考资料