|
 |
栏目导栏 |
|
| |
|
|
|
|
 |
资料搜索 |
|
| |
|
|
|
|
 |
热门文章 |
|
| |
|
|
|
|
 |
最新文章 |
|
| |
|
|
|
| |
| |
|
|
|
|
[ 作者: 加入时间:2006-06-20 12:54:03 来自:
] | |
|
| iptables 源码分析 | | 作者:独孤九贱 出处:www.chinaunix.net 更新时间: 2005年12月22日 | 一、规则的显示6ckLinux联盟 选择先来说明规则的显示,因为他涉及到的东东简单,而且又全面,了解了规则的显示,对于其它操作的了解就显得容易了。6ckLinux联盟 6ckLinux联盟 iptables version 1.2.76ckLinux联盟 6ckLinux联盟 iptables有两条线:ipv4 和ipv6,这里只分析v4的,因为v6偶暂时还用不着,没有去看。6ckLinux联盟 6ckLinux联盟 iptables_standardone.c6ckLinux联盟 主函数:6ckLinux联盟 int main(int argc, char *argv[])6ckLinux联盟 {6ckLinux联盟 int ret;6ckLinux联盟 char *table = "filter"; /*默认的表是filter*/6ckLinux联盟 iptc_handle_t handle = NULL;6ckLinux联盟 6ckLinux联盟 program_name = "iptables";6ckLinux联盟 program_version = IPTABLES_VERSION;6ckLinux联盟 6ckLinux联盟 #ifdef NO_SHARED_LIBS6ckLinux联盟 init_extensions();6ckLinux联盟 #endif6ckLinux联盟 6ckLinux联盟 /*进入命令行处理函数*/6ckLinux联盟 ret = do_command(argc, argv, &table, &handle);6ckLinux联盟 if (ret)6ckLinux联盟 ret = iptc_commit(&handle);6ckLinux联盟 6ckLinux联盟 if (!ret)6ckLinux联盟 fprintf(stderr, "iptables: %s\n",6ckLinux联盟 iptc_strerror(errno));6ckLinux联盟 6ckLinux联盟 exit(!ret);6ckLinux联盟 }6ckLinux联盟 table表示表的名称,就是iptables -t 后面跟的那个,默认是"filter"6ckLinux联盟 iptc_handle_t handle = NULL; 这个东东很重要,现在初始化NULL,后面他被用来存储一个表的所有规则的快照。6ckLinux联盟 program_name = "iptables";6ckLinux联盟 program_version = IPTABLES_VERSION;6ckLinux联盟 设置名称和版本。6ckLinux联盟 #ifdef NO_SHARED_LIBS6ckLinux联盟 init_extensions();6ckLinux联盟 #endif6ckLinux联盟 iptables很多东东,是用共享库*.so的形式(我们安装会,可以在诸如/lib/iptables下边看到),如果不采用共享库,则进行一个初始化操作。我们假设是采用共享库的,忽略它。6ckLinux联盟 6ckLinux联盟 然后就进入核心处理模块:6ckLinux联盟 do_command(argc, argv, &table, &handle);6ckLinux联盟 6ckLinux联盟 do_command 函数是整个系统的核心,负责处理整个用户的输入命令。函数首先对一些结构、变量进行初始化,初始化完毕后,进入while循环,分析用户输入的命令,设置相关的标志变量,然后根据相应标志,调用对应的处理函数。6ckLinux联盟 6ckLinux联盟 struct ipt_entry fw, *e = NULL;6ckLinux联盟 int invert = 0;6ckLinux联盟 unsigned int nsaddrs = 0, ndaddrs = 0;6ckLinux联盟 struct in_addr *saddrs = NULL, *daddrs = NULL;6ckLinux联盟 6ckLinux联盟 int c, verbose = 0;6ckLinux联盟 const char *chain = NULL;6ckLinux联盟 const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;6ckLinux联盟 const char *policy = NULL, *newname = NULL;6ckLinux联盟 unsigned int rulenum = 0, options = 0, command = 0;6ckLinux联盟 const char *pcnt = NULL, *bcnt = NULL;6ckLinux联盟 int ret = 1;6ckLinux联盟 struct iptables_match *m;6ckLinux联盟 struct iptables_target *target = NULL;6ckLinux联盟 struct iptables_target *t;6ckLinux联盟 const char *jumpto = "";6ckLinux联盟 char *protocol = NULL;6ckLinux联盟 const char *modprobe = NULL;6ckLinux联盟 6ckLinux联盟 /*初始化变量*/6ckLinux联盟 memset(&fw, 0, sizeof(fw));6ckLinux联盟 6ckLinux联盟 opts = original_opts;6ckLinux联盟 global_option_offset = 0;6ckLinux联盟 6ckLinux联盟 /* re-set optind to 0 in case do_command gets called6ckLinux联盟 * a second time */6ckLinux联盟 optind = 0;6ckLinux联盟 6ckLinux联盟 /*初始化两个全局变量*/6ckLinux联盟 /* clear mflags in case do_command gets called a second time6ckLinux联盟 * (we clear the global list of all matches for security)*/6ckLinux联盟 for (m = iptables_matches; m; m = m->next) {6ckLinux联盟 m->mflags = 0;6ckLinux联盟 m->used = 0;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 for (t = iptables_targets; t; t = t->next) {6ckLinux联盟 t->tflags = 0;6ckLinux联盟 t->used = 0;6ckLinux联盟 }6ckLinux联盟 ps:开头一大堆的变量定义和初始化,可以在程序分析的时候看它们的作用,有两个全局结构变量很重要:iptables_matches和iptables_targets。现在来分析他们的作用会有一点困难,因为它们涉及到了太多方面的东东,这里,可以先把它们“想像成”用户空间用来读取内核规则的结构(当然,这有点错误)。6ckLinux联盟 6ckLinux联盟 /*开始化析命令行*/6ckLinux联盟 while ((c = getopt_long(argc, argv,6ckLinux联盟 "-A:C:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:",6ckLinux联盟 opts, NULL)) != -1) 6ckLinux联盟 {6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 这个while循环处理所有的用户输入,对应规则输出-L,有:6ckLinux联盟 case 'L':6ckLinux联盟 add_command(&command, CMD_LIST, CMD_ZERO,6ckLinux联盟 invert);6ckLinux联盟 if (optarg) chain = optarg;6ckLinux联盟 else if (optind < argc && argv[optind][0] != '-'6ckLinux联盟 && argv[optind][0] != '!')6ckLinux联盟 chain = argv[optind++];6ckLinux联盟 break;6ckLinux联盟 6ckLinux联盟 add_command函数负责将命令标志变量command与令标志 CMD_LIST求&运算, CMD_ZERO只是一个附加的判断标志而已,invert);然后,从命令行中取得要显示的链名(如果有的话)。6ckLinux联盟 6ckLinux联盟 与此相关的还有用t参数指定了表名:6ckLinux联盟 case 't':6ckLinux联盟 if (invert)6ckLinux联盟 exit_error(PARAMETER_PROBLEM,6ckLinux联盟 "unexpected ! flag before --table");6ckLinux联盟 *table = argv[optind-1];6ckLinux联盟 break;6ckLinux联盟 即,如果有’t’参数,则取’t’后跟的表名:*table = argv[optind-1],否则,它应该是主函数中默认的filter表。6ckLinux联盟 6ckLinux联盟 命令处理完毕后,即进入执行模块:6ckLinux联盟 /*因为程序定义了共享库的话,iptables_matches/iptables_target这两个结构运行至此是NULL,并且target也是NULL,对于规则显示而言,这一部份的处理目前没有实际意义,回过头再来看这一段更易理解。final_check成员函数的作用是作最终的标志检查,如果检测失则,则退出*/6ckLinux联盟 for (m = iptables_matches; m; m = m->next) {6ckLinux联盟 if (!m->used)6ckLinux联盟 continue;6ckLinux联盟 6ckLinux联盟 m->final_check(m->mflags);6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 if (target)6ckLinux联盟 target->final_check(target->tflags);6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 接着对参数作一些必要的合法性检查:6ckLinux联盟 /* Fix me: must put inverse options checking here --MN */6ckLinux联盟 6ckLinux联盟 if (optind < argc)6ckLinux联盟 exit_error(PARAMETER_PROBLEM,6ckLinux联盟 "unknown arguments found on commandline");6ckLinux联盟 if (!command)6ckLinux联盟 exit_error(PARAMETER_PROBLEM, "no command specified");6ckLinux联盟 if (invert)6ckLinux联盟 exit_error(PARAMETER_PROBLEM,6ckLinux联盟 "nothing appropriate following !");6ckLinux联盟 6ckLinux联盟 /*对于如果要进行(CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)处理来说,如果没有设置来源/目的地址及掩码,则给予它们一个默认值*/6ckLinux联盟 if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {6ckLinux联盟 if (!(options & OPT_DESTINATION))6ckLinux联盟 dhostnetworkmask = "0.0.0.0/0";6ckLinux联盟 if (!(options & OPT_SOURCE))6ckLinux联盟 shostnetworkmask = "0.0.0.0/0";6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 /*对来源/目的地址及掩码进行拆分,它们总是以 addr/mask的形式来出现的,根据’/’前面的字符串取得地址值,根据’/’后面的掩码位数,求得正确的掩码值,值得注意的是,同时要处理主机地址和网络地址的情况*/6ckLinux联盟 if (shostnetworkmask)6ckLinux联盟 parse_hostnetworkmask(shostnetworkmask, &saddrs,6ckLinux联盟 &(fw.ip.smsk), &nsaddrs);6ckLinux联盟 6ckLinux联盟 if (dhostnetworkmask)6ckLinux联盟 parse_hostnetworkmask(dhostnetworkmask, &daddrs,6ckLinux联盟 &(fw.ip.dmsk), &ndaddrs);6ckLinux联盟 6ckLinux联盟 /*然后检查来源/目的网络地址的合法性*/6ckLinux联盟 if ((nsaddrs > 1 || ndaddrs > 1) &&6ckLinux联盟 (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))6ckLinux联盟 exit_error(PARAMETER_PROBLEM, "! not allowed with multiple"6ckLinux联盟 " source or destination IP addresses");6ckLinux联盟 6ckLinux联盟 /*对命令行格式进行合法性检查*/6ckLinux联盟 generic_opt_check(command, options);6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 如果前面只是热身的话,那么从现在开始,就进入实质性阶段了:6ckLinux联盟 6ckLinux联盟 do_command函数最后一个参数handle,是一个指向了具体表,如filter、nat表的句柄,这里判断,如果handle为空,则调用iptc_init,根据table的名称,让handle指针指向相应的表的地址空间,也就是把对应表的所有信息从内核中取出来:6ckLinux联盟 /* only allocate handle if we weren't called with a handle */6ckLinux联盟 if (!*handle)6ckLinux联盟 *handle = iptc_init(*table);6ckLinux联盟 6ckLinux联盟 /*如果获取换败,将试着插入模块,再次获取*/6ckLinux联盟 if (!*handle) {6ckLinux联盟 /* try to insmod the module if iptc_init failed */6ckLinux联盟 iptables_insmod("ip_tables", modprobe);6ckLinux联盟 *handle = iptc_init(*table);6ckLinux联盟 /*仍然失败,则退出*/6ckLinux联盟 if (!*handle)6ckLinux联盟 exit_error(VERSION_PROBLEM,6ckLinux联盟 "can't initialize iptables table `%s': %s",6ckLinux联盟 *table, iptc_strerror(errno));6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 /*继续进行一些简单的判断*/6ckLinux联盟 if (command == CMD_APPEND6ckLinux联盟 || command == CMD_DELETE6ckLinux联盟 || command == CMD_INSERT6ckLinux联盟 || command == CMD_REPLACE) {6ckLinux联盟 /*List命令不在判断之列,暂时不分析*/6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 /*判断命令标志,调用相关函数进行处理*/6ckLinux联盟 switch (command) {6ckLinux联盟 case CMD_LIST:6ckLinux联盟 ret = list_entries(chain,6ckLinux联盟 options&OPT_VERBOSE,6ckLinux联盟 options&OPT_NUMERIC,6ckLinux联盟 options&OPT_EXPANDED,6ckLinux联盟 options&OPT_LINENUMBERS,6ckLinux联盟 handle);6ckLinux联盟 }6ckLinux联盟 list_entries是规则显示的主要处理函数。6ckLinux联盟 Options是显示的标志变量:6ckLinux联盟 OPT_VERBOSE:对应-v6ckLinux联盟 OPT_NUMERIC:对应-n6ckLinux联盟 OPT_EXPANDED:对应-x6ckLinux联盟 OPT_LINENUMBERS: -l6ckLinux联盟 6ckLinux联盟 看来很简单,说了这么大一圈子,就是调用 iptc_init获取表的规则信息,调用list_entries函数显示规则。6ckLinux联盟 1.1 表的查找6ckLinux联盟 再回到iptc_init 函数上来,它根据表名,从内核获取对应的表的相关信息,handle是一个iptc_handle_t类型的指针,在libiptc.c中,有如下定义:6ckLinux联盟 /* Transparent handle type. */6ckLinux联盟 typedef struct iptc_handle *iptc_handle_t;6ckLinux联盟 6ckLinux联盟 在Libip4tc中:6ckLinux联盟 #define STRUCT_TC_HANDLE struct iptc_handle6ckLinux联盟 在Libiptc.c中,可以找到STRUCT_TC_HANDLE的定义:6ckLinux联盟 STRUCT_TC_HANDLE6ckLinux联盟 {6ckLinux联盟 /* Have changes been made? */6ckLinux联盟 int changed;6ckLinux联盟 /* Size in here reflects original state. */6ckLinux联盟 STRUCT_GETINFO info;6ckLinux联盟 6ckLinux联盟 struct counter_map *counter_map;6ckLinux联盟 /* Array of hook names */6ckLinux联盟 const char **hooknames;6ckLinux联盟 6ckLinux联盟 /* Cached position of chain heads (NULL = no cache). */6ckLinux联盟 unsigned int cache_num_chains;6ckLinux联盟 unsigned int cache_num_builtins;6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 /* Rule iterator: terminal rule */6ckLinux联盟 STRUCT_ENTRY *cache_rule_end;6ckLinux联盟 6ckLinux联盟 /* Number in here reflects current state. */6ckLinux联盟 unsigned int new_number;6ckLinux联盟 STRUCT_GET_ENTRIES entries;6ckLinux联盟 };6ckLinux联盟 6ckLinux联盟 再来看看iptc_init函数,同样在在Libip4tc中,有如下定义:6ckLinux联盟 #define TC_INIT iptc_init6ckLinux联盟 6ckLinux联盟 在Libiptc.c中,可以看到函数的实现,基本上iptables与内核的交互,都是使用setsockopt函数来实现的,对于获取取规是信息来说,标志位是SO_GET_INFO,而从内核返回回来的规则信息是一个STRUCT_GETINFO结构:6ckLinux联盟 6ckLinux联盟 TC_HANDLE_T TC_INIT(const char *tablename)6ckLinux联盟 {6ckLinux联盟 TC_HANDLE_T h;6ckLinux联盟 STRUCT_GETINFO info;6ckLinux联盟 unsigned int i;6ckLinux联盟 int tmp;6ckLinux联盟 socklen_t s;6ckLinux联盟 6ckLinux联盟 iptc_fn = TC_INIT;6ckLinux联盟 6ckLinux联盟 if (sockfd != -1)6ckLinux联盟 close(sockfd);6ckLinux联盟 6ckLinux联盟 /*为获取信息打开一个套接字接口*/6ckLinux联盟 sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);6ckLinux联盟 if (sockfd < 0)6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 s = sizeof(info);6ckLinux联盟 if (strlen(tablename) >= TABLE_MAXNAMELEN) {6ckLinux联盟 errno = EINVAL;6ckLinux联盟 return NULL;6ckLinux联盟 }6ckLinux联盟 strcpy(info.name, tablename);6ckLinux联盟 /*获取规则信息*/6ckLinux联盟 if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0)6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 if ((h = alloc_handle(info.name, info.size, info.num_entries))6ckLinux联盟 == NULL)6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 /* Too hard --RR */6ckLinux联盟 #if 06ckLinux联盟 sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name);6ckLinux联盟 dynlib = dlopen(pathname, RTLD_NOW);6ckLinux联盟 if (!dynlib) {6ckLinux联盟 errno = ENOENT;6ckLinux联盟 return NULL;6ckLinux联盟 }6ckLinux联盟 h->hooknames = dlsym(dynlib, "hooknames");6ckLinux联盟 if (!h->hooknames) {6ckLinux联盟 errno = ENOENT;6ckLinux联盟 return NULL;6ckLinux联盟 }6ckLinux联盟 #else6ckLinux联盟 h->hooknames = hooknames;6ckLinux联盟 #endif6ckLinux联盟 6ckLinux联盟 /* Initialize current state */6ckLinux联盟 h->info = info;6ckLinux联盟 h->new_number = h->info.num_entries;6ckLinux联盟 for (i = 0; i < h->info.num_entries; i++)6ckLinux联盟 h->counter_map6ckLinux联盟 = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i});6ckLinux联盟 6ckLinux联盟 h->entries.size = h->info.size;6ckLinux联盟 6ckLinux联盟 tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;6ckLinux联盟 6ckLinux联盟 if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,6ckLinux联盟 &tmp) < 0) {6ckLinux联盟 free(h);6ckLinux联盟 return NULL;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 CHECK(h);6ckLinux联盟 return h;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 函数为h分配空间,然后赋予相应的值。要理解这个函数,还需要了解STRUCT_GETINFO结构和分配内存空间的函数alloc_handle。6ckLinux联盟 6ckLinux联盟 #define STRUCT_GETINFO struct ipt_getinfo6ckLinux联盟 6ckLinux联盟 /* The argument to IPT_SO_GET_INFO */ 6ckLinux联盟 6ckLinux联盟 struct ipt_getinfo6ckLinux联盟 {6ckLinux联盟 /* Which table: caller fills this in. */6ckLinux联盟 char name[IPT_TABLE_MAXNAMELEN];6ckLinux联盟 6ckLinux联盟 /* Kernel fills these in. */6ckLinux联盟 /* Which hook entry points are valid: bitmask */6ckLinux联盟 unsigned int valid_hooks;6ckLinux联盟 6ckLinux联盟 /* Hook entry points: one per netfilter hook. */6ckLinux联盟 unsigned int hook_entry[NF_IP_NUMHOOKS];6ckLinux联盟 6ckLinux联盟 /* Underflow points. */6ckLinux联盟 unsigned int underflow[NF_IP_NUMHOOKS];6ckLinux联盟 6ckLinux联盟 /* Number of entries */6ckLinux联盟 unsigned int num_entries;6ckLinux联盟 6ckLinux联盟 /* Size of entries. */6ckLinux联盟 unsigned int size;6ckLinux联盟 };6ckLinux联盟 6ckLinux联盟 /* Allocate handle of given size */6ckLinux联盟 static TC_HANDLE_T6ckLinux联盟 alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)6ckLinux联盟 {6ckLinux联盟 size_t len;6ckLinux联盟 TC_HANDLE_T h;6ckLinux联盟 6ckLinux联盟 len = sizeof(STRUCT_TC_HANDLE)6ckLinux联盟 + size6ckLinux联盟 + num_rules * sizeof(struct counter_map);6ckLinux联盟 6ckLinux联盟 if ((h = malloc(len)) == NULL) {6ckLinux联盟 errno = ENOMEM;6ckLinux联盟 return NULL;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 h->changed = 0;6ckLinux联盟 h->cache_num_chains = 0;6ckLinux联盟 h->cache_chain_heads = NULL;6ckLinux联盟 h->counter_map = (void *)h6ckLinux联盟 + sizeof(STRUCT_TC_HANDLE)6ckLinux联盟 + size;6ckLinux联盟 strcpy(h->info.name, tablename);6ckLinux联盟 strcpy(h->entries.name, tablename);6ckLinux联盟 6ckLinux联盟 return h;6ckLinux联盟 } 6ckLinux联盟 函数list_entries用于显示表下边的链:6ckLinux联盟 /*显示某table下的chain*/6ckLinux联盟 static int6ckLinux联盟 list_entries(const ipt_chainlabel chain, int verbose, int numeric,6ckLinux联盟 int expanded, int linenumbers, iptc_handle_t *handle)6ckLinux联盟 {6ckLinux联盟 int found = 0;6ckLinux联盟 unsigned int format;6ckLinux联盟 const char *this;6ckLinux联盟 6ckLinux联盟 format = FMT_OPTIONS; /*设置输出格式*/6ckLinux联盟 if (!verbose) /*详细输出模式,,对应-v ,显示匹配的包的数目,包的大小等*/6ckLinux联盟 format |= FMT_NOCOUNTS;6ckLinux联盟 else6ckLinux联盟 format |= FMT_VIA; 6ckLinux联盟 6ckLinux联盟 if (numeric) /*对应-n,以数字的形式输出地址和端口*/6ckLinux联盟 format |= FMT_NUMERIC;6ckLinux联盟 6ckLinux联盟 if (!expanded) /*对应-x,expand numbers (display exact values)*/6ckLinux联盟 format |= FMT_KILOMEGAGIGA;6ckLinux联盟 6ckLinux联盟 if (linenumbers) /*输出行的编号*/6ckLinux联盟 format |= FMT_LINENUMBERS;6ckLinux联盟 6ckLinux联盟 for (this = iptc_first_chain(handle); /*遍历当前table的所有chain*/6ckLinux联盟 this;6ckLinux联盟 this = iptc_next_chain(handle))6ckLinux联盟 { 6ckLinux联盟 const struct ipt_entry *i;6ckLinux联盟 unsigned int num;6ckLinux联盟 6ckLinux联盟 if (chain && strcmp(chain, this) != 0) /*匹配指定chain名,这里用chain &&,即若不指定chain,输出所有chain*/6ckLinux联盟 continue;6ckLinux联盟 6ckLinux联盟 if (found) printf("\n");6ckLinux联盟 6ckLinux联盟 print_header(format, this, handle); /*输出标头*/6ckLinux联盟 i = iptc_first_rule(this, handle); /*移至当前chain的第一条规则*/6ckLinux联盟 6ckLinux联盟 num = 0;6ckLinux联盟 while (i) {6ckLinux联盟 print_firewall(i, /*输出当前规则*/6ckLinux联盟 iptc_get_target(i, handle),6ckLinux联盟 num++,6ckLinux联盟 format,6ckLinux联盟 *handle);6ckLinux联盟 i = iptc_next_rule(i, handle); /*移至下一条规则*/6ckLinux联盟 }6ckLinux联盟 found = 1;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 errno = ENOENT;6ckLinux联盟 return found;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 可见,在函数中,由iptc_first_chain和iptc_next_chain实现了遍历,iptc_first_rule和iptc_next_rule实现了链中规是的遍历,print_firewall函数在遍历到规则的时候,向终端输出防火墙规则,其第二个参数iptc_get_target又用于获取规则的target。6ckLinux联盟 6ckLinux联盟 前面提到过,在内核中,handler指针指向了从内核中返回的对应的表的信息,handler对应的结构中,涉及到链的结构成员主要有两个:6ckLinux联盟 struct chain_cache *cache_chain_heads;6ckLinux联盟 struct chain_cache *cache_chain_iteration;6ckLinux联盟 前者用于指向第一个链,后者指向当前链。而struct chain_cache的定义如下:6ckLinux联盟 struct chain_cache6ckLinux联盟 {6ckLinux联盟 char name[TABLE_MAXNAMELEN]; /*链名*/6ckLinux联盟 STRUCT_ENTRY *start; /*该链的第一条规则*/6ckLinux联盟 STRUCT_ENTRY *end; /*该链的最后一条规则*/6ckLinux联盟 };6ckLinux联盟 理解了这两个成员,和结构struct chain_cache,再来理解链的遍历函数就不难了。所谓链的遍历,就是将handler对应成员的值取出来。6ckLinux联盟 6ckLinux联盟 #define TC_FIRST_CHAIN iptc_first_chain6ckLinux联盟 #define TC_NEXT_CHAIN iptc_next_chain6ckLinux联盟 6ckLinux联盟 函数TC_FIRST_CHAIN用于返回第一个链:6ckLinux联盟 /* Iterator functions to run through the chains. */6ckLinux联盟 const char *6ckLinux联盟 TC_FIRST_CHAIN(TC_HANDLE_T *handle)6ckLinux联盟 {6ckLinux联盟 /*链首为空,则返回NULL*/6ckLinux联盟 if ((*handle)->cache_chain_heads == NULL6ckLinux联盟 && !populate_cache(*handle))6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 /*当前链的指针指向链表首部*/6ckLinux联盟 (*handle)->cache_chain_iteration6ckLinux联盟 = &(*handle)->cache_chain_heads[0];6ckLinux联盟 /*返回链的名称*/6ckLinux联盟 return (*handle)->cache_chain_iteration->name;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 /* Iterator functions to run through the chains. Returns NULL at end. */6ckLinux联盟 const char *6ckLinux联盟 TC_NEXT_CHAIN(TC_HANDLE_T *handle)6ckLinux联盟 {6ckLinux联盟 /*很简单,用heads开始,用++就可以实现遍历了*/6ckLinux联盟 (*handle)->cache_chain_iteration++;6ckLinux联盟 6ckLinux联盟 if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads6ckLinux联盟 == (*handle)->cache_num_chains)6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 return (*handle)->cache_chain_iteration->name;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 规则的遍历6ckLinux联盟 当遍历到某个链的时候,接下来,就需要遍历当前链下的所有规则了,输出之了。前面叙述了链的遍历,那么规则的遍历,应该就是根据链的名称,找到对应的成员结构struct chain_cache ,这里面包含了当前链的第一条规则与最后一条规则的指针:6ckLinux联盟 6ckLinux联盟 #define TC_FIRST_RULE iptc_first_rule6ckLinux联盟 #define TC_NEXT_RULE iptc_next_rule6ckLinux联盟 /* Get first rule in the given chain: NULL for empty chain. */6ckLinux联盟 const STRUCT_ENTRY *6ckLinux联盟 TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)6ckLinux联盟 {6ckLinux联盟 struct chain_cache *c;6ckLinux联盟 6ckLinux联盟 c = find_label(chain, *handle); /*根据链名,返回对应的struct chain_cache结构*/6ckLinux联盟 if (!c) { /*没有找到,返回NULL*/6ckLinux联盟 errno = ENOENT;6ckLinux联盟 return NULL;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 /* Empty chain: single return/policy rule */6ckLinux联盟 if (c->start == c->end) /*如果是空链*/6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 (*handle)->cache_rule_end = c->end;6ckLinux联盟 return c->start; /*返回链的首条规则*/6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 /* Returns NULL when rules run out. */6ckLinux联盟 const STRUCT_ENTRY *6ckLinux联盟 TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)6ckLinux联盟 {6ckLinux联盟 if ((void *)prev + prev->next_offset6ckLinux联盟 == (void *)(*handle)->cache_rule_end)6ckLinux联盟 return NULL;6ckLinux联盟 6ckLinux联盟 return (void *)prev + prev->next_offset;6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 要更解TC_NEXT_RULE函数是如何实现查找下一条规则的,需要首先理解STRUCT_ENTRY结构:6ckLinux联盟 #define STRUCT_ENTRY struct ipt_entry6ckLinux联盟 ipt_entry结构用于存储链的规则,每一个包过滤规则可以分成两部份:条件和动作。前者在Netfilter中,称为match,后者称之为target。Match又分为两部份,一部份为一些基本的元素,如来源/目的地址,进/出网口,协议等,对应了struct ipt_ip,我们常常将其称为标准的match,另一部份match则以插件的形式存在,是动态可选择,也允许第三方开发的,常常称为扩展的match,如字符串匹配,p2p匹配等。同样,规则的target也是可扩展的。这样,一条规则占用的空间,可以分为:struct ipt_ip+n*match+n*target,(n表示了其个数,这里的match指的是可扩展的match部份)。基于此,规则对应的结构如下:6ckLinux联盟 /* This structure defines each of the firewall rules. Consists of 36ckLinux联盟 parts which are 1) general IP header stuff 2) match specific6ckLinux联盟 stuff 3) the target to perform if the rule matches */6ckLinux联盟 struct ipt_entry6ckLinux联盟 {6ckLinux联盟 struct ipt_ip ip; /*标准的match部份*/6ckLinux联盟 6ckLinux联盟 /* Mark with fields that we care about. */6ckLinux联盟 unsigned int nfcache;6ckLinux联盟 6ckLinux联盟 /* Size of ipt_entry + matches */6ckLinux联盟 u_int16_t target_offset; /*target的开始位置,是sizeof(ipt_entry+n*match)*/6ckLinux联盟 /* Size of ipt_entry + matches + target */6ckLinux联盟 u_int16_t next_offset; /*下一条规则相对于本条规则的位置,是sizeof(ipt_entry)加上所有的match,以及所有的target*/6ckLinux联盟 6ckLinux联盟 /* Back pointer */6ckLinux联盟 unsigned int comefrom;6ckLinux联盟 6ckLinux联盟 /* Packet and byte counters. */6ckLinux联盟 struct ipt_counters counters;6ckLinux联盟 6ckLinux联盟 /* The matches (if any), then the target. */6ckLinux联盟 unsigned char elems[0];6ckLinux联盟 };6ckLinux联盟 6ckLinux联盟 有了这样的基础,就不难理解遍历规则中,寻找下一条规则语句:6ckLinux联盟 return (void *)prev + prev->next_offset;6ckLinux联盟 即是本条规则加上下一条规则的偏移值。 6ckLinux联盟 输出规则6ckLinux联盟 print_firewall 函数用于规则的输出:6ckLinux联盟 print_firewall(i, iptc_get_target(i, handle), num++,format,*handle);6ckLinux联盟 i:当前的规则;6ckLinux联盟 iptc_get_target(i, handle):用于规则的target部份的处理;6ckLinux联盟 num:规则序号;6ckLinux联盟 format:输出格式;6ckLinux联盟 handler:表的信息;6ckLinux联盟 6ckLinux联盟 6ckLinux联盟 /* e is called `fw' here for hysterical raisins */6ckLinux联盟 static void6ckLinux联盟 print_firewall(const struct ipt_entry *fw,6ckLinux联盟 const char *targname,6ckLinux联盟 unsigned int num,6ckLinux联盟 unsigned int format,6ckLinux联盟 const iptc_handle_t handle)6ckLinux联盟 {6ckLinux联盟 struct iptables_target *target = NULL;6ckLinux联盟 const struct ipt_entry_target *t;6ckLinux联盟 u_int8_t flags;6ckLinux联盟 char buf[BUFSIZ];6ckLinux联盟 6ckLinux联盟 if (!iptc_is_chain(targname, handle))6ckLinux联盟 target = find_target(targname, TRY_LOAD);6ckLinux联盟 else6ckLinux联盟 target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED);6ckLinux联盟 6ckLinux联盟 t = ipt_get_target((struct ipt_entry *)fw);6ckLinux联盟 flags = fw->ip.flags;6ckLinux联盟 6ckLinux联盟 if (format & FMT_LINENUMBERS) /*输出行号*/6ckLinux联盟 printf(FMT("%-4u ", "%u "), num+1);6ckLinux联盟 6ckLinux联盟 if (!(format & FMT_NOCOUNTS)) { /*详细模式,列出计数器*/6ckLinux联盟 print_num(fw->counters.pcnt, format); /*匹配当前规则的数据包个数*/6ckLinux联盟 print_num(fw->counters.bcnt, format); /*--------------------大小*/6ckLinux联盟 }6ckLinux联盟 /*输出目标名称*/6ckLinux联盟 if (!(format & FMT_NOTARGET)) /*目标名称,即拦截、通过等动作*/6ckLinux联盟 printf(FMT("%-9s ", "%s "), targname);6ckLinux联盟 6ckLinux联盟 /*输出协议名*/6ckLinux联盟 fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout);6ckLinux联盟 {6ckLinux联盟 char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);6ckLinux联盟 if (pname)6ckLinux联盟 printf(FMT("%-5s", "%s "), pname);6ckLinux联盟 else6ckLinux联盟 printf(FMT("%-5hu", "%hu "), fw->ip.proto);6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 /*输出选项字段*/6ckLinux联盟 if (format & FMT_OPTIONS) {6ckLinux联盟 if (format & FMT_NOTABLE)6ckLinux联盟 fputs("opt ", stdout);6ckLinux联盟 fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); //#define IP_FW_INV_FRAG 0x0080 /* Invert the sense of IP_FW_F_FRAG. */ 6ckLinux联盟 fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); //#define IP_FW_F_FRAG 0x0004 /* Set if rule is a fragment rule */6ckLinux联盟 fputc(' ', stdout);6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 if (format & FMT_VIA) {6ckLinux联盟 char iface[IFNAMSIZ+2];6ckLinux联盟 6ckLinux联盟 if (fw->ip.invflags & IPT_INV_VIA_IN) { /*输入端口取反标志*/6ckLinux联盟 iface[0] = '!'; /*设置取反标志符*/6ckLinux联盟 iface[1] = '\0';6ckLinux联盟 }6ckLinux联盟 else iface[0] = '\0';6ckLinux联盟 6ckLinux联盟 if (fw->ip.iniface[0] != '\0') {6ckLinux联盟 strcat(iface, fw->ip.iniface);6ckLinux联盟 }6ckLinux联盟 else if (format & FMT_NUMERIC) strcat(iface, "*");6ckLinux联盟 else strcat(iface, "any");6ckLinux联盟 printf(FMT(" %-6s ","in %s "), iface); /*输出输入端口*/6ckLinux联盟 6ckLinux联盟 if (fw->ip.invflags & IPT_INV_VIA_OUT) { /*输出端口取反标志*/6ckLinux联盟 iface[0] = '!'; /*设置取反标志符*/6ckLinux联盟 iface[1] = '\0';6ckLinux联盟 }6ckLinux联盟 else iface[0] = '\0';6ckLinux联盟 6ckLinux联盟 if (fw->ip.outiface[0] != '\0') {6ckLinux联盟 strcat(iface, fw->ip.outiface);6ckLinux联盟 }6ckLinux联盟 else if (format & FMT_NUMERIC) strcat(iface, "*"); 6ckLinux联盟 else strcat(iface, "any");6ckLinux联盟 printf(FMT("%-6s ","out %s "), iface); /*输出输出端口*/6ckLinux联盟 } /*end print in/out interface */6ckLinux联盟 6ckLinux联盟 /*输出源地址及掩码*/6ckLinux联盟 fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); /*源地址取反标志*/6ckLinux联盟 if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) /*源地址为任意*/6ckLinux联盟 printf(FMT("%-19s ","%s "), "anywhere");6ckLinux联盟 else { 6ckLinux联盟 if (format & FMT_NUMERIC)6ckLinux联盟 sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src)));6ckLinux联盟 else6ckLinux联盟 sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src)));6ckLinux联盟 strcat(buf, mask_to_dotted(&(fw->ip.smsk)));6ckLinux联盟 printf(FMT("%-19s ","%s "), buf);6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 /*输出目的地址及掩码*/6ckLinux联盟 fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);6ckLinux联盟 if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))6ckLinux联盟 printf(FMT("%-19s","-> %s"), "anywhere");6ckLinux联盟 else {6ckLinux联盟 if (format & FMT_NUMERIC)6ckLinux联盟 sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst)));6ckLinux联盟 else6ckLinux联盟 sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst)));6ckLinux联盟 strcat(buf, mask_to_dotted(&(fw->ip.dmsk)));6ckLinux联盟 printf(FMT("%-19s","-> %s"), buf);6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 if (format & FMT_NOTABLE)6ckLinux联盟 fputs(" ", stdout);6ckLinux联盟 6ckLinux联盟 /*输出扩展的MATCH*/6ckLinux联盟 6ckLinux联盟 IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC); 6ckLinux联盟 6ckLinux联盟 /*输出扩展的TARGET*/6ckLinux联盟 if (target) {6ckLinux联盟 if (target->print)6ckLinux联盟 /* Print the target information. */6ckLinux联盟 target->print(&fw->ip, t, format & FMT_NUMERIC);6ckLinux联盟 } else if (t->u.target_size != sizeof(*t))6ckLinux联盟 printf("[%u bytes of unknown target data] ",6ckLinux联盟 t->u.target_size - sizeof(*t));6ckLinux联盟 6ckLinux联盟 if (!(format & FMT_NONEWLINE))6ckLinux联盟 fputc('\n', stdout);6ckLinux联盟 }6ckLinux联盟 6ckLinux联盟 函数分为三部份:6ckLinux联盟 输出标准的match部份;6ckLinux联盟 输出扩展的match部份,调用IPT_MATCH_ITERATE实现;6ckLinux联盟 调用对应的target的print函数输出target部份。 6ckLinux联盟 match的输出6ckLinux联盟 IPT_MATCH_ITERATE 宏用于实现扩展match的遍历。这个宏定义在内核include/Linux/Netfilter-ipv4/Ip_tables.h中:6ckLinux联盟 #define IPT_MATCH_ITERATE(e, fn, args...) \6ckLinux联盟 ({ |
|
|
| |