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

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

文章分类

全部博文(55)

文章存档

2019年(1)

2018年(1)

2017年(1)

2016年(2)

2015年(20)

2014年(10)

2013年(7)

2012年(12)

2011年(1)

分类: 网络与安全

2015-07-23 18:46:37

    之前大致写过iptables即netfilter的基本框架,在安全领域用到的比较多,一般小的设备防火墙,也有自己开发的比如思科的ACL,简单的包过滤什么的,都可以支持,但是它最大的缺点就是影响性能,或许我们需要借鉴tcpdump/wireshark的机制(它们都包含了对报文的解析) ,现在流行的dpi 什么的, 甚至工控领域也用到了。虽然它没有那么完美,但是框架比较好,而我们可以去改进和定制它.
     参考:iptables1.4.21    kernel 3.8.13
iptables的机制分两个部分:
1. 用户空间的iptables工具
2. 内核netfilter机制的支持
它分ipv4部分和ipv6部分,这里只分析ipv4部分.代码目录(内核):
    net/ipv4/netfilter    
    net/ipv6/netfilter
    net/netfilter
这里先分析内核部分。
我们都知道netfiter分四个基本模块
1. Ct 链接追踪
2. Filter 过滤
3. Nat 地址转换
4. Mangle 修改数据报文
CT是基础核心模块是状态防火墙和nat的基础. 而其他模块会维护一个全局表,也即我们用iptables命令的时候需要指定的表。
它们工作在内核的五个钩子点上,即链的概念.
对于上面的模块在实际代码中是相对独立的初始化的:
net/ipv4/netfilter/
1.iptable_filter.c       -->iptable_filter_init
2. iptable_mangle.c  -->iptable_mangle_init
3.iptable_nat.c         -->iptable_nat_init
4.iptable_raw.c        -->iptable_raw_init
5.iptable_security.c  --->iptable_security_init

而关于链接追踪它是其他的基础,比较特殊。
Net/netfilter/nf_conntrack_core.c
nf_conntrack_init
又被nf_conntrack_standalone.c

点击(此处)折叠或打开

  1. static int nf_conntrack_net_init(struct net *net)
  2. {
  3.     int ret;

  4.     ret = nf_conntrack_init(net);
  5.     if (ret < 0)
  6.         goto out_init;
  7.     ret = nf_conntrack_standalone_init_proc(net);
  8.     if (ret < 0)
  9.         goto out_proc;
  10.     net->ct.sysctl_checksum = 1;
  11.     net->ct.sysctl_log_invalid = 0;
  12.     ret = nf_conntrack_standalone_init_sysctl(net);
  13.     if (ret < 0)
  14.         goto out_sysctl;
  15.     return 0;

  16. out_sysctl:
  17.     nf_conntrack_standalone_fini_proc(net);
  18. out_proc:
  19.     nf_conntrack_cleanup(net);
  20. out_init:
  21.     return ret;
  22. }

  23. static void nf_conntrack_net_exit(struct net *net)
  24. {
  25.     nf_conntrack_standalone_fini_sysctl(net);
  26.     nf_conntrack_standalone_fini_proc(net);
  27.     nf_conntrack_cleanup(net);
  28. }

  29. static struct pernet_operations nf_conntrack_net_ops = {
  30.     .init = nf_conntrack_net_init,
  31.     .exit = nf_conntrack_net_exit,
  32. };

  33. static int __init nf_conntrack_standalone_init(void)
  34. {
  35.     return register_pernet_subsys(&nf_conntrack_net_ops);
  36. }
register_pernet_subsys这个函数不多说,它是内核命名空间注册子系统的一个接口.
对于内核netfilter那么多代码,想简单理清一个框架思路,首先还是要看makefile:
1. Net/netfilter/Makefile
首先是一些基础核心的代码。然后
1. Netfilter_netlink接口相关的
2. 链接追踪支持的协议l3/l4等:nf_conntrack_l4proto_register、nf_conntrack_l3proto_register
3. 链接追踪的 helpers:nf_conntrack_helper_register (主要关联连接,其他ct的识别等)
4. 其他就是match注册和targets注册
5. 核心CT的初始化等.
6. nat和ct相关的基础。
7.工具ipset、ipvs

那么net/ipv4/netfilter/Makefile呢?
它主要针对ipv4协议的处理:
1. Nat相关的helpers和协议注册
2. 链接追踪
3. Filter、mangle、nat、raw、security实例
4. Matches和targets的注册
5. Arp其他一些东西.
做了一个简单的了解,那么就从filter说起,要使配置生效比如filter那么它会从内核调用ipt_do_table查询rules配置并触发target。
具体先看filter初始化流程:
先看iptable_filter.c中的初始化函数:
iptable_filter_init
注册钩子需要注册函数接口:nf_register_hooks

点击(此处)折叠或打开

  1. static struct nf_hook_ops ipt_ops[] __read_mostly = {
  2.     {
  3.         .hook        = ipt_local_in_hook,
  4.         .owner        = THIS_MODULE,
  5.         .pf        = NFPROTO_IPV4,
  6.         .hooknum    = NF_INET_LOCAL_IN,
  7.         .priority    = NF_IP_PRI_FILTER,
  8.     },
  9.     {
  10.         .hook        = ipt_hook,
  11.         .owner        = THIS_MODULE,
  12.         .pf        = NFPROTO_IPV4,
  13.         .hooknum    = NF_INET_FORWARD,
  14.         .priority    = NF_IP_PRI_FILTER,
  15.     },
  16.     {
  17.         .hook        = ipt_local_out_hook,
  18.         .owner        = THIS_MODULE,
  19.         .pf        = NFPROTO_IPV4,
  20.         .hooknum    = NF_INET_LOCAL_OUT,
  21.         .priority    = NF_IP_PRI_FILTER,
  22.     },
  23. };

