Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1188170
  • 博文数量: 56
  • 博客积分: 400
  • 博客等级: 一等列兵
  • 技术积分: 2800
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-30 13:08
个人简介

一个人的差异在于业余时间

文章分类

全部博文(56)

文章存档

2023年(1)

2019年(1)

2018年(1)

2017年(1)

2016年(2)

2015年(20)

2014年(10)

2013年(7)

2012年(12)

2011年(1)

分类: 网络与安全

2015-07-24 15:24:47

     上一篇文章分析了iptables代码下发运作的流程细节,篇幅有限还有很多需要补充.关于netfilter的框架网上已经被讲烂了,框架很简单,但是实现却不简单.但不论什么都要最终归到实际应用上,才能体现其价值. 
    下面我们就以iptables1.4.21  ubuntu14 32位  内核版本 3.13.0为环境来开发一个最常规的扩展应用.
    首先下载iptables源码: (这个网站有很多东西,可以好好看看)
    关于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

点击(此处)折叠或打开

  1. #ifndef _XT_PKTSIZE_H
  2. #define _XT_PKTSIZE_H

  3. struct xt_pktsize_info {
  4.     int    pktsize;
  5.     int    invert;
  6. };
  7. #endif /*_XT_PKTSIZE_H*/
xt_pktsize.c

点击(此处)折叠或打开

  1. /*
  2.  * Shared library add-on to iptables to match
  3.  * packets by their size
  4.  *
  5.  */
  6. #include <stdio.h>
  7. #include <string.h>
  8. #include <xtables.h>
  9. #include <linux/if_packet.h>
  10. #include <linux/netfilter/xt_pktsize.h>

  11. enum {
  12.     O_pktsize = 0,
  13. };

  14. struct pktsizes {
  15.     const char *name;
  16.     unsigned char pktsize;
  17.     unsigned char printhelp;
  18.     const char *help;
  19. };



  20. static void print_types(void)
  21. {
  22.     unsigned int    i;
  23.     
  24.     printf("Valid packet sizes:64-65535\n");

  25. }

  26. static void pktsize_help(void)
  27. {
  28.     printf(
  29. "pktsize match options:\n"
  30. "[!] --pkt-size packetsize match packet size\n");
  31.     print_types();
  32. }

  33. static const struct xt_option_entry pktsize_opts[] = {
  34.     {.name = "pkt-size", .id = O_pktsize, .type = XTTYPE_STRING,
  35.      .flags = XTOPT_MAND | XTOPT_INVERT},
  36.     XTOPT_TABLEEND,
  37. };

  38. static void parse_pktsize(const char *pktsize, struct xt_pktsize_info *info)
  39. {
  40.     unsigned int    i,size;
  41.     char *buffer;
  42.     
  43.             printf("pktsize is %s...\n",pktsize);
  44.             buffer = strdup(pktsize);
  45.             if(!xtables_strtoui(buffer, NULL, &size, 0, UINT16_MAX))
  46.                 xtables_error(PARAMETER_PROBLEM, "Bad packet size '%s'", pktsize);
  47.             
  48.             info->pktsize=size;
  49.             free(buffer);
  50.         
  51. }

  52. static void pktsize_parse(struct xt_option_call *cb)
  53. {
  54.     struct xt_pktsize_info *info = cb->data;

  55.     xtables_option_parse(cb);
  56.     parse_pktsize(cb->arg, info);
  57.     if (cb->invert)
  58.         info->invert = 1;
  59. }

  60. static void print_pktsize(const struct xt_pktsize_info *info)
  61. {
  62.     unsigned int    i;
  63.     
  64.     

  65.     printf("%d", info->pktsize);    /* in case we didn't find an entry in named-packtes */
  66. }

  67. static void pktsize_print(const void *ip, const struct xt_entry_match *match,
  68.                           int numeric)
  69. {
  70.     const struct xt_pktsize_info *info = (const void *)match->data;
  71.     
  72.     printf(" pktsize %s= ", info->invert ? "!" : "");
  73.     print_pktsize(info);
  74. }

  75. static void pktsize_save(const void *ip, const struct xt_entry_match *match)
  76. {
  77.     const struct xt_pktsize_info *info = (const void *)match->data;
  78.     
  79.     printf("%s --pkt-type ", info->invert ? " !" : "");
  80.     print_pktsize(info);
  81. }

  82. static struct xtables_match pktsize_match = {
  83.     .family        = NFPROTO_UNSPEC,
  84.     .name        = "pktsize",
  85.     .version    = XTABLES_VERSION,
  86.     .size        = XT_ALIGN(sizeof(struct xt_pktsize_info)),
  87.     .userspacesize    = XT_ALIGN(sizeof(struct xt_pktsize_info)),
  88.     .help        = pktsize_help,
  89.     .print        = pktsize_print,
  90.     .save        = pktsize_save,
  91.     .x6_parse    = pktsize_parse,
  92.     .x6_options    = pktsize_opts,
  93. };

  94. void _init(void)
  95. {
  96.     xtables_register_match(&pktsize_match);
  97. }
