DPDK報文分類與訪問控制

原創翻譯,轉載請註明出處。算法

 

dpdk提供了一個訪問控制庫,提供了基於一系列分類規則對接收到的報文進行分類的能力。
ACL庫用來在一系列規則上執行N元組查找,能夠實現多個分類和對每一個分類查找最佳匹配(最高優先級),ACL庫的api提供以下基本操做:數據庫

  • 建立一個新的訪問控制(AC)環境實例(context)
  • 添加規則到這個環境實例
  • 爲這個實例裏全部的規則,建立必需的運行時結構體來指針報文分類
  • 執行接收報文分類
  • 刪除AC環境實例和對應的運行時結構體,並釋放內存


概述
1.規則定義
當前的實現容許用戶對將要執行的報文分類須要的每個context指定它獨有規則(字段集合)。但這在規則字段上有一些限制條件:
    規則定義的第一個字段必須是一個字節的長度
    以後的字段必須以4個連續的字節分組
這主要是爲性能考慮,查找函數處理第一個輸入字節作爲這個流的設置的一部分,而後這查找函數的內部循環被展開來同時處理4字節的輸入。
要定義規則的每個字段,須要使用以下的結構體:api

1 struct rte_acl_field_def {
2     uint8_t type;        /*< type - ACL_FIELD_TYPE. */
3     uint8_t size;        /*< size of field 1,2,4, or 8. */
4     uint8_t field_index; /*< index of field inside the rule. */
5     uint8_t input_index; /*< 0-N input index. */
6     uint32_t offset;     /*< offset to start of field. */
7 };


type
字段的類型,有3種選項:
    _MASK    表示有值和掩碼的IP地址字段,定義相關的bit位
    _RANGE   表示端口字段的低位和高位值
    _BITMASK 表示協議標識字段的值和掩碼位

size 這個參數定義了字段的字節數大小。容許的值範圍有(1,2,4,8)bytes,注意,因爲輸入字節的分組,1或2字節的字段必須定義爲連續的來組成4字節連續。通用,最好的作法是定義8或更多字節數的字段,這樣構建進程會消除那些亂的字段。

field_index
一個0開始的值,用來指定字段在規則內部的位置,0~n-1表示n個字段。

input_index
上面提到過,全部輸入字段,除了第一個,其餘必須以4個連續字節分組,這個input_index就是來指定字段在那個組。

offset
這個定義了字段的偏移量,爲查找指定了從緩衝區的起始位置的偏移。

舉個栗子,定義一個IPv4的五元組的分類:數組

1 struct ipv4_5tuple {
2     uint8_t  proto;
3     uint32_t ip_src;
4     uint32_t ip_dst;
5     uint16_t port_src;
6     uint16_t port_dst;
7 };

須要使用下面的字段定義數組:網絡

 1 struct rte_acl_field_def ipv4_defs[5] = {
 2     /* first input field - always one byte long. */
 3     {
 4         .type = RTE_ACL_FIELD_TYPE_BITMASK,
 5         .size = sizeof (uint8_t),
 6         .field_index = 0,
 7         .input_index = 0,
 8         .offset = offsetof (struct ipv4_5tuple, proto),
 9     },
10     /* next input field (IPv4 source address) - 4 consecutive bytes. */
11     {
12         .type = RTE_ACL_FIELD_TYPE_MASK,
13         .size = sizeof (uint32_t),
14         .field_index = 1,
15         .input_index = 1,
16         .offset = offsetof (struct ipv4_5tuple, ip_src),
17     },
18     /* next input field (IPv4 destination address) - 4 consecutive bytes. */
19     {
20         .type = RTE_ACL_FIELD_TYPE_MASK,
21         .size = sizeof (uint32_t),
22         .field_index = 2,
23         .input_index = 2,
24         .offset = offsetof (struct ipv4_5tuple, ip_dst),
25     },
26     /*
27     * Next 2 fields (src & dst ports) form 4 consecutive bytes.
28     * They share the same input index.
29     */
30     {
31         .type = RTE_ACL_FIELD_TYPE_RANGE,
32         .size = sizeof (uint16_t),
33         .field_index = 3,
34         .input_index = 3,
35         .offset = offsetof (struct ipv4_5tuple, port_src),
36     },
37     {
38         .type = RTE_ACL_FIELD_TYPE_RANGE,
39         .size = sizeof (uint16_t),
40         .field_index = 4,
41         .input_index = 3,
42         .offset = offsetof (struct ipv4_5tuple, port_dst),
43     },
44 };

一個典型的IPv4五元組規則以下:數據結構

