2

当FRR学到一个本地VNI被配置,如果用户没有显示的为这个VNI配置RD和RT的时候,FRR会自动为该VNI派生RD和RTS(包括导入和导出)。派生规则如下:

RD格式为 “Type:Routerid:VNI-Index”

RT格式为 “AS:VNI”

RD和RT在进行EVPN路由交换的时候会被使用。RD用于区分不同的VNI的EVPN路由(因为在不同的VLAN中可以存在相同的mac地址,在不同的VRF中可以存在相同的IP地址,这里的RD是与VNI一一对应的,与我之前以为的一个L3 VNI一个RD是不同的)。RT用来描述这个路由的所属关系,即谁发布了该路由,用于路由过滤。通过结合导入导出RT来对路由的导入导出进行过滤。(通俗的将,RT就好像一个密码,路由器发送路由的时候携带对应的导出RT,接收方收到该路由后,检查该RT是否和本地该VNI对应的导入RT是否一致,一致则接受,否则拒绝)。

VNI-index是路由器本地维护的一个ID分配器生成的,本地配置一个VNI后,将会为该VNI分配一个索引。所以RD的作用仅仅是隔开不同VNI之间的路由,没有其它的含义,是一个本地的ID。邻居路由器接收到路由后不会解析RD的具体含义,只用作路由区分符号。该值的范围为0到65535。

RD由8个字节组成:

TYPE:RD类型,两个字节,现在有三个值:RD_TYPE_AS(两字节AS类型,Routerid字段填写的是AS号,这个是否该字段占用两个字节,剩下两个字节会有其它字段补齐,类型值为0),RD_TYPE_IP(IP类型,RouterId字段填写的是RouterId,该字段四个字节),RD_TYPE_AS4(四字节AS类型,Routerid字段填写的是AS号,这个是否该字段占用四个字节,类型值为2)。该字段在配置过程中不可见,由系统填充。

RouterId:路由器ID,四个字节,不同的类型填充的内容不同。

VNI-Index:两个字节。

RT值由两部分组成,6个字节

其中AS部分只占两个字节,即使使用的是AS4,该字段也是两个字节,取AS4的低两字节,VNI字段占用四个字节。由VNI字段来保证不同VNI全局唯一。RT用在EVPN的扩展团体中,在发布路由时使用。RT字段确保不同VNI有不同的RT,同时确保同一个自治区中的相同VNI有相同的RT。

对于eBGP EVPN对等体来说,它们的AS是不同的。如果使用派生的RT来进行路由导入,将会导致路由导入不成功。为了解决ebgp对等体导入同一个VNI的路由问题。FRR在实现的时候做了特殊处理:当收到一个邻居的EVPN路由时,首先检测路由中的VNI对应的RT值是否是本路由器中的对应的VNI的RT值,如果是则允许导入,如果不是则使用“*:VNI”格式进行匹配(即通配AS部分)。

这种方法只会在RT是派生的情况下生效,当RT是用户手动配置的,将会采用严格匹配方式。

用户手动配置RD和RT

FRR支持手动配置RD和RT。

address-family l2vpn evpn
    advertise-all-vni
    vni 10200
        rd 172.16.100.1:20
        route-target import 65100:20

上面的配置中,缩进表示所属关系。从配置可知,RD和RT需要在对应的VNI下进行配置,而且必须在evpn地址族下配置。当用户删除RD和RT后,系统将会再次使用派生的RD和RT,就好像用户没有配置过一样。

RD值与VNI值是一一对应的,而RT值,一个VNI可以配置多个。

address-family l2vpn evpn
    vni 10400
        route-target import 100:400
        route-target import 100:500
    vni 10500
        route-target import 65000:500
        route-target export 65000:500

EVPN RD

/*
 * Derive RD automatically for VNI using passed information - it
 * is of the form RouterId:unique-id-for-vni.
 * 自动为VNI派生一个RD,RD格式为 路由ID:VNI-INDEX。
 */
void bgp_evpn_derive_auto_rd(struct bgp *bgp, struct bgpevpn *vpn)
{
    char buf[100];

    vpn->prd.family = AF_UNSPEC;
    vpn->prd.prefixlen = 64;
    sprintf(buf, "%s:%hu", inet_ntoa(bgp->router_id), vpn->rd_id);
    (void)str2prefix_rd(buf, &vpn->prd);
    UNSET_FLAG(vpn->flags, VNI_FLAG_RD_CFGD);
}

