Author basilguo@163.com
Date Aug. 22, 2023
Description NIST BGPsec SRx对于BGP-UPDATE报文的修改
TOC
BGP协议很复杂,它的实现自然也不会简单到哪里去。但是关注过多的细节并不会让我们立刻成为协议专家,只能是限制了我们总览全局的视角。但是时间总归是有限的,需要能够快速找到可以下手的地方。如果想要从头分析,可以去看看CSDN上这个FRR的专栏,这位博主做了详尽的分析。FRR和Quagga是一脉相承的,至于区别就去GitHub的changelog看吧。
我这里是需要学习下BGPsec的设计与实现,NIST的人就是做BGPsec的标准的,所以他们做的实现应该会提交的IETF中。
我分析的是现在的master分支上的,对应的应该是NIST BGP SRx v6.2.0版本的代码。下载NIST-BGP-SRx代码,其实线上查看也挺方便的。我不想下载,所以就在线上查看了。
在quaaga-srx/bgpd中,bgp_packet.c中包含了BGP-UPDATE报文的处理。虽然不全是,但也大差不差。这个文件一方面包含了peer之间处理报文的代码,另一方面又包含了本机之间的函数,所以看得时候晕头晕脑的。顺次分析各个函数吧,有些函数特别简单,顺带提一嘴就是了,需要特别说明的再进行额外解释。
c
/*
// 这个函数就是填充头部的,我们这里给出来Message Header Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ +
| Marker |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
static int bgp_packet_set_marker (struct stream *s, u_char type)
// 在所有内容都填充完毕之后,需要将message header中的Length字段做一次填充。
static int bgp_packet_set_size (struct stream *s)
// 将fifo队列中第一个包free
static void bgp_packet_delete (struct peer *peer)
// 检查连接是否建立
static void bgp_connect_check (struct peer *peer)
接下来是一些NIST BGP SRx自定义的函数,对于这些函数,就是和BGPsec紧密相关了,但对于BGPsec我还不是很清楚,所以它做的这些修改,还不是那么清楚明白。
c
// 这个是Extended Community,应该就是和Community团体属性相关的内容。
// BGPsec还需要修改这个?真不知道。或者就是NIST自己搞的吧,反正BGPsec也是他们在提标准。
// https://datatracker.ietf.org/doc/rfc8374/,其中提到:
// Extended Community携带iBGP中origin validation state是在RFC8097中规定的
// 所以这些函数应该和origin validation state相关,应该是RPKI ROV的结果。
struct ecommunity* srxEcommunityChange(struct bgp *bgp, struct attr *attr, struct bgp_info *binfo)
int srxEcommunityRestore(struct attr *attr, struct ecommunity* ecom_orig)
接下来就是一个比较重要的函数了,但是和FRRouting中又不一样,只分析这里的。需要知道调用链条:bgp_write()
调用了bgp_write_packet()
,然后调用了bgp_update_packet()
。
c
// 构建BGP-UPDATE报文
static struct stream *bgp_update_packet (struct peer *peer, afi_t afi, safi_t safi)
// 发送下一个bgp packet
static struct stream *bgp_write_packet (struct peer *peer)
// 发送报文给peer
int bgp_write (struct thread *thread)
在bgp_write()
中,它先读取了peer中是否有报文要发送bgp_write_packet()
,要发送就调用write()
发出去,然后根据报文类型进行了分类,进行了一些count等操作,然后就接着读取下一个。反正报文类型也就是BGP_MSG_OPEN | BGP_MSG_UPDATE | BGP_MSG_NOTIFY | BGP_MSG_KEEPALIVE BGP_MSG_ROUTE_REFRESH_NEW | BGP_MSG_ROUTE_REFRESH_OLD | BGP_MSG_CAPABILITY |
。至于为啥这么多,实际上IANA目前已经分配的只有5个,猜测NEW、OLD应该是Quaaga自己的区分,伴随Quagga发展以及RFC标准制定,目前应该只需要有NEW,令人难绷的是,新版本的FRRouting中还是这样区分,老代码不好修改吧。BGP_MSG_CAPABILITY
,值为6,IANA并没有分配这个Message Type,不知道是从哪里来的。BGPsec的在BGP_MSG_UPDATE
中进行了修改。不过只是给fd加上了TCP_CORK。
在bgp_write_packet()
中,首先从peer的obuf的fifo队列中获取头节点s
,这个s
就是实际的报文。如果s
有内容,那么就直接返回,只能先发送出去,现在不能用,只能用空的。接下来就是两个循环,判断afi和safi类型,其实就是IPv4和IPv6的各种类型,例如Unicast、Anycast、Multicast、Broadcast。先是bgp_withdraw_packet()
,再是bgp_update_packet()
。获取update的fifo队列head节点adv = FIFO_HEAD (&peer->sync[afi][safi]->update);
,如果可用且通过检查后,就可以填充BGP-UPDATE报文内容了。如果填充了,那么就能发送了,否则就需要调用bgp_update_packet_eor()
。
在bgp_update_packet()
中,首先NIST先加了两个变量,一个u_char bFrag =0;
,一个bool useASpath = false;
,前者应该是控制分片(不知是否正确),后者应该是是否使用AS_PATH路径属性,默认BGPsec_PATH和AS_PATH是对立关系,有你没我。获取一个节点,adv = FIFO_HEAD (&peer->sync[afi][safi]->update);
,接下来就比较复杂,很多字段如果不知道意思,可以结合这个链接来看。我还是只看有USE_SRX
的。它既然要使用Extended Community,那么就需要好好填充这个Path Attr,这里就牵涉到bgp_packet_attribute()
函数,这个函数在bgp_attr.c
中,主要作用就是填充BGP-UPDATE-PATH-ATTRIBUTE。
同理,也需要知道接收BGP-UPDATE报文函数的调用链条:bgp_read()
调用bgp_update_receive()
。对应于,也有解析BGP-UPDATE-PATH-ATTRIBUTE的函数bgp_attr_parse()
。在bgp_attr_parse()
中,它的真正的解析BGPsecPath的函数位于bgp_attr_bgpsec(&attr_args);
c
// 解析BGP-UPDATE报文
static int bgp_update_receive (struct peer *peer, bgp_size_t size)
// 读取peer的报文
int bgp_read (struct thread *thread)
所以,我们主要关注的两个函数应该是bgp_update_packet()
以及bgp_update_receive()
。而在这两个函数中应该重点分别关注bgp_packet_attribute()
以及bgp_attr_parse()
,和PATH-ATTRIBUTE相关的函数都在bgp_attr.c
中。
后续是一些出现在bgp_packet.c
中的函数,大概记录一下。
c
// 这2个函数并没有真正的使用。
void bgp_default_update_send (struct peer *peer, struct attr *attr,
afi_t afi, safi_t safi, struct peer *from)
void bgp_default_withdraw_send (struct peer *peer, afi_t afi, safi_t safi)
// 部分写入的时候可以立即发送
static int bgp_write_proceed (struct peer *peer)
// 用于发送NOTIFICATION报文
static int bgp_write_notify (struct peer *peer)
// 发送KEEPALIVE报文,keepalive没有payload
void bgp_keepalive_send (struct peer *peer)
// 收到KEEPALIVE报文,
static void bgp_keepalive_receive (struct peer *peer, bgp_size_t size)
// 发送open报文
// 我们上一期分析了的BGP-OPEN报文,其中有个`bgp_open_capability()`函数,
// 就是在这里调用的。
// 这个函数会把BGP的其他如header填充后发送出去。实际上是加入到了peer的fifo消息队列中。
// 最终调用的函数应该是`bgp_write()`,使用`write()`系统调用写入了peer的socket中
void bgp_open_send (struct peer *peer)
// 收到open报文
static int bgp_open_receive (struct peer *peer, bgp_size_t size)
// notify和bgp错误类型息息相关,所以单独说这些函数没有意义
void bgp_notify_send_with_data (struct peer *peer, u_char code, u_char sub_code,
u_char *data, size_t datalen)
void bgp_notify_send (struct peer *peer, u_char code, u_char sub_code)
static void bgp_notify_receive (struct peer *peer, bgp_size_t size)
// 发送BGP-ROUTE-REFRESH报文
void bgp_route_refresh_send (struct peer *peer, afi_t afi, safi_t safi,
u_char orf_type, u_char when_to_refresh, int remove)
static void bgp_route_refresh_receive (struct peer *peer, bgp_size_t size)
// 发送BGP-CAPABILITY报文
void bgp_capability_send (struct peer *peer, afi_t afi, safi_t safi,
int capability_code, int action)
static int bgp_capability_msg_parse (struct peer *peer, u_char *pnt, bgp_size_t length)
int bgp_capability_receive (struct peer *peer, bgp_size_t size)
// RFC 1771 6.8节中提到的连接冲突检测
static int bgp_collision_detect (struct peer *new, struct in_addr remote_id)