通过代码我们看到主要工作就是初始化struct xtables_match,然后注册而已. 它的核心函数是x6_parse/parse

点击(此处)折叠或打开

  1. /* Function which parses command options; returns true if it
  2.            ate an option */
  3.     /* entry is struct ipt_entry for example */
  4.     int (*parse)(int c, char **argv, int invert, unsigned int *flags,
  5.          const void *entry,
  6.          struct xt_entry_match **match);
或者

点击(此处)折叠或打开

  1. /* New parser */
  2.     void (*x6_parse)(struct xt_option_call *);
例子中为 pktsize_parse函数,之前我们已经分析过如何调用到某一个match的parse函数.主要解析ipt_entry_match里data(其实就是解析命令参数赋值给它)
除了parse函数还有options,即解析--XXX的参数需要用到的东西,也需要我们去填写。

下面看看内核部分:
xt_pktsize.h

点击(此处)折叠或打开

  1. #ifndef _XT_PKTSIZE_H
  2. #define _XT_PKTSIZE_H

  3. struct xt_pktsize_info {
  4.     int    pktsize;
  5.     int    invert;
  6. };
  7. #endif /*_XT_PKTSIZE_H*/
xt_pktsize.c

点击(此处)折叠或打开

  1. #include <linux/module.h>
  2. #include <linux/skbuff.h>
  3. #include <linux/if_ether.h>
  4. #include <linux/if_packet.h>
  5. #include <linux/in.h>
  6. #include <linux/ip.h>
  7. #include <linux/ipv6.h>

  8. //#include <linux/netfilter/xt_pktsize.h>
  9. #include "xt_pktsize.h"
  10. #include <linux/netfilter/x_tables.h>

  11. MODULE_LICENSE("GPL");
  12. MODULE_AUTHOR("jack chen");
  13. MODULE_DESCRIPTION("Xtables: link layer packet size match");
  14. MODULE_ALIAS("ipt_pktsize");
  15. MODULE_ALIAS("ip6t_pktsize");

  16. static bool
  17. pktsize_mt(const struct sk_buff *skb, struct xt_action_param *par)
  18. {
  19.     const struct xt_pktsize_info *info = par->matchinfo;
  20.     u_int32_t size;
  21.     const struct iphdr *iph = ip_hdr(skb);
  22.     size=ntohs(iph->tot_len) - (iph->ihl*4);
  23.     printk("ip data pktsize is %d......,%d..,proto :%x\n",size,info->pktsize,iph->protocol);

  24.     return (info->pktsize < size) ;
  25.     //return 1;
  26. }

  27. static struct xt_match pktsize_mt_reg __read_mostly = {
  28.     .name = "pktsize",
  29.     .revision = 0,
  30.     .family = NFPROTO_UNSPEC,
  31.     .match = pktsize_mt,
  32.     .matchsize = sizeof(struct xt_pktsize_info),
  33.     .me = THIS_MODULE,
  34. };

  35. static int __init pktsize_mt_init(void)
  36. {
  37.     return xt_register_match(&pktsize_mt_reg);
  38. }

  39. static void __exit pktsize_mt_exit(void)
  40. {
  41.     xt_unregister_match(&pktsize_mt_reg);
  42. }

  43. module_init(pktsize_mt_init);
  44. module_exit(pktsize_mt_exit);
这里我们计算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扩展的例子,仅仅作为抛砖引玉,一个小小的开始.





阅读(4951) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~