博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DPDK LPM路由存储与查找
阅读量:5299 次
发布时间:2019-06-14

本文共 6125 字,大约阅读时间需要 20 分钟。

前言:DPDK的LPM模块实现了一种最长前缀匹配,其中的KEY是32位的,可以说是为查找路由量身定做的,为了实现快速查找,实现上使用了用空间换时间的思路。同时为了最大限度的减少查询次数,把32位的KEY值划分为24位和8位两张表中。这样的设计思路可以用于以后的前缀查找。本篇分析以16.07版本为例。

一. LPM的设计概览

对于路由的查找,有多种方法,前缀匹配,红黑树,各有优劣。这都在昭示着上帝创世界是均衡的。从统计上看,只有少数的路由网段深度大于24,因此使用了DIR-24-8表,大部分匹配在第一次查找时即可命中。

对于二级表tbl8,理论上有2^24个,但是我们不能存储这个多个表,否则总共的表项不就是2^32了嘛,相当于每个ip地址都有一个条目对应。所以,可以通过限定tbl8表中的组的数目,减少空间占用。每个组也可以再次限定条目的数目,最大自然就是256了。直接使用下面的图来表示:

614525-20170507201508414-1578884301.png

在16.04版本后,比如16.07版本中,就出现了LPM操作的两套接口,如rte_lpm_add_v20()rte_lpm_add_v1604(),区别在于下一跳的值范围,v20版本是8位的,v1604是32位的。这里的下一跳并不是地址的下一跳,仅仅是表示一个索引,可以用来指示自己的表项。

二.LPM的代码实现

在代码分析上,我们以v20版本做分析,v1604的版本基本一样。

2.1 LPM路由的存储

路由的存储是从rte_lpm_add_v20()开始的:

首先,获得网段,

ip_masked = ip & depth_to_mask(depth);

然后是把路由信息添进规则表--rule_add_v20(),

规则表是按照掩码深度排的,共有32个组,分别对应不同的掩码深度。

既然是添加rule,首先要看是否已经有了这个rule。这里使用了lpm->rule_info结构来存储每个组的第一个rule在rule table的index。所以,查找过程就是:先看对应深度的组是否有元素,如果没有,直接下一步插入,如果有,就找到对应组的第一个元素,然后遍历,找到就返回index,没找到就下一步插入。

主要来说一下这个插入位置,由于各个组的rule存储是连着的,没有空余位置,因此当要添加一个20位深度的rule时,如果已经添加了大于20的rule,就要挪出位置来。

rule_index = 0;for (i = depth - 1; i > 0; i--) {    if (lpm->rule_info[i - 1].used_rules > 0) {        rule_index = lpm->rule_info[i - 1].first_rule                + lpm->rule_info[i - 1].used_rules;        break;    }}if (rule_index == lpm->max_rules)    return -ENOSPC;lpm->rule_info[depth - 1].first_rule = rule_index;

这是计算出新的rule要在表中的位置。然后接下来就是为这个rule挪出位置来:

/* Make room for the new rule in the array. */for (i = RTE_LPM_MAX_DEPTH; i > depth; i--) {    if (lpm->rule_info[i - 1].first_rule            + lpm->rule_info[i - 1].used_rules == lpm->max_rules)        return -ENOSPC;    if (lpm->rule_info[i - 1].used_rules > 0) {        lpm->rules_tbl[lpm->rule_info[i - 1].first_rule            + lpm->rule_info[i - 1].used_rules]                = lpm->rules_tbl[lpm->rule_info[i - 1].first_rule];        lpm->rule_info[i - 1].first_rule++;    }}

从32位深度的组开始,大于depth的组往后依次挪一个位置。

最后把填充规则信息:

lpm->rules_tbl[rule_index].ip = ip_masked;lpm->rules_tbl[rule_index].next_hop = next_hop;/* Increment the used rules counter for this rule group. */lpm->rule_info[depth - 1].used_rules++;

这样,规则表就添加完成了!

