1. Netfilter简介
netfilter是由Rusty Russell提出的Linux 2.4内核防火墙框架,该框架既简洁又灵活,可实现安全策略应用中的许多功能,如数据包过滤、数据包处理、地址伪装、透明代理、动态网络地址转换(Network Address Translation,NAT),以及基于用户及媒体访问控制(Media Access Control,MAC)地址的过滤和基于状态的过滤、包速率限制等。
netfilter的架构就是在数据包在通过网络协议栈的若干位置放置了一些检测点(HOOKS),不同防火墙功能的子模块通过netfilter框架提供的API注册相应的HOOK回调函数,并在回调函数中实现对数据包的过滤、解包修改后重新打包等操作。已注册的HOOK回调函数会在数据包流经netfilter框架HOOK点时被系统自动调用执行。
2. Ethernet Bridge 的Netfilter框架
计算机网络的五层协议体系结构中,网络层(IP层)和数据链路层的防火墙功能都是基于netfilter框架实现。分别对应着上层应用配置工具iptables和ebtables。本章节主要介绍Ethernet Bridge的Netfilter(本文简称EB-NF)框架及其实现原理。
2.1 EB-NF的HOOK点
从linux的网桥代码中,我们可以看到Ethernet Bridge的Netfilter框架有6个HOOK点,分别为BROUTING、PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。其中BROUTING是网桥独有的,且不是通过调用netfilter框架的register函数来进行钩子的回调注册。
/* Bridge Hooks */
/* After promisc drops, checksum checks. */
#define NF_BR_PRE_ROUTING 0
/* If the packet is destined for this box. */
#define NF_BR_LOCAL_IN 1
/* If the packet is destined for another interface. */
#define NF_BR_FORWARD 2
/* Packets coming from a local process. */
#define NF_BR_LOCAL_OUT 3
/* Packets about to hit the wire. */
#define NF_BR_POST_ROUTING 4
/* Not really a hook, but used for the ebtables broute table */
#define NF_BR_BROUTING 5
#define NF_BR_NUMHOOKS 6
基于这6个HOOK点我们可以描绘出EB-NF大致框架为如下:
2.2 EB-NF框架/ebtable命令中的常用关键名词
2.2.1 链(CHAINS)
Linux内核ebtables模块中包含有3个表,每个表默认包含有多条链。表是用于把不同组的防火墙规则划分为不同功能。而不同组的规则我们称之为链。每一条链都由一组有序的防火墙规则组成,用于对以太帧的处理。每一条规则都指定了对“命中”匹配条件的数据帧的处理办法,该处理方法称之为“target”。当以太帧没有匹配到当前的防火墙规则时,则会尝试匹配链中的下一条规则,如此类推。Ebtables模块中默认有6个链和上文的HOOKS对应。除此之外,用户还可以自定义新的链。用户自定义的链在上层可通过在默认链中添加防火墙规则在规则匹配时跳转到自定义链进行规则匹配。
2.2.2 目标(TARGETS)
一条防火墙规则需要指定一条执行目标。执行目标(TARGETS)可以是以下:
(1) ACCEPT(让以太帧通过此链)
(2) DROP (丢弃该以太帧)
(3) CONTINUE (检查同一条链的下一条规则,一般用于统计通过该条链的流量)
(4) RETURN (停止检查该条链的余下规则,检查调用该条链的上一条链的下一条规则)
(5) “TARGET EXTENSION”(扩展的执行目标,如 –mark-set等)
(6) 跳转到自定义的链进行规则匹配。
另外,BROUTING链中ACCEPT和DROP的意思会不太一样,ACCEPT表示以太帧进入转发处理流程,DROP表示以太帧进入本地路由的处理流程。
2.2.3 表(TABLES)
如前面所说,ebtables模块按功能划分为3个表,分别为如下:
(1) filter表
filter表是上层ebtables应用程序默认操作的表,它包含有3个链,分别为INPUT链(数据帧的目的地址是网桥本身)、OUTPUT链(针对本地生成和桥接路由的数据帧)、FORWARD链(被网桥转发的数据帧)。
(2) nat表
nat表一般用于修改以太帧的mac地址,同样,它也包含有3个链,分别为PREROUTING链(当接收到以太帧时立即修改数据帧)、OUTPUT链(修改本地生成和桥接路由的数据帧,在它们桥接前 )、POSTROUTING链。
注意:Ebtables中PREROUTING链和POSTROUTING链的名称是由基于IP层协议的iptables引申过来。在数据链路层中,实际命名为PREFORWARDING和POSTFORWARDING会理解得更加准确。
(3) Broute表
Broute表用于支持网桥路由的设备。它只有一个BROUTING链。处于forwarding状态的以太帧进入网桥设备后首先通过的就是BROUTING链,经过BROUTING后才决定数据包是进入网桥转发处理流程还是本地路由处理流程。大部分时候,这些数据帧都是会被进行网桥转发,我们可以在BROUTING链中添加防火墙规则让它进行本地路由。另外,处于混杂模式的网桥设备,数据包除了会进行网桥转发外还会克隆一份skb进行本地路由。
2.3 EB-NF框架中“表-链-规则”的关系
如上文提到,不同“表”代表着不同防火墙功能模块,每个表可以有多条链,一条链由一组有序的防火墙规则组成,一条防火墙规则包含匹配条件和执行目标。其关系图如下所示:
2.4 EB-NF框架中“表-链-规则”的内核源代码实现
EB-NF框架的源代码在”$(kernel_sourcetree_top)/net/bridge/nefilter/”目录下。其中前文提到的ebtables模块中3张表分别对应着ebtables_broute.c、ebtables_filter.c、以及ebtables_nat.c。
以下ebtables_filter.c为例进行分析。
2.4.1 Ebtables 模块 “表-链-规则”的初始化创建过程
Ebtable模块的表的数据结构体为struct ebt_table:
struct ebt_table {
struct list_head list;
char name[EBT_TABLE_MAXNAMELEN];
struct ebt_replace_kernel *table;
unsigned int valid_hooks;
rwlock_t lock;
/* e.g. could be the table explicitly only allows certain
* matches, targets, ... 0 == let it in */
int (*check)(const struct ebt_table_info *info,
unsigned int valid_hooks);
/* the data used by the kernel */
struct ebt_table_info *private;
struct module *me;
};
内核启动的时候Ebtable的filter表通过ebt_register_table()函数注册到netfilter子系统。
static int __net_init frame_filter_net_init(struct net *net)
{
net->xt.frame_filter = ebt_register_table(net, &frame_filter);
return PTR_ERR_OR_ZERO(net->xt.frame_filter);
}
该表命名为“filter”。默认有3个chains,分别命令为“INPUT”、“FORWARD”、“OUTPUT”,分别和BR_NF的6个钩子中的NF_BR_LOCAL_IN、NF_BR_FORWARD、NF_BR_LOCAL_OUT对应。
#define FILTER_VALID_HOOKS ((1 << NF_BR_LOCAL_IN) | (1 << NF_BR_FORWARD) | \
(1 << NF_BR_LOCAL_OUT))
static struct ebt_entries initial_chains[] = {
// filter表初始化的3个链
{
.name = "INPUT",
.policy = EBT_ACCEPT,
},
{
.name = "FORWARD",
.policy = EBT_ACCEPT,
},
{
.name = "OUTPUT",
.policy = EBT_ACCEPT,
},
};
static struct ebt_replace_kernel initial_table = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.entries_size = 3 * sizeof(struct ebt_entries),
.hook_entry = {
[NF_BR_LOCAL_IN] = &initial_chains[0],
[NF_BR_FORWARD] = &initial_chains[1],
[NF_BR_LOCAL_OUT] = &initial_chains[2],
},
.entries = (char *)initial_chains,
};
static const struct ebt_table frame_filter = {
.name = "filter", //表的名称为“filter”
.table = &initial_table,
.valid_hooks = FILTER_VALID_HOOKS,
.check = check,
.me = THIS_MODULE,
};
一条的链的数据结构体为struct ebt_entries, 一条防火墙规则的数据结构体为struct ebt_entry。
struct ebt_entries {
/* this field is always set to zero
* See EBT_ENTRY_OR_ENTRIES.
* Must be same size as ebt_entry.bitmask */
unsigned int distinguisher;
/* the chain name */
char name[EBT_CHAIN_MAXNAMELEN];
/* counter offset for this chain */
unsigned int counter_offset;
/* one standard (accept, drop, return) per hook */
int policy;
/* nr. of entries */
unsigned int nentries;
/* entry list */
char data[0] __attribute__ ((aligned (__alignof__(struct ebt_replace)))); // struct_ebt_replace包含了该条链所包含的防火墙规则数量,以及防火墙规则链表头等信息
};
/* one entry */
struct ebt_entry {
/* this needs to be the first field */
unsigned int bitmask;
unsigned int invflags;
__be16 ethproto;
/* the physical in-dev */
char in[IFNAMSIZ];
/* the logical in-dev */
char logical_in[IFNAMSIZ];
/* the physical out-dev */
char out[IFNAMSIZ];
/* the logical out-dev */
char logical_out[IFNAMSIZ];
unsigned char sourcemac[ETH_ALEN];
unsigned char sourcemsk[ETH_ALEN];
unsigned char destmac[ETH_ALEN];
unsigned char destmsk[ETH_ALEN];
/* sizeof ebt_entry + matches */
unsigned int watchers_offset; //扩展的match(struct ebt_entry_match)和其相邻的watcher()
/* sizeof ebt_entry + matches + watchers */
unsigned int target_offset; //扩展的target(struct ebt_entry_target)
/* sizeof ebt_entry + matches + watchers + target */
unsigned int next_offset;
unsigned char elems[0] __attribute__ ((aligned (__alignof__(struct ebt_replace))));
};
2.4.2 Ebtables模块表规则的检查过程
Ebtables的filter模块除了注册了filter表外,还注册了bridge netfilter的钩子函数,让数据包通过协议栈的钩子检测点时调用ebt_do_table()函数来检查filter表中的防火墙规则。
static int __init ebtable_filter_init(void)
{
int ret;
ret = register_pernet_subsys(&frame_filter_net_ops);
if (ret < 0)
return ret;
ret = nf_register_hooks(ebt_ops_filter, ARRAY_SIZE(ebt_ops_filter));
if (ret < 0)
unregister_pernet_subsys(&frame_filter_net_ops);
return ret;
}
static struct nf_hook_ops ebt_ops_filter[] __read_mostly = {
{
.hook = ebt_in_hook,
.pf = NFPROTO_BRIDGE,
.hooknum = NF_BR_LOCAL_IN,
.priority = NF_BR_PRI_FILTER_BRIDGED,
},
{
.hook = ebt_in_hook,
.pf = NFPROTO_BRIDGE,
.hooknum &#