1. 简介

透明防火墙(Transparent Firewall)又称桥接模式防火墙(Bridge Firewall)。简单来说,就是在网桥设备上加入防火墙功能。透明防火墙具有部署能力强、隐蔽性好、安全性高的优点。

通俗来说,桥接模式防火墙 是 Linux 内核的一种巧妙的“欺骗术”和“中介服务”,它让本应只做简单MAC转发的网桥流量,能在关键的转折点上停下来,把里面的IP包裹拿给更聪明(能识别IP/端口)的Netfilter防火墙去检查一下,检查通过再放行继续转发。这样,你就拥有了一个既能识别设备物理位置(MAC/二层)又能识别网络内容(IP/端口/四层)的强大防火墙网关!

典型应用: Docker/Kubernetes 网络(默认依赖此机制实现容器间隔离、端口映射和NAT网关)、透明防火墙网关、虚拟机网络隔离。

核心目标:让网桥也能看懂(并过滤)IP层的东西!

想象一下你有一个网桥(比如一个 Docker 主机上的虚拟交换机 br0),连接着物理网卡 eth0 和几个容器(veth1, veth2)。在普通的网桥模式下:
二层操作(MAC地址层): 网桥就像个“快递仓库管理员”,只关心包裹(数据帧)上的“收件人地址”(目的MAC地址)。它会查看 MAC 地址,决定把这个包裹转发给哪个连接在网桥上的设备(端口),或者广播给所有设备。它不会拆开包裹看里面的“具体内容”(IP 报头、TCP/UDP 端口等)。

三层防火墙(iptables/nftables): 传统的 iptables/nftables 规则,是基于 IP 地址、TCP/UDP 端口等三层及以上信息工作的。它们更像是“海关检查员”,要拆开包裹,检查里面的文件内容(IP/TCP 头)。但普通防火墙规则主要作用在设备的路由路径上,也就是说,数据包要经过设备的三层网络栈时才会被检查。

问题来了:
如果容器 veth1 想访问外网互联网,它的流量路径是:veth1 -> br0 -> eth0 -> 物理网络。
这条路径中,数据包只是在 br0 这个网桥(二层)被转发,并没有真正进入 Linux 主机自己的 IP 网络栈(三层)。因此,传统的 iptables/nftables FORWARD 链(用于过滤转发的数据包)对这些经过网桥的流量完全不起作用!你无法根据 IP 或端口来允许或阻止容器之间的通信或者容器访问外网。

解决方案:br_netfilter

br_netfilter 就像一个“神通广大的中介”,它能让那些只在网桥(二层)上流动的数据包,临时拐个弯,去给 Linux 内核中的 Netfilter 防火墙框架(iptables/nftables 的底层)瞅一眼,让防火墙有机会根据 IP 地址和端口号(三层/四层)来决定是放行还是丢弃它们。本质上,它让网桥流量也变得对三层防火墙“可见”。

2. 工作原理

识别目标流量: br_netfilter 被加载后,会告诉内核的 Netfilter 框架:“嘿,别只盯着走路由路径的流量(进主机、出主机、主机自身转发),那些在网桥设备上传输,目的不是本机(要转发的)的IPv4/IPv6流量(二层帧里的内容是 IP 包),我也可以想办法让它们经过你!”

数据包路径“拐弯”: 当数据帧到达网桥设备准备转发时:

br_netfilter 相关的钩子(hook)会在网桥转发决策之前和网桥转发决策之后插入。
触发 Netfilter: 在这些关键钩子点上(主要是 NF_BR_PRE_ROUTING 和 NF_BR_POST_ROUTING),br_netfilter 将当前正在处理的二层数据帧临时“升级”。它取出帧内部封装的 IP 数据包。

伪装成路由器: br_netfilter 把这个 IP 数据包伪装成它是刚从一个网络接口(伪装的“源接口”)进来,即将要从另一个网络接口(伪装的“目的接口”)出去的样子。实际上,这两个“接口”对应的就是网桥连接的那些端口(比如 veth1 和 eth0 对于 br0)。

