for(temp = fw_ip_packet_type; temp; temp=temp->next) dev_add_pack(temp); } } 不难看出,FW1把ip_packet_type歇载掉了,然后自己在自己的处理函数(fw_filterin)中调ip_recv。 输出的挂载和lkm的手法一样,更改dev->hard_start_xmit。dev结构在2.2版本的发展过程中变了一次,为了兼容FW1对这点也做了处理(通过检查版本号来取偏移)。 还有一款linux下的防火墙产品WebGuard(http://www.gennet.com.tw/b5/csub_webguard.html)采用的手法与FW1其非常类似。有兴趣的人可以自行研究一下。 四 规则 4.0 综述 4.1 ipchains man ipfw可以看到这一段的详细解释。关键数据结构如下: 规则链用ip_chain结构来表示,缺省有input,ouptput,forward三条链。在配置规则的时候,也可以添加新的链。每条链事实上就是一组相关的规则,以链表的形式存储。 struct ip_chain { ip_chainlabel label; /* Defines the label for each block */ struct ip_chain *next; /* Pointer to next block */ struct ip_fwkernel *chain; /* Pointer to first rule in block */ __u32 refcount; /* Number of refernces to block */ int policy; /* Default rule for chain. Only * * used in built in chains */ struct ip_reent reent[0]; /* Actually several of these */ }; 每条规则用一个ip_fwkernel结构表示: struct ip_fwkernel { struct ip_fw ipfw; struct ip_fwkernel *next; /* where to go next if current * rule doesn't match */ struct ip_chain *branch; /* which branch to jump to if * current rule matches */ int simplebranch; /* Use this if branch == NULL */ struct ip_counters counters[0]; /* Actually several of these */ }; ip_fwkernel中的一个重要部分就是ip_fw,用来表示待匹配的数据包消息: struct ip_fw { struct in_addr fw_src, fw_dst; /* Source and destination IP addr */ struct in_addr fw_smsk, fw_dmsk; /* Mask for src and dest IP addr */ __u32 fw_mark; /* ID to stamp on packet */ __u16 fw_proto; /* Protocol, 0 = ANY */ __u16 fw_flg; /* Flags word */ __u16 fw_invflg; /* Inverse flags */ __u16 fw_spts[2]; /* Source port range. */ __u16 fw_dpts[2]; /* Destination port range. */ __u16 fw_redirpt; /* Port to redirect to. */ __u16 fw_outputsize; /* Max amount to output to NETLINK */ char fw_vianame[IFNAMSIZ]; /* name of interface "via" */ __u8 fw_tosand, fw_tosxor; /* Revised packet priority */ }; 2.2内核中网络包与规则的实际匹配在ip_fw_check中进行。 4.2 iptables 一条规则分为三部分: struct ipt_entry file://主要用来匹配IP头 struct ip_match file://额外的匹配(tcp头,mac地址等) struct ip_target file://除缺省的动作外(如ACCEPT,DROP),可以增加新的(如REJECT)。 man iptable: >A firewall rule specifies criteria for a packet, and a >target. If the packet does not match, the next rule in >the chain is the examined; if it does match, then the next >rule is specified by the value of the target, which can be >the name of a user-defined chain, or one of the special >values ACCEPT, DROP, QUEUE, or RETURN. 2.4内核中网络包与规则的实际匹配在ip_do_table中进行。这段代码的流程在 netfilter hacking howto 4.1.3描述的非常清楚。
简化代码如下: /* Returns one of the generic firewall policies, like NF_ACCEPT. */ unsigned int ipt_do_table(struct sk_buff **pskb, unsigned int hook, const struct net_device *in, const struct net_device *out, struct ipt_table *table, void *userdata) { struct ipt_entry *e; struct ipt_entry_target *t; unsigned int verdict = NF_DROP;
table_base = (void *)table->private->entries + TABLE_OFFSET(table->private, cpu_number_map(smp_processor_id())); e = get_entry(table_base, table->private->hook_entry[hook]); ... ip_packet_match(ip, indev, outdev, &e->ip, offset); ... IPT_MATCH_ITERATE(e, do_match, *pskb, in, out, offset, protohdr, datalen, &hotdrop) ... t = ipt_get_target(e); ... verdict = t->u.kernel.target->target(pskb, hook, in, out, t->data, userdata);//非标准的target走这一步 ... return verdict; } 流程: --->NF_HOOK();(/include/linux/netfilter.h) --->nf_hook_slow;(/net/core/netfilter.c) --->nf_iterate();(/net/core/netfilter.c) --->然后运行登记的函数;如果你希望有一套ipt_entry结构规则,并将它放到table里,你此时便可调用ipt_do_table来匹配。 在2.4内核中,规则本身也是可扩展的,体现可自己定义并添加新的ip_match和ip_target上。 4.2 FW1 未作分析。 五 与应用层的交互 5.0 综述 防火墙除了内核里的功能以外,还需要在应用层有相应的的配置工具,如添加修改规则等,这就涉及如何与内核通信的问题。 内核模块有三种办法与进程打交道:首先是系统调用,缺点是必须添加新的系统调用或修改原有的,造成对内核代码原有结构的变换;第二种办法是通过设备文件(/dev目录下的文件),不必修改编译原有的代码,但在使用之前要先用mknod命令产生一个这样的设备;第三个办法便是使用proc文件系统。 5.1 ipchains 由于ipchains是已经是内核的正式一部分,它采用了修改系统调用的办法来添加修改命令,采用的办法就是扩展setsockopt系统调用: int setsockopt (int socket, IPPROTO_IP, int command, void *data, int length) man ipfw可以获得这方面的细节。 ipchains应用程序首先要需要建立一个raw socket(libipfwc.c),然后在之上调用setsockopt。 sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW) 调用顺序: ipchains在应用层调用setsockopt,进入内核: --->sys_socketcall(net/socket.c) --->sys_setsockopt(net/socket.c) --->inet_setsockopt(net/ipv4/af_inet.c) --->sock_setsockopt(net/core/sock.c) --->raw_setsockopt(net/ipv4/raw.c) --->ip_setsockopt(net/ipv4/ip_sockglue.c) --->ip_fw_ctl(net/ipv4/ip_fw.c) 5.2 iptables 原理同ipchains, 但内部命令格式作了大幅简化。详见nf_setsockopt()。 5.3 FW1 FW1 登记了一个字符设备,通过它来进行用户空间与内核空间的交互。相关代码(从汇编代码翻译成的C程序)如下: static unsigned int fw_major=0; static struct file_operations fw_fops= { NULL, /* lseek */ fw_read, /* read */ fw_write, /* write */ NULL, /* readdir */ fw_poll, /* poll */ fw_ioctl, /* ioctl */ NULL, /* mmap */ fw_open, /* open */ NULL, /* flush */ fw_release /* release */ NULL, /* fsync */ }; int init_module() { ... /*man register_chrdev On success, register_chrdev returns 0 if major is a number other then 0, otherwise Linux will choose a major number and return the chosen value.*/ if(fw_major=register_chrdev(UNNAMED_MAJOR, “fw”, &fw_fops)) return -1; ... } void cleanup_module() { ... unregister_chrdev(fw_major, "fw"); ... fw_ioctl()用来做配置工作。 六 碎片的处理 6.0 综述 关于分片重组的实现可参看【13】。 6.1 ipchains 在2.2内核中除非设置了alway_defrag,否则包过滤模块不会对经过的包进行重组。它采用的办法是只看第一片,因为只有这一片中有完整的头信息,而后序的分片一律允许通过。为了防止分片欺骗(比如第一片极小,把传输层信息放到了第二片中),对这种正常情况中不可能出现的包做了而外的处理(太小的分片会被丢弃)。 6.2 iptables 在2.4内核有些变化,如果启动了conncetion track,所有到达防火墙的碎片都会重组,这点在以后可能会变化,正如howto 中说的,现在考虑的还只是功能的完备性,效率还要在以后的版本中改进。检测点也有了变化,输入检测在改在重组之后。 6.3 FW1 FW1对分片也做了额外处理,但目前尚未对其实现做仔细的分析。 七 状态检测 7.0 综述 基于状态的检测对管理规则提出了非常大的方便,现在已成了防火墙的一项基本要求。但目的明确之后,其实现可以选择多种不同的方法。 7.1 ipchains ipchains本身不能完成状态检测,但有几份pacth为它做了一下这方面的补充,采用的是简单的动态添加规则的办法,这是作者对其的介绍: > I believe it does exactly what I want: Installing a temporary > "backward"-rule to let packets in as a response to an > outgoing request. 7.2 iptables 在2.4内核中,基于状态的检测已经实现,利用的是connection track模块。此模块检查所有到来的数据包,将得到的状态(enum ip_conntrack_status)保留在sk_buff结构中(即skb->nfct,可通过ip_conntrack_get()得到)。 在规则中要指明状态信息(作为一个ipt_match),既实际上仍是比较每一条规则。从效率上,这种处理方式感觉不如下面FW1采用的方式好。 7.3 FW1 这段的代码没有做分析,但有一些文章通过黑箱操作的办法“猜测“出了它的实现原理,如【1】。除规则表以外,FW1另外维护一份状态表。当一个新的连接发生的时候,FW1与规则表配备,如果允许通过的话,则在状态表中建立相应表项。以后的数据过来的时候首先匹配状态表,如果它属于一个连接,便允许通过,而不再检查规则表。 草草看了一下BSD下的防火墙ipfilter的howto,感觉它的实现与FW1基本相同。 八 函数指针的问题