很明显filter只在local_in /local_out/ forward三个钩子,并且它们的钩子函数最终都调用了
点击(此处)折叠或打开

  1. /* Returns one of the generic firewall policies, like NF_ACCEPT. */
  2. unsigned int
  3. ipt_do_table(struct sk_buff *skb,
  4.          unsigned int hook,
  5.          const struct net_device *in,
  6.          const struct net_device *out,
  7.          struct xt_table *table)

那么我们就从ipt_do_table函数分析吧
关于参数传递除了struct xt_table需要说明下外其他不需要说明了吧
1. NF_HOOK的调用查询hook函数
 elem = &nf_hooks[pf][hook];
找到对应的nf_hook_ops
2. hook函数的调用
对比2.6.32和3.8.13已结有了较大的改动,不过原理一样。
在iptable_filter.c中 filter的钩子函数已经统一成一个函数了即

点击(此处)折叠或打开

  1. static unsigned int
  2. iptable_filter_hook(unsigned int hook, struct sk_buff *skb,
  3.          const struct net_device *in, const struct net_device *out,
  4.          int (*okfn)(struct sk_buff *))
  5. {
  6.     const struct net *net;

  7.     if (hook == NF_INET_LOCAL_OUT &&
  8.      (skb->len < sizeof(struct iphdr) ||
  9.          ip_hdrlen(skb) < sizeof(struct iphdr)))
  10.         /* root is playing with raw sockets. */
  11.         return NF_ACCEPT;

  12.     net = dev_net((in != NULL) ? in : out);
  13.     return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter);
  14. }
对于ipt_do_table最需要我们关心的就是net->ipv4.iptable_filter这个东西从哪里来的,当然我们知道它就是存放rules的地方 从搜索的内核代码来看: 点击(此处)折叠或打开

  1. static int __net_init iptable_filter_net_init(struct net *net)
  2. {
  3.     struct ipt_replace *repl;

  4.     repl = ipt_alloc_initial_table(&packet_filter);
  5.     if (repl == NULL)
  6.         return -ENOMEM;
  7.     /* Entry 1 is the FORWARD hook */
  8.     ((struct ipt_standard *)repl->entries)[1].target.verdict =
  9.         forward ? -NF_ACCEPT - 1 : -NF_DROP - 1;

  10.     net->ipv4.iptable_filter =
  11.         ipt_register_table(net, &packet_filter, repl);
  12.     kfree(repl);
  13.     return PTR_RET(net->ipv4.iptable_filter);
  14. }

上述代码的12,13 行初始化了它,而net则是inet层初始化的时候创建的全局变量.

net->ipv4.iptable_filter的类型是struct xt_table *

用struct xt_table表示,每一个功能模块都需要维护一个表,注册API:ipt_register_table(它是xt_register_table的封装)
Include/linux/netfilter中X_tables.h

点击(此处)折叠或打开


  1. /* Furniture shopping... */
  2.   
  3. struct xt_table {
  4.  
  5.     struct list_head list;
  6.  
  7.  
  8.     /* What hooks you will enter on */
  9.  
  10.     unsigned int valid_hooks;
  11.  
  12.  
  13.     /* Man behind the curtain... */
  14.  
  15.     struct xt_table_info *private;
  16.  
  17.  
  18.     /* Set this to THIS_MODULE if you are a module, otherwise NULL */
  19.  
  20.     struct module *me;
  21.  
  22.  
  23.     u_int8_t af; /* address/protocol family */
  24.  
  25.     int priority; /* hook order */
  26.  
  27.  
  28.     /* A unique name... */
  29.  
  30.     const char name[XT_TABLE_MAXNAMELEN];
  31.  
  32. };

还有另外一个重要的结构体:      struct xt_table_info *private;

然关于tablesrules需要用户空间来下发配置。以及注册matchtarget相关的东西.关于rules的结构和表的关系后续我们会讲到.

关于全局表的维护:通过API接口注册的表都挂到了net->xt.tables.
  
xt的类型是struct netns_xt:

点击(此处)折叠或打开

  1. struct netns_xt {
  2.     struct list_head tables[NFPROTO_NUMPROTO];
  3.     bool notrack_deprecated_warning;
  4. #if defined(CONFIG_BRIDGE_NF_EBTABLES) || \
  5.     defined(CONFIG_BRIDGE_NF_EBTABLES_MODULE)
  6.     struct ebt_table *broute_table;
  7.     struct ebt_table *frame_filter;
  8.     struct ebt_table *frame_nat;
  9. #endif
  10. };
还有另外一个全局变量Struct xt_af xt

点击(此处)折叠或打开

  1. struct xt_af {
  2.     struct mutex mutex;
  3.     struct list_head match;
  4.     struct list_head target;
  5. #ifdef CONFIG_COMPAT
  6.     struct mutex compat_mutex;
  7.     struct compat_delta *compat_tab;
  8.     unsigned int number; /* number of slots in compat_tab[] */
  9.     unsigned int cur; /* number of used slots in compat_tab[] */
  10. #endif
  11. };