接下来就是重要的往DIR-24-8表中添加条目了。

对于掩码深度<=24位的,只需要添加tbl24表,而掩码深度大于24位的,还需要添加tbl8表。因此,这里有两个入口函数,先看掩码深度<=24的处理:add_depth_small_v20()

首先要知道的是,对于tbl24表,存的是地址前24位的排列,最大共2^24个条目。此时想象一下,如果配置了一个20位深度的路由,那么前24位就有2^(24-20)个可能,这些条目也都要填充为这个条目。

因此,先算出来对于这样深度的路由,其前24位可能的范围。

tbl24_index = ip >> 8;tbl24_range = depth_to_range(depth);

然后对这个范围内的条目操作:

for (i = tbl24_index; i < (tbl24_index + tbl24_range); i++)

更新或者新增新的tbl24条目。如果有tbl8表的,找到对应的表,更新或者添加tbl8表。

接下来我们看一下当深度>24时,对于这两张表是怎么操作来插入路由的:add_depth_big_v20()

首先也是确定他的高24位的段和后8位的范围,

tbl24_index = (ip_masked >> 8);tbl8_range = depth_to_range(depth);

然后主要分了3类情况处理,

第一种,连对应的tbl24条目都不存在的,自然是要先增添tbl24条目

if (!lpm->tbl24[tbl24_index].valid) {    /* Search for a free tbl8 group. */    tbl8_group_index = tbl8_alloc_v20(lpm->tbl8);    /* Check tbl8 allocation was successful. */    if (tbl8_group_index < 0) {        return tbl8_group_index;    }    /* Find index into tbl8 and range. */    tbl8_index = (tbl8_group_index *            RTE_LPM_TBL8_GROUP_NUM_ENTRIES) +            (ip_masked & 0xFF);    /* Set tbl8 entry. */    for (i = tbl8_index; i < (tbl8_index + tbl8_range); i++) {        lpm->tbl8[i].depth = depth;        lpm->tbl8[i].next_hop = next_hop;        lpm->tbl8[i].valid = VALID;    }    /*     * Update tbl24 entry to point to new tbl8 entry. Note: The     * ext_flag and tbl8_index need to be updated simultaneously,     * so assign whole structure in one go     */    struct rte_lpm_tbl_entry_v20 new_tbl24_entry = {        { .group_idx = (uint8_t)tbl8_group_index, },        .valid = VALID,        .valid_group = 1,        .depth = 0,    };    lpm->tbl24[tbl24_index] = new_tbl24_entry;}

这里的逻辑是比较容易理解的,同时创建tbl24和tbl8的条目,这里注意到tbl8表组索引的分配,就是在最大限定的组数量范围内,找出没使用的组,默认最大为256个组,所以,默认只能存256个大于32位深度的路由,在16.04版本中,这个组数量是在初始化可配置的。

第二种情况,tbl24表存在,但是没有tbl8的标识,比如先配了192.168.1.0/24,此时,tbl24已经创建了一个条目,如果再配置192.168.1.0/28,那么,先检查tbl24时,已经创建,但是没有tbl8标识,现在要做的就是更新tbl24条目,同时创建tbl8条目。

这里就很有意思了,既然更新tbl24,那么主要就是nexthop,valid标识,此时,因为查询大于24位深度的路由时要使用tbl8,而如果查询小于24位的又不查tbl8,这样怎么定nexthop呢?所以,就把先把tbl8条目都设置成tbl24的内容,也就是把对应网断的tbl24条目搬到tbl8中,然后再根据tbl8的范围,刷新tbl8,这样,老的tbl24的条目和新添加tbl8的条目都存在这个tbl8中了。