source addr/mask destination addr/mask source ports dest ports protocol/mask
192.168.1.0/24 192.168.2.31/32 0:65535 1234:1234 17/0xff


任何帶有協議ID爲17(UDP),源地址爲192.168.1.[0-255],目的地址爲192.168.2.31,源端口在[0-65535],目的端口爲1234的ipv4報文將會匹配上面的規則。

定義IPv6 2元組: <protocol, IPv6 source address>的報文分類,
IPv6 頭:app

1 struct struct ipv6_hdr {
2     uint32_t vtc_flow; /* IP version, traffic class & flow label. */
3     uint16_t payload_len; /* IP packet length - includes sizeof(ip_header). */
4     uint8_t proto; /* Protocol, next header. */
5     uint8_t hop_limits; /* Hop limits. */
6     uint8_t src_addr[16]; /* IP address of source host. */
7     uint8_t dst_addr[16]; /* IP address of destination host(s). */
8 } __attribute__((__packed__));

須要使用下面的字段定義數組:less

 1 struct struct rte_acl_field_def ipv6_2tuple_defs[5] = {
 2     {
 3         .type = RTE_ACL_FIELD_TYPE_BITMASK,
 4         .size = sizeof (uint8_t),
 5         .field_index = 0,
 6         .input_index = 0,
 7         .offset = offsetof (struct ipv6_hdr, proto),
 8     },
 9     {
10         .type = RTE_ACL_FIELD_TYPE_MASK,
11         .size = sizeof (uint32_t),
12         .field_index = 1,
13         .input_index = 1,
14         .offset = offsetof (struct ipv6_hdr, src_addr[0]),
15     },
16     {
17         .type = RTE_ACL_FIELD_TYPE_MASK,
18         .size = sizeof (uint32_t),
19         .field_index = 2,
20         .input_index = 2,
21         .offset = offsetof (struct ipv6_hdr, src_addr[4]),
22     },
23     {
24         .type = RTE_ACL_FIELD_TYPE_MASK,
25         .size = sizeof (uint32_t),
26         .field_index = 3,
27         .input_index = 3,
28         .offset = offsetof (struct ipv6_hdr, src_addr[8]),
29     },
30     {
31         .type = RTE_ACL_FIELD_TYPE_MASK,
32         .size = sizeof (uint32_t),
33         .field_index = 4,
34         .input_index = 4,
35         .offset = offsetof (struct ipv6_hdr, src_addr[12]),
36     },
37 };

一個典型的IPv4二元組規則以下:socket

source addr/mask protocol/mask
2001:db8:1234:0000:0000:0000:0000:0000/48 6/0xff

任何帶有協議ID爲6 (TCP),源地址在
[2001:db8:1234:0000:0000:0000:0000:0000 - 2001:db8:1234:ffff:ffff:ffff:ffff:ffff] 之間的報文會匹配上的規則。

下面的例子,查找Key最後的元素是8bit長,這樣就會引發4字節連續的問題:ide

1 struct acl_key {
2     uint8_t ip_proto;
3     uint32_t ip_src;
4     uint32_t ip_dst;
5     uint8_t tos; /*< This is partially using a 32-bit input element */
6 };

以下定義下面的字段定義數組:

 1 struct rte_acl_field_def ipv4_defs[4] = {
 2     /* first input field - always one byte long. */
 3     {
 4         .type = RTE_ACL_FIELD_TYPE_BITMASK,
 5         .size = sizeof (uint8_t),
 6         .field_index = 0,
 7         .input_index = 0,
 8         .offset = offsetof (struct acl_key, ip_proto),
 9     },
10     /* next input field (IPv4 source address) - 4 consecutive bytes. */
11     {
12         .type = RTE_ACL_FIELD_TYPE_MASK,
13         .size = sizeof (uint32_t),
14         .field_index = 1,
15         .input_index = 1,
16         .offset = offsetof (struct acl_key, ip_src),
17     },
18     /* next input field (IPv4 destination address) - 4 consecutive bytes. */
19     {
20         .type = RTE_ACL_FIELD_TYPE_MASK,
21         .size = sizeof (uint32_t),
22         .field_index = 2,
23         .input_index = 2,
24         .offset = offsetof (struct acl_key, ip_dst),
25     },
26     /*
27     * Next element of search key (Type of Service) is indeed 1 byte long.
28     * Anyway we need to allocate all the 4 consecutive bytes for it.
29     */
30     {
31         .type = RTE_ACL_FIELD_TYPE_BITMASK,
32         .size = sizeof (uint32_t), /* All the 4 consecutive bytes are allocated */
33         .field_index = 3,
34         .input_index = 3,
35         .offset = offsetof (struct acl_key, tos),
36     },
37 };

