iptables源码分析(3)

本文详细介绍了iptables中规则和链的遍历方法,包括如何使用特定宏和函数获取链和规则信息,以及如何输出这些信息。重点讲解了遍历过程中涉及的关键结构和函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面提到过,在内核中,handler指针指向了从内核中返回的对应的表的信息,handler对应的结构中,涉及到链的结构成员主要有两个:

struct chain_cache *cache_chain_heads;

struct chain_cache *cache_chain_iteration;

前者用于指向第一个链,后者指向当前链。而struct chain_cache的定义如下:

struct chain_cache

{

char name[TABLE_MAXNAMELEN]; /*链名*/

STRUCT_ENTRY *start; /*该链的第一条规则*/

STRUCT_ENTRY *end; /*该链的最后一条规则*/

};

理解了这两个成员,和结构struct chain_cache,再来理解链的遍历函数就不难了。所谓链的遍历,就是将handler对应成员的值取出来。

 

#define TC_FIRST_CHAIN iptc_first_chain

#define TC_NEXT_CHAIN iptc_next_chain

 

函数TC_FIRST_CHAIN用于返回第一个链:

/* Iterator functions to run through the chains. */

const char *

TC_FIRST_CHAIN(TC_HANDLE_T *handle)

{

/*链首为空,则返回NULL*/

if ((*handle)->cache_chain_heads == NULL

&& !populate_cache(*handle))

return NULL;

 

/*当前链的指针指向链表首部*/

(*handle)->cache_chain_iteration

= &(*handle)->cache_chain_heads[0];

/*返回链的名称*/

return (*handle)->cache_chain_iteration->name;

}

 

/* Iterator functions to run through the chains. Returns NULL at end. */

const char *

TC_NEXT_CHAIN(TC_HANDLE_T *handle)

{

/*很简单,用heads开始,用++就可以实现遍历了*/

(*handle)->cache_chain_iteration++;

 

if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads

== (*handle)->cache_num_chains)

return NULL;

 

return (*handle)->cache_chain_iteration->name;

}

 

规则的遍历

当遍历到某个链的时候,接下来,就需要遍历当前链下的所有规则了,输出之了。前面叙述了链的遍历,那么规则的遍历,应该就是根据链的名称,找到对应的成员结构struct chain_cache ,这里面包含了当前链的第一条规则与最后一条规则的指针:

 

#define TC_FIRST_RULE iptc_first_rule

#define TC_NEXT_RULE iptc_next_rule

/* Get first rule in the given chain: NULL for empty chain. */

const STRUCT_ENTRY *

TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)

{

struct chain_cache *c;

 

c = find_label(chain, *handle); /*根据链名,返回对应的struct chain_cache结构*/

if (!c) { /*没有找到,返回NULL*/

errno = ENOENT;

return NULL;

}

 

/* Empty chain: single return/policy rule */

if (c->start == c->end) /*如果是空链*/

return NULL;

 

(*handle)->cache_rule_end = c->end;

return c->start; /*返回链的首条规则*/

}

 

/* Returns NULL when rules run out. */

const STRUCT_ENTRY *

TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)

{

if ((void *)prev + prev->next_offset

== (void *)(*handle)->cache_rule_end)

return NULL;

 

return (void *)prev + prev->next_offset;

}

 

要更解TC_NEXT_RULE函数是如何实现查找下一条规则的,需要首先理解STRUCT_ENTRY结构:

#define STRUCT_ENTRY struct ipt_entry

ipt_entry结构用于存储链的规则,每一个包过滤规则可以分成两部份:条件和 动作。前者在Netfilter中,称为match,后者称之为target。Match又分为两部份,一部份为一些基本的元素,如来源/目的地址,进 /出网口,协议等,对应了struct ipt_ip,我们常常将其称为标准的match,另一部份match则以插件的形式存在,是动态可选择,也允许第三方开发的,常常称为扩展的 match,如字符串匹配,p2p匹配等。同样,规则的target也是可扩展的。这样,一条规则占用的空间,可以分为:struct ipt_ip+n*match+n*target,(n表示了其个数,这里的match指的是可扩展的match部份)。基于此,规则对应的结构如下:

/* This structure defines each of the firewall rules. Consists of 3

parts which are 1) general IP header stuff 2) match specific

stuff 3) the target to perform if the rule matches */

struct ipt_entry

{

struct ipt_ip ip; /*标准的match部份*/

 

/* Mark with fields that we care about. */

unsigned int nfcache;

 

/* Size of ipt_entry + matches */

u_int16_t target_offset; /*target的开始位置,是sizeof(ipt_entry+n*match)*/

/* Size of ipt_entry + matches + target */

u_int16_t next_offset; /*下一条规则相对于本条规则的位置,是sizeof(ipt_entry)加上所有的match,以及所有的target*/

 

/* Back pointer */

unsigned int comefrom;

 

/* Packet and byte counters. */

struct ipt_counters counters;

 

/* The matches (if any), then the target. */

unsigned char elems[0];

};

 

