libvirt nwfilter 简单使用
nwfilter(network filter)是libvirt中的网络数据包过滤子系统,用于过滤进出虚拟机的网络流量,仅支持qemu kvm虚拟机。本文主要记录官网文档中理解稍有困难的部分,其他更多详细信息参考官网文档 libvirt: Network Filters。
本文操作系统环境为 CentOS Linux release 7.5.1804,libvirt使用版本为libvirt-3.9.0-14。
概念
nwfilter以XML格式书写。一个nwfilter可以包含流量过滤规则,也可以引用其他nwfilter,两者可以同时存在。nwfilter以被虚拟机网卡接口引用的方式生效。其实现依赖ebtables、iptables和ip6tables工具。
以libvirt自带的clean-traffic为例说明nwfilter中的几个概念。
virsh nwfilter-dumpxml clean-traffic
1 | <filter name='clean-traffic' chain='root'> |
- filter
最外层元素filter是固定的,属性有name和chain。name是唯一的,nwfilter间不能冲突。chain是一个链的概念,有几种选择可以枚举,后面具体介绍。- uuid
唯一的,可以由libvirt自动生成,不需要关注。 - filterref
该元素代表了对另一个nwfilter的引用。其中只有一个属性filter,值为引用的另一个nwfilter的name。被引用的nwfilter中rule所属的chain不做变动,与引用者的chain无关。 - rule
真正的过滤规则所在,有匹配规则、动作和优先级三种成分构成。action表示对数据包的动作,direction表示数据包需要匹配的方向,priority表示优先级(后面具体介绍)。- mac
rule中的内部嵌套元素,该元素表示了rule中匹配的数据包协议类型,有几种类型可以枚举。其支持的属性与数据包类型有关,表示数据包需要匹配的规则。
- mac
- uuid
虚拟机配置中interface元素中嵌套插入filterref而生效该nwfilter,虚拟机如果变更引用的nwfilter需要关机重启,已经引用的nwfilter内容变更可以修改nwfilter而不需要重启虚拟机。
1 | <interface type='network'> |
chain
nwfilter中过滤规则(rule)是以链(chain)的方式组织起来的。chain有几种值可以选择:
- root
- mac
- stp
- vlan
- arp、rarp
- ipv4
- ipv6
上面的chain名字中,除root外其他的都可以作为一个名字的前缀(比如ipv4-a和ipv4-b)。rule需要挂载到一个chain上,既可以是root,也可以是其他chain。其他chain是挂载到root上的。所有数据包都先经过root(这里只说会先经过root,但不一定会流经root链中的rule,因为是否流经root上的某一个rule取决于该rule前面是否有更高优先级的其他rule或chain以及它们对数据包做何种裁决),其他chain的名字前缀表示了哪种协议类型的数据包会经过该chain,因此一个rule应该包含在哪个chain中需要考虑流经的数据包是否有意义(比如在arp的chain中过滤ipv4数据包是没有意义的)。
可以看到chain中没有四层的TCP或UDP等协议,这与chain的实现有关。nwfilter中chain的部分对应了ebtables对三层协议的匹配能力,因此chain只包含了三层协议的匹配。这里面的mac是二层协议,其实就是ebtables收到的所有数据包,不匹配三层协议就是它了,它与root所过滤的数据包是一致的,但是它是挂载到root上的。
chain在实现上依赖ebtables的nat表,因为nat表中包含了PREROUTING和POSTROUTING链,这是内核的两个hook点,可以过滤从虚拟机发出和进入虚拟机的流量。
实现
nwfilter中的chain与ebtables中的chain(链)是直接对应的,chain的实现参考生成的ebtables规则会比较好理解,以一个配置例子说明:
1 | <filter name='s-engine-nwfilter' chain='root'> |
1 | <filter name='filter-test1' chain='ipv4-abc' priority='-700'> |
1 | <filter name='filter-test2' chain='mac-def' priority='-600'> |
运行命令ebtables -t nat -L
可以看到ebtables中生成的规则:
1 | Bridge table: nat |
nwfilter解释如下:
- s-engine-nwfilter
配置了名为root的chain,这个chain最先接收所有的数据包,因此不需要优先级。同时引用filter-test1和filter-test2(引用者和被引用者的chain没有任何关系,纯粹是单纯的并列关系)。 - filter-test1
配置了名为ipv4-abc的chain,从名字前缀可以看到这个chain接收三层协议为IPV4的数据包,优先级-700(chain中优先级范围为[-1000, 1000],数字越小越优先)。 - filter-test2
配置名为mac-def的chain,从名字前缀可以看到这个chain接收二层数据包,优先级-600。
对比ebtables中的规则:
- PREROUTING链
将vnet1(虚拟机网卡在host上对应该端口)上进入网桥的数据包(也就是虚拟机发出的数据包)跳转到用户自定义链libvirt-I-vnet1中。- libvirt-I-vnet1链
这个链其实就对应名为root的chain,但它只是该chain的一半,负责虚拟机向外发出的数据包。包含了直接挂载到root上的direction为out的rule,比如这里的将IPV4协议数据包中上层协议为UDP且目的端口为10086的数据包drop掉。还挂载的其他chain(如果这个chain中包含了direction为out的rule)。比如IPV4数据包跳转用户自定义链I-vnet1-ipv4-abc,比如所有数据包跳转用户自定义链I-vnet1-mac-def。- I-vnet1-ipv4-abc链
对应名为ipv4-abc的chain的一半。挂载了一条rule,将IPV4协议数据包中上层协议为UDP且目的端口9999的数据包drop掉。 - I-vnet1-mac-def链
对应名为mac-def的chain的一般。挂载了一条rule,将IPV4协议数据包中上层协议为UDP且目的端口从9996到9999的数据包drop掉。
- I-vnet1-ipv4-abc链
- libvirt-I-vnet1链
- POSTROUTING链
将要发往vnet1的数据包(也就是虚拟机将要接收的数据包)跳转到用户自定义链libvirt-O-vnet1中。- libvirt-O-vnet1链
同libvirt-I-vnet1一样也都对应名为root的chain,区别在于这个链负责发往虚拟机的数据包。由于该方向只有一条rule,而且是直接挂载到root上的,因此该链上只有一条规则,没有其他用户自定义链的跳转。
- libvirt-O-vnet1链
priority
实现一节中说明了chain和其中的rule如何对应到ebtables中,由于chain和rule都有priority属性,因此实际使用时还要注意priority(优先级)的配置。
- rule靠优先级与同一chain上的其他rule做排序。
- 非root的chain靠优先级与其他chain做排序。
- root上的rule和chain是并列关系,靠优先级做混合排序。
比如ebtables中的这一段,由于libvirt-I-vnet1对应了名为root的chain,可以看到对应名为ipv4-abc和mac-def的两个chain和root上该方向上的rule通过优先级做了混合排序。
1 | Bridge chain: libvirt-I-vnet1, entries: 3, policy: ACCEPT |
chain在不配置priority时,libvirt会赋予默认优先级,参考官网文档。
rule
一个rule代表了一个过滤规则,一个rule节点可以包含几个属性以及内部嵌套的协议节点。
1 | <rule action='drop' direction='in' priority='-800'> |
属性
- action
必选项。可以选择以下几种:- drop
直接丢弃数据包。 - reject
丢弃数据包并生成一个ICMP消息。 - accept
接收该数据包并跳过其他所有chain和rule。 - return
跳过当前chain上的其他过滤器,并返回到root上继续检查后续chain或rule。如果当前位于root,则接收该数据包。 - continue
不产生影响,可以用于计数。
- drop
- direction
必选项。可以选择以下几种:- in
进入虚拟机的数据包。 - out
虚拟机发出的数据包。 - inout
双向数据包。
- in
- priority
rule的优先级。 - statematch
默认是true。对于四层协议可以对放行的数据包所对应的连接做状态保持,也就是放行该连接的后续数据包,该功能依赖iptables或ip6tables。这避免ebtables单纯判定一方端口的情况,对于防御攻击效果更好。
协议节点
rule内部嵌套的协议节点代表了该rule需要匹配何种协议。三层协议依赖ebtables,四层协议依赖iptables和ip6tables。每种协议节点有不同的过滤属性。具体信息参考官网文档。
需要说明的是由于nwfilter在三层协议数据包处理上依赖ebtables,四层协议数据包处理上依赖iptables(或ip6tables),而这两种工具在内核中使用的是不同的hook点。也就是说同一个数据包,可能在ebtables中被accept了,但是在iptables中可能做出不同的裁决(比如drop),这里对该情况做明确说明。
如果有一组复杂的nwfilter,某一个rule中配置了ip协议节点的过滤规则并且做了accept裁决,另一个rule中配置了tcp协议节点的过滤规则并且做了drop裁决,当一个数据包同时匹配了这两个rule时,将会首先经过ebtables并accept通过,然后经过iptables并被drop丢弃。三层节点与四层节点过滤器的先后关系与rule的priority完全无关,四层节点之间比较时priority才有意义。
除了明显的四层协议名节点,all这个节点名也是生效到iptables中的。
虽然三层ip协议节点可以通过属性protocol配置过滤简单的四层协议头信息,但如果使用中存在四层协议节点,那么推荐在三层协议节点中对出入的ip数据包都做accept处理,但是优先级靠后(比如libvirt提供的clean-traffic),这样可以在靠前的位置放置各种防伪造策略,然后将所有四层相关的过滤规则都使用四层协议节点配置,这样既避免了逻辑上的混淆,同时也利用了四层协议节点默认的状态保持功能。比如以下配置:
1 | <filter name='s-engine-filter' chain='root'> |
- 引用clean-traffic,确保虚拟机不会发出各种伪造数据包,只让干净合法的流量出入。
- 其余都使用四层协议节点配置,生效到iptables。
- 对来源在172.16.128.0到172.16.255.254范围内的入包做放行,并保持连接状态放行相应的出包。
- 对目的地址172.16.0.1,目的端口1821的UDP数据出包做放行,并保持连接状态。
- 后面两条是对DHCP协议数据包做放行,其中172.16.0.1是DHCP服务器地址。
- 丢弃其他所有的四层数据包(这个prirority优先级最低)。
action
关于action有一点需要特别说明。ebtables的用户自定义链是有policy的(iptables用户自定义链没有policy),而且libvirt使用的是默认policy,也就是accept。这就是说如果数据包被跳转到一个chain,而该chain中的rule并没有对数据包做出裁决,最后将会生效该chain的默认policy,也就是accept,这阻止了数据包继续被其他chain或rule检查。如果不注意这一点有可能导致实际效果与预期不符。
以实现一节中的例子来说,由于名为ipv4-abc的chain比名为mac-def的chain优先级配置数字更小,因此IPV4数据包会先被跳转到ipv4-abc链中,如果虚拟机发出一个目的端口是9996的UDP数据包,该链并没有drop该数据包,最后该链的policy会accept该数据包,而mac-def中的rule将不会如预期drop该数据包。
使用
nwfilter的使用比较简单,分为命令行和API两种方式。
命令行
命令virsh help filter
可以查看nwfilter相关的命令。
1 | Network Filter (help keyword 'filter'): |
API
API的使用直接参考官网文档就可以了。
API列表 - Module libvirt-nwfilter from libvirt
更多API - Reference Manual for libvirt
XML Format
PS:使用API对已经存在的nwfilter做修改时,需要先拿到旧的uuid,在XML文本中设置该uuid,再调用virNWFilterDefineXML
函数。