DPDK ACL算法介绍

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DPDK ACL算法介绍相关的知识,希望对你有一定的参考价值。

参考技术A DPDK提供了三种classify算法:最长匹配LPM、精确匹配(Exact Match)和通配符匹配(ACL)。

其中的ACL算法,本质是步长为8的Multi-Bit Trie,即每次可匹配一个字节。一般来说步长为n时,Trie中每个节点的出边为2^n,但DPDK在生成run-time structures时,采用DFA/QRANGE/SINGLE这几种不同的方式进行数据结构的压缩,有效去除了冗余的出边。本文将为大家介绍ACL算法的基本原理,主要内容包括:trie树的构造、运行时的node array生成和匹配原理。对于ACL接口的使用,参考DPDK的官方文档即可。

ACL规则主要面向的是IP流量中的五元组信息,即IP/PORT/PROTO,算法在这个基础上进行了抽象,提供了三种类型的匹配区域:

熟悉这三种类型的使用后,完全可以用它们去匹配网络报文的其它区域,甚至将其应用到其它场景中。

具体来说,rte_acl_field_def有5个成员:type、size、field_index、input_index、offset。
如果要深入理解算法,可以思考这几个字段的意义,或者换个角度来看:

对于规则的定义,要注意如下两点:

比如定义了5个field,那么请给出每一个的具体定义:

像field[1]中IP和mask都为0,表示匹配所有的IP地址;field[3]中range由0到65535,表示匹配所有。类似这样的全匹配一定要显示的定义出来,因为如果不明确定义,这些字段的值取决于编译器的,最后编译的ACL规则很可能与原有设想存在偏差。

如果在规则中,对于某个field不进行限制,对于不同type的field,规则书写时有一定差异:
对于BITMASK和MASK类型,全0代表匹配所有,如上例中的field[0]、field[1];
对于RANGE,则按照上述field[3]中的形式定义。

规则定义好后,会转换为trie树并最终合并到一起。
实际处理过程中,build_trie函数会自底向上的将rule中的每个field转换为node,然后将这些node合并生成这条rule的trie,最后将这个trie与已有的trie进行merge,最终生成整个rule set的trie。

tire由node组成,其主要数据成员如下:

node中values成员用于记录匹配信息,ptrs则用于描述node的出边,用于指向转换后的node。

values采用bitmap进行压缩,其数据结构为struct rte_acl_bitset values; 一个byte取值范围是[0,255],可通过256个bit位来进行对应,并实现byte值的快速查找:即通过第x位的bit值是否为1来判断是否包含数值x(0 <= x < 256)。

num_ptrs用于描述出边数目,ptrs即为实际的出边,它记录了其匹配值values和匹配后的节点指针。
match_flag和mrt则用于记录匹配结果,trie树中叶子节点一定是记录匹配结果的节点。

trie树其详细结构比较复杂,这里将其结构进行简化,如下所示:

上图的trie树有4个node,通过ptrs进行指向,values字段为匹配值的bitmap表示,为了表述的简洁,后续会采用simple的方式进行描述。
在trie simple中,实心节点表示匹配节点,边上的数字代表匹配值(为便于阅读,采用实际值而不再是bitmap形式),…代表其它匹配值。

不同type的field,转换为node的方式会有所不同。
目前提供的3种类型:BITMASK描述一个byte的匹配,支持mask模式;MASK用于描述4个byte的匹配,支持mask模式;RANGE描述2个byte的匹配,此时mask表示上限。
field到node的转换,见build_trie中的for循环,具体转换函数则参考:

对于BITMASK,如.value.u8 = 6, .mask_range.u8 = 0xff,,它最后的转换形式如下:

构造field的node时,总会在结尾添加一个空的end节点,最后一个field除外(它是match node)。在for循环中每完成了一个field的解析后,会将其合并到root中,从而生成这个rule的trie。
合并前,也会先构造一个空的end node(见build_trie函数中,while循环下的root创建),让它与field构成的node头合并,因为不相交,所以merge时会将匹配信息合并到end node并释放原有的头,并将field链的end节点返回(保存到end_prev中),下次合并时,就用此end节点与新的node头合并。
循环遍历完所有的field后,这些node就串联起来了,构成这个rule的trie。

对于多个rule,每次构造完成后会merge到整体的trie中。
这里详细介绍下merge算法原理,其实仔细阅读acl_merge_trie函数的注释即可。

对于node A和node B的merge, acl_merge_trie函数返回一个节点,这个节点指向它们路径的交集。
这里给出三个例子用于展示merge前后的变化。为了减少状态点,构造rte_acl_field_def如下:

示例1:

acl_rules[1]为trie A,acl_rules[0]对应trie B,最终trie B合并到trie A上,具体如下:

1和1’合并时,因为level为0,所以1’直接合并到1中;
4和4’合并时,因为节点无交集,所以创建新节点c1(node 4的拷贝),并将4'上的边拷贝到c1中。

示例2,rule类别相同,但优先级不同:

acl_rules[1]为trie A,acl_rules[0]对应trie B,最终trie B合并到trie A上,具体如下:

6和6’是match node,类别相同,且6的优先级为2大于6’的优先级。
6和6’合并时,直接返回6。而前面创建的新节点,如d1,已包含5’的所有边(非ACL_INTERSECT_B),所以最终返回5,free d1。
同理依次往上回溯,a4,b3,c2,也依次被释放,最终merge的trie即为原来的trie A。

示例3,rule类别不同,优先级相同:

acl_rules[1]为trie A,acl_rules[0]对应trie B,最终trie B合并到trie A上,具体如下:

6和6’是match node,因为类别不同,所以最终创建了新node e1,这也导致示例3和示例2最终merge结果的不同。

合并是一个递归的过程,逆向思考构造过程会有助于理解算法。另外,在build_trie之前会sort_rule,匹配范围更大的rule会放到前面优先构造trie,个人为这样node A包含node B的概率更大,这可能也是merge时创建的node C是A的拷贝而不是B的拷贝的原因,因为这样出现ACL_INTERSECT_B的概率相对较低。

一些说明:

trie树构造完成后,会将其由指针跳转的形式转换为等效的数组索引形式,即node array,既可让匹配数据更紧凑,也可提高匹配算法的效率。
采用node array的方式进行状态点的压缩是很常见的优化方式,比如snort里面的ac算法(acsmx.c):

笔者也曾经做过类似的优化,通过将出边由指针方式修改为索引方式,整个匹配tree的内存占用只需要原来的1/5。
将指针方式转换为node array形式是优化的第一步,对于Next[256]出边又可以采用多种压缩方式,比如snort中新的ac算法(acsmx2.c),就采用了Sparse rows和Banded rows等多种压缩方式,但其原理是将出边进行映射转换,本质上还是做DFA状态跳转。

DPDK对边的压缩方式与上述类似,不过它优化的粒度更细,不同type的node有不同的压缩方式:

比如在示例三中,node 1为DFA节点(根节点强制使用DFA方式),2、3、a5、b4、c3、d2为QRANGE,4、5为SINGLE,6、e1为MATCH。
2、3、a5、b4虽然在图上仅有一条有效边,但它不为SINGLE,因为对于无效的匹配其实也会有出边,所以它的真实出边数目并不唯一,只有像4、5这类全匹配节点才是真正的SINGLE节点。

在构造node array前,会调用acl_calc_counts_indices函数更新node的node type,fanout等信息。
node type依据其fanout值决定,fanout计算见acl_count_fanout函数,其原理是:

比如对于示例3中的d2节点:

fanout计算完成后,若其值为1则为SINGLE节点,(1, 5]为QRANGE节点,(5, 256]为DFA节点。
注意:对于trie树的root节点,不论fanout值为多少,会强制将其构造为DFA节点,且其fanout值会重新计算。