有了这样的基础,就不难理解遍历规则中,寻找下一条规则语句:

return (void *)prev + prev->next_offset;

即是本条规则加上下一条规则的偏移值。

输出规则

print_firewall 函数用于规则的输出:

print_firewall(i, iptc_get_target(i, handle), num++,format,*handle);

i:当前的规则;

iptc_get_target(i, handle):用于规则的target部份的处理;

num:规则序号;

format:输出格式;

handler:表的信息;

 

 

/* e is called `fw' here for hysterical raisins */

static void

print_firewall(const struct ipt_entry *fw,

const char *targname,

unsigned int num,

unsigned int format,

const iptc_handle_t handle)

{

struct iptables_target *target = NULL;

const struct ipt_entry_target *t;

u_int8_t flags;

char buf[BUFSIZ];

 

if (!iptc_is_chain(targname, handle))

target = find_target(targname, TRY_LOAD);

else

target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED);

 

t = ipt_get_target((struct ipt_entry *)fw);

flags = fw->ip.flags;

 

if (format & FMT_LINENUMBERS) /*输出行号*/

printf(FMT("%-4u ", "%u "), num+1);

 

if (!(format & FMT_NOCOUNTS)) { /*详细模式,列出计数器*/

print_num(fw->counters.pcnt, format); /*匹配当前规则的数据包个数*/

print_num(fw->counters.bcnt, format); /*--------------------大小*/

}

/*输出目标名称*/

if (!(format & FMT_NOTARGET)) /*目标名称,即拦截、通过等动作*/

printf(FMT("%-9s ", "%s "), targname);

 

/*输出协议名*/

fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout);

{

char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);

if (pname)

printf(FMT("%-5s", "%s "), pname);

else

printf(FMT("%-5hu", "%hu "), fw->ip.proto);

}

 

/*输出选项字段*/

if (format & FMT_OPTIONS) {

if (format & FMT_NOTABLE)

fputs("opt ", stdout);

fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); //#define IP_FW_INV_FRAG 0x0080 /* Invert the sense of IP_FW_F_FRAG. */

fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); //#define IP_FW_F_FRAG 0x0004 /* Set if rule is a fragment rule */

fputc(' ', stdout);

}

 

if (format & FMT_VIA) {

char iface[IFNAMSIZ+2];

 

if (fw->ip.invflags & IPT_INV_VIA_IN) { /*输入端口取反标志*/

iface[0] = '!'; /*设置取反标志符*/

iface[1] = '/0';

}

else iface[0] = '/0';

 

if (fw->ip.iniface[0] != '/0') {

strcat(iface, fw->ip.iniface);

}

else if (format & FMT_NUMERIC) strcat(iface, "*");

else strcat(iface, "any");

printf(FMT(" %-6s ","in %s "), iface); /*输出输入端口*/

 

if (fw->ip.invflags & IPT_INV_VIA_OUT) { /*输出端口取反标志*/

iface[0] = '!'; /*设置取反标志符*/

iface[1] = '/0';

}

else iface[0] = '/0';

 

if (fw->ip.outiface[0] != '/0') {

strcat(iface, fw->ip.outiface);

}

else if (format & FMT_NUMERIC) strcat(iface, "*");

else strcat(iface, "any");

printf(FMT("%-6s ","out %s "), iface); /*输出输出端口*/

} /*end print in/out interface */

 

/*输出源地址及掩码*/

fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); /*源地址取反标志*/

if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) /*源地址为任意*/

printf(FMT("%-19s ","%s "), "anywhere");

else {

if (format & FMT_NUMERIC)

sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src)));

else

sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src)));

strcat(buf, mask_to_dotted(&(fw->ip.smsk)));

printf(FMT("%-19s ","%s "), buf);

}

 

/*输出目的地址及掩码*/

fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);

if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))

printf(FMT("%-19s","-> %s"), "anywhere");

else {

if (format & FMT_NUMERIC)

sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst)));

else

sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst)));

strcat(buf, mask_to_dotted(&(fw->ip.dmsk)));

printf(FMT("%-19s","-> %s"), buf);

}

 

if (format & FMT_NOTABLE)

fputs(" ", stdout);

 

/*输出扩展的MATCH*/

 

IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);

 

/*输出扩展的TARGET*/

if (target) {

if (target->print)

/* Print the target information. */

target->print(&fw->ip, t, format & FMT_NUMERIC);

} else if (t->u.target_size != sizeof(*t))

printf("[%u bytes of unknown target data] ",

t->u.target_size - sizeof(*t));

 

if (!(format & FMT_NONEWLINE))

fputc('/n', stdout);

}

 

函数分为三部份:

输出标准的match部份;

输出扩展的match部份,调用IPT_MATCH_ITERATE实现;

调用对应的target的print函数输出target部份。

match的输出

IPT_MATCH_ITERATE 宏用于实现扩展match的遍历。这个宏定义在内核include/Linux/Netfilter-ipv4/Ip_tables.h中:

#define IPT_MATCH_ITERATE(e, fn, args...) /

({ /

unsigned int __i; /

int __ret = 0; /

struct ipt_entry_match *__match; /

/

for (__i = sizeof(struct ipt_entry); /

__i < (e)->target_offset; /

__i += __match->u.match_size) { /

__match = (void *)(e) + __i; /

/

__ret = fn(__match , ## args); / /*每找到一个match,就交由fn函数来处理,在print_firewall中,传递过来的是函数print_match*/

if (__ret != 0) /

break; /

} /

__ret; /

})

 

要理解这个宏,需要先了解规则的存储,前面提到过,因为match/target都 是可变的,所以在内存中,采取了ip_entry+n*match+n*target,即在规则后,是连续的若干个match,而mathc后面,又是若 干个target,在结构ip_entry中,成员u_int16_t target_offset;代表了target的偏移地址,即target的开始,match的结束。我们要查到当前规则对应的所有match,需要了 解三个要素:

1、match从哪里开始:起始地址应该是 [当前规则地址+sizeof(struct ipt_entry)];

2、match从哪里结束:结束地址,应该是 [当前规则地址+target_offet];

3、每一个match的大小,在内核中,match对应的结构是ipt_entry_match,其成员u.match_size指明了当前match的大小;

这三点,对应了for循环:

for (__i = sizeof(struct ipt_entry); __i < (e)->target_offset; __i += __match->u.match_size)

这样,i就对应了某个match的偏移植,通过:

__match = (void *)(e) + __i;

就得到了match的地址。

再通过

__ret = fn(__match , ## args);

输出之。

fn函数是在print_firewall中,传递过来的是函数print_match。

 

static int

print_match(const struct ipt_entry_match *m,

const struct ipt_ip *ip,

int numeric)

{

/*根据match名称进行查找,返回一个iptables_match结构,然后调用其中封装的print函数输出该match的信息*/

struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD);

 

if (match) {

if (match->print)

match->print(ip, m, numeric);

else

printf("%s ", match->name);

} else {

if (m->u.user.name[0])

printf("UNKNOWN match `%s' ", m->u.user.name);

}

/* Don't stop iterating. */

return 0;

}

 

这里涉及到两个重要的结构:

struct ipt_entry_match:在内核中用于存储扩展match信息

struct ipt_entry_match

{

union {

struct {

u_int16_t match_size;

 

/* Used by userspace */

char name[IPT_FUNCTION_MAXNAMELEN];

} user;

struct {

u_int16_t match_size;

 

/* Used inside the kernel */

struct ipt_match *match;

} kernel;

 

/* Total length */

u_int16_t match_size;

} u;

 

unsigned char data[0];

};

 

struct iptables_match:用于用户级的match存储:

/* Include file for additions: new matches and targets. */

struct iptables_match

{

/* Match链,初始为NULL */

struct iptables_match *next;

 

/* Match名,和核心模块加载类似,作为动态链接库存在的Iptables Extension的命名规则为libipt_'name'.so */

ipt_chainlabel name;

 

/*版本信息,一般设为NETFILTER_VERSION */

const char *version;

 

/* Match数据的大小,必须用IPT_ALIGN()宏指定对界*/

size_t size;

 

/*由于内核可能修改某些域,因此size可能与确切的用户数据不同,这时就应该把不会被改变的数据放在数据区的前面部分,而这里就应该填写被改变的数据区大小;一般来说,这个值和size相同*/

size_t userspacesize;

 

/*当iptables要求显示当前match的信息时(比如iptables-m ip_ext -h),就会调用这个函数,输出在iptables程序的通用信息之后. */

void (*help)(void);

 

/*初始化,在parse之前调用. */

void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);

 

/*扫描并接收本match的命令行参数,正确接收时返回非0,flags用于保存状态信息*/

int (*parse)(int c, char **argv, int invert, unsigned int *flags,

const struct ipt_entry *entry,

unsigned int *nfcache,

struct ipt_entry_match **match);

 

/* 前面提到过这个函数,当命令行参数全部处理完毕以后调用,如果不正确,应该

退出(exit_error())*/

void (*final_check)(unsigned int flags);

 

/*当查询当前表中的规则时,显示使用了当前match的规则*/

void (*print)(const struct ipt_ip *ip,

const struct ipt_entry_match *match, int numeric);

 

/*按照parse允许的格式将本match的命令行参数输出到标准输出,用于iptables-save命令. */

void (*save)(const struct ipt_ip *ip,

const struct ipt_entry_match *match);

 

/* NULL结尾的参数列表,struct option与getopt(3)使用的结构相同*/

const struct option *extra_opts;

 

/* Ignore these men behind the curtain: */

unsigned int option_offset;

struct ipt_entry_match *m;

unsigned int mflags;

unsigned int used;

#ifdef NO_SHARED_LIBS

unsigned int loaded; /* simulate loading so options are merged properly */

#endif

};

 

理解了这两个结构后,再来看find_match函数:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值