Author basilguo@163.com
Date Aug. 22, 2023
Description NIST BGPsec SRx对于BGP-OPEN报文的修改
BGP协议很复杂,它的实现自然也不会简单到哪里去。但是关注过多的细节并不会让我们立刻成为协议专家,只能是限制了我们总览全局的视角。但是时间总归是有限的,需要能够快速找到可以下手的地方。如果想要从头分析,可以去看看CSDN上这个FRR的专栏,这位博主做了详尽的分析。FRR和Quagga是一脉相承的,至于区别就去GitHub的changelog看吧。
我这里是需要学习下BGPsec的设计与实现,NIST的人就是做BGPsec的标准的,所以他们做的实现应该会提交的IETF中。
我分析的是现在的master分支上的,对应的应该是NIST BGP SRx v6.2.0版本的代码。下载NIST-BGP-SRx代码,其实线上查看也挺方便的。我不想下载,所以就在线上查看了。
BGP-OPEN报文是相对比较简单的,从quagga-srx的bgpd下bgp_open.h文件也能看出来,只提供了5个外部函数,从名字中大概也能猜测出来他们是干嘛的。
// 解析bgp open option
extern int bgp_open_option_parse (struct peer *, u_char, int *);
// 填充bgp open capability option
extern void bgp_open_capability (struct stream *, struct peer *);
// vtysh 使用,但是搜索全文没有找到使用的地方
extern void bgp_capability_vty_out (struct vty *, struct peer *);
// 是否使用了as4 capability,也就是ASN占用是2B还是4B
extern as_t peek_for_as4_capability (struct peer *, u_char);
// afi safi有效性标志
extern int bgp_afi_safi_valid_indices (afi_t, safi_t *);
还有一个完全不一样的命名规则的makeBgpsecCapability(),应该是NIST的人为了区分搞得,实际上这个作为一个内部函数应该更好一些,其他地方并不会使用。
BGP-OPEN最重要的是两个函数,一个是bgp_open_option_parse()解析函数,一个是bgp_open_capability()填充函数。quagga的模块化做的很好,但是函数命名上感觉有些混乱,按照习惯来说,感觉这两个外部函数命名应该是:
// 解析option
bgp_open_option_parse();
// 填充option
bgp_open_option_fill();
然后对应才应该有内部函数:
// 解析Capability option
bgp_capability_parse();
// 填充Capability option
bgp_capability_fill();
这些个命名,也有可能是历史遗留问题吧,或者就是quagga独特的命名方式吧,这个应该还好。
我们着重看解析函数和填充函数。打开bgp_open.c,和BGP-OPEN报文相关的填充和解析都在这里,但是对收到报文的类型判断不在这里。这个文件中有大量有static修饰的内部函数,这些大多是capability的填充和解析,如果不关心的话就可以不用管。搜索USE_SRX就能知道NIST改了哪些地方,后续就是按照这个顺序,结合先变量定义后函数说明的方式进行分析。
在capcode_str中,它塞入了CAPABILITY_CODE_EXTENDED,表示的意思是Extended Message,这个宏定义在bgp_open.h中。查看IANA对于BGP-OPEN-CAPABILITY的分配值,这个是定义在RFC8654中,大概意思就是超过4096字节的BGP报文,就需要使用到这个Capability了,最大支持65535字节,猜测这个对应的修改,应该是控制length字段所占用字节的吧。在新版本的FRRouting中,已经有了这个,叫做CAPABILITY_CODE_EXT_MESSAGE。
在cap_minsizes中,添加了CAPABILITY_CODE_EXTENDED的长度CAPABILITY_CODE_EXTENDED_LEN,实际值为0。
接下来的修改主要都是在解析和填充函数中了。
在bgp_capability_parse()中,它先是定义了一个变量struct BgpsecCapVal cv,这个结构体struct BgpsecCapVal,主要是version_dir以及AFI,这是BGPsec Capability的Capability的结构,但是我觉着定义不定义都可以,反正就两个字段共3字节,当然定义之后更加清晰了。
继续向下,添加了对于CAPABILITY_CODE_BGPSEC、CAPABILITY_CODE_EXTENDED的长度检查。Quagga 0.99版本还是在判断大小,新版本的FRR 9.x-dev版本则是多了用取余数的方式来判断。
接下来就是正式的解析部分了。这里完整代码如下:
#ifdef USE_SRX
case CAPABILITY_CODE_BGPSEC:
// 获取填充bgpsec capability结构体
memset(&cv, 0xff, sizeof(struct BgpsecCapVal));
cv.version_dir = stream_getc(s);
cv.afi = stream_getw(s);
/* 日志记录,要看`/etc/frr/frr.conf`中定义的日志记录级别已经位置。
* 默认应该是在`/var/log/frr/frr.log`中,级别是debugging
* 下边的if条件是宏定义,比较烦,可以移除,永远都打就行吧
*/
if (BGP_DEBUG (bgpsec, BGPSEC_IN) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
{
zlog_debug("[BGPSEC] ------- BGPSEC capability parsing -------");
zlog_debug("[BGPSEC] %s capability code: %d length:%d version_dir:%02x afi:%02x", \
peer->host, caphdr.code, caphdr.length, cv.version_dir, cv.afi);
}
/* 检查peer是否有recv方向的的bgpsec能力 */
if( (cv.version_dir & ~(BGPSEC_CAP_DIR_RECV)) == 0) // 0x00 : dir bit
{
// 设置peer的recv能力
SET_FLAG (peer->cap, PEER_CAP_BGPSEC_ADV);
if (BGP_DEBUG (bgpsec, BGPSEC_IN) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] peer Capability RECV set");
}
/* 检查peer是否有send方向的bgpsec能力 */
//if( (cv.version_dir & 0x08) == 0) // 0x08 : dir bit
if( cv.version_dir & (BGPSEC_CAP_DIR_SEND)) // 0x08 : dir bit
{
// 设置peer的send能力
SET_FLAG (peer->cap, PEER_CAP_BGPSEC_ADV_SEND);
if (BGP_DEBUG (bgpsec, BGPSEC_IN) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] peer Capability SEND set");
}
break;
/* 对于Extended Message的支持 */
case CAPABILITY_CODE_EXTENDED:
/* 设置peer能力 */
SET_FLAG (peer->cap, PEER_CAP_EXTENDED_MSG_SUPPORT);
if(peer->ibuf)
{
if(stream_get_size (peer->ibuf) <= BGP_MAX_PACKET_SIZE)
stream_resize (peer->ibuf, BGP_MAX_PACKET_SIZE_EXTENDED);
}
if(peer->work)
{
if(stream_get_size (peer->work) <= BGP_MAX_PACKET_SIZE)
stream_resize (peer->work, BGP_MAX_PACKET_SIZE_EXTENDED);
}
break;
#endif
后续来到bgp_open_capability()中,它通过宏控制定义了一个变量,bool mpnlri_v6_announced = false;,通过是否定义HAVE_IPV6来决定其值,这个就是是否宣告AFI=IPV6的能力。
OK,接下来是这个填充函数比较重要的部分了。
/* 我把这个函数提前了,它干的事情,就是填充bgpsec capability到open报文中 */
#ifdef USE_SRX
void makeBgpsecCapability(struct stream* s, uint8_t version, uint8_t dir, uint16_t afi)
{
struct BgpsecCapVal cv;
memset(&cv, 0x0, sizeof(struct BgpsecCapVal));
// 以下就是一个 & 的宏定义。
SET_FLAG(cv.version_dir, version);
SET_FLAG(cv.version_dir, dir);
cv.afi = afi;
// 以下的内容就是3字节的BGPsec Capability
// 一个字节
stream_putc (s, cv.version_dir);
// 两个字节
stream_putw (s, cv.afi);
}
#endif
/* 以下内容是bgp_open_capability()函数填充BGPsec Capability内容主体 */
#ifdef USE_SRX
/* 以下的flags的设置,就是用于控制是否填充
* 这些flags是通过vty配置来控制的,需要查看bgpd/bgp_vty.c。
* 为了简便,应该不进行判断而直接写也行,只要你确定你这个功能是需要一直开着的。
*/
/* BGPSEC Capability */
if (CHECK_FLAG (peer->flags, PEER_FLAG_BGPSEC_CAPABILITY_SEND))
{
/* direction - send, IPv4 capability */
stream_putc (s, BGP_OPEN_OPT_CAP);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN + 2);
stream_putc (s, CAPABILITY_CODE_BGPSEC);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN);
makeBgpsecCapability(s, BGPSEC_CAP_VERSION, BGPSEC_CAP_DIR_SEND, BGPSEC_CAP_AFI_IPv4);
if (BGP_DEBUG (bgpsec, BGPSEC_OUT) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] %s: BGPSEC SEND Capability set for IPv4", __FUNCTION__);
}
if(CHECK_FLAG (peer->flags, PEER_FLAG_BGPSEC_CAPABILITY_RECV))
{
/* direction - recv, IPv4 capability */
stream_putc (s, BGP_OPEN_OPT_CAP);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN + 2);
stream_putc (s, CAPABILITY_CODE_BGPSEC);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN);
makeBgpsecCapability(s, BGPSEC_CAP_VERSION, BGPSEC_CAP_DIR_RECV, BGPSEC_CAP_AFI_IPv4);
if (BGP_DEBUG (bgpsec, BGPSEC_OUT) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] %s: BGPSEC RECV Capability set for IPv4", __FUNCTION__);
}
else if(!CHECK_FLAG (peer->flags, PEER_FLAG_BGPSEC_CAPABILITY_SEND))
{
if (BGP_DEBUG (bgpsec, BGPSEC_OUT) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] %s: BGPSEC Capability NOT set for IPv4", __FUNCTION__);
}
/* BGPSec capability IPv6 */
if (mpnlri_v6_announced
&& (CHECK_FLAG (peer->flags, PEER_FLAG_BGPSEC_CAPABILITY_SEND)))
{
/* direction - send, IPv6 capability */
stream_putc (s, BGP_OPEN_OPT_CAP);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN + 2);
stream_putc (s, CAPABILITY_CODE_BGPSEC);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN);
makeBgpsecCapability(s, BGPSEC_CAP_VERSION, BGPSEC_CAP_DIR_SEND, BGPSEC_CAP_AFI_IPv6);
if (BGP_DEBUG (bgpsec, BGPSEC_OUT) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] %s: BGPSEC SEND Capability set for IPv6", __FUNCTION__);
}
if (mpnlri_v6_announced
&& (CHECK_FLAG (peer->flags, PEER_FLAG_BGPSEC_CAPABILITY_RECV)))
{
/* direction - recv, IPv6 capability */
stream_putc (s, BGP_OPEN_OPT_CAP);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN + 2);
stream_putc (s, CAPABILITY_CODE_BGPSEC);
stream_putc (s, CAPABILITY_CODE_BGPSEC_LEN);
makeBgpsecCapability(s, BGPSEC_CAP_VERSION, BGPSEC_CAP_DIR_RECV, BGPSEC_CAP_AFI_IPv6);
if (BGP_DEBUG (bgpsec, BGPSEC_OUT) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] %s: BGPSEC RECV Capability set for IPv6", __FUNCTION__);
}
else if(!CHECK_FLAG (peer->flags, PEER_FLAG_BGPSEC_CAPABILITY_SEND))
{
if (BGP_DEBUG (bgpsec, BGPSEC_OUT) || BGP_DEBUG(bgpsec, BGPSEC_DETAIL))
zlog_debug("[BGPSEC] %s: BGPSEC Capability NOT set for IPv6", __FUNCTION__);
}
/* Extended Message Support for BGP */
if (CHECK_FLAG (peer->flags, PEER_FLAG_EXTENDED_MESSAGE_SUPPORT))
{
if (BGP_DEBUG (normal, NORMAL))
zlog_debug("%s: Extended Message Support Capability Set", __FUNCTION__);
stream_putc (s, BGP_OPEN_OPT_CAP);
stream_putc (s, CAPABILITY_CODE_EXTENDED_LEN+ 2);
stream_putc (s, CAPABILITY_CODE_EXTENDED);
stream_putc (s, CAPABILITY_CODE_EXTENDED_LEN);
}
#endif