type和fanout计算完成后,会统计各类节点数目,信息保存在acl_calc_counts_indices传入的counts参数中,随后rte_acl_gen依据这些信息将整块的node array内存分配出来,其布局大致如下:

Data indexes中用于保存在rte_acl_field_def中定义的offset;
Results对应match node,用于保存匹配结果。
Trans table包含整个匹配过程中的跳转点:

静态将整块node array分配完成后,就需要依据trie 树的node信息填充Trans table和Results了,具体过程见acl_gen_node函数;Data indexes的填充则在acl_set_data_indexes中完成。

2.2中的内存布局大致描绘了各种类型节点的分布情况,DFAs内部由一个一个的DFA节点组成,QUADs和SINGLEs也一样,都是由相同类型的节点构成。
对于每一个节点,其结构则类似如下形式:

DFA节点的fanout一般为4,出边数为fanout*RTE_ACL_DFA_GR64_SIZE;(图中画的为fanout为4的情况,256条出边)
QUAD节点的fanout不超过5,即为节点的出边数不超过5;(图中画的为fanout为4的情况)
SINGLE节点只有一个出边;
图中的trans即为这个节点的出边,它本质是一个uint64的数据结构,通过trans和input信息即可计算得到下一个节点的index,从而实现匹配跳转。trans中不同bit位包含着丰富的信息,具体见acl.h中的说明即可。

高32位对于不同类型的节点有不同的解释:

低32位:

在实际处理过程中,通过高32位与input_byte计算得到index,与低32位中的addr,即可快速定位到下一个trans:trans_table + (addr+index)。
这里的处理其实与传统的DFA跳转差别很大,传统处理时,next = node[‘input’],跳转到下一个节点,然后采用next[‘input’]进行跳转和匹配,即使有数据结构的压缩,跳转目标仍是状态点。但DPDK中,跳转时直接采用trans_table + (addr+index),直接找到了状态点的边(trans),而不是到状态点。

跳转表具体构建时,采用acl_gen_node函数完成:

匹配的过程与跳转表的构建其实是互为一体的,如何构建跳转表就决定了如何进行匹配。

在2.3节,对于跳转的形式已进行了说明,具体可阅读rte_acl_classify_scalar函数:跳转时直接采用trans_table + (addr+index),直接找到了状态点的边(trans),而不是到状态点。

对于具体的匹配过程,还有一点需要注意,即GET_NEXT_4BYTES的使用,每次匹配时候都会去4BTYTES进行匹配,这也是为什么定义input fields时要求4字节连续。比如我在dpdk-dev邮件组中问的这个 问题 。

解决4字节连续,可以通过定义相同的input_index来解决,比如像邮件中提到的设置sport/dport的input_index相同,这是因为data indexes的构造取决于input_index,见acl_build_index函数;同时field_index不同、input_index相同时可避免对field区间的优化(如果优化,将某个field去掉了,这时4字节匹配会失效)。邮件中的问题,正是因为field[3]被优化掉后,4字节连续匹配出现问题。

在特定的场合还必须通过指定.size为32来解决,即使type类型为BITMASK,见DPDK的ACL文档中关于 tos示例的说明 。

另外再说下field_index,前面提出一个问题:field_index是否多余?
答案是不多余,因为算法中会对field进行优化,如果不指定field_index字段,这个优化就无法进行了,具体的优化处理见acl_rule_stats函数。
优化过程中要进行input_index的判断,这是因为相同的input_index可以有多个field,但其中只有某个field是completely wild时应避免进行优化。只有相同input_index的所有field都是completely wild时,才应该将这个field优化掉。

上面的一系列说明,都是针对GET_NEXT_4BYTES每次匹配四个字节的匹配进行的补充说明。

匹配的具体过程,这里用图形的方式进行简要说明,为了能有多种类型的node,这里构造规则如下:

trie树如下所述:

对应的node array如下图所示:

假设输入数据为:proto 16, ip 192.12.8.8,则transition跳转方式如上图红线所示:

注意:node array中indexes、DFA0和idle省略了。

关于trie树相关的理论知识参考 这里 。

本文主要介绍了DPDK的ACL算法,详细描述了如何由规则生成trie,并将trie转换为node array的过程,在文末通过示例介绍了具体的匹配过程。文章旨在介绍ACL算法的基本思路,希望对大家能有所帮助。

HAProxy 之 ACL介绍和使用

1  概述

访问控制列表(ACL)的使用为HAProxy提供了一个灵活的解决方案来执行内容交换,并且通常基于从请求中提取的内容、响应或任何环境状态进行决策,HAProxy基于ACL实现了灵活的调度

本文介绍ACL语句中各个参数含义,定义ACL,使用ACL,以及结合例子来介绍ACL的使用

 2  ACL作为条件时的逻辑关系

-与:隐式(默认)使用,默认为与的关系

-或:使用“or” “||”表示

-否定:使用“!“ 表示

示例:

有两个条件为invalid_src invalid_port

if  invalid_src invalid_port #与关系
if invalid_src|| invalid_port #或关系
if  ! invalid_src #非关系

3 ACL 格式定义

3.1  ACL格式介绍

格式如下:

acl  <aclname>   <criterion>  [flags] [operator] [<value>] ...

参数介绍

<aclname>ACL名称,自定义,可使用字母,数字,: . -_ 等符号,同时区分字符大小写

<criterion>:比较的标准和条件,下一小节介绍具体用法。

<value>的类型:

-boolean

-integeror integer range

-IPaddress / network

-string(exact, substring, suffix, prefix, subdir, domain)

-regularexpression:正则表达式

-hexblock

<flags>

-i不区分大小写

-m使用指定的pattern匹配方法

-n不做DNS解析

-u强制每个ACL必须唯一ID,否则多个同名ACL或关系

-- 强制flag结束. 当字符串和某个flag相似时使用

[operator]

匹配整数值:eqgegtlelt

匹配字符串:

-exactmatch (-m str) :字符串必须完全匹配模式

-substringmatch (-m sub) :在提取的字符串中查找模式,如果其中任何一个被发现,ACL将匹配

-prefixmatch (-m beg) :其中,begbegin的缩写,在提取的字符串首部中查找模式,如果其中任何一个被发现,ACL将匹配

-suffixmatch (-m end) :将模式与提取字符串的尾部进行比较,如果其中任何一个匹配,则ACL进行匹配

-subdirmatch (-m dir) :查看提取出来的用斜线分隔(“/”)的字符串,如果其中任何一个匹配,则ACL进行匹配

-domainmatch (-m dom) :查找提取的用点(“.”)分隔字符串,如果其中任何一个匹配,则ACL进行匹配

3.2  参数criterion介绍

criterion是比较的标准和条件,比较条件和标准很多:dstdst_portsrc,src_port, base : string, path : string, url: string,req.hdr([<name>[,<occ>]]) : stringstatus: integer 

这里将一一介绍相关用法

dst目标IP

dst_port目标PORT

srcIP

src_portPORT

示例:

定义一个acl,名称为invalid_src,指定源ip是172.18.50.61

acl  invalid_src src  172.18.50.61

.base: string

返回第一个主机头和请求的路径部分的连接,该请求从第一个斜杠开始,并在问号之前结束,对虚拟主机有用

完整的url中:<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>base指的是<host>:<port>/<path>;<params>

base: exact string match
base_beg: prefix match
base_dir: subdir match
base_dom: domain match
base_end: suffix match
base_len: length match
base_reg: regex match
base_sub: substring match

.path: string

提取请求的URL路径,该路径从第一个斜杠开始,并在问号之前结束(无主机部分),默认会被调度到同一后端主机上。

完整的url中:<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>path/<path>;<params>