有了表,我们看看matchtarget的注册:matchtarget放到了xt.match xt.target中(和之前内核不太一样了表和match/target已经分开处理了

list_add(&match->list,&xt[af].match);

注册接口:

xt_register_table

xt_register_match

xt_register_target

对应的结构体:

Struct xt_table

Struct xt_match

Struct xt_target

那么规则呢? Rules呢?我们先看一段注释说明:

/* This structure defines each of the firewall rules. Consists of 3 parts which are
1) general IP header stuff

2) match specificstuff
3) the target to perform if the rule

matches */

struct ipt_entry {

        ...

我们继续看ipt_do_table函数

找到第一个ipt_entry然后调用ip_packet_match(五元组匹配模式开启!知道找到匹配的才结束)

比较ip头和 ipt_entry->ipt_ip

不匹配则返回false

1. 比较sip dip

然后比较入接口和出接口名字名字

2. 协议的比较

3. 判断IPT_F_FRAG

找到匹配的entry

 

接着调用entry里的match函数

Ipt_entry->elems :match and  target 


? ipt_entry:标准匹配结构,主要包含数据包的源、目的IP,出、入接口和掩码等;
? ipt_entry_match:扩展匹配。一条rule规则可能有零个或多个ipt_entry_match结构;
? ipt_entry_target:一条rule规则有且仅有一个target动作。就是当所有的标准匹配和扩展匹配都符合之后才来执行该target.
具体代码:

点击(此处)折叠或打开

  1. xt_ematch_foreach(ematch, e) {
  2.   
  3.             acpar.match = ematch->u.kernel.match;
  4.  
  5.             acpar.matchinfo = ematch->data;
  6.  
  7.             if (!acpar.match->match(skb, &acpar))
  8.  
  9.                 goto no_match;
  10.  
  11.         }
那么match函数从哪里来?Ipt_entry又在什么赋值的呢?后续我们会看到答案. 
如果match匹配那么调用相应的target,这里不要把类似struct xt_match 和struct xt_entry_match搞混了。
例:
点击(此处)折叠或打开
  1. struct xt_entry_match {
  2.   
  3.     union {
  4.  
  5.         struct {
  6.  
  7.             __u16 match_size;
  8.  
  9.             /* Used by userspace */
  10.  
  11.             char name[XT_EXTENSION_MAXNAMELEN];
  12.  
  13.             __u8 revision;
  14.  
  15.         } user;
  16.  
  17.         struct {
  18.  
  19.             __u16 match_size;
  20.  
  21.             /* Used inside the kernel */
  22.  
  23.             struct xt_match *match;
  24.  
  25.         } kernel;
  26.  
  27.         /* Total length */
  28.  
  29.         __u16 match_size;
  30.  
  31.     } u;
  32.  
  33.     unsigned char data[0];
  34.  
  35. };

对于现在来说,它通过ipt_entry找到rules;关于rules的组成部分 
Ipt_entry+ipt_entry_match+ …+target 
但是iptables如何传递过来的呢?
又是如何和已经注册的match和target关联起来的呢?上面我们知道了内核通过表找到需要的rules然后解析匹配动作,还没有和应用联系起来,是时候统一一下了.
我们先看一条简单的命令比如过滤tcp协议:
iptables -A OUTPUT -p tcp --dport 31337 -j DROP
我们找到iptables1.4.21的源代码:
iptables命令的主函数在iptables-standalone.c
Iptables_main
而它又被封装了一层在xtables-multi.c:

点击(此处)折叠或打开

  1. static const struct subcommand multi_subcommands[] = {
  2.   
  3. #ifdef ENABLE_IPV4
  4.  
  5.     {"iptables", iptables_main},
  6.  
  7.     {"main4", iptables_main},
  8.  
  9.     {"iptables-save", iptables_save_main},
  10.  
  11.     {"save4", iptables_save_main},
  12.  
  13.     {"iptables-restore", iptables_restore_main},
  14.  
  15.     {"restore4", iptables_restore_main},
  16.  
  17. #endif
  18.  
  19.     {"iptables-xml", iptables_xml_main},
  20.  
  21.     {"xml", iptables_xml_main},
  22.  
  23. #ifdef ENABLE_IPV6
  24.  
  25.     {"ip6tables", ip6tables_main},
  26.  
  27.     {"main6", ip6tables_main},
  28.  
  29.     {"ip6tables-save", ip6tables_save_main},
  30.  
  31.     {"save6", ip6tables_save_main},
  32.  
  33.     {"ip6tables-restore", ip6tables_restore_main},
  34.  
  35.     {"restore6", ip6tables_restore_main},
  36.  
  37. #endif
  38.  
  39.     {NULL},
  40.  
  41. };
  42.  
  43.  
  44. int main(int argc, char **argv)
  45.  
  46. {
  47.  
  48.     return subcmd_main(argc, argv, multi_subcommands);
  49.  
  50. }
编译过iptables后,查看sbin下iptables命令都软连接到了xtables-multi。

先看主函数:

点击(此处)折叠或打开

  1. int
  2. iptables_main(int argc, char *argv[])
  3.  
  4. {
  5.  
  6.     int ret;
  7.  
  8.     char *table = "filter";
  9.  
  10.     struct xtc_handle *handle = NULL;
  11.  
  12.  
  13.     iptables_globals.program_name = "iptables";
  14.  
  15.     ret = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
  16.  
  17.     if (ret < 0) {
  18.  
  19.         fprintf(stderr, "%s/%s Failed to initialize xtables\n",
  20.  
  21.                 iptables_globals.program_name,
  22.  
  23.                 iptables_globals.program_version);
  24.  
  25.                 exit(1);
  26.  
  27.     }
  28.  
  29. #if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
  30.  
  31.     init_extensions();
  32.  
  33.     init_extensions4();
  34.  
  35. #endif
  36.  

  37.  
  38.     ret = do_command4(argc, argv, &table, &handle, false);
  39.  
  40.     if (ret) {
  41.  
  42.         ret = iptc_commit(handle);
  43.  
  44.         iptc_free(handle);
  45.  
  46.     }
  47. ...
我们看到命令行解析是在do_command4里

点击(此处)折叠或打开

  1. opts = xt_params->orig_opts;
  2.   
  3.     while ((cs.c = getopt_long(argc, argv,
  4.  
  5.      "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvwnt:m:xc:g:46",
  6.  
  7.                        opts, NULL)) != -1) {

而opts是什么?  

#define opts iptables_globals.opts 还有在之前初始化的时候:xt_params = &iptables_globals;

点击(此处)折叠或打开

  1. struct xtables_globals *xt_params = NULL;
这个结构体包含什么呢?

点击(此处)折叠或打开

  1. struct xtables_globals
  2.   
  3. {
  4.  
  5.     unsigned int option_offset;
  6.  
  7.     const char *program_name, *program_version;
  8.  
  9.     struct option *orig_opts;
  10.  
  11.     struct option *opts;
  12.  
  13.     void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
  14.  
  15. };
关键点就是struct option,既然上面orig_opts和opts指向相同的地方,而xt_params又指向iptables_globals

点击(此处)折叠或打开

  1. static struct option original_opts[] = {
  2.   
  3.     {.name = "append", .has_arg = 1, .val = 'A'},
  4.  
  5.     {.name = "delete", .has_arg = 1, .val = 'D'},
  6.  
  7.     {.name = "check", .has_arg = 1, .val = 'C'},
  8.  
  9.     {.name = "insert", .has_arg = 1, .val = 'I'},
  10.     ...
点击(此处)折叠或打开
  1. struct xtables_globals iptables_globals = {
  2.   
  3.     .option_offset = 0,
  4.  
  5.     .program_version = IPTABLES_VERSION,
  6.  
  7.     .orig_opts = original_opts,
  8.  
  9.     .exit_err = iptables_exit_error,
  10.  
  11. };

那么可能好奇struct option了是什么玩意,我们来看一下:

原型在getopt.h中

点击(此处)折叠或打开

  1. struct option {
  2.   
  3.    const char *name;
  4.  
  5.    int has_arg;
  6.  
  7.    int *flag;
  8.  
  9.    int val;
  10.  
  11. };
因为getopt_long里要用到,对这个函数不熟悉的,可以自己编个小程序测试下用法.加深理解。
补充说明:
extern char *optarg;  //选项的参数指针
extern int optind,   //下一次调用getopt的时,从optind存储的位置处重新开始检查选项。 
extern int opterr,  //当opterr=0时,getopt不向stderr输出错误信息。
extern int optopt;  //当命令行选项字符不包括在optstring中或者选项缺少必要的参数时,该选项存储在optopt中,getopt返回'?’
1.单个字符,表示选项,
2.单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋给optarg。
3 单个字符后跟两个冒号,表示该选项后必须跟一个参数。参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。(这个特性是GNU的扩张)。
4 optind 下个参数选项的索引,可以通过argv[optind]查看每解析一个选项optind就会加1.
getopt_long根据传递的opt来解析长字符串参数.
对于单字母的参数很容易解析出来,但是类似--dport由于longstring里没有对应的说明那么就会进入default处理。
对于tcp相关的dport处理代码在libxt_tcp.c:tcp match的注册

点击(此处)折叠或打开

  1. static struct xtables_match tcp_match = {
  2.   
  3.     .family = NFPROTO_UNSPEC,
  4.  
  5.     .name = "tcp",
  6.  
  7.     .version = XTABLES_VERSION,
  8.  
  9.     .size = XT_ALIGN(sizeof(struct xt_tcp)),
  10.  
  11.     .userspacesize = XT_ALIGN(sizeof(struct xt_tcp)),
  12.  
  13.     .help = tcp_help,
  14.  
  15.     .init = tcp_init,
  16.  
  17.     .parse = tcp_parse,
  18.  
  19.     .print = tcp_print,
  20.  
  21.     .save = tcp_save,
  22.  
  23.     .extra_opts = tcp_opts, //...
  24.  
  25. };

在注册match的时候xtables_register_match
/* place on linked list of matches pending
full registration */
        me->next= xtables_pending_matches;
        xtables_pending_matches= me;
...

点击(此处)折叠或打开

  1. static void tcp_help(void)
  2.   
  3. {
  4.  
  5.     printf(
  6.  
  7. "tcp match options:\n"
  8.  
  9. "[!] --tcp-flags mask comp match when TCP flags & mask == comp\n"
  10.  
  11. " (Flags: SYN ACK FIN RST URG PSH ALL NONE)\n"
  12.  
  13. "[!] --syn match when only SYN flag set\n"
  14.  
  15. " (equivalent to --tcp-flags SYN,RST,ACK,FIN SYN)\n"
  16.  
  17. "[!] --source-port port[:port]\n"
  18.  
  19. " --sport ...\n"
  20.  
  21. " match source port(s)\n"
  22.  
  23. "[!] --destination-port port[:port]\n"
  24.  
  25. " --dport ...\n"
  26.  
  27. " match destination port(s)\n"
  28.  
  29. "[!] --tcp-option number match if TCP option set\n");
  30.  
  31. }
  32.  
  33. static const struct option tcp_opts[] = {
  34.  
  35.     {.name = "source-port", .has_arg = true, .val = '1'},
  36.  
  37.     {.name = "sport", .has_arg = true, .val = '1'}, /* synonym */
  38.  
  39.     {.name = "destination-port", .has_arg = true, .val = '2'},
  40.  
  41.     {.name = "dport", .has_arg = true, .val = '2'}, /* synonym */
  42.  
  43.     {.name = "syn", .has_arg = false, .val = '3'},
  44.  
  45.     {.name = "tcp-flags", .has_arg = true, .val = '4'},
  46.  
  47.     {.name = "tcp-option", .has_arg = true, .val = '5'},
  48.  
  49.     XT_GETOPT_TABLEEND,
  50.  
  51. };

这就是参数的解析过程,不论什么参数都是这样解析,那么解析完如何把rules传递给内核呢?
我们在回顾一下iptables配置的命令: 
iptables -A OUTPUT -p tcp --dport 31337 -j DROP

下面我们就逐一跟踪命令的解析过程:

(需要说明的是默认是filter表,-t filter)

1. 默认初始化char *table = "filter";

2. Do_command4命令解析之getopt_long

这个自测过,对于-开头的命令可以直接解析,但是对于--的就需要进入default处理流程。

1> 第一个命令 -A  OUTPUT 点击(此处)折叠或打开

  1. case 'A':
  2.   
  3.             add_command(&command, CMD_APPEND, CMD_NONE,
  4.  
  5.                     cs.invert);
  6.  
  7.             chain = optarg;
  8.  
  9.             break;

add_command即把command赋值为CMD_APPEND
cs.invert为0
Chain 为 OUTPUT
或许我们需要回顾下getopt_long点击(此处)折叠或打开

  1. static struct option original_opts[] = {
  2.     {.name = "append", .has_arg = 1, .val = 'A'},
根据上面的初始化值来解析参数A,明显has_arg=1表示A后面跟一个参数即OUTPUT
2>第二个命令-p tcp


点击(此处)折叠或打开

  1. /*
  2.   
  3.              * Option selection
  4.  
  5.              */
  6.  
  7.         case 'p':
  8.  
  9.             set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags,
  10.  
  11.                    cs.invert);
  12.  

  13.  
  14.             /* Canonicalize into lower case */
  15.  
  16.             for (cs.protocol = optarg; *cs.protocol; cs.protocol++)
  17.  
  18.                 *cs.protocol = tolower(*cs.protocol); // tolower把字符串转换成小写.
  19.  

  20.  
  21.             cs.protocol = optarg;
  22.  
  23.             cs.fw.ip.proto = xtables_parse_protocol(cs.protocol);
  24.  

  25.  
  26.             if (cs.fw.ip.proto == 0
  27.  
  28.              && (cs.fw.ip.invflags & XT_INV_PROTO))
  29.  
  30.                 xtables_error(PARAMETER_PROBLEM,
  31.  
  32.                      "rule would never match protocol");
  33.  
  34.             break; 

 cs.options = OPT_PROTOCOL;
 cs.protocol = optarg;  //  optarg为tcp,赋值给cs.protocol
 cs.fw.ip.proto =xtables_parse_protocol(cs.protocol);  // IPPROTO_TCP
xtables_parse_protocol判断这个协议是否支持
return xtables_chain_protos[i].num;返回协议号
协议初始化在libxtables中xtables.c

点击(此处)折叠或打开

  1. const struct xtables_pprot xtables_chain_protos[] = {
  2.    {"tcp", IPPROTO_TCP},
  3.    {"sctp", IPPROTO_SCTP},
  4.    {"udp", IPPROTO_UDP},
  5.    {"udplite", IPPROTO_UDPLITE},
  6.    {"icmp", IPPROTO_ICMP},
  7.    {"icmpv6", IPPROTO_ICMPV6},
  8.    {"ipv6-icmp", IPPROTO_ICMPV6},
  9.    {"esp", IPPROTO_ESP},
  10.    {"ah", IPPROTO_AH},
  11.    {"ipv6-mh", IPPROTO_MH},
  12.    {"mh", IPPROTO_MH},
  13.    {"all", 0},
  14.    {NULL},
  15. };
Cs.fw.ip.proto值为IPPROTO_TCP
3>. 第三个命令--dport 31337
进入command_default流程后
首先判断cs->target是否为null,
然后判断cs->matches是否为null;点击(此处)折叠或打开


  1. for (matchp = cs->matches; matchp; matchp = matchp->next) {
  2.   
  3.         m = matchp->match;

当然第一次处理直接进入load_proto

会根据协议名字struct xtables_match * xtables_find_match(const char *name, enum xtables_tryload tryload,   struct xtables_rule_match **matches)

cs.matchs初始化为tcp match

点击(此处)折叠或打开

  1. find_proto(cs->protocol, XTF_TRY_LOAD,
  2.   
  3.               cs->options & OPT_NUMERIC, &cs->matches);

xtables_find_match中分两个部分:

1.点击(此处)折叠或打开

  1. /* Second and subsequent clones */
  2.             clone = xtables_malloc(sizeof(struct xtables_match));
  3.             memcpy(clone, ptr, sizeof(struct xtables_match));
  4.             clone->udata = NULL;
  5.             clone->mflags = 0;
  6.             /* This is a clone: */
  7.             clone->next = clone;

  8.             ptr = clone;
  9.             break;
  10.         }
2.点击(此处)折叠或打开
  1. if (ptr && matches) {
  2.         struct xtables_rule_match **i;
  3.         struct xtables_rule_match *newentry;

  4.         newentry = xtables_malloc(sizeof(struct xtables_rule_match));

  5.         for (i = matches; *i; i = &(*i)->next) {
  6.             if (strcmp(name, (*i)->match->name) == 0)
  7.                 (*i)->completed = true;
  8.         }
  9.         newentry->match = ptr;
  10.         newentry->completed = false;
  11.         newentry->next = NULL;
  12.         *i = newentry;
  13.     }

完成了cs.matchs初始化为tcp match(cloned)。

找到tcp注册的match之后 ,申请xt_entry_match节点(xtables_calloc),调用xs_init_match(m);

对于tcp match就是:点击(此处)折叠或打开

  1. static void tcp_init(struct xt_entry_match *m)
  2. {
  3.     struct xt_tcp *tcpinfo = (struct xt_tcp *)m->data;

  4.     tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF;
  5. }
然后调用xtables_merge_options把tcp match的ext_opt复制到全局的opts中。optind-- ,回退重新处理--dport。
重新进入command_default:由于cs.matchs不为null

点击(此处)折叠或打开

  1. for (matchp = cs->matches; matchp; matchp = matchp->next) {
  2.         m = matchp->match;

  3.         if (matchp->completed ||
  4.          (m->x6_parse == NULL && m->parse == NULL))
  5.             continue;
  6.         if (cs->c < matchp->match->option_offset ||
  7.          cs->c >= matchp->match->option_offset + XT_OPTION_OFFSET_SCALE)
  8.             continue;
  9.         xtables_option_mpcall(cs->c, cs->argv, cs->invert, m, &cs->fw);
  10.         return 0;
  11.     }

进入xtables_option_mpcall解析

点击(此处)折叠或打开

  1. if (m->x6_parse == NULL) {
  2.         if (m->parse != NULL)
  3.             m->parse(c - m->option_offset, argv, invert,
  4.                  &m->mflags, fw, &m->m);
  5.         return;
  6.     }

m->parse即之前已经注册tcp_match中的tcp_parse

由于之前已经把tcp扩展的options添加到了全局表中,所以重新解析后cs.c值为2optarg即我们传递的端口号

点击(此处)折叠或打开

  1. static int
  2. tcp_parse(int c, char **argv, int invert, unsigned int *flags,
  3.           const void *entry, struct xt_entry_match **match)
  4. {
  5.     struct xt_tcp *tcpinfo = (struct xt_tcp *)(*match)->data;

  6.     switch (c) {
  7.     case '1':
  8.         if (*flags & TCP_SRC_PORTS)
  9.             xtables_error(PARAMETER_PROBLEM,
  10.                  "Only one `--source-port' allowed");
  11.         parse_tcp_ports(optarg, tcpinfo->spts);
  12.         if (invert)
  13.             tcpinfo->invflags |= XT_TCP_INV_SRCPT;
  14.         *flags |= TCP_SRC_PORTS;
  15.         break;

  16.     case '2':
  17.         if (*flags & TCP_DST_PORTS)
  18.             xtables_error(PARAMETER_PROBLEM,
  19.                  "Only one `--destination-port' allowed");
  20.         parse_tcp_ports(optarg, tcpinfo->dpts);
  21.         if (invert)
  22.             tcpinfo->invflags |= XT_TCP_INV_DSTPT;
  23.         *flags |= TCP_DST_PORTS;
  24.         break;
struct xt_tcp *tcpinfo = (struct xt_tcp *)(*match)->data; 和optarg

记得之前找到match后会对xt_entry_match初始化data强制转换成xt_tcp结构指针类型初始化(柔性数组动态扩展

parse_tcp_ports解析端口信息,如果是数字字符串则转换成数字赋值,如果不是则以它为名字查询服务获得服务的端口号(查询/etc/services通过getservbyname接口)
            (1)cs->matchs =xtables_malloc(sizeof(struct xtables_rule_match));
            (2)cs->matchs->match = tcp_match; // cloned
            (3) cs->matchs->match->m->data 赋值 (dport)

4>. 接着处理第4个参数 –j DROP

cs->options | = OPT_JUMP;

Cs->jumpto=optarg (DROP) --> standard
cs->target  = target_standard (cloned) // 跟match find 几乎一样的处理流程。 struct xtable_target
在libxt_standard.c

点击(此处)折叠或打开

  1. static void standard_help(void)
  2. {
  3.     printf(
  4. "standard match options:\n"
  5. "(If target is DROP, ACCEPT, RETURN or nothing)\n");
  6. }

  7. static struct xtables_target standard_target = {
  8.     .family        = NFPROTO_UNSPEC,
  9.     .name        = "standard",
  10.     .version    = XTABLES_VERSION,
  11.     .size        = XT_ALIGN(sizeof(int)),
  12.     .userspacesize    = XT_ALIGN(sizeof(int)),
  13.     .help        = standard_help,
  14. };

  15. void _init(void)
  16. {
  17.     xtables_register_target(&standard_target);
  18. }
cs->target->t  =xtables_calloc(1, size); 申请空间。 // struct xt_entry_match
cs->target->t赋值strcpy(cs->target->t->u.user.name, cs->jumpto)等。
终于几个参数初步解析完了,其他参数解析也类似。看后续的代码:
一些安全检查后

Shostnetworkmask 、Dhostnetworkmask 初始化为0.0.0.0/0 默认
iptables有参数-d,可以指定网址,比如 iptables -A OUTPUT -d -j DROP
则会对Dhostnetworkmask赋值处理和解析。 主要是根据主机名解析所有的ip地址,使用了gethostbyname函数. 这只是一个小插曲. 到这里需要注意:
点击(此处)折叠或打开

  1. 1.        if (!*handle)
  2.         *handle = iptc_init(*table);
系统初始化时handle为null,*table为“filter”:

点击(此处)折叠或打开

  1. 1.        if (!*handle)
  2.         *handle = iptc_init(*table);

在Libip4tc.c中:iptc_init
#define TC_INIT iptc_init
1.
建立了socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
2.fcntl(sockfd, F_SETFD, FD_CLOEXEC)
3.getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s)
info结构信息:

点击(此处)折叠或打开

  1. /* The argument to IPT_SO_GET_INFO */
  2. struct ipt_getinfo {
  3.     /* Which table: caller fills this in. */
  4.     char name[XT_TABLE_MAXNAMELEN];

  5.     /* Kernel fills these in. */
  6.     /* Which hook entry points are valid: bitmask */
  7.     unsigned int valid_hooks;

  8.     /* Hook entry points: one per netfilter hook. */
  9.     unsigned int hook_entry[NF_INET_NUMHOOKS];

  10.     /* Underflow points. */
  11.     unsigned int underflow[NF_INET_NUMHOOKS];

  12.     /* Number of entries */
  13.     unsigned int num_entries;

  14.     /* Size of entries. */
  15.     unsigned int size;
  16. };
它和内核的struct xt_table_info很相似

点击(此处)折叠或打开

  1. /* The table itself */
  2. struct xt_table_info {
  3.     /* Size per table */
  4.     unsigned int size;
  5.     /* Number of entries: FIXME. --RR */
  6.     unsigned int number;
  7.     /* Initial number of entries. Needed for module usage count */
  8.     unsigned int initial_entries;

  9.     /* Entry points and underflows */
  10.     unsigned int hook_entry[NF_INET_NUMHOOKS];
  11.     unsigned int underflow[NF_INET_NUMHOOKS];

  12.     /*
  13.      * Number of user chains. Since tables cannot have loops, at most
  14.      * @stacksize jumps (number of user chains) can possibly be made.
  15.      */
  16.     unsigned int stacksize;
  17.     unsigned int __percpu *stackptr;
  18.     void ***jumpstack;
  19.     /* ipt_entry tables: one per CPU */
  20.     /* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */
  21.     void *entries[1];
  22. };
关于getinfo的操作,它下发到内核获取信息:

点击(此处)折叠或打开

  1. static int
  2. do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
  3. {
  4.     int ret;

  5.     if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
  6.         return -EPERM;

  7.     switch (cmd) {
  8.     case IPT_SO_GET_INFO:
  9.         ret = get_info(sock_net(sk), user, len, 0);
  10.         break;

4.创建handle,并初始化。
5.getsockopt(h->sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,&tmp)  获取entires信息

点击(此处)折叠或打开

  1. /* The argument to IPT_SO_GET_ENTRIES. */
  2. struct ipt_get_entries {

  3.     /* Which table: user fills this in. */
  4.     char name[XT_TABLE_MAXNAMELEN];

  5.     /* User fills this in: total entry size. */
  6.     unsigned int size;
  7.  
  8.     /* The entries. */
  9.     struct ipt_entry entrytable[0];
  10.  
  11. };
一开始认识info 和entries应该为空,其实错了,在初始化filter表的时候,即在iptable_filter_net_init里面做了重要工作:

点击(此处)折叠或打开

  1. struct ipt_replace *repl;

  2.     repl = ipt_alloc_initial_table(&packet_filter);

点击(此处)折叠或打开

  1. void *ipt_alloc_initial_table(const struct xt_table *info)
  2. {
  3.     return xt_alloc_initial_table(ipt, IPT);
  4. }
很有意思的是上面这个函数:

点击(此处)折叠或打开

  1. #define xt_alloc_initial_table(type, typ2) ({ \
  2.     unsigned int hook_mask = info->valid_hooks; \
  3.     unsigned int nhooks = hweight32(hook_mask); \
  4.     unsigned int bytes = 0, hooknum = 0, i = 0; \
  5.     struct { \
  6.         struct type##_replace repl; \
  7.         struct type##_standard entries[nhooks]; \
  8.         struct type##_error term; \
  9.     } *tbl = kzalloc(sizeof(*tbl), GFP_KERNEL); \
  10.     if (tbl == NULL) \
  11.         return NULL; \
  12.     strncpy(tbl->repl.name, info->name, sizeof(tbl->repl.name)); \
  13.     tbl->term = (struct type##_error)typ2##_ERROR_INIT; \
  14.     tbl->repl.valid_hooks = hook_mask; \
  15.     tbl->repl.num_entries = nhooks + 1; \
  16.     tbl->repl.size = nhooks * sizeof(struct type##_standard) + \
  17.      sizeof(struct type##_error); \
  18.     for (; hook_mask != 0; hook_mask >>= 1, ++hooknum) { \
  19.         if (!(hook_mask & 1)) \
  20.             continue; \
  21.         tbl->repl.hook_entry[hooknum] = bytes; \
  22.         tbl->repl.underflow[hooknum] = bytes; \
  23.         tbl->entries[i++] = (struct type##_standard) \
  24.             typ2##_STANDARD_INIT(NF_ACCEPT); \
  25.         bytes += sizeof(struct type##_standard); \
  26.     } \
  27.     tbl; \
  28. })
为什么这么说,还记得iptcc_find_label这个函数吗?它去查询handle->chains 而在TC_INIT的时候,从内核获取info和entries之后通过parse_table赋值给handle.
补充几个结构体:

点击(此处)折叠或打开

  1. /* Standard entry. */
  2. struct ipt_standard {
  3.     struct ipt_entry entry;
  4.     struct xt_standard_target target;
  5. };

点击(此处)折叠或打开

  1. /* Standard entry. */
  2. struct ipt_standard {
  3.     struct ipt_entry entry;
  4.     struct xt_standard_target target;
  5. };

点击(此处)折叠或打开

  1. struct xt_standard_target {
  2.     struct xt_entry_target target;
  3.     int verdict;
  4. };
下面看看filter表初始化的时候做了什么工作:

点击(此处)折叠或打开

  1. #define IPT_STANDARD_INIT(__verdict)                     \
  2. {                                     \
  3.     .entry        = IPT_ENTRY_INIT(sizeof(struct ipt_standard)),     \
  4.     .target        = XT_TARGET_INIT(XT_STANDARD_TARGET,         \
  5.                      sizeof(struct xt_standard_target)), \
  6.     .target.verdict    = -(__verdict) - 1,                 \
  7. }

点击(此处)折叠或打开

  1. #define XT_TARGET_INIT(__name, __size)                     \
  2. {                                     \
  3.     .target.u.user = {                         \
  4.         .target_size    = XT_ALIGN(__size),             \
  5.         .name        = __name,                 \
  6.     },                                 \
  7. }
Entry主要初始化了target_offset为ipt_entry大小 和 next_offset 为外加target
Target初始化了name 为XT_STANDARD_TARGET 和target_size
总共初始化了三个entry根据filter的hooknum
在xt_register_table的时候把struct xt_table_info *newinfo;赋给table->private
这也就是为什么用户空间为一开始获取内核的entries。然后才能顺利开展后续工作.


回到主函数命令处理:
然后进入生成entry阶段,这里比较关键,它初始化了
iptables table ,后续很重要的部分:

点击(此处)折叠或打开

  1. else {
  2.   
  3.         e= generate_entry(&cs.fw, cs.matches, cs.target->t);
  4.  
  5.         free(cs.target->t);
  6.  
  7.                    }


因为它用代码说明了rules的组成:点击(此处)折叠或打开

  1. static struct ipt_entry *
  2. generate_entry(const struct ipt_entry *fw,
  3.      struct xtables_rule_match *matches,
  4.      struct xt_entry_target *target)
  5. {
  6.     unsigned int size;
  7.     struct xtables_rule_match *matchp;
  8.     struct ipt_entry *e;

  9.     size = sizeof(struct ipt_entry);
  10.     for (matchp = matches; matchp; matchp = matchp->next)
  11.         size += matchp->match->m->u.match_size;

  12.     e = xtables_malloc(size + target->u.target_size);
  13.     *e = *fw;
  14.     e->target_offset = size;
  15.     e->next_offset = size + target->u.target_size;

  16.     size = 0;
  17.     for (matchp = matches; matchp; matchp = matchp->next) {
  18.         memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size);
  19.         size += matchp->match->m->u.match_size;
  20.     }
  21.     memcpy(e->elems + size, target, target->u.target_size);

  22.     return e;
  23. }


Ipt_entry+ xt_entry_match+…+xt_entry_target  (…表示match可以不止一个,但target只有一个)
申请新的ipt_entry *e空间,这里把ipt_entry->ip 赋值,复制entry_matchs和entry_target.
接着是对command的处理,



点击(此处)折叠或打开

  1. switch (command) {
  2.   
  3.     case CMD_APPEND:
  4.  
  5.         ret = append_entry(chain, e,
  6.  
  7.                    nsaddrs, saddrs, smasks,
  8.  
  9.                    ndaddrs, daddrs, dmasks,
  10.  
  11.                    cs.options&OPT_VERBOSE,
  12.  
  13.                  *handle);
  14.  
  15.         break;



append_entry的参数的传递这里就不解释了,前面都分析完了.
1.ip初始化 2.iptc_append_entry. 这个函数很好理解,就是附加entry.
处理完命令 ,然后释放掉原来的空间.
完成do_comand4后,自然是要提交我们的东西到内核里
即通过ipt_commit函数

原理是利用Setsockopt下发配置,关于setsockopt这里就不详细讲解了.


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