netfilter从Linux2.4引入linux内核,是现在3.10版本的防火墙框架,该框架可实现数据包过滤、数据包处理、地址伪装、透明代理、动态网络地址转换(NetworkAddressTranslation,NAT),以及基于用户及媒体访问控制(MediaAccess Control,MAC)地址的过滤和基于状态的过滤、包速率限制等
netfilter为每种网络协议(IPv4、IPv6等)定义一套hook函数。Netfilter在Linux中的框架如图11.1.1所示,后文是基于源码对这张拓扑的分析,但是ivp6的内容就没有涉及了,主要还是以ipv4/tcp协议为主线的。
图11.1.1 netfilter框架
ipv4有5个hook函数,这些hook函数在数据报流过协议栈的5个关键点被调用,与相应的规则链进行比较,规则链存放在于表中,这些表包括nat、mangle、raw、filter、security等,根据检查结果,将决定数据包的命运:
l 原样放回IPv4协议栈,继续向上层递交;
l 经过修改,再放回网络协议栈;
l 丢弃。
数据在netfilter框架中的流通如图11.1.2所示。
图11.1.2 五个hook点及各点的表
在第一章的时候,对module_init初始化的函数被跳过了,这里先看看和防火墙的初始化工作吧。以下文件目录前缀均是/net/ipv4。
./netfilter.c:206:module_init(ipv4_netfilter_init);
./netfilter/ipt_ah.c:90:module_init(ah_mt_init);
./netfilter/iptable_raw.c:88:module_init(iptable_raw_init);
./netfilter/iptable_security.c:109:module_init(iptable_security_init);
./netfilter/arp_tables.c:1913:module_init(arp_tables_init);
./netfilter/iptable_mangle.c:147:module_init(iptable_mangle_init);
./netfilter/ip_tables.c:2269:module_init(ip_tables_init);
./netfilter/iptable_nat.c:333:module_init(iptable_nat_init);
./netfilter/iptable_filter.c:109:module_init(iptable_filter_init);
./netfilter/nf_defrag_ipv4.c:125:module_init(nf_defrag_init);
./netfilter/ipt_rpfilter.c:146:module_init(rpfilter_mt_init);
./netfilter/arptable_filter.c:90:module_init(arptable_filter_init);
ipv4_netfilter_init用于为INET协议族中的协议初始化netfilter,netfilter支持internet的协议类型有以下几种:
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
不同的协议类型的netfilter的具体细节并不一样,这里仅以ipv4协议对netfilter实现进行追踪,所以本章接下来的内容不加说明则其属于ipv4的范畴。
ipv4_netfilter_init:该函数注册internet协议族的netfilter,其参数nf_ip_afinfo的afinfo就是address family information缩写,该函数就是将nf_ip_afinfo结构体挂接到nf_afinfo的数组上去,nf_afinfo数组定义于同名文件的开始处。
const struct nf_afinfo __rcu *nf_afinfo[NFPROTO_NUMPROTO] __read_mostly;
nf_ip_afinfo 结构体定义如下:
static const struct nf_afinfo nf_ip_afinfo = {
.family = AF_INET,
.checksum = nf_ip_checksum,
.checksum_partial= nf_ip_checksum_partial,
.route = nf_ip_route,
.saveroute = nf_ip_saveroute,
.reroute = nf_ip_reroute,
.route_key_size= sizeof(struct ip_rt_info),
};
上述注册的函数是netfilter对ip层数据包的处理函数,checksum是cpu计算ip头校验和验证,checksum_partial是因为现在有些网卡自带校验和计算,它们可以解决cpu的资源,后面的三个都是用来路由的,后面遇到再看。
ah_mt_init:注册防火墙表中规则项的match函数,该函数用于头信息匹配判断。
iptable_security_init:
iptable_raw_init:
iptable_mangle_init:
iptable_nat_init:
iptable_filter_init:
这五张是内核防火墙使用的表,这里注册了这几张表,这几张表将构成一个链式结,表和链的拓扑结构可以参看11.2.1。由于这几张表的初始化过程差别不大,并且filter常用,所以这里就只看filter表的初始化了。
static int __init iptable_filter_init(void)
{
ret = register_pernet_subsys(&iptable_filter_net_ops); /* Register hooks */
filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);
}
又见register_pernet_subsys,iptable_filter_net_ops会被添加first_device_ops链表上,该函数注册一个网络命名空间子系统。
int register_pernet_subsys(struct pernet_operations *ops)
{
int error;
mutex_lock(&net_mutex);
error = register_pernet_operations(first_device, ops);
mutex_unlock(&net_mutex);
return error;
}
如果iptable_filter_net_ops有init成员,则init成员会被调用。
static struct pernet_operations iptable_filter_net_ops = {
.init = iptable_filter_net_init,
.exit = iptable_filter_net_exit,
};
回调函数iptable_filter_net_init函数如下
58 static int __net_init iptable_filter_net_init(struct net *net)
59 {
60 struct ipt_replace *repl;
61
62 repl = ipt_alloc_initial_table(&packet_filter);
63 if (repl == NULL)
64 return -ENOMEM;
65 /* Entry 1 is the FORWARD hook */
66 ((struct ipt_standard *)repl->entries)[1].target.verdict =
67 forward ? -NF_ACCEPT - 1 : -NF_DROP - 1;
68
69 net->ipv4.iptable_filter =
70 ipt_register_table(net, &packet_filter, repl);
71 kfree(repl);
72 return PTR_RET(net->ipv4.iptable_filter);
73 }
62行packet_filter定义如下,调用xt_alloc_initial_table宏进行初始化。
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT))
static const struct xt_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_FILTER,
};
62的函数xt_alloc_initial_table用于创建表的规则链,这个函数的具体实现比较复杂,先看完整个函数的流程再回过头来细细分析该函数的实现细节。
66行将FORWARD的hook项设置为接受,forward默认值是true,当然也可以在加载模块时动态改变该选择为flase,这样就不支持非本机数据包的转发功能了。
69向内核注册filter表。
再回到62行,这里该函数内的宏经过展开处理了,以函数的形式展现在这里了,该“函数”的参数是上面的packe_filer,即filter表结构。
void *ipt_alloc_initial_table(const struct xt_table *info)
{
unsigned int hook_mask = info->valid_hooks; //LOCAL_IN、FORWARD、LOCAL_OUT
unsigned int nhooks = hweight32(hook_mask); //这里得到3,上面hookmask对应三个hook点。
unsigned int bytes = 0, hooknum = 0, i = 0;
看到函数的最后,知道返回值是tbl,而这里的结构体内嵌的三个结构体是tbl的组成,三个结构体的数据结构拓扑图如图11.1.3。
struct {
struct ipt_replace repl;
struct ipt _standard entries[nhooks];
struct ipt_error term;
} *tbl = kzalloc(sizeof(*tbl), GFP_KERNEL);
if (tbl == NULL)
return NULL;
strncpy(tbl->repl.name, info->name, sizeof(tbl->repl.name));
tbl->term = (struct ipt_error)IPT_ERROR_INIT;
tbl->repl.valid_hooks = hook_mask;
tbl->repl.num_entries = nhooks + 1;
tbl->repl.size = nhooks * sizeof(struct ipt_standard) + sizeof(struct ipt_error);
for (; hook_mask != 0; hook_mask >>= 1, ++hooknum) {
if (!(hook_mask & 1))
continue;
tbl->repl.hook_entry[hooknum] = bytes;
tbl->repl.underflow[hooknum] = bytes;
tbl->entries[i++] = (struct ipt_standard) IPT_STANDARD_INIT(NF_ACCEPT);
bytes += sizeof(struct ipt_standard);
}
return tbl;
}
看到函数的最后,知道返回值是tbl,而这里
图11.1.3 tbl数据结构拓扑图
ipt_table各字段的意义如下:
name:其所属的表,对于filter表这里会赋值为filter;
valid_hooks:该表能够作用的hook点,对于filter表,有IN、FORWARD和OUT三个hook点。
num_entries:entry的入口点数目,为hook数目加一。
Size:所有entry项的size之和。
hook_entry:hook点的入口项。
Underflow:underflow入口点。
num_counters、counters:兼容旧的netfilter所用。
entries:hook入口项。
tbl的初始化的结果如图11.1.4所示,图中的LEN是为缩小图像大小而自己标记的一个值,对应的三个hook点和它的hook入口项,图中棕色字段均为初始化过程中设置的值。
图11.1.4 tbl初始化后各字段初始值
ipt_register_table用于向内核注册filter表,
2058 struct xt_table *ipt_register_table(struct net *net,
2059 const struct xt_table *table,
2060 const struct ipt_replace *repl)
2061 {
2062 int ret;
2063 struct xt_table_info *newinfo;
2064 struct xt_table_info bootstrap = {0};
2065 void *loc_cpu_entry;
2066 struct xt_table *new_table;
2067
2068 newinfo = xt_alloc_table_info(repl->size);
2069 if (!newinfo) {
2070 ret = -ENOMEM;
2071 goto out;
2072 }
2073
2074 /* choose the copy on our node/cpu, but dont care about preemption */
2075 loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
2076 memcpy(loc_cpu_entry, repl->entries, repl->size);
2077
2078 ret = translate_table(net, newinfo, loc_cpu_entry, repl);
2079 if (ret != 0)
2080 goto out_free;
2081
2082 new_table = xt_register_table(net, table, &bootstrap, newinfo);
2083 if (IS_ERR(new_table)) {
2084 ret = PTR_ERR(new_table);
2085 goto out_free;
2086 }
2088 return new_table;
2094 }
2068行,根据前面repl的size大小为每一个核申请内存,如果repl的size大于一个页,内核会使用vmalloc_node申请,否则使用kmalloc_node申请。
2075行,获得SMP情况下,本地cpu的入口地址;
2076行,将图11.1.4中的三个entries拷贝到本地cpu的表中。
2078行,将repl的相关信息复制到newinfo中,因为内核中防火墙的表是由xt_table表示的而xt_table_info描述防火墙表自身信息的。
2082行,xt_register_table用于将表注册到struct net的struct netns_xt xt;字段的链表上去,并将xt_table的prive字段设置成描述表信息的xt_table_info类型的成员。最后返回生成的表。该返回的表由于是ipv4协议的,所以在struct net表示的网络中将其成员struct netns_ipv4 ipv4的iptable_filter成员赋值成生成的表。
xt_hook_link创建和注册了filter 表的hook函数,这里的hook函数是iptable_filter_hook。
struct nf_hook_ops *xt_hook_link(const struct xt_table *table, nf_hookfn *fn)
{
unsigned int hook_mask = table->valid_hooks;
uint8_t i, num_hooks = hweight32(hook_mask);
uint8_t hooknum;
struct nf_hook_ops *ops;
for (i = 0, hooknum = 0; i < num_hooks && hook_mask != 0;
hook_mask >>= 1, ++hooknum) {
if (!(hook_mask & 1))
continue;
ops[i].hook = fn;
ops[i].owner = table->me;
ops[i].pf = table->af;
ops[i].hooknum = hooknum;
ops[i].priority = table->priority;
++i;
}
ret = nf_register_hooks(ops, num_hooks);
}
这里注册的iptable_filter_hook钩子函数不放在这个小节,其内容会放到规则表的遍历一节。
11.2.1表、链、规则关系
防火墙的的每条规则由以下三个部分组成。
•ipt_entry
•ipt_entry_match/xt_entry_match
•ipt_entry_targe/xt_entry_targett
防火墙的每一条规则由ipt_entry定义,每一条规则包括三个部分,1)IP头 2)和match相关的 3)如果match和规则匹配则会被执行的target。在11.1节我们已经见过ipt_entry了,并且在第一节中,有一个module_init宏定义的函数ip_tables_init,其用于注册netfilter的target和match项。
2208 static int __net_init ip_tables_net_init(struct net *net)
2209 {
2210 return xt_proto_init(net, NFPROTO_IPV4);
2211 }
2218 static struct pernet_operations ip_tables_net_ops = {
2219 .init = ip_tables_net_init,
2220 .exit = ip_tables_net_exit,
2221 };
2223 static int __init ip_tables_init(void)
2224 {
2225 int ret;
2227 ret = register_pernet_subsys(&ip_tables_net_ops);
2231 /* No one else will be downing sem now, so we won't sleep */
2232 ret = xt_register_targets(ipt_builtin_tg, ARRAY_SIZE(ipt_builtin_tg));
2235 ret = xt_register_matches(ipt_builtin_mt, ARRAY_SIZE(ipt_builtin_mt));
2239 /* Register setsockopt */
2240 ret = nf_register_sockopt(&ipt_sockopts);
…
2255 }
2227行会调用ip_tables_net_init,该函数在proc/net/目录下创建:
"ip_tables_names"、"ip_tables_matches"、"ip_tables_targets"这三个文件。
防火墙的规则由struct xt_af类型的xt统一来管理,在系统初始化时会初始化该表。
net/filter/x_tables.c
1372 static int __init xt_init(void)
1373 {
1381 xt = kmalloc(sizeof(struct xt_af) * NFPROTO_NUMPROTO, GFP_KERNEL);
1385 for (i = 0; i < NFPROTO_NUMPROTO; i++) {
1386 mutex_init(&xt[i].mutex);
1391 INIT_LIST_HEAD(&xt[i].target);
1392 INIT_LIST_HEAD(&xt[i].match);
1393 }
1394 rv = register_pernet_subsys(&xt_net_ops);
1398 }
1406 module_init(xt_init);
1381行,根据支持的协议类型申请内存,这些协议包括ipv4、arp、bridge、ipv6、DECNET等。xt的ipv4项初始化后数据结构拓扑如图11.2.2,由于和其它协议使用同一个循环体完成的,所以其它协议的xt初始化和这里的类似。
11.2.2 ipv4 xt初始化
1394行是初始化网络空间中的防火墙表项。
static int __net_init xt_net_init(struct net *net)
{
for (i = 0; i < NFPROTO_NUMPROTO; i++)
INIT_LIST_HEAD(&net->xt.tables[i]);
}
这里的xt是structnetns_xt类型的,这里将该网络命名空间中的所有协议的表初始化一下。
回到ip_tables_init函数的2232、2235行接着看,其主要将下述定义的ipt_builtin_tg和ipt_builtin_mt添加到xt链表上去,这是是xt表的默认项。链表的结构图见图11.2.3。
2161 static struct xt_target ipt_builtin_tg[] __read_mostly = {
2162 {
2163 .name = XT_STANDARD_TARGET,
2164 .targetsize = sizeof(int),
2165 .family = NFPROTO_IPV4,
2171 },
2172 {
2173 .name = XT_ERROR_TARGET,
2174 .target = ipt_error,
2175 .targetsize = XT_FUNCTION_MAXNAMELEN,
2176 .family = NFPROTO_IPV4,
2177 },
2178 };
2197 static struct xt_match ipt_builtin_mt[] __read_mostly = {
2198 {
2199 .name = "icmp",
2200 .match = icmp_match,
2201 .matchsize = sizeof(struct ipt_icmp),
2202 .checkentry = icmp_checkentry,
2203 .proto = IPPROTO_ICMP,
2204 .family = NFPROTO_IPV4,
2205 },
2206 };
图11.2.4链表结构拓扑图
规则依次存放,在ipt_entry中,其target_offset和next_offset字段分别标记了target偏移和下一个规则的偏移,在初始化处可以看到它们实现的细节。
图11.2.5 规则拓扑
//该结构体包含源地址、目的地址、接口、协议等相关信息,这也是一条规则需要的信息。 69 struct ipt_ip { 70 /* Source and destination IP addr */ 71 struct in_addr src, dst; //其实际上就是大端格式的32bit无符号整数。 72 /* Mask for src and dest IP addr */ 73 struct in_addr smsk, dmsk; 74 char iniface[IFNAMSIZ], outiface[IFNAMSIZ]; 75 unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ]; 76 77 /* Protocol, 0 = ANY */ 78 __u16 proto; 79 80 /* Flags word */ 81 __u8 flags; 82 /* Inverse flags */ 83 __u8 invflags; 84 }; include/uapi/linux/netfilter/x_tables.h 10 struct xt_entry_match { 11 union { 12 struct { 13 __u16 match_size; 14 15 /* Used by userspace */ 16 char name[XT_EXTENSION_MAXNAMELEN]; 17 __u8 revision; 18 } user; 19 struct { 20 __u16 match_size; 21 22 /* Used inside the kernel */ 23 struct xt_match *match; 24 } kernel; 25 26 /* Total length */ 27 __u16 match_size; 28 } u; 29 30 unsigned char data[0]; 31 }; include/linux/netfilter/x_tables.h 105 struct xt_match { 106 struct list_head list; 107 108 const char name[XT_EXTENSION_MAXNAMELEN]; 109 u_int8_t revision; 110 111 /* Return true or false: return FALSE and set *hotdrop = 1 to 112 force immediate packet drop. */ 113 /* Arguments changed since 2.6.9, as this must now handle 114 non-linear skb, using skb_header_pointer and 115 skb_ip_make_writable. */ 116 bool (*match)(const struct sk_buff *skb, 117 struct xt_action_param *); 118 119 /* Called when user tries to insert an entry of this type. */ 120 int (*checkentry)(const struct xt_mtchk_param *); 121 122 /* Called when entry of this type deleted. */ 123 void (*destroy)(const struct xt_mtdtor_param *); 124 #ifdef CONFIG_COMPAT 125 /* Called when userspace align differs from kernel space one */ 126 void (*compat_from_user)(void *dst, const void *src); 127 int (*compat_to_user)(void __user *dst, const void *src); 128 #endif 129 /* Set this to THIS_MODULE if you are a module, otherwise NULL */ 130 struct module *me; 131 132 const char *table; 133 unsigned int matchsize; 137 unsigned int hooks; 138 unsigned short proto; 139 140 unsigned short family; 141 };
防火墙遍历规则链中的具体规则的入口函数是ipt_do_table,内核使用图11.3.1所示的6个函数调用该入口函数。对于ipv4而言,iptable_filter_hook是其入口的直接函数。
图11.3.1 防火墙规则遍历点
35 static unsigned int
36 iptable_filter_hook(unsigned int hook, struct sk_buff *skb,
37 const struct net_device *in, const struct net_device *out,
38 int (*okfn)(struct sk_buff *))
39 {
42 if (hook == NF_INET_LOCAL_OUT &&
43 (skb->len < sizeof(struct iphdr) ||
44 ip_hdrlen(skb) < sizeof(struct iphdr)))
45 /* root is playing with raw sockets. */
46 return NF_ACCEPT;
48 net = dev_net((in != NULL) ? in : out);
49 return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter);
}
42~46对于raw类型的数据包,直接放行;
48行,如果in非空,则说明是入数据包,否则是出数据包。
49行,调用ipt_do_table查找规则链。
ipt_do_table参数的意义如下:
l skb,对应的socket buffer
l in到达数据包的设备,如果是发送操作,则这里是NULL
l out发送数据包的设备,如果是接收操作,这里将是NULL
l table,防火墙的表,ipv4则是初始化时的net->ipv4.iptable_filter表
288 unsigned int
289 ipt_do_table(struct sk_buff *skb,
290 unsigned int hook,
291 const struct net_device *in,
292 const struct net_device *out,
293 struct xt_table *table)
294 {
295 static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
296 const struct iphdr *ip;
297 /* Initializing verdict to NF_DROP keeps gcc happy. */
/* verdict是遍历规则以后返回的值,可能的值如下。
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
*/
298 unsigned int verdict = NF_DROP;
299 const char *indev, *outdev;
300 const void *table_base;
301 struct ipt_entry *e, **jumpstack;
302 unsigned int *stackptr, origptr, cpu;
303 const struct xt_table_info *private;
304 struct xt_action_param acpar;
305 unsigned int addend;
306
307 /* Initialization */
308 ip = ip_hdr(skb);
309 indev = in ? in->name : nulldevname;
310 outdev = out ? out->name : nulldevname;
311 /* We handle fragments by dealing with the first fragment as
312 * if it was a normal packet. All other fragments are treated
313 * normally, except that they will NEVER match rules that ask
314 * things we don't know, ie. tcp syn flag or ports). If the
315 * rule is also a fragment-specific rule, non-fragments won't
316 * match it. */
317 acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
318 acpar.thoff = ip_hdrlen(skb);
319 acpar.hotdrop = false;
320 acpar.in = in;
321 acpar.out = out;
322 acpar.family = NFPROTO_IPV4;
323 acpar.hooknum = hook;
324
325 IP_NF_ASSERT(table->valid_hooks & (1 << hook));
326 local_bh_disable();
327 addend = xt_write_recseq_begin();
328 private = table->private;
329 cpu = smp_processor_id();
330 table_base = private->entries[cpu];
331 jumpstack = (struct ipt_entry **)private->jumpstack[cpu];
332 stackptr = per_cpu_ptr(private->stackptr, cpu);
333 origptr = *stackptr;
334
335 e = get_entry(table_base, private->hook_entry[hook]);
336
337 pr_debug("Entering %s(hook %u); sp at %u (UF %p)\n",
338 table->name, hook, origptr,
339 get_entry(table_base, private->underflow[hook]));
340
341 do {
342 const struct xt_entry_target *t;
343 const struct xt_entry_match *ematch;
344
345 IP_NF_ASSERT(e);
346 if (!ip_packet_match(ip, indev, outdev,
347 &e->ip, acpar.fragoff)) {
348 no_match:
349 e = ipt_next_entry(e);
350 continue;
351 }
352
353 xt_ematch_foreach(ematch, e) {
354 acpar.match = ematch->u.kernel.match;
355 acpar.matchinfo = ematch->data;
356 if (!acpar.match->match(skb, &acpar))
357 goto no_match;
358 }
359
360 ADD_COUNTER(e->counters, skb->len, 1);
361
362 t = ipt_get_target(e);
363 IP_NF_ASSERT(t->u.kernel.target);
371 /* Standard target? */
372 if (!t->u.kernel.target->target) {
373 int v;
374
375 v = ((struct xt_standard_target *)t)->verdict;
376 if (v < 0) {
377 /* Pop from stack? */
378 if (v != XT_RETURN) {
379 verdict = (unsigned int)(-v) - 1;
380 break;
381 }
382 if (*stackptr <= origptr) {
383 e = get_entry(table_base,
384 private->underflow[hook]);
385 pr_debug("Underflow (this is normal) "
386 "to %p\n", e);
387 } else {
388 e = jumpstack[--*stackptr];
389 pr_debug("Pulled %p out from pos %u\n",
390 e, *stackptr);
391 e = ipt_next_entry(e);
392 }
393 continue;
394 }
395 if (table_base + v != ipt_next_entry(e) &&
396 !(e->ip.flags & IPT_F_GOTO)) {
397 if (*stackptr >= private->stacksize) {
398 verdict = NF_DROP;
399 break;
400 }
401 jumpstack[(*stackptr)++] = e;
402 pr_debug("Pushed %p into pos %u\n",
403 e, *stackptr - 1);
404 }
405
406 e = get_entry(table_base, v);
407 continue;
408 }
409
410 acpar.target = t->u.kernel.target;
411 acpar.targinfo = t->data;
412
413 verdict = t->u.kernel.target->target(skb, &acpar);
414 /* Target might have changed stuff. */
415 ip = ip_hdr(skb);
416 if (verdict == XT_CONTINUE)
417 e = ipt_next_entry(e);
418 else
419 /* Verdict */
420 break;
421 } while (!acpar.hotdrop);
422 pr_debug("Exiting %s; resetting sp from %u to %u\n",
423 __func__, *stackptr, origptr);
424 *stackptr = origptr;
425 xt_write_recseq_end(addend);
426 local_bh_enable();
435 }
该函数的返回值是防火墙对套接字buffer中数据包匹配规则后的结果,有以下几种可能:
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP
317~323 根据 得到的数据包头 得到的数据包头 得到的数据包头 得到的数据包头 ,将分段 、头长 等字段 等字段 保存在 保存在 acpar acpar acpar变量 中。
325验证 hook hookhook点是否合法。 点是否合法。 点是否合法。 点是否合法。
328private是struct xt_table_info类型的变量,ipv4使用translate_table函数获得表信息的,这里将表信息存放在private字段。
329获得本地CPU号。
330获得本地CPU的防火墙规则表的入口点地址。
335行,根据得到的规则点地址和hook入口点,获得入口项,它们的关系可以见图11.1.4。
static inline struct ipt_entry *
get_entry(const void *base, unsigned int offset)
{
return (struct ipt_entry *)(base + offset);
}
这里base就是基地址,offset值对应于图11.1.4中的0、LEN、2*LEN;其中LEN等于sizeof(ipt_standard);
341~421遍历规则链中的规则表。
342~343target和match的意义参看图11.2.5就能明白,ematch用于规则匹配和匹配上后执行的target动作。
346~347 判断规则的匹配性了,其第一个参数是根据skbbuffer获得的ip头,第四个参数是一个规则指向的ip向,第五个参数是分片标志。
74 static inline bool
75 ip_packet_match(const struct iphdr *ip,
76 const char *indev,
77 const char *outdev,
78 const struct ipt_ip *ipinfo,
79 int isfrag)
80 {
81 unsigned long ret;
82
83 #define FWINV(bool, invflg) ((bool) ^ !!(ipinfo->invflags & (invflg)))
84
85 if (FWINV((ip->saddr&ipinfo->smsk.s_addr) != ipinfo->src.s_addr,
86 IPT_INV_SRCIP) ||
87 FWINV((ip->daddr&ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr,
88 IPT_INV_DSTIP)) {
89 dprintf("Source or dest mismatch.\n");
90
91 dprintf("SRC: %pI4. Mask: %pI4. Target: %pI4.%s\n",
92 &ip->saddr, &ipinfo->smsk.s_addr, &ipinfo->src.s_addr,
93 ipinfo->invflags & IPT_INV_SRCIP ? " (INV)" : "");
94 dprintf("DST: %pI4 Mask: %pI4 Target: %pI4.%s\n",
95 &ip->daddr, &ipinfo->dmsk.s_addr, &ipinfo->dst.s_addr,
96 ipinfo->invflags & IPT_INV_DSTIP ? " (INV)" : "");
97 return false;
98 }
99
100 ret = ifname_compare_aligned(indev, ipinfo->iniface, ipinfo->iniface_mask);
101
102 if (FWINV(ret != 0, IPT_INV_VIA_IN)) {
103 dprintf("VIA in mismatch (%s vs %s).%s\n",
104 indev, ipinfo->iniface,
105 ipinfo->invflags&IPT_INV_VIA_IN ?" (INV)":"");
106 return false;
107 }
108
109 ret = ifname_compare_aligned(outdev, ipinfo->outiface, ipinfo->outiface_mask);
110
111 if (FWINV(ret != 0, IPT_INV_VIA_OUT)) {
112 dprintf("VIA out mismatch (%s vs %s).%s\n",
113 outdev, ipinfo->outiface,
114 ipinfo->invflags&IPT_INV_VIA_OUT ?" (INV)":"");
115 return false;
116 }
117
118 /* Check specific protocol */
119 if (ipinfo->proto &&
120 FWINV(ip->protocol != ipinfo->proto, IPT_INV_PROTO)) {
121 dprintf("Packet protocol %hi does not match %hi.%s\n",
122 ip->protocol, ipinfo->proto,
123 ipinfo->invflags&IPT_INV_PROTO ? " (INV)":"");
124 return false;
125 }
126
127 /* If we have a fragment rule but the packet is not a fragment
128 * then we return zero */
129 if (FWINV((ipinfo->flags&IPT_F_FRAG) && !isfrag, IPT_INV_FRAG)) {
130 dprintf("Fragment rule but not fragment.%s\n",
131 ipinfo->invflags & IPT_INV_FRAG ? " (INV)" : "");
132 return false;
133 }
134
135 return true;
136 }
85~98匹配源地址和目的地址
100~116匹配接收和发送接口,如eth0、eth1等
119~125匹配协议
129~133匹配分片
上述有任一项违反规则项,则说明规则并不匹配,则349行获得下一个规则,重复进行上述检查,直至获得一个匹配的规则。
353~358,根据规则项,遍历该规则项对应的match函数,使用match对skb进行检查,如果match了,则向下进行,否则继续遍历规则链。
360统计验证过的数据字节数和packet数,字节数即len,packet就是简单加一。
362根据match的项,获得对应的target,将调用target对skb处理以决定其最后的命运。
363~408标准类型的target执行流程。
413用户定义类型的target执行
结构体
static struct nf_hook_ops ipv4_defrag_ops[] = {
{
.hook = ipv4_conntrack_defrag,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_DEFRAG,
},
{
.hook = ipv4_conntrack_defrag,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_CONNTRACK_DEFRAG,
},
};
net/ipv4/netfilter/nf_defrag_ipv4.c
static int __initnf_defrag_init(void)
{
returnnf_register_hooks(ipv4_defrag_ops, ARRAY_SIZE(ipv4_defrag_ops));
}
intnf_register_hooks(struct nf_hook_ops *reg, unsigned int n)
{
unsigned int i;
int err = 0;
for (i = 0; i pf][reg->hooknum], list) {
if(reg->priority < elem->priority)
break;
}
list_add_rcu(®->list,elem->list.prev);
mutex_unlock(&nf_hook_mutex);
return 0;
}
int ip_output(structsk_buff*skb)
{
struct net_device *dev= skb_dst(skb)->dev;
IP_UPD_PO_STATS(dev_net(dev),IPSTATS_MIB_OUT, skb->len);
skb->dev = dev;
skb->protocol = htons(ETH_P_IP);
return NF_HOOK_COND(NFPROTO_IPV4,NF_INET_POST_ROUTING, skb,NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags& IPSKB_REROUTED));
}
staticinline int
NF_HOOK_COND(uint8_tpf, unsigned inthook, structsk_buff*skb,
structnet_device*in, structnet_device*out,
int(*okfn)(structsk_buff*), boolcond)
{
int ret;
if (!cond ||
((ret = nf_hook_thresh(pf,hook, skb,in, out, okfn,INT_MIN)) == 1))
ret = okfn(skb);
return ret;
}
•/**
•* nf_hook_thresh- call a netfilterhook
•*
•* Returns 1 if the hook has allowed thepacket to pass. The function
•* okfn must be invoked by the caller in thiscase. Any other return
•* value indicates the packet has beenconsumed by the hook.
•*/
•staticinline intnf_hook_thresh(u_int8_tpf, unsigned inthook,
• struct sk_buff *skb,
• struct net_device *indev,
• struct net_device *outdev,
• int (*okfn)(struct sk_buff *), intthresh)
•{
• if (nf_hooks_active(pf,hook))
• return nf_hook_slow(pf,hook, skb,indev,outdev,okfn,thresh);
• return 1;
•}
/* Returns 1 if okfn() needs to be executed by the caller,
* -EPERM for NF_DROP, 0 otherwise. */
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
struct net_device *indev,
struct net_device *outdev,
int (*okfn)(struct sk_buff *),
int hook_thresh)
{
struct nf_hook_ops *elem;
unsigned int verdict;
int ret = 0;
/* We may already have this, but read-locks nest anyway */
rcu_read_lock();
elem = list_entry_rcu(&nf_hooks[pf][hook], struct nf_hook_ops, list);
next_hook:
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
outdev, &elem, okfn, hook_thresh);
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
ret = 1;
} else if ((verdict & NF_VERDICT_MASK) == NF_DROP) {
kfree_skb(skb);
ret = NF_DROP_GETERR(verdict);
if (ret == 0)
ret = -EPERM;
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
int err = nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
verdict >> NF_VERDICT_QBITS);
if (err < 0) {
if (err == -ECANCELED)
goto next_hook;
if (err == -ESRCH &&
(verdict & NF_VERDICT_FLAG_QUEUE_BYPASS))
goto next_hook;
kfree_skb(skb);
}
}
rcu_read_unlock();
return ret;
}
•./ipv4/ip_output.c:273: NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING,
•./ipv4/ip_output.c:289: NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING, newskb,
•./ipv4/ip_output.c:100:return nf_hook(NFPROTO_IPV4,NF_INET_LOCAL_OUT, skb,NULL,
•./ipv4/ipmr.c:1780: NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,skb,skb->dev,dev,
•./ipv4/ip_forward.c:183: return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,skb,skb->dev,
•./ipv4/xfrm4_input.c:64: NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, skb->dev, NULL,
•./ipv4/ip_input.c:255: return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,skb,skb->dev,NULL,
•./ipv4/ip_input.c:445: return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
•./ipv4/arp.c:686: NF_HOOK(NFPROTO_ARP, NF_ARP_OUT, skb,NULL, skb->dev,dev_queue_xmit);
•./ipv4/arp.c:967: return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb,dev,NULL, arp_process);
•./ipv4/raw.c:398: err = NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT,skb,NULL,
include/linux/netfilter.h
externstruct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
include/uapi/linux/netfilter.h
enum nf_inet_hooks {
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,
NF_INET_NUMHOOKS
};
enum {
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
NF_HOOK宏的参数分别为:
[1]:NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),目的地址转换在此点进行;
[2]:NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
[3]:NF_IP_FORWARD:要转发的包通过此检测点,FORWARD包过滤在此点进行;
[4]:NF_IP_POST_ROUTING:所有即将通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
[5]:NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。
NF_HOOK宏的参数分别为:
⒈ pf:协议族名,netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如
PF_INET6,PF_DECnet等名字。
⒉hook:HOOK点的名字,对于IP层,就是取上面的五个值;
⒊skb:不用多解释了吧;
⒋indev:进来的设备,以structnet_device结构表示;
⒌outdev:出去的设备,以structnet_device结构表示;
(后面可以看到,以上五个参数将传到用nf_register_hook登记的处理函数中。)
⒍okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。
这些点是已经在内核中定义好的,除非你是这部分内核代码的维护者,否则无权增加
或修改,而在此检测点进行的处理,则可由用户指定。像packet filter,NAT,connection
track这些功能,也是以这种方式提供的。正如netfilter的当初的设计目标--提供一
个完善灵活的框架,为扩展功能提供方便。
如果我们想加入自己的代码,便要用nf_register_hook函数,其函数原型为:
intnf_register_hook(struct nf_hook_ops *reg)