int str2prefix_rd(const char *str, struct prefix_rd *prd)
{
    int ret;  /* ret of called functions */
    int lret; /* local ret, of this func */
    char *p;
    char *p2;
    struct stream *s = NULL;
    char *half = NULL;
    struct in_addr addr;

    s = stream_new(8);

    prd->family = AF_UNSPEC;
    prd->prefixlen = 64;

    lret = 0;
    //先查找冒号,冒号前面是路由id
    p = strchr(str, ':');
    if (!p)
        goto out;
    //冒号后面是vni-index,必须全是数字。 
    if (!all_digit(p + 1))
        goto out;

    half = XMALLOC(MTYPE_TMP, (p - str) + 1);
    memcpy(half, str, (p - str));
    half[p - str] = '\0';
    //点分十进制,找到第一个点号。
    p2 = strchr(str, '.');
    //非IP地址,可能是as。evpn使用的IP地址。
    if (!p2) {
        unsigned long as_val;

        if (!all_digit(half))
            goto out;

        as_val = atol(half);
        if (as_val > 0xffff) {
            stream_putw(s, RD_TYPE_AS4);
            stream_putl(s, as_val);
            stream_putw(s, atol(p + 1));
        } else {
            stream_putw(s, RD_TYPE_AS);
            stream_putw(s, as_val);
            stream_putl(s, atol(p + 1));
        }
    } else {
        //将点分十进制转换为整形。
        ret = inet_aton(half, &addr);
        if (!ret)
            goto out;
        //设置RD类型为IP。哦后面跟着IP地址,然后是两个字节的vni-index。
        //RD一共8个字节。
        stream_putw(s, RD_TYPE_IP);
        stream_put_in_addr(s, &addr);
        stream_putw(s, atol(p + 1));
    }
    memcpy(prd->val, s->data, 8);
    lret = 1;

out:
    if (s)
        stream_free(s);
    XFREE(MTYPE_TMP, half);
    return lret;
}

EVPN RT

派生RT

/*
 * Create RT extended community automatically from passed information:
 * of the form AS:VNI.
 * NOTE: We use only the lower 16 bits of the AS. This is sufficient as
 * the need is to get a RT value that will be unique across different
 * VNIs but the same across routers (in the same AS) for a particular
 * VNI.
 */
static void form_auto_rt(struct bgp *bgp, vni_t vni, struct list *rtl)
{
    struct ecommunity_val eval;
    struct ecommunity *ecomadd;

    if (bgp->advertise_autort_rfc8365)
        vni |= EVPN_AUTORT_VXLAN;
    encode_route_target_as((bgp->as & 0xFFFF), vni, &eval);
    //创建一个新的扩展团体
    ecomadd = ecommunity_new();
    //设置扩展团体的值
    ecommunity_add_val(ecomadd, &eval);
    //将rt扩展团体加入到rt链表中
    listnode_add_sort(rtl, ecomadd);
}
/*
 * Encode BGP Route Target AS:nn.
 */
static inline void encode_route_target_as(as_t as, uint32_t val,
                      struct ecommunity_val *eval)
{
    eval->val[0] = ECOMMUNITY_ENCODE_AS;//扩展团体类型
    eval->val[1] = ECOMMUNITY_ROUTE_TARGET;//扩展团体子类型
    eval->val[2] = (as >> 8) & 0xff;//as
    eval->val[3] = as & 0xff;
    eval->val[4] = (val >> 24) & 0xff;//vni
    eval->val[5] = (val >> 16) & 0xff;
    eval->val[6] = (val >> 8) & 0xff;
    eval->val[7] = val & 0xff;
}
/*
 * Map the RTs (configured or automatically derived) of a VNI to the VNI.
 * The mapping will be used during route processing.
 * 映射RTS到对应的VNI。主要是import rt。这些rt在收到update消息时进行路由过滤。
 */
void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn)
{
    int i;
    struct ecommunity_val *eval;
    struct listnode *node, *nnode;
    struct ecommunity *ecom;

    for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) {
        for (i = 0; i < ecom->size; i++) {
            eval = (struct ecommunity_val *)(ecom->val
                             + (i
                                * ECOMMUNITY_SIZE));
            map_vni_to_rt(bgp, vpn, eval);
        }
    }
}
/*
 * Map one RT to specified VNI.
 * 映射一个RT到一个指定的VNI。
 */
static void map_vni_to_rt(struct bgp *bgp, struct bgpevpn *vpn,
              struct ecommunity_val *eval)
{
    struct irt_node *irt;
    struct ecommunity_val eval_tmp;

    /* If using "automatic" RT, we only care about the local-admin
     * sub-field.
     * This is to facilitate using VNI as the RT for EBGP peering too.
     */
    memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE);
    if (!is_import_rt_configured(vpn))
        mask_ecom_global_admin(&eval_tmp, eval);

    irt = lookup_import_rt(bgp, &eval_tmp);
    if (irt)
        if (is_vni_present_in_irt_vnis(irt->vnis, vpn))
            /* Already mapped. */
            return;

    if (!irt) {
        irt = import_rt_new(bgp, &eval_tmp);
        assert(irt);
    }

    /* Add VNI to the hash list for this RT. */
    listnode_add(irt->vnis, vpn);
}