下面是一個典型的IPv4四元組規則:

source addr/mask destination addr/mask tos/mask protocol/mask
192.168.1.0/24 192.168.2.31/32 1/0xff 6/0xff

任何帶有協議ID 6 (TCP), 源地址 192.168.1.[0-255], 目的地址 192.168.2.31, ToS 1的IPv4報文就會匹配這個規則.

當建立一個規則集合時,對每個規則,也必須添加的附加信息:
priority
用來度量規則的權重,越高越好。若是輸入的元組匹配到多個元組,那麼就會使用權重最高的那條規則。若是匹配多個權重相同的規則,那麼返回那條規則這個是未定義的。推薦的作法是,每一個規則都設置一個惟一的權重。

category_mask
每一個規則都使用的一個掩碼位,用來選擇規則的分類。當執行查找時,查找結果裏包含了每個分類。這樣在使能了一個查找返回多個結果的狀況,有效的支持了並行查找。舉個栗子,有4個不一樣的ACL規則集合,一個用於訪問控制,一個用於路由,等等。每個集合有本身的分類,並組織在一個數據庫裏,一個查找就能夠返回包含4個集合的結果。

userdata
一個用戶定義的字段,該字段能夠設置任何值除了0。對每個分類,一個成功的查找返回匹配最高優先級的規則的用戶數據。

注意:添加新規則到ACL環境中是,全部的字段都必須是主機字節序,當對輸入的元組執行查找時,元組的全部字段都必須是網絡字節序。

2.RT內存大小限制
rte_acl_build()建立了一個給定規則集合的內部數據結構,用來給未來的運行時遍歷。當前的實現是一個multi-bit tries(字典樹,步長等於8)。根據這個規則集合的規模,可能會消耗大量的內存。若是以固定的空間建立ACL字典樹,同時把給定的規則集合分割成數個無關的子集並創建各自的字典樹的話,一樣取決於規則集合的規模,它會減小RT內存的大小要求可是會增長報文分類的時間。
在建立AC環境的時候,能夠指定內部RT結構的最大內存數量限制。經過rte_acl_config的max_size字段來設置。設置一個大於0的值,在rte_acl_build()裏:
    嘗試在RT表裏最小化字典樹的數目,but
    確保RT表的大小不會超過給定的值。
    
設置爲0的話,rte_acl_build()會默認是:儘可能以最小大小來建立RT數據,但不作任何限制。
下面爲用戶提供了一個權衡性能與空間的方式,舉個栗子:

 1 struct rte_acl_ctx * acx;
 2 struct rte_acl_config cfg;
 3 int ret;
 4 /*
 5 * assuming that acx points to already created and
 6 * populated with rules AC context and cfg filled properly.
 7 */
 8 /* try to build AC context, with RT structures less then 8MB. */
 9 cfg.max_size = 0x800000;
10 ret = rte_acl_build(acx, &cfg);
11 /*
12 * RT structures can't fit into 8MB for given context.
13 * Try to build without exposing any hard limit.
14 */
15 if (ret == -ERANGE) {
16     cfg.max_size = 0;
17     ret = rte_acl_build(acx, &cfg);
18 }

3.報文分類方法
rte_acl_build()成功以後,將開始執行報文分類,對輸入的數據查找最高優先級的規則。下面有幾個分類算法的實現:
RTE_ACL_CLASSIFY_SCALAR 通用實現,不須要任何硬件實現。
RTE_ACL_CLASSIFY_SSE 向量實現,能並行處理到8條流,要求SS4 4.1支持。
RTE_ACL_CLASSIFY_AVX2 向量實現,能並行處理到16條流,要求AVX2支持。

這個徹底由運行時決定選擇哪一個算法,在建立的時候並無什麼不一樣。全部的算法都使用相同的RT數據結構和相同的原理。主要的不一樣是向量實現能手動利用IA SIMD指令和並行處理數據流。在啓動ACL庫以後會基於當前的平臺來決定使用哪一個最有效的分類方法,並把它當成默認設置。但用戶能夠重寫這個默認的分類方法,這要求用戶本身實現當前的平臺選擇的分類方法。

