Iptables应用层库函数实现机制:(iptables-1.2.7)
1. iptables命令概述
首先分析iptables命令的特点:命令、选项、匹配(match)、target四部分组成。命令中只允许一个命令,每次添加一个命令时通过add_command来检验命令是否唯一。允许多个不同的选项但是不允许多个相同的选项(除了--verbose选项,-v选项可以指定多个,例如iptables –L INPUT –v –v其显示的信息比iptables –L INPUT –v要多,会把内核里有关iptables规则相对位置等等更详细的信息都显示出来-----主要是用于规则信息的调试),每次添加一个选项都通过set_option来实现。可通过Inverse_for_options数组查看哪些选项允许前面带有!符号,SOURCE、DESTINATION、PROTOCOL、VIANAMEIN、VIANAMEOUT、FRAGMENT选项允许前面带有!符号。分片选项-f的!有点特殊是这样实现的iptables … ! –f …是放在-f前面而不是后面,那么在命令解析中是怎样实现这一点的,首先命令解析出!是无效选项case 1:在无效选项处理中将invert标志为1。
1.1 命令:
命令类型:INSERT、APPEND、DELETE、REPLACE、LIST、FLUSH、ZERO、NEWCHAIN、DELETECHAIN、RENAMECHAIN、POLICY
1.2 选项:
选项类型:VERBOSE、NUMERIC、LINENUMBERS、COUNTERS、SOURCE、DESTINATION、PROTOCOL、JUMP、EXACT、VIANAMEIN、VIANAMEOUT、FRAGMENT
1.3 匹配:
匹配的种类:第一类是generic matches(通用的匹配),适用于所有的规则;第二类是TCP matches,顾名思义,这只能用于TCP包;第三类是UDP matches,当然它只能用在UDP包上了;第四类是ICMP matches ,针对ICMP包的;第五类比较特殊,针对的是状态(state),所有者(owner)和访问的频率限制(limit)等,它们已经被分到更多的小类当中,尽管它们并不是完全不同的。
对于通用匹配、隐含匹配(TCP matches、UDP matches、ICMP matches)其实已经包含在选项中,只是它们是特殊的选项。对于第五类匹配(隐含匹配)是给用户进行匹配模块扩展用的。
1.3.1 通用匹配
无论我们使用的是何种协议,也不管我们又装入了匹配的何种扩展,通用匹配都使可用的。也就是说,它们可以直接使用,而不需要什么前提条件,在后面你会看到,有很多匹配操作是需要其他的匹配作为前提的。 如上面选项:PROTOCOL、SOURCE、DESTINATION、VIANAMEIN、VIANAMEOUT、FRAGMENT。
1.3.2 隐含匹配
这种匹配操作是自动地或隐含地装载入内核的。例如我们使用—protocol=tcp 时,不需再装入任何东西就可以匹配只有IP包才有的一些特点。现在有三种隐含的匹配针对三种不同的协议,即TCP matches,UDP matches和 ICMP matches。它们分别包括一套只适用于相应协议的判别标准。相对于隐含匹配的是显式匹配,它们必须使用-m或--match被明确地装载。隐含匹配用的命令格式是iptables … -p tcp …用-p而不是-m来指定,隐含匹配还会更改(struct ipt_entry *)a->ip.proto,显示匹配就不会更改该结构单元。
1.3.3 显式匹配
显式匹配必须用-m或--match装载,比如要使用状态匹配就必须使用-m state。有些匹配还需要指定协议,有些就不需要,比如连接状态就不要。这些状态是NEW(还未建立好的连接的第一个包), ESTABLISHED(已建立的连接,也就是已经在内核里注册过的),RELATED(由已经存在的、处于已建立状态的连接生成的新连接),等等。有些匹配还处在开发阶段,或者还只是为了说明iptables的强大能力。这说明不是所有的匹配一开始就是实用的,但以后你可能会用到它。随着iptables 新版本的发布,会有一些新的匹配可用。隐含匹配和显式匹配最大的区别就是一个是跟随协议匹配自动装载的,一个是显式装载的。(其实在应用层隐含匹配运行时加载的模块形式如libipt_udp.so、libipt_tcp.so、libipt_icmp.so是在运行时才加载的,而内核模块ip_tables.ko中早已经包含了对TCP、UDP、ICMP隐含匹配的支持,不必在运行过程忠加载)。
2. iptables-save、iptables-restore命令介绍
iptables-save将表中的规则信息转换成命令形式保存到输出端所指向的文件,-c选项保存时指定了计数信息。iptables-restore依据文件中所记录的有关表规则信息(命令+其它一些信息)重新定义防火墙规则。例如:iptables –F;iptables –A INPUT –p tcp –sport 79 –dport 85 –j ACCEPT;iptables –A INPUT –m limit –j ACCEPT,然后iptables-save –t filter得到结果:
# Generated by iptables-save v1.2.7a on Wed Feb 28 22:04:58 2007
*filter
:INPUT ACCEPT [2518:174845]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [16271:1049199]
-A INPUT -p tcp -m tcp --sport 79 --dport 85 -j ACCEPT
-A INPUT -m limit --limit 3/hour -j ACCEPT
COMMIT
# Completed on Wed Feb 28 22:04:58 2007
可以看出其实ipables-save就是读取表中的规则信息转换成命令,iptables-save –t filter -c得到结果:
# Generated by iptables-save v1.2.7a on Wed Feb 28 22:08:04 2007
*filter
:INPUT ACCEPT [4604:306714]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [18350:1180946]
[0:0] -A INPUT -p tcp -m tcp --sport 79 --dport 85 -j ACCEPT
[5:360] -A INPUT -m limit --limit 3/hour -j ACCEPT
COMMIT
# Completed on Wed Feb 28 22:08:04 2007
中括号[]里记录的就是通过该规则的数据包和字节总数。
3. do_command函数介绍:
(1)iptables命令解释得到的命令、选项、匹配、target等信息存放在command、options、m、target中,命令与选项必须是合法搭配的(通过数组commands_v_options中的记录来保证命令与选项的合法搭配),通过generic_opt_check(command, options)函数来判断其搭配的合法性。
遇到选项--protocol、--source-、--destination、--in-interface、--out-interface、--fragments,会预先设置nfcache(结构体struct ipt_entry中字段nfcache),例如fw.nfcache=NFC_IP_PROTO、NFC_IP_SRC、NFC_IP_DST、NFC_IP_IF_IN、NFC_IP_IF_OUT、NFC_IP_FRAG。
(2)iptables命令解释完,就要将用户定义的规则信息载入内核iptables中去,其具体步骤如下:
①获得指向内核iptables对应表的句柄,(参阅TC_INIT函数实现)应用程序可以通过创建一个"原始IP套接字"获得访问Netfilter的句柄,socket(TC_AF, SOCK_RAW, IPPROTO_RAW) 其中TC_AF就是AF_INET,该函数所返回的描述符成为getsockopt()和setsockopt()系统调用来读取、更改Netfilter设置。
在libiptc.c文件中经常碰到一些对函数、结构体的宏定义,这些宏定义的命名规则是这样的,例如:对结构体的宏定义规则是这样的
#define STRUCT_GETINFO struct ipt_getinfo
#define STRUCT_GET_ENTRIES struct ipt_get_entries
前面有一个STRUCT
对函数的宏定义规则是这样的
#define TC_INIT iptc_init
#define TC_IS_CHAIN iptc_is_chain
前面有一个TC
通过getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s)命令SO_GET_INFO获得表信息复制给info所指向的struct ipt_getinfo类型的存储空间,结构体struct ipt_getinfo与struct ipt_table_info的一些字段定义是一样的,只是struct ipt_table_info内核用来表示iptables中的规则信息,struct ipt_getinfo主要是用来将规则信息传递给应用程序,对结构体中具体结构单元的解释可以参见图1中的struct ipt_table_info。
通过info获得内核iptables具体表信息之后,alloc_handle来获得指向内核iptables对应表的句柄。句柄的结构类型为struct iptc_handle(对该结构体的说明可参看libiptc.c源代码注释)
通过getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,&tmp)命令SO_GET_ENTRIES获得表中的规则库信息存放在句柄中,从而完成获得内核中具体表的句柄操作。
②若返回的句柄为空,那么很可能是模块ip_tables.o还没有加载上去。因此加载模块ip_tables.o。
重要的函数
iptc_strerror用于输出错误信息(#define iptc_strerror TC_STRERROR)
const char *TC_STRERROR(int err)
在iptables实现中有很大一部分代码是用于错误处理的代码。所以单独引入了一个用于提供错误输出信息的函数TC_STRERROR。
iptc_is_chain其原型是int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle)
用来判断chain是否为一个链名,为了加快对规则库中每条链上的各个规则的快速搜索,引入了cache_chain_heads、cache_chain_iteration等结构单元(在struct iptc_handle结构体中)对其具体解释可参见libiptc.c中对该结构体的解释,其具体含义可参见图2。
③对命令中是否指定target的判断
1. 命令中未指定target:例如iptables –L INPUT或iptables –F INPUT或iptables –Z INPUT等等。
2. 命令中指定的target是一个自定义链名。
前面这两种情况do_command的处理方式是给他们指定的target为IPT_STANDARD_TARGET,target的名字为jumpto(即自定义链则为自定义链名,未指定target则为空名字)
3. 命令中指定的target不存在:不存在对应target名字的加载程序。
接下来根据前面命令解释得到的有关选项的信息(存放在fw、iptables_matches中)、target信息产生一条规则(e = generate_entry(&fw, iptables_matches, target->t);)。
④依据命令中指定的命令进行处理
首先对target类型进行总结:
在iptables命令行中通过-j选项来对target进行指定,可以指定的target类型如下:
1. iptables中已经定义的五个target,当然""在命令中是不能指定的,只是当命令中没有指定-j选项的时候,do_command会为其指定一个IPT_STANDARD_TARGET类型的target。这些是IPT_STANDARD_TARGET类型的target
例如iptables ... -j DROP/ACCEPT/QUEUE/RETURN/"",命令解析之后得到
target.u.user.name="DROP"/"ACCEPT"/"QUEUE"/"RETURN"/""
当转换成内核的规则时就需要对该target部分进行修改,
将target.u.kernel.target.name=IPT_STANDARD_TARGET="",
对于这些类型的target,内核中单独为其分配了一个数据结构struct ipt_standard_target,所以对于具体是哪一个target是由verdict来决定的。
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define RETURN IPT_RETURN
#define IPT_RETURN (-NF_MAX_VERDICT - 1)
#define NF_MAX_VERDICT NF_REPEAT (linux-2.4.20-8/include/linux/netfilter.h)
verdict=-NF_ACCEPT – 1(对应ACCEPT)
verdict= -NF_DROP - 1 (对应DROP)
verdict= -NF_QUEUE - 1 (对应QUEUE)
verdict=RETURN(对应RETURN)
2.所要跳转到的自定义链
例如iptables -N newchain
iptables ... -j newchain,命令解析之后得到target.u.user.name=newchain
转换后得到target.u.kernel.target.name=IPT_STANDARD_TARGET=""
verdict>0,verdict为自定义链起始位置的相对偏移量,而且这个自定义链的起始位置是第二条规则,不是target为error target类型的首规则。
3.用户扩展的target.例如iptables ... -j DNAT,用户可以自己扩展iptables,
命令解析之后得到target.u.user.name=DNAT
转换后得到target.u.kernel.target.name保持不变。
对应的扩展库函数为libipt_DNAT.so
4.上面是命令中可以指定的target,还有一类target在内核中也单独为其分配了一个数据结构struct ipt_error_target,这类target的用处,在每条自定义链的首规则的target部分就是struct ipt_error_target,其target.u.user.name=ERROR,struct ipt_error_target的errorname[]=自定义链名。每个表的最后一条规则也是struct ipt_error_target,其target.u.user.name=ERROR,struct ipt_error_target的errorname[]=ERROR。
表中各条链规则的分布介绍:
每个表中各条链的分布情况可参见图2,前面是内建链,表的最后一条规则比较特殊是ipt_entry+ipt_error_target,在内建链与表的最后一条规则之间就是自定义链的规则。自定义链中的规则分布形式是首规则是记录有自定义链名且target为ipt_error_target的规则,尾规则是target部分为RETURN类型的规则。内建链的规则分布形式是尾规则是target为ACCEPT或DROP类型的规则。
各个命令的处理:
添加规则命令:--append -A
显示规则信息命令:--list -L
创建一条自定义链:--new-chain、-N
通过TC_CREATE_CHAIN来创建自定义链,这里有个疑问:??应该根据对齐后得到的结构长度来分配newc大小,而不是这里一开始就将newc大小定死了??
个人理解:在TC_CREAT_CHAIN函数实现中所定义的变量newc本身就已经是在__alignof__(struct ipt_entry)边界上对齐的。
struct {
STRUCT_ENTRY head;
struct ipt_error_target name;
STRUCT_ENTRY ret;
STRUCT_STANDARD_TARGET target;
} newc;
⑤ret=do_command(…);(iptables-standalone.c)
do_command处理完毕,返回一个值给ret。然后通过TC_COMMIT将更新后的规则信息载入内核
重要的函数、宏及数据结构
struct ipt_replace
用于用户空间将其所新定义的规则信息载入内核空间,通过命令IPT_SO_SET_REPLACE从而将规则信息载入内核。
iptc_commint函数原型:int TC_COMMIT(TC_HANDLE_T *handle)
通过命令IPT_SO_SET_REPLACE,
if(setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,sizeof(*repl) + (*handle)->entries.size)
该命令在内核中主要实现了两个主要功能:
1. 将repl->entries所表示的新的表规则库信息载入到内核中去,而内核里原有的旧规则库信息被删除。
2. 将旧规则库信息中所记录的数据包通过计数重新累计,并记录在用户空间的数据repl->counters中。
在这里要对结构体struct iptc_handle中的counter_map进行详细的介绍,counter_map的数据类型是struct counter_map,它的主要用途是:依据counter_map所记录的每个规则计数映射类型结合通过命令IPT_SO_SET_REPLACE所获得的旧规则库信息中所记录的数据包通过个数repl->counters,来计算最新规则中所记录的数据包通过个数。现对结构体struct counter_map所记录的计数映射类型maptype进行一一介绍:
COUNTER_MAP_NOMAP:遇到这种计数映射类型,对应规则计数都赋为零。
COUNTER_MAP_NORMAL_MAP:结合mappos(用来记录该规则所对应的旧规则索引号)可以计算出最新规则的计数。在获得指向内核iptables对应表的句柄时,所分配的counter_map就是对应旧规则的计数映射类型为COUNTER_MAP_NORMAL_MAP。
COUNTER_MAP_ZEROED:遇到这种计数映射类型,对应规则计数赋为零。
例如:命令-Z便是将对应链中每一条规则计数赋为零。
COUNTER_MAP_SET:设置对应规则的计数,mappos信息在这里没有被用到
例如:添加一条规则,该添加规则的计数就是零,通过设置规则中ipt_counter为零来实现。还有设置计数选项--set-counters。
阅读(1444) | 评论(0) | 转发(0) |