说到包过滤器,可能大部分人想到的是iptable。但是它实现的初衷,并不是这样。
现在我们对网络协议的理解基本上局限于TCP/IP,HTTP等,协议本身的实现由Linux内核完成,我们直接使用就行。但是,在<<The Packet Filter: An Efficient Mechanism for User-level Network Code>>这篇文章发布的1987年,在用户层实现专用的网络协议还是很流行的,原因主要是内核源码封闭,内核中开发网络协议栈调试困难等。在用户态实现网络协议,这里面就涉及到一些问题:
1. 如何在不同的用户程序间分发数据包
2. 如何保证用户态网络协议的性能
在CSPF出现之前,出现的方案
这种方案显然有很大的性能问题,但是,如果在内核分发,难点就在于由于网络协议的内容是不确定的,无法通过预先定义好比如TCP协议分发给进程A,UDP协议分发给进程B这种确定的行为,由此,出现了CSPF,一种运行在内核中的包过滤器。
内核中实现的包过滤器要解决的问题,便是如何根据不同的协议过滤数据包,数据包的协议是不特定的,要过滤的内容不确定,而且还有像IP协议这种包头带变量的(CSPF无法处理),变量定义了IP头的长度,要想找到TCP头,需要先解析该变量,这些都给过滤器的设计带来挑战。
CSPF最大的特点在于用户可以将过滤规则动态添加到内核执行。本质上,是一种指令翻译技术,通过在内核实现一个虚拟机,收到包后触发虚拟机运行,虚拟机的执行流程就是解释执行用户定义的过滤规则,判断是否接受数据包。由此完成数据包过滤。
有了这个理念后,接下来就是看虚拟机如何设计的了,CSPF的虚拟机是一个stack machine。其指令规则如下:
用户传给内核的过滤器由一条条上面格式的指令构成,每条指令包含两个关键部分:字节操作与栈操作。
栈操作包括:
操作数在哪?用户可以控制传入参数的只有 PUSHLIT指令,后面加个参数,如包类型。而提取数据包的那个字节内容由PUSHWORD+n控制。
字节操作包括:
通过例子可以很好的理解其运行原理:
1. 数据包的第一个字压入栈;数字2压入栈;出栈两个操作数,比较是否相等,结果压入栈;
2. 数据包的第三个字压入栈;0x00FF压入栈;出栈两个操作数,做AND操作,结果压入栈;
3. 数字0压入栈;出栈两个操作数,比较大小,结果压入栈;
4. 截图中有错误,应该是 FF00; 出栈两个操作数,做AND操作,结果压入栈;
5. 数字100压入栈;出栈两个操作数,比较大小,结果压入栈;
6. 出栈两个操作数,做AND操作,结果压入栈;
7. 出栈两个操作数,做AND操作,结果压入栈;
此时结果就是栈中保留的数字是True还是False了。到这应该理解什么叫stack machine了。
CSPF的理念非常好,给包过滤带来了新思路,但是其也有设计缺陷,后面出来的BPF主要是解决这些设计缺陷。
1. 无法处理包头中带有变量的情况
2. 存在冗余处理步骤,例如上面的例子中,如果第一步协议判断失败的话,应该直接退出,不应该继续向下执行了。
周五了,人少的时候适合总结一周所学内容。