派生导入RT

/*
 * Derive Import RT automatically for VNI and map VNI to RT.
 * The mapping will be used during route processing.
 * 为vni派生import rt
 */
void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, struct bgpevpn *vpn)
{
    form_auto_rt(bgp, vpn->vni, vpn->import_rtl);
    UNSET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD);

    /* Map RT to VNI 将RT添加到对应的vni描述控制块中 */
    bgp_evpn_map_vni_to_its_rts(bgp, vpn);
}

派生导出RT

/*
 * Derive Export RT automatically for VNI.
 */
void bgp_evpn_derive_auto_rt_export(struct bgp *bgp, struct bgpevpn *vpn)
{
    form_auto_rt(bgp, vpn->vni, vpn->export_rtl);
    UNSET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD);
}

RT在路由导入时的作用

/*
 * Given a route entry and a VNI, see if this route entry should be
 * imported into the VNI i.e., RTs match.
 * 给定一个路由表项和一个VNI。查看路由表项是否应该导入该VNI表项,主要是
 * 匹配RTs。
 */
static int is_route_matching_for_vni(struct bgp *bgp, struct bgpevpn *vpn,
                     struct bgp_path_info *pi)
{
    struct attr *attr = pi->attr;
    struct ecommunity *ecom;
    int i;

    assert(attr);
    /* Route should have valid RT to be even considered. */
    /* RT放在扩展团体中,所以必须携带扩展团体属性 */
    if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)))
        return 0;

    ecom = attr->ecommunity;
    if (!ecom || !ecom->size)
        return 0;

    /* For each extended community RT, see if it matches this VNI. If any RT
     * matches, we're done.
     */
    for (i = 0; i < ecom->size; i++) {
        uint8_t *pnt;
        uint8_t type, sub_type;
        struct ecommunity_val *eval;
        struct ecommunity_val eval_tmp;
        struct irt_node *irt;

        /* Only deal with RTs */
        pnt = (ecom->val + (i * ECOMMUNITY_SIZE));
        eval = (struct ecommunity_val *)(ecom->val
                         + (i * ECOMMUNITY_SIZE));
        type = *pnt++;
        sub_type = *pnt++;
        // 我们只关心RT扩展团体。
        if (sub_type != ECOMMUNITY_ROUTE_TARGET)
            continue;

        /* See if this RT matches specified VNIs import RTs */
        /* 查找本地是否配置了该RT */
        irt = lookup_import_rt(bgp, eval);
        if (irt)//配置了则需要进一步校验,vpn对应的vni是否使用了该rt
            if (is_vni_present_in_irt_vnis(irt->vnis, vpn))
                return 1;

        /* Also check for non-exact match. In this, we mask out the AS
         * and
         * only check on the local-admin sub-field. This is to
         * facilitate using
         * VNI as the RT for EBGP peering too.
         * 没有匹配的话,进行通配处理,这种情况用于解决ebgp邻居,因为ebgp两者不同的as。所以采用*:xxx进行匹配。
         */
        irt = NULL;
        if (type == ECOMMUNITY_ENCODE_AS
            || type == ECOMMUNITY_ENCODE_AS4
            || type == ECOMMUNITY_ENCODE_IP) {
            memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE);
            //掩码匹配rt属性。用于解决ebgp邻居之间的路由导入,通配as。
            mask_ecom_global_admin(&eval_tmp, eval);
            //继续查找。
            irt = lookup_import_rt(bgp, &eval_tmp);
        }
        //找到,则看对应的vni是否使用了该rt
        if (irt)
            if (is_vni_present_in_irt_vnis(irt->vnis, vpn))
                return 1;
    }

    return 0;
}
/*
 * Mask off global-admin field of specified extended community (RT),
 * just retain the local-admin field.
 * 只匹配RT扩展团体的本地管理域。即只匹配VNI域,将as域置为0。
 */
static inline void mask_ecom_global_admin(struct ecommunity_val *dst,
                      struct ecommunity_val *src)
{
    uint8_t type;
    //第一个字节是类型
    type = src->val[0];
    dst->val[0] = 0;
    if (type == ECOMMUNITY_ENCODE_AS) {
        dst->val[2] = dst->val[3] = 0;//将as赋值为0
    } else if (type == ECOMMUNITY_ENCODE_AS4
           || type == ECOMMUNITY_ENCODE_IP) {
        dst->val[2] = dst->val[3] = 0;
        dst->val[4] = dst->val[5] = 0;
    }
}

ouyangxibao
189 声望162 粉丝

不生产代码,只是代码的搬运工