送入防火墙框架: 现在,这个伪装的 IP 数据包被送入 Netfilter 的 FORWARD 链。这个链本来是用来过滤路由转发的数据包的,现在也可以用来过滤网桥转发的数据包了!br_netfilter 还确保了相关的 conntrack 连接跟踪也能正常工作。

应用规则 & 返回: iptables/nftables 规则像往常一样检查这个“伪装”的数据包(检查 IP、端口、连接状态等),并做出判决(ACCEPT/DROP/REJECT 等)。判决结果返回给 br_netfilter。

按判决执行:

ACCEPT: br_netfilter 告诉网桥:“继续你的转发操作吧,就像我从没拦过它一样。”数据包继续按 MAC 地址转发。
DROP/REJECT: br_netfilter 告诉网桥:“别转发这个包了,把它扔掉!”数据包在此终结。

恢复状态: 无论判决如何,br_netfilter 都需要清理它为了调用 Netfilter 而创建的临时伪装状态。

3. 实现架构

核心模块 (br_netfilter.ko):

这是实现透明桥接防火墙功能的核心内核模块。加载它才能启用相关功能。

注册 Netfilter 钩子(NF_BR_PRE_ROUTING, NF_BR_LOCAL_IN, NF_BR_FORWARD, NF_BR_LOCAL_OUT, NF_BR_POST_ROUTING),主要关注 PRE_ROUTING (入桥后,转发决策前) 和 POST_ROUTING (转发决策后,出桥前)。

在钩子函数中包含处理逻辑:提取 IP 数据包,伪装接口信息,送入 Netfilter 框架(iptables/nftables 的底层),处理判决结果。

Netfilter 框架 (netfilter):

Linux 内核内置的、通用的防火墙框架。

提供规则表(filter, nat, mangle, raw)和钩子点的抽象。br_netfilter 将网桥流量引导到 Netfilter 的 FORWARD 链(主要)以及其他相关链(如 PREROUTING, POSTROUTING 用于 DNAT/SNAT)。

执行用户空间配置的规则(通过 iptables/nftables 命令)。
连接跟踪 (nf_conntrack, nf_conntrack_bridge):

跟踪网络连接状态(如 TCP 的握手、ESTABLISHED 状态,UDP/ICMP 的关联)。

br_netfilter 的透明特性依赖于连接跟踪才能正确工作。它需要 nf_conntrack 来识别属于同一连接的数据包,尤其在 FORWARD 链做有状态过滤或 NAT 时。

透明桥接的 DNAT/SNAT 也必须依赖连接跟踪修改后续包的反向路径。

Procfs/Sysctl 控制开关 (/proc/sys/net/bridge/bridge-nf-*):

/proc/sys/net/bridge/bridge-nf-call-iptables (默认通常为1):最关键开关!设置为 1 表示让网桥流量进入 Netfilter FORWARD 等链(iptables)。设置为 0 则禁用透明防火墙过滤(流量纯二层转发)。

其他开关:

bridge-nf-call-arptables: 是否处理 ARP 帧 (较少用)。

bridge-nf-call-ip6tables: 对 IPv6 流量启用。

bridge-nf-filter-vlan-tagged: 是否处理 VLAN 帧里的 IP 包。

bridge-nf-pass-vlan-input-dev: 复杂 VLAN 场景相关。

协议适配层 (内核内部):

负责将网桥收到的原始二层以太网帧“还原”为 IP 数据包,并构建必要的上下文信息(伪装的输入/输出接口),以便 Netfilter 的通用接口处理它。

处理判决后数据包如何回到二层转发路径(或丢弃)。

4. 网络协议细节

数据包路径对比:

普通路由转发路径(涉及防火墙):

