上一篇文章分析了iptables代码下发运作的流程细节,篇幅有限还有很多需要补充.关于netfilter的框架网上已经被讲烂了,框架很简单,但是实现却不简单.但不论什么都要最终归到实际应用上,才能体现其价值.
下面我们就以iptables1.4.21 ubuntu14 32位 内核版本 3.13.0为环境来开发一个最常规的扩展应用.
首先下载iptables源码:http://www.netfilter.org (这个网站有很多东西,可以好好看看)
关于linux kernel netfilter开启的配置,这里不多说,默认内核已经配置好了.(当然对于ubuntu14 系统默认把netfilter大部分都编译成了内核模块,需要什么自己手动安装即可)
先说说编译iptables,很简单 看看里面的说明文档即可,例INSTALL:
$./configure
$make
$make install
./configure 还可以支持一些选项参数 ,里面都有说明。例子如下:
./configure --prefix=/home/iptables --host=arm-linux // host可以不指定,自动判定,默认就是x86
这里还要补充点知识:动作的说明:
iptables动作---------DROP、ACCEPT、REJECT
◆ACCEPT
一旦包满足了指定的匹配条件,就会被ACCEPT,并且不会再去匹配当前链中的其他规则或同一个表内的其他规则,但它还要通过其他表中的链
◆DROP
如果包符合条件,这个target就会把它丢掉,也就是说包的生命到此结束,不会再向前走一步,效果就是包被阻塞了。在某些情况下,这个target会引起意外的结果,因为它不会向发送者返回任何信息,也不会向路由器返回信息,这就可能会使连接的另一方的sockets因苦等回音而亡:)
解决这个问题的较好的办法是使用REJECT target,(注:因为它在丢弃包的同时还会向发送者返回一个错误信息,这样另一方就能正常结束),尤其是在阻止端口扫描工具获得更多的信息时,可以隐蔽被过滤掉的端口等等(译者注:因为扫描工具扫描一个端口时,如果没有返回信息,一般会认为端口未打开或被防火墙等设备过滤掉了)。还要注意如果包在子链中被DROP了,那么它在主链里也不会再继续前进,不管是在当前的表还是在其他表里。
◆REJECT
REJECT和DROP基本一样,区别在于它除了阻塞包之外,还向发送者返回错误信息。现在,此target还只能用在INPUT、FORWARD、OUTPUT和它们的子链里,而且包含 REJECT的链也只能被它们调用,否则不能发挥作用。它只有一个选项,是用来控制返回的错误信息的种类的。
还有其他的动作:
LOG 用来记录与数据包相关的信息
MARK 设置mark值,这个值是一个无符号的整数
MASQUERADE 和SNAT的作用相同,区别在于它不需要指定--to-source
SNAT 源网络地址转换
DNAT 目的网络地址转换
REDIRECT 转发数据包一另一个端口
REJECT REJECT和DROP都会将数据包丢弃,区别在于REJECT除了丢弃数据包外,还向发送者返回错误信息
RETURN 使数据包返回上一层
TOS 用来设置IP头部中的Type Of Service字段
TTL 用于修改IP头部中Time To Live字段的值
ULOG ULOG可以在用户空间记录被匹配的包的信息,这些信息和整个包都会通过netlink socket被多播
QUEUE 为用户空间的程序或应用软件管理包队列
MIRROR 颠倒IP头部中的源目地址,然后再转发包
先一个实际的命令应用:
IPT -A INPUT -m pkttype –pkt-type broadcast -j REJECT
这里我们自己注册一个match 传递自己的参数并解析处理。
功能是禁止大于特定长度的ip报文通过 size由我们指定。
需要两个部分的工作
1.用户空间match的注册
2.内核空间match的注册
根据代码里pkttype的实际例子作为参考,很快我们就能干一票了.
需要修改的文件:
用户空间
Xt_pktsize.c // 路径extensions下
Xt_pktsize.h // 路径 include/linux/netfilter/
内核:这里编译为模块的方式
Xt_pktsize.c
Xt_pktsize.h
注册match当然首先要初始化它各个节点的元素
用户空间代码如下:
xt_pktsize.h
xt_pktsize.c
通过代码我们看到主要工作就是初始化struct xtables_match,然后注册而已. 它的核心函数是x6_parse/parse
或者
例子中为 pktsize_parse函数,之前我们已经分析过如何调用到某一个match的parse函数.主要解析ipt_entry_match里data(其实就是解析命令参数赋值给它)
除了parse函数还有options,即解析--XXX的参数需要用到的东西,也需要我们去填写。
下面看看内核部分:
xt_pktsize.h
xt_pktsize.c
这里我们计算ip报文的有效载荷的长度即ip包的总长度-ip头的长度
size=ntohs(iph->tot_len) - (iph->ihl*4);
同样或许我们还需要判断协议类型 protocal字等,简单说几个常用:
Decimal Keyword Protocol References
------- --------------- --------------------------------------- ------------------
1 ICMP Internet Control Message [RFC792]
6 TCP Transmission Control [RFC793]
17 UDP User Datagram [RFC768][JBP]
...
ip报文格式如下:
版本:占4位(bit),指IP协议的版本号。目前的主要版本为IPV4,即第4版本号,也有一些教育网和科研机构在使用IPV6。在进行通信时,通信双方的IP协议版本号必须一致,否则无法直接通信。
首部长度:占4位(bit),指IP报文头的长度。最大的长度(即4个bit都为1时)为15个长度单位,每个长度单位为4字节(TCP/IP标准,DoubleWord),所以IP协议报文头的最大长度为60个字节,最短为上图所示的20个字节。
服务类型:占8位(bit),用来获得更好的服务。其中的前3位表示报文的优先级,后面的几位分别表示要求更低时延、更高的吞吐量、更高的可靠性、更低的路由代价等。对应位为1即有相应要求,为0则不要求。
总长度:16位(bit),指报文的总长度。注意这里的单位为字节,而不是4字节,所以一个IP报文的的最大长度为65535个字节。
标识(identification):该字段标记当前分片为第几个分片,在数据报重组时很有用。
标志(flag):该字段用于标记该报文是否为分片(有一些可能不需要分片,或不希望分片),后面是否还有分片(是否是最后一个分片)。
片偏移:指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。
生存时间:TTL(Time to Live)。该字段表明当前报文还能生存多久。每经过1ms或者一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。使用过Ping命令的用户应该有印象,在windows中输入ping命令,在返回的结果中即有TTL的数值。
协议:该字段指出在上层(网络7层结构或TCP/IP的传输层)使用的协议,可能的协议有UDP、TCP、ICMP、IGMP、IGP等。
首部校验和:用于检验IP报文头部在传播的过程中是否出错,主要校验报文头中是否有某一个或几个bit被污染或修改了。
源IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
目的IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
当然对ip理解的越深刻越好了,那么这样就完成了一个简单的match扩展的例子,仅仅作为抛砖引玉,一个小小的开始.
下面我们就以iptables1.4.21 ubuntu14 32位 内核版本 3.13.0为环境来开发一个最常规的扩展应用.
首先下载iptables源码:http://www.netfilter.org (这个网站有很多东西,可以好好看看)
关于linux kernel netfilter开启的配置,这里不多说,默认内核已经配置好了.(当然对于ubuntu14 系统默认把netfilter大部分都编译成了内核模块,需要什么自己手动安装即可)
先说说编译iptables,很简单 看看里面的说明文档即可,例INSTALL:
$./configure
$make
$make install
./configure 还可以支持一些选项参数 ,里面都有说明。例子如下:
./configure --prefix=/home/iptables --host=arm-linux // host可以不指定,自动判定,默认就是x86
这里还要补充点知识:动作的说明:
iptables动作---------DROP、ACCEPT、REJECT
◆ACCEPT
一旦包满足了指定的匹配条件,就会被ACCEPT,并且不会再去匹配当前链中的其他规则或同一个表内的其他规则,但它还要通过其他表中的链
◆DROP
如果包符合条件,这个target就会把它丢掉,也就是说包的生命到此结束,不会再向前走一步,效果就是包被阻塞了。在某些情况下,这个target会引起意外的结果,因为它不会向发送者返回任何信息,也不会向路由器返回信息,这就可能会使连接的另一方的sockets因苦等回音而亡:)
解决这个问题的较好的办法是使用REJECT target,(注:因为它在丢弃包的同时还会向发送者返回一个错误信息,这样另一方就能正常结束),尤其是在阻止端口扫描工具获得更多的信息时,可以隐蔽被过滤掉的端口等等(译者注:因为扫描工具扫描一个端口时,如果没有返回信息,一般会认为端口未打开或被防火墙等设备过滤掉了)。还要注意如果包在子链中被DROP了,那么它在主链里也不会再继续前进,不管是在当前的表还是在其他表里。
◆REJECT
REJECT和DROP基本一样,区别在于它除了阻塞包之外,还向发送者返回错误信息。现在,此target还只能用在INPUT、FORWARD、OUTPUT和它们的子链里,而且包含 REJECT的链也只能被它们调用,否则不能发挥作用。它只有一个选项,是用来控制返回的错误信息的种类的。
还有其他的动作:
LOG 用来记录与数据包相关的信息
MARK 设置mark值,这个值是一个无符号的整数
MASQUERADE 和SNAT的作用相同,区别在于它不需要指定--to-source
SNAT 源网络地址转换
DNAT 目的网络地址转换
REDIRECT 转发数据包一另一个端口
REJECT REJECT和DROP都会将数据包丢弃,区别在于REJECT除了丢弃数据包外,还向发送者返回错误信息
RETURN 使数据包返回上一层
TOS 用来设置IP头部中的Type Of Service字段
TTL 用于修改IP头部中Time To Live字段的值
ULOG ULOG可以在用户空间记录被匹配的包的信息,这些信息和整个包都会通过netlink socket被多播
QUEUE 为用户空间的程序或应用软件管理包队列
MIRROR 颠倒IP头部中的源目地址,然后再转发包
先一个实际的命令应用:
IPT -A INPUT -m pkttype –pkt-type broadcast -j REJECT
这里我们自己注册一个match 传递自己的参数并解析处理。
功能是禁止大于特定长度的ip报文通过 size由我们指定。
需要两个部分的工作
1.用户空间match的注册
2.内核空间match的注册
根据代码里pkttype的实际例子作为参考,很快我们就能干一票了.
需要修改的文件:
用户空间
Xt_pktsize.c // 路径extensions下
Xt_pktsize.h // 路径 include/linux/netfilter/
内核:这里编译为模块的方式
Xt_pktsize.c
Xt_pktsize.h
注册match当然首先要初始化它各个节点的元素
用户空间代码如下:
xt_pktsize.h
点击(此处)折叠或打开
- #ifndef _XT_PKTSIZE_H
- #define _XT_PKTSIZE_H
-
- struct xt_pktsize_info {
- int pktsize;
- int invert;
- };
- #endif /*_XT_PKTSIZE_H*/
点击(此处)折叠或打开
- /*
- * Shared library add-on to iptables to match
- * packets by their size
- *
- */
- #include stdio.h>
- #include string.h>
- #include xtables.h>
- #include linux/if_packet.h>
- #include linux/netfilter/xt_pktsize.h>
-
- enum {
- O_pktsize = 0,
- };
-
- struct pktsizes {
- const char *name;
- unsigned char pktsize;
- unsigned char printhelp;
- const char *help;
- };
-
-
-
- static void print_types(void)
- {
- unsigned int i;
-
- printf("Valid packet sizes:64-65535\n");
-
- }
-
- static void pktsize_help(void)
- {
- printf(
- "pktsize match options:\n"
- "[!] --pkt-size packetsize match packet size\n");
- print_types();
- }
-
- static const struct xt_option_entry pktsize_opts[] = {
- {.name = "pkt-size", .id = O_pktsize, .type = XTTYPE_STRING,
- .flags = XTOPT_MAND | XTOPT_INVERT},
- XTOPT_TABLEEND,
- };
-
- static void parse_pktsize(const char *pktsize, struct xt_pktsize_info *info)
- {
- unsigned int i,size;
- char *buffer;
-
- printf("pktsize is %s...\n",pktsize);
- buffer = strdup(pktsize);
- if(!xtables_strtoui(buffer, NULL, &size, 0, UINT16_MAX))
- xtables_error(PARAMETER_PROBLEM, "Bad packet size '%s'", pktsize);
-
- info->pktsize=size;
- free(buffer);
-
- }
-
- static void pktsize_parse(struct xt_option_call *cb)
- {
- struct xt_pktsize_info *info = cb->data;
-
- xtables_option_parse(cb);
- parse_pktsize(cb->arg, info);
- if (cb->invert)
- info->invert = 1;
- }
-
- static void print_pktsize(const struct xt_pktsize_info *info)
- {
- unsigned int i;
-
-
-
- printf("%d", info->pktsize); /* in case we didn't find an entry in named-packtes */
- }
-
- static void pktsize_print(const void *ip, const struct xt_entry_match *match,
- int numeric)
- {
- const struct xt_pktsize_info *info = (const void *)match->data;
-
- printf(" pktsize %s= ", info->invert ? "!" : "");
- print_pktsize(info);
- }
-
- static void pktsize_save(const void *ip, const struct xt_entry_match *match)
- {
- const struct xt_pktsize_info *info = (const void *)match->data;
-
- printf("%s --pkt-type ", info->invert ? " !" : "");
- print_pktsize(info);
- }
-
- static struct xtables_match pktsize_match = {
- .family = NFPROTO_UNSPEC,
- .name = "pktsize",
- .version = XTABLES_VERSION,
- .size = XT_ALIGN(sizeof(struct xt_pktsize_info)),
- .userspacesize = XT_ALIGN(sizeof(struct xt_pktsize_info)),
- .help = pktsize_help,
- .print = pktsize_print,
- .save = pktsize_save,
- .x6_parse = pktsize_parse,
- .x6_options = pktsize_opts,
- };
-
- void _init(void)
- {
- xtables_register_match(&pktsize_match);
- }
点击(此处)折叠或打开
- /* Function which parses command options; returns true if it
- ate an option */
- /* entry is struct ipt_entry for example */
- int (*parse)(int c, char **argv, int invert, unsigned int *flags,
- const void *entry,
- struct xt_entry_match **match);
点击(此处)折叠或打开
- /* New parser */
- void (*x6_parse)(struct xt_option_call *);
除了parse函数还有options,即解析--XXX的参数需要用到的东西,也需要我们去填写。
下面看看内核部分:
xt_pktsize.h
点击(此处)折叠或打开
- #ifndef _XT_PKTSIZE_H
- #define _XT_PKTSIZE_H
-
- struct xt_pktsize_info {
- int pktsize;
- int invert;
- };
- #endif /*_XT_PKTSIZE_H*/
点击(此处)折叠或打开
- #include linux/module.h>
- #include linux/skbuff.h>
- #include linux/if_ether.h>
- #include linux/if_packet.h>
- #include linux/in.h>
- #include linux/ip.h>
- #include linux/ipv6.h>
-
- //#include linux/netfilter/xt_pktsize.h>
- #include "xt_pktsize.h"
- #include linux/netfilter/x_tables.h>
-
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("jack chen");
- MODULE_DESCRIPTION("Xtables: link layer packet size match");
- MODULE_ALIAS("ipt_pktsize");
- MODULE_ALIAS("ip6t_pktsize");
-
- static bool
- pktsize_mt(const struct sk_buff *skb, struct xt_action_param *par)
- {
- const struct xt_pktsize_info *info = par->matchinfo;
- u_int32_t size;
- const struct iphdr *iph = ip_hdr(skb);
- size=ntohs(iph->tot_len) - (iph->ihl*4);
- printk("ip data pktsize is %d......,%d..,proto :%x\n",size,info->pktsize,iph->protocol);
-
- return (info->pktsize size) ;
- //return 1;
- }
-
- static struct xt_match pktsize_mt_reg __read_mostly = {
- .name = "pktsize",
- .revision = 0,
- .family = NFPROTO_UNSPEC,
- .match = pktsize_mt,
- .matchsize = sizeof(struct xt_pktsize_info),
- .me = THIS_MODULE,
- };
-
- static int __init pktsize_mt_init(void)
- {
- return xt_register_match(&pktsize_mt_reg);
- }
-
- static void __exit pktsize_mt_exit(void)
- {
- xt_unregister_match(&pktsize_mt_reg);
- }
-
- module_init(pktsize_mt_init);
- module_exit(pktsize_mt_exit);
size=ntohs(iph->tot_len) - (iph->ihl*4);
同样或许我们还需要判断协议类型 protocal字等,简单说几个常用:
Decimal Keyword Protocol References
------- --------------- --------------------------------------- ------------------
1 ICMP Internet Control Message [RFC792]
6 TCP Transmission Control [RFC793]
17 UDP User Datagram [RFC768][JBP]
...
ip报文格式如下:
首部长度:占4位(bit),指IP报文头的长度。最大的长度(即4个bit都为1时)为15个长度单位,每个长度单位为4字节(TCP/IP标准,DoubleWord),所以IP协议报文头的最大长度为60个字节,最短为上图所示的20个字节。
服务类型:占8位(bit),用来获得更好的服务。其中的前3位表示报文的优先级,后面的几位分别表示要求更低时延、更高的吞吐量、更高的可靠性、更低的路由代价等。对应位为1即有相应要求,为0则不要求。
总长度:16位(bit),指报文的总长度。注意这里的单位为字节,而不是4字节,所以一个IP报文的的最大长度为65535个字节。
标识(identification):该字段标记当前分片为第几个分片,在数据报重组时很有用。
标志(flag):该字段用于标记该报文是否为分片(有一些可能不需要分片,或不希望分片),后面是否还有分片(是否是最后一个分片)。
片偏移:指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。
生存时间:TTL(Time to Live)。该字段表明当前报文还能生存多久。每经过1ms或者一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。使用过Ping命令的用户应该有印象,在windows中输入ping命令,在返回的结果中即有TTL的数值。
协议:该字段指出在上层(网络7层结构或TCP/IP的传输层)使用的协议,可能的协议有UDP、TCP、ICMP、IGMP、IGP等。
首部校验和:用于检验IP报文头部在传播的过程中是否出错,主要校验报文头中是否有某一个或几个bit被污染或修改了。
源IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
目的IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
当然对ip理解的越深刻越好了,那么这样就完成了一个简单的match扩展的例子,仅仅作为抛砖引玉,一个小小的开始.