4.API使用舉例:
舒適提示:若是想了解ACL API的更多細節,請參考DPDK API手冊。
下面是一個IPV4五元組報文分類使用多個分類的規則的例子(Classify with Multiple Categories):

 1 struct rte_acl_ctx * acx;
 2 struct rte_acl_config cfg;
 3 int ret;
 4 /* define a structure for the rule with up to 5 fields. */
 5 RTE_ACL_RULE_DEF(acl_ipv4_rule, RTE_DIM(ipv4_defs));
 6 /* AC context creation parameters. */
 7 struct rte_acl_param prm = {
 8     .name = "ACL_example",
 9     .socket_id = SOCKET_ID_ANY,
10     .rule_size = RTE_ACL_RULE_SZ(RTE_DIM(ipv4_defs)),
11     /* number of fields per rule. */
12     .max_rule_num = 8, /* maximum number of rules in the AC context. */
13 };
14 
15 struct acl_ipv4_rule acl_rules[] = {
16     /* matches all packets traveling to 192.168.0.0/16, applies for categories: 0,1 *
17     {
18         .data = {.userdata = 1, .category_mask = 3, .priority = 1},
19         /* destination IPv4 */
20         .field[2] = {.value.u32 = IPv4(192,168,0,0),. mask_range.u32 = 16,},
21         /* source port */
22         .field[3] = {.value.u16 = 0, .mask_range.u16 = 0xffff,},
23         /* destination port */
24         .field[4] = {.value.u16 = 0, .mask_range.u16 = 0xffff,},
25     },
26     /* matches all packets traveling to 192.168.1.0/24, applies for categories: 0 */
27     {
28         .data = {.userdata = 2, .category_mask = 1, .priority = 2},
29         /* destination IPv4 */
30         .field[2] = {.value.u32 = IPv4(192,168,1,0),. mask_range.u32 = 24,},
31         /* source port */
32         .field[3] = {.value.u16 = 0, .mask_range.u16 = 0xffff,},
33         /* destination port */
34         .field[4] = {.value.u16 = 0, .mask_range.u16 = 0xffff,},
35     },
36     /* matches all packets traveling from 10.1.1.1, applies for categories: 1 */
37     {
38         .data = {.userdata = 3, .category_mask = 2, .priority = 3},
39         /* source IPv4 */
40         .field[1] = {.value.u32 = IPv4(10,1,1,1),. mask_range.u32 = 32,},
41         /* source port */
42         .field[3] = {.value.u16 = 0, .mask_range.u16 = 0xffff,},
43         /* destination port */
44         .field[4] = {.value.u16 = 0, .mask_range.u16 = 0xffff,},
45     },
46 };
47 
48 /* create an empty AC context */
49 if ((acx = rte_acl_create(&prm)) == NULL) {
50 /* handle context create failure. */
51 }
52 /* add rules to the context */
53 ret = rte_acl_add_rules(acx, acl_rules, RTE_DIM(acl_rules));
54 if (ret != 0) {
55 /* handle error at adding ACL rules. */
56 }
57 /* prepare AC build config. */
58 cfg.num_categories = 2;
59 cfg.num_fields = RTE_DIM(ipv4_defs);
60 memcpy(cfg.defs, ipv4_defs, sizeof (ipv4_defs));
61 /* build the runtime structures for added rules, with 2 categories. */
62 ret = rte_acl_build(acx, &cfg);
63 if (ret != 0) {
64 /* handle error at build runtime structures for ACL context. */
65 }

對於以下的元組源IP地址:10.1.1.1,目的地址:192.168.1.15,一旦下面語句執行:

1 uint32_t results[4]; /* make classify for 4 categories. */
2 rte_acl_classify(acx, data, results, 1, 4);

那麼返回的結果數組裏會以下:
results[4] = {2, 3, 0, 0};

對於分類0,規則1和規則2都匹配了,可是規則2有更高的優先級,所以result[0]包含了規則2的userdata。
對於分類1,規則1和規則3都匹配了,可是規則3有更高的優先級,所以result[1]包含了規則3的userdata。
對於分類2,3,都沒有匹配,所以result[2],results[3]都是0,表示沒有匹配到這些分類。

對於以下的元組源IP地址:192.168.1.1,目的地址:192.168.2.11,一旦下面語句執行:

1 uint32_t results[4]; /* make classify for 4 categories. */
2 rte_acl_classify(acx, data, results, 1, 4);

那麼返回的結果數組裏會以下:
results[4] = {1, 1, 0, 0};

對於分類0,1,只有規則1匹配;
對於分類2,3,都沒有匹配。

對於以下的元組源IP地址:10.1.1.1,目的地址:202.212.111.12,一旦下面語句執行:

1 uint32_t results[4]; /* make classify for 4 categories. */
2 rte_acl_classify(acx, data, results, 1, 4);

那麼返回的結果數組裏會以下:results[4] = {0, 3, 0, 0};對於分類1,只有規則3匹配;對於分類0,2,3,都沒有匹配。

相關文章
相關標籤/搜索