for (i = tbl8_group_start; i < tbl8_group_end; i++) {    lpm->tbl8[i].valid = VALID;    lpm->tbl8[i].depth = lpm->tbl24[tbl24_index].depth;    lpm->tbl8[i].next_hop =            lpm->tbl24[tbl24_index].next_hop;}tbl8_index = tbl8_group_start + (ip_masked & 0xFF);/* Insert new rule into the tbl8 entry. */for (i = tbl8_index; i < tbl8_index + tbl8_range; i++) {    lpm->tbl8[i].valid = VALID;    lpm->tbl8[i].depth = depth;    lpm->tbl8[i].next_hop = next_hop;}

最后更新tbl24表

struct rte_lpm_tbl_entry_v20 new_tbl24_entry = {        { .group_idx = (uint8_t)tbl8_group_index, },        .valid = VALID,        .valid_group = 1,        .depth = 0,};lpm->tbl24[tbl24_index] = new_tbl24_entry;

第三种情况是如果已经有对应的tbl8组了,那自然就是更新啦。如果理解了第二种情况,这种也很好理解

tbl8_group_index = lpm->tbl24[tbl24_index].group_idx;tbl8_group_start = tbl8_group_index *    RTE_LPM_TBL8_GROUP_NUM_ENTRIES;tbl8_index = tbl8_group_start + (ip_masked & 0xFF);for (i = tbl8_index; i < (tbl8_index + tbl8_range); i++) {if (!lpm->tbl8[i].valid ||        lpm->tbl8[i].depth <= depth) {    struct rte_lpm_tbl_entry_v20 new_tbl8_entry = {        .valid = VALID,        .depth = depth,        .valid_group = lpm->tbl8[i].valid_group,    };    new_tbl8_entry.next_hop = next_hop;    /*     * Setting tbl8 entry in one go to avoid race     * condition     */    lpm->tbl8[i] = new_tbl8_entry;

就是把最新添加的深度的条目更新一下,如老的是192.168.1.0/28的,新添加的是192.168.1.0/30的,根据最长匹配,那么就把最长网段(30掩码的)中的4个路由更新一下。

这样,路由添加就完成了!

2.2 LPM路由的查找

对应条目的查找就很简单了,只要找到nexthop就行了,但要注意的是,这里的nexthop可不是最后的下一跳,因为看结构定义,只有8位

struct rte_lpm_tbl_entry_v20 {    uint8_t depth       :6;    uint8_t valid_group :1;    uint8_t valid       :1;    union {        uint8_t group_idx;        uint8_t next_hop;    };};

因此,这个nexthop实际上也只是个索引,根据nexthop的索引,我们可以自己进行后续处理。

后记:

LPM模块是DPDK的重要组成部分,主要用于查找路由,其使用的以空间换时间的方式,同时为了尽量减少空间消耗,采用DIR-24-8的二级表,再加上可配置的条目总数限制,在保证速度的前提下,最大可能降低了资源消耗,是一种很值得借鉴的设计方式。

转载于:https://www.cnblogs.com/yhp-smarthome/p/6822085.html

你可能感兴趣的文章
5月4日上午学习日志
查看>>
(译)IOS block编程指南 2 block开始
查看>>
Data Structure Binary Tree: Lowest Common Ancestor in a Binary Tree
查看>>
java第二次作业
查看>>
Java 9 揭秘(14. HTTP/2 Client API)
查看>>
麦包包也看到了个性化推荐:数据驱动销售——个性化推荐引擎
查看>>
[原]NYOJ-子串和44
查看>>
django常用模板语言
查看>>
MySql数据库操作命令
查看>>
教科书般的亵渎题解
查看>>
Bing Maps进阶系列四:路由功能服务(RouteService)
查看>>
C++让类只在堆或栈上分配
查看>>
Nginx的配置文件简介及在Nginx中配置基于不同ip的虚拟主机
查看>>
Eclipse启动项目正常,放到tomcat下单独启动就报错的 一例
查看>>
linux和unix下crontab的使用
查看>>
re方法
查看>>
android sdk
查看>>
简洁明了的Noip考场策略 / 平时做题也适用
查看>>
关于指数循环节的证明
查看>>
org.apache.log4j与org.apache.commons.logging这两个包有什么区别
查看>>