path : exact string match
path_beg : prefix match
path_dir : subdir match
path_dom : domain match
path_end : suffix match
path_len : length match
path_reg : regex match
path_sub : substring match

 例子

表示当来自ip 172.18.50.61访问http:/ip/adminurl就会被拒绝,61访问其他的资源不会被拒绝

acl deny_src src 172.18.50.61
acl sunnypath path_beg /sunny
block if deny_srcsunnypath

.url:string

提取请求中的URL。一个典型的应用是具有预取能力的缓存,以及需要从数据库聚合多个信息并将它们保存在缓存中的网页门户入口

url: exact string match
url_beg: prefix match
url_dir: subdir match
url_dom: domain match
url_end: suffix match
url_len: length match
url_reg: regex match
url_sub: substring match

.req.hdr([<name>[,<occ>]]): string

提取在一个HTTP请求报文的首部

hdr([<name>[,<occ>]]) :exact string match
hdr_beg([<name>[,<occ>]]): prefix match
hdr_dir([<name>[,<occ>]]): subdir match
hdr_dom([<name>[,<occ>]]): domain match
hdr_end([<name>[,<occ>]]): suffix match
hdr_len([<name>[,<occ>]]): length match
hdr_reg([<name>[,<occ>]]): regex match
hdr_sub([<name>[,<occ>]]): substring match

示例一:

禁止使用curl命令

acl not_curl hdr_sub(User-Agent) -i curl
block if not_curl

示例二:

实现域名的调度,根据首部实现,将地址为www.sunny.com 满足acl 为imagehost的请求调度到image这组backend的服务器请求,其他的请求调度到默认的组为websrv处理请求。这个使用要保证测试主机能够解析haproxy服务器为www.sunny.com

acl imagehost hdr(host) www.sunny.com
use_backend image if imagehost
default_backendwebsrv

.status: integer

返回在响应报文中的状态码,根据返回的状态码进行匹配

3.3  预定义ACL

系统预定义的ACL,可以直接使用

以下将介绍预定义ACL的等价配置和用法说明

格式为 

 ACL名称:ACL等价配置;ACL用法说明

TRUE:always_true;总是匹配
FALSE:always_false;从不匹配
HTTP:req_proto_http;匹配HTTP协议
HTTP_1.0:req_ver 1.0;匹配HTTP协议1.0
HTTP_1.1:req_ver 1.1;匹配HTTP协议1.1
HTTP_CONTENT:hdr_val(content-length)gt 0;匹配已存在内容长度
HTTP_URL_ABS:url_reg ^[^/:]*://;匹配URL绝对路径
HTTP_URL_SLASH:url_beg /;匹配URL相对路径
HTTP_URL_STAR:url *;匹配URL 等于"*"
LOCALHOST:src 127.0.0.1/8;匹配从localhost来的连接
METH_CONNECT:method CONNECT;匹配HTTP CONNECT方法
METH_GET:method GET HEAD;匹配HTTP GET 或者 HEAD 方法
METH_HEAD:method HEAD;匹配 HTTP HEAD 方法
METH_OPTIONS:method OPTIONS;匹配 HTTP OPTIONS方法
METH_POST:method POST;匹配 HTTP POST 方法
METH_TRACE:method TRACE;匹配 HTTP TRACE方法
RDP_COOKIE:req_rdp_cookie_cntgt 0;匹配RDPcookie的存在
REQ_CONTENT:req_len gt 0;匹配请求缓冲区中的数据
WAIT_END:wait_end;等待内容分析的结束

4  ACL使用

 use_backend配置

.use_backend   <backend>[{if | unless} <condition>]

if/unless一个基于ACL的条件匹配时切换指定backend

例子

以下例子实现动静分离,当访问php文件的时候,就往dynhost这组backend调度,其他资源默认都发到websrv这组backend

acl dynhost path_end  .php
acl imagehost hdr(host) www.sunny.com
use_backend image if dynhost
default_backend  websrv

block配置

 阻止7层请求if/unless一个条件匹配