物理接口接收 ->
[NF_INET_PRE_ROUTING] -> Conntrack 建立/查找 -> DNAT? ->
[路由决策] -> 目标接口是另一个接口? ->
[NF_INET_FORWARD] -> 过滤规则!->
[NF_INET_POST_ROUTING] -> SNAT? -> Conntrack 确认 ->
物理接口发送

桥接转发路径(无 br_netfilter):

物理接口或 veth 接收 ->
网桥端口接收 -> 学习源 MAC -> 查找目的端口(MAC 表) ->
网桥端口发送(纯 MAC 层操作)

桥接转发路径(启用 br_netfilter & bridge-nf-call-iptables=1):

物理接口或 veth 接收 -> (进入桥端口)
** [NF_BR_PRE_ROUTING] ** (br_netfilter hook) {
    如果是 IPv4/IPv6 且目的 MAC 非本机?
    提取内部 IP 包 (skb->network_header)
    伪装输入接口 (skb->dev = 接收该帧的物理/veth 接口)
    调用 NF_INET_PRE_ROUTING 链 (DNAT! Conntrack!)
}
-> 网桥学习源 MAC -> 查找目的端口(MAC 表)->
(决定转发了) -> 
** [NF_BR_FORWARD] ** (br_netfilter hook) {
    提取 IP 包
    伪装输入接口(接收帧的端口)
    伪装输出接口(即将转发帧的端口)
    调用 NF_INET_FORWARD 链(核心过滤发生在这里!)
    根据 NF_FORWARD 链判决(ACCEPT/DROP)决定是否继续
} -> [NF_BR_POST_ROUTING] (br_netfilter hook) {
    提取 IP 包
    伪装输出接口(即将转发帧的端口)
    调用 NF_INET_POST_ROUTING 链(SNAT! Conntrack 确认!}
-> 网桥端口发送(按 MAC 地址从指定端口发出去)

关键点:

二层 vs 三层: br_netfilter 的魔法在于在二层(网桥)路径中插入了对三层(IP)信息的检查点。它确保防火墙规则能看到 IP 地址和端口。

伪装的接口: skb->dev 被 br_netfilter 临时修改为真实的物理或 veth 接口,这对 Netfilter 规则正确识别流量来源和目标至关重要。规则可以针对具体的接口(-i eth0, -o veth1)。

FORWARD 链为主战场: 大部分桥接流量的过滤规则(ACCEPT/DROP/REJECT)都写在 Netfilter 的 FORWARD 链上,就像过滤路由器流量一样。

NAT 的可行性: 通过在 NF_INET_PRE_ROUTING (DNAT) 和 NF_INET_POST_ROUTING (SNAT) 链应用规则,结合 conntrack,可以对网桥流量进行目标地址转换(DNAT - 如端口映射)和源地址转换(SNAT - 如容器共享主机IP上网)。

ARP/IPv6 处理: br_netfilter 主要处理 IP 包。需要额外设置开关(bridge-nf-call-arptables, bridge-nf-call-ip6tables)来让 ARP 或 IPv6 流量也经过相应的防火墙(arptables, ip6tables)。这些通常默认关闭。

性能影响: 这种“拐弯”操作增加了处理开销(内核协议栈操作、规则匹配)。对性能要求极高的场景需注意。

5. br_netfilter代码流程

br_netfilter_init注册了一些HOOK

ret = nf_register_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
static struct nf_hook_ops br_nf_ops[] __read_mostly = {
	{
		.hook = br_nf_pre_routing,
		.owner = THIS_MODULE,
		.pf = PF_BRIDGE,
		.hooknum = NF_BR_PRE_ROUTING,
		.priority = NF_BR_PRI_BRNF,
	},
	{
		.hook = br_nf_local_in,
		.owner = THIS_MODULE,
		.pf = PF_BRIDGE,
		.hooknum = NF_BR_LOCAL_IN,
		.priority = NF_BR_PRI_BRNF,
	},
	{
		.hook = br_nf_forward_ip, 
		.owner = THIS_MODULE,
		.pf = PF_BRIDGE,
		.hooknum = NF_BR_FORWARD,
		.priority = NF_BR_PRI_BRNF - 1,
	},
	{
		.hook = br_nf_forward_arp,
		.owner = THIS_MODULE,
		.pf = PF_BRIDGE,
		.hooknum = NF_BR_FORWARD,
		.priority = NF_BR_PRI_BRNF,
	},
	{
		.hook = br_nf_post_routing,
		.owner = THIS_MODULE,
		.pf = PF_BRIDGE,
		.hooknum = NF_BR_POST_ROUTING,
		.priority = NF_BR_PRI_LAST,
	},
	{
		.hook = ip_sabotage_in,
		.owner = THIS_MODULE,
		.pf = PF_INET,
		.hooknum = NF_INET_PRE_ROUTING,
		.priority = NF_IP_PRI_FIRST,
	},
	{
		.hook = ip_sabotage_in,
		.owner = THIS_MODULE,
		.pf = PF_INET6,
		.hooknum = NF_INET_PRE_ROUTING,
		.priority = NF_IP6_PRI_FIRST,
	},
};


.hook = br_nf_forward_ip, 对应br_nf_forward_ip函数

/* This is the 'purely bridged' case.  For IP, we pass the packet to
 * netfilter with indev and outdev set to the bridge device,
 * but we are still able to filter on the 'real' indev/outdev
 * because of the physdev module. For ARP, indev and outdev are the
 * bridge ports. */
static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff *skb,
				     const struct net_device *in,
				     const struct net_device *out,
				     int (*okfn)(struct sk_buff *))
{
	struct nf_bridge_info *nf_bridge;
	struct net_device *parent;
	u_int8_t pf;

	if (LDSEC_DBG_BRIDGE_ON)
		LDSEC_PRINT_FUNC("br_nf_forward_ip");

	if (!skb->nf_bridge)
		return NF_ACCEPT;

	/* Need exclusive nf_bridge_info since we might have multiple
	 * different physoutdevs. */
	if (!nf_bridge_unshare(skb))
		return NF_DROP;

	parent = bridge_parent(out);
	if (!parent)
		return NF_DROP;

	if (skb->protocol == htons(ETH_P_IP) || IS_VLAN_IP(skb) ||
	    IS_PPPOE_IP(skb))
		pf = PF_INET;
	else if (skb->protocol == htons(ETH_P_IPV6) || IS_VLAN_IPV6(skb) ||
		 IS_PPPOE_IPV6(skb))
		pf = PF_INET6;
	else
		return NF_ACCEPT;

	nf_bridge_pull_encap_header(skb);

	nf_bridge = skb->nf_bridge;
	if (skb->pkt_type == PACKET_OTHERHOST) {
		skb->pkt_type = PACKET_HOST;
		nf_bridge->mask |= BRNF_PKT_TYPE;
	}

	/* The physdev module checks on this */
	nf_bridge->mask |= BRNF_BRIDGED;
	nf_bridge->physoutdev = skb->dev;
	if (pf == PF_INET)
		skb->protocol = htons(ETH_P_IP);
	else
		skb->protocol = htons(ETH_P_IPV6);

	NF_HOOK(pf, NF_INET_FORWARD, skb, bridge_parent(in), parent,
		br_nf_forward_finish);

	return NF_STOLEN;
}

br_nf_forward_ip最终调用ip层的NF_INET_FORWARD钩子

	NF_HOOK(pf, NF_INET_FORWARD, skb, bridge_parent(in), parent,
		br_nf_forward_finish);

参考:
http://blog.csdn.net/dog250/article/details/7314927
http://ebtables.netfilter.org/documentation/bridge-nf.html
http://ebtables.netfilter.org/misc/brnf-faq.html
http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html
https://www.linuxjournal.com/article/8172

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