第十一章 Linux包过滤防火墙-netfilter--基于Linux3.10

11.1 netfilter框架

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 防火墙规则表

现行Linux下主要有raw、mangle、nat、filter和security这五张表,每张表各自又包含PRE_ROUTING、LOCAL_IN、FPRWARD、LOCAL_OUT和POST_ROUTING这五条链的一个组合,每一条链又会包含若干的规则项,源地址、目的地址、传输协议(TCP/UDP/ICMP)以及服务类型(HTTP、SNMP等)就包含在这些规则项中,raw用于数据跟踪处理、mangle表用于数据包的重构、nat表用于网络地址转换、filter表用于包过滤、security表是新引入的用于安全处理;目前这些表规则的用户空间的配置工具是iptables,iptables用于修改这些规则项,这些表的优先级按照图中从左至右的顺序递减。本小节主要分析下面这张表中的规则项。

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"这三个文件。

11.2.1 xt_init初始化防火墙表

防火墙的规则由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链表结构拓扑图

11.2.2 规则的组成

规则依次存放,在ipt_entry中,其target_offset和next_offset字段分别标记了target偏移和下一个规则的偏移,在初始化处可以看到它们实现的细节。


图11.2.5 规则拓扑

</pre><pre name="code" class="cpp">//该结构体包含源地址、目的地址、接口、协议等相关信息,这也是一条规则需要的信息。
 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 };

11.3 防火墙规则遍历

防火墙遍历规则链中的具体规则的入口函数是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执行

11.3.2 Hook函数

结构体

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,
},
};

11.3.2.2 钩子函数注册

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 <n; i++) {
err =nf_register_hook(&reg[i]);
}
}
intnf_register_hook(struct nf_hook_ops *reg)
{
struct nf_hook_ops*elem;
int err;
err =mutex_lock_interruptible(&nf_hook_mutex);
if (err < 0)
return err;
list_for_each_entry(elem,&nf_hooks[reg->pf][reg->hooknum], list) {
if(reg->priority < elem->priority)
break;
}
list_add_rcu(&reg->list,elem->list.prev);
mutex_unlock(&nf_hook_mutex);
return 0;
}

11.3.2.3 Hook函数的调用实例

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;
}

11.3.2.4  nf_hook_thresh

•/**
•*  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;
}

11.3.2.5 ipv4 hook函数调用点

•./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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值