.block { if | unless }<condition>

.示例:

acl invalid_src  src172.16.200.2
block if invalid_src

http-request配置

 7层请求的访问控制,主要指http-request

.http-request   {allow | deny |add-header <name> <fmt> |set-header <name><fmt> } [ { if | unless }  <condition>]

 根据第4层条件对传入连接执行操作

.tcp-request connection  {accept|reject}  [{if | unless} <condition>]

注意协议和模式的匹配,默认为HTTP协议,haproxy一般用来调度http,如果非http协议就用mode来单独定义,如mode  tcp,不过生产中,mysql一般用专业调度数据库的工具来调度

5  例子

例子一:实现ssh的调度

先在ssh的配置文件更改ssh监听的ipip为非22端口,然后haproxy配置如下

listen ssh
bind 172.18.50.63:22
balance leastconn
acl invalid_src  src  172.18.50.
tcp-request connection reject if  invalid_src
mode tcp
server sshsrv1 172.18.50.65:22 check
server sshsrv2 172.18.50.75:22 check  backup

测试 

在172.18.50.61上测试,每次测试完成后,都要把172.18.50.63下/root/.ssh/known_hosts下的172.18.50.63的记录清理掉然后在重新ssh连接,否则调度中,有一台会因为mac不一致导致安全问题,调度成功了,但是连接不上。

ssh  172.18.50.63

例子二:实现mysql调度

实现只有主机172.18.50.61能够通过172.18.50.73这台机器调度到后端的mysql,其他的机器都不能通过172.18.50.73调度连接

listenmysql
    mode tcp 
    bind 172.18.50.73:3306
    balance roundrobin
    acl valid_src src 172.18.50.61
    tcp-request connection reject unless valid_src
    server mysqlsrv1 172.18.50.65:3306 check
       server mysqlsrv2172.18.50.75:3306 check

测试

登陆数据库

mysql -uwpadmin -pPass123456 -h 172.18.50.73

登陆数据库后,如果用system可以调用本机的linux命令,但是查看的是本机的相关信息,如system  hostname 查看的是本机的计算机名,通过查看变量名在mysql里查看远程的连接计算机名

show variables like ‘hostname‘;

例子三:基于ACL 实现wordpress动静分离

注意,因为应用wordpress在调用php脚本时,需要用到其他资源,所以default_backend要设置在处理wordpress  php文件的服务器组中,如以下的dynamicblog专门用来处理php文件,所以将default_backend设置为dynamicblog这一组服务器。

frontend  http
    bind *:80
    acl url_dyn path_end -i.php
    acl url_stac path_end-i  .jpg .gif .png .css .js .html .txt
    default_backenddynamicblog if url_dyn
    use_backend    staticblog if url_stac
    reqadd sunny-x-via:\ haproxy7c
    rspdel Server
    rspadd  Server:\ Sunny-proxy7c
    option forwardfor    header sunny-x-client
backend staticblog
    balance     roundrobin
    cookie WEBSRV insertnocache
    server      web6e 172.18.50.65:80 check weight 1   inter 3000 rise 2 fall 2 cookie cksrv1
backend dynamicblog
    balance     roundrobin
    cookie WEBSRV insertnocache
    server     web6e 172.18.50.75:80 check weight 1  inter 3000 rise 2 fall 2 cookie cksrv2
listen stats
bind :9091
stats enable
stats auth  admin:admin
stats admin  if TRUE



本文出自 “阳光运维” 博客,请务必保留此出处http://ghbsunny.blog.51cto.com/7759574/1978996

以上是关于DPDK ACL算法介绍的主要内容,如果未能解决你的问题,请参考以下文章

DPDK- program_guide 2

DPDK盒子使用手册——DPDK入门zz

DPDKring从DPDK的ring来看x86无锁队列的实现

情感分析与推荐算法专场(视频+实录+PPT)| AIS预讲会全程干货分享

ACL学习

ACL学习