基于 Trie 的防火墙策略搜索方法

作者: songtianyi

前言

在网络工程师对防火墙策略作运维变更时,通常需要知道防火墙是否存在一条或者多条策略满足了当前需求,或者当前需求冲突;需要知道某个端口是否已经被开放等等,因此基于用户输入的五元组信息搜索出关联策略是一个高频需求。暴力的方法是遍历已有策略,根据输入条件进行过滤得到结果,但在金融企业里,当结果可能存在于任意一台防火墙上时,策略总数可能多达十几万条,而且策略之间的对比比较复杂,O(N)的复杂度难以应对高频次的搜索需求。一个简单的优化是对输入和输出作映射并缓存,但并不解决根本问题。

Trie

Trie(读作/ˈtriː/)又称前缀树或字典树,是一种有序树,一个节点的所有子孙都有相同的前缀。它的用途很广泛,比如用户搜索提示,当输入一个网址(公共前缀)时,可以从 Trie 中找到所有可能的选择,也可以用于词频统计,字符串查询等方面。

Trie 的构造

对于字符串数组 S, 其元素为:

["A", "to", "tea", "ted", "ten", "i", "in","inn"]

先创建一个空的根节点,枚举 S,对于元素 Si, 从根节点开始,找到和其第一个字符匹配的节点,如果没有则创建,再以匹配到的节点为始,找到和其第二个字符匹配的节点,如果没有则创建,依次类推,构建出整棵树。最终得到 Trie 结构:

image

防火墙策略搜索

防火墙策略的搜索是输入的五元组+动作和已有策略的比较过程,如果我们将已有策略划分成不相关的集合(即,有集合 A 和 B,如果策略 P 只存在于 A 或者只存在于 B,那么认为 A 和 B 是不相关的),并且集合內的策略是有序的,那么比较次数会大大减少。Trie 的树形结构能够表达集合之间的不相交特性,所以适合用 Trie 来表达已有策略,最终搜索已有策略的过程变成搜索 Trie 的过程。

防火墙策略预处理

防火墙策略的结构本质是五元组+动作的六元组,即(SrcAddr, DstAddr, Protocol, SrcPort, DstPort, Action)。

经过预处理的策略表示为:

R = (a, p, [sal, sar], [dal, dar], [spl, spr], [dpl, dpr])
构造策略树

现有经过预处理的已有策略数组 S, S = [R1, R2, …, Rn], 其 Json 格式为:

{ "S":[ { "sal":167876354, "sar":167876354, "dal":3232244222, "dar":3232244222, "p":6, "spl":0, "spr":65535, "dpl":88, "dpr":88, "a":1 }, { "sal":3232243969, "sar":3232243969, "dal":3232235777, "dar":3232235777, "p":6, "spl":0, "spr":65535, "dpl":80, "dpr":80, "a":1 }, { "sal":3232243969, "sar":3232243969, "dal":3232235809, "dar":3232235809, "p":6, "spl":0, "spr":65535, "dpl":80, "dpr":80, "a":1 }, { "sal":3232244001, "sar":3232244001, "dal":3232235789, "dar":3232235789, "p":6, "spl":0, "spr":65535, "dpl":80, "dpr":80, "a":1 }, { "sal":167903844, "sar":167903844, "dal":167838052, "dar":167838052, "p":17, "spl":0, "spr":65535, "dpl":2667, "dpr":65535, "a":0 }, { "sal":3232235886, "sar":3232235887, "dal":3232235976, "dar":3232235976, "p":-1, "spl":0, "spr":65535, "dpl":0, "dpr":65535, "a":1 }, { "sal":-1, "sar":-1, "dal":-1, "dar":-1, "p":-1, "spl":0, "spr":65535, "dpl":0, "dpr":65535, "a":1 } ] }

我们可以把一个六元组看成 trie 树例子中的字符串,依次处理六元组的每个元素。比如对于协议,我们的构造的结果如图所示:

image

以此类推,构造出整棵树, 由于图片大小限制,只给出部分构造,并且省略了保存策略的叶子结点:

image

这里以策略(3232243969, 3232243969, 3232235809, 3232235809, 6, 0, 65535, 80, 80, 1)为例, 从策略树中找出包含该策略的策略,流程如下:

image

从图中可以看出, 0 所在的分枝被立即排除在结果集之外,达到了优化比较次数的目的,但效果有限。

不相交特性

看了上图你会发现,我们在搜索的时候需要遍历某个节点的所有子节点,比如第二步和第五步,这是因为我们在构造策略树没有保证它们的不相交特性,结果存在于 6 分枝的叶子结点中,也可能存在于 -1 分枝的叶子结点中,因为 -1 是包含 6 的。同样的,五元组的每个元素都存在这个问题,所以只能称之为部分不相交。

参考资料

  1. 小白详解 Trie 树