(十二)洞悉linux下的Netfilteramp;iptables:iptables命令行工具源码解析【下】
iptables用戶空間和內(nèi)核空間的交互
iptables目前已經(jīng)支持IPv4和IPv6兩個版本了,因此它在實現(xiàn)上也需要同時兼容這兩個版本。iptables-1.4.0在這方面做了很好的設(shè)計,主要是由libiptc庫來實現(xiàn)。libiptc是iptables control library的簡稱,是Netfilter的一個編程接口,通常被用來顯示、操作(查詢、修改、添加和刪除)netfilter的規(guī)則和策略等。使用libipq庫和ip_queue模塊,幾乎可以實現(xiàn)任何在內(nèi)核中所實現(xiàn)的功能。
? ? ? ? libiptc庫位于iptables源碼包里的libiptc目錄下,共六個文件還是比較容易理解。我們都知道,運行在用戶上下文環(huán)境中的代碼是可以阻塞的,這樣,便可以使用消息隊列和?UNIX?域套接字來實現(xiàn)內(nèi)核態(tài)與用戶態(tài)的通信。但這些方法的數(shù)據(jù)傳輸效率較低,Linux?內(nèi)核提供?copy_from_user()/copy_to_user()?函數(shù)來實現(xiàn)內(nèi)核態(tài)與用戶態(tài)數(shù)據(jù)的拷貝,但這兩個函數(shù)會引發(fā)阻塞,所以不能用在硬、軟中斷中。一般將這兩個特殊拷貝函數(shù)用在類似于系統(tǒng)調(diào)用一類的函數(shù)中,此類函數(shù)在使用中往往"穿梭"于內(nèi)核態(tài)與用戶態(tài)。此類方法的工作原理為:
?
? ? ? ??其中相關(guān)的系統(tǒng)調(diào)用是需要用戶自行編寫并載入內(nèi)核。一般情況都是,內(nèi)核模塊注冊一組設(shè)置套接字選項的函數(shù)使得用戶空間進程可以調(diào)用此組函數(shù)對內(nèi)核態(tài)數(shù)據(jù)進行讀寫。我們的libiptc庫正是基于這種方式實現(xiàn)了用戶空間和內(nèi)核空間數(shù)據(jù)的交換。
????為了后面便于理解,這里我們簡單了解一下在socket編程中經(jīng)常要接觸的兩個函數(shù):
int setsockopt(int sockfd, int proto, int cmd, void *data, int datalen)
int getsockopt(int sockfd, int proto, int cmd, void *data, int datalen)
這個兩個函數(shù)用來控制相關(guān)socket文件描述符的一些選項值,如設(shè)置(獲取)接受或發(fā)送緩沖區(qū)的大小、設(shè)置(獲取)接受或發(fā)送超時值、允許(禁止)重用本地端口和地址等等。
參數(shù)說明:
sockfd:為socket的文件描述符;
proto:sock協(xié)議,IP RAW的就用SOL_SOCKET/SOL_IP等,TCP/UDP socket的可用SOL_SOCKET/SOL_IP/SOL_TCP/SOL_UDP等,即高層的socket是都可以使用低層socket的命令字?的;
cmd:操作命令字,由自己定義,一般用于擴充;
data:數(shù)據(jù)緩沖區(qū)起始位置指針,set操作時是將緩沖區(qū)數(shù)據(jù)寫入內(nèi)核,get的時候是將內(nèi)核中的數(shù)據(jù)讀入該緩沖區(qū);
datalen:參數(shù)data中的數(shù)據(jù)長度。
?
我們可以通過擴充新的命令字(即前面的cmd字段)來實現(xiàn)特殊應(yīng)用程序的內(nèi)核與用戶空間的數(shù)據(jù)交換,內(nèi)核實現(xiàn)新的sockopt命令字有兩類:一類是添加完整的新的協(xié)議后引入;一類是在原有協(xié)議命令集的基礎(chǔ)上增加新的命令字。以netfilter為例,它就是在原有的基礎(chǔ)上擴展命令字,實現(xiàn)了內(nèi)核與用戶空間的數(shù)據(jù)交換。Netfilter新定義的命令字如下:
setsockopt新增命令字:
#define IPT_SO_SET_REPLACE //設(shè)置規(guī)則
#define IPT_SO_SET_ADD_COUNTERS???//加入計數(shù)器
getsockopt新增命令字;
#define IPT_SO_GET_INFO???????????????//獲取ipt_info
#define IPT_SO_GET_ENTRIES?????????//獲取規(guī)則
#define IPT_SO_GET_REVISION_MATCH?//獲取match
#define IPT_SO_GET_REVISION_TARGET??????//獲取target
???????一個標準的setsockopt()操作的調(diào)用流程如下:
?
在ip_setsockopt調(diào)用時,如果發(fā)現(xiàn)是一個沒有定義的協(xié)議,并且判斷現(xiàn)在這個optname是否為netfilter所設(shè)置,如果是則調(diào)用netfilter所設(shè)置的特殊處理函數(shù),于是加入netfilter對sockopt特殊處理后,新的流程如下:
?
? ? ? ? netfitler對于會實例化一些struct nf_sockopt_ops{}對象,然后通過nf_register_sockopt()將其注冊到全局鏈表nf_sockopts里。
| struct?nf_sockopt_ops { ?????????struct?list_head?list; ?????????int pf; ? ?????????/* Non-inclusive ranges: use 0/0/NULL to never get called. */ ?????????int set_optmin; ?????????int set_optmax; ?????????int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len); ?????????int (*compat_set)(struct sock *sk, int optval,void __user *user, unsigned int len); ? ?????????int get_optmin; ?????????int get_optmax; ?????????int (*get)(struct sock *sk, int optval, void __user *user, int *len); ?????????int (*compat_get)(struct sock *sk, int optval,void __user *user, int *len); ? ?????????/* Number of users inside set() or get(). */ ?????????unsigned int use; ?????????struct task_struct *cleanup_task; }; |
?
? ? ? ? ?繼續(xù)回到libiptc中。libiptc庫中的所有函數(shù)均以“iptc_”開頭,主要有下面一些接口(節(jié)選自libiptc.h):
| typedef?struct iptc_handle?*iptc_handle_t; ? /* Does this chain exist? */ int?iptc_is_chain(const char *chain, const?iptc_handle_t?handle); ? /* Take a snapshot of the rules.??Returns NULL on error. */ iptc_handle_t?iptc_init(const char *tablename); ? /* Cleanup after iptc_init(). */ void?iptc_free(iptc_handle_t?*h); ? /* Iterator functions to run through the chains.??Returns NULL at end. */ const char *iptc_first_chain(iptc_handle_t?*handle); const char *iptc_next_chain(iptc_handle_t?*handle); ? /* Get first rule in the given chain: NULL for empty chain. */ const struct ipt_entry *iptc_first_rule(const char *chain,iptc_handle_t?*handle); /* Returns NULL when rules run out. */ const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,iptc_handle_t?*handle); ? /* Returns a pointer to the target name of this entry. */ const char *iptc_get_target(const struct ipt_entry *e,iptc_handle_t?*handle); ? /* Is this a built-in chain? */ int?iptc_builtin(const char *chain, const?iptc_handle_t?handle); ? int?iptc_append_entry(const ipt_chainlabel chain, ?????????????????????????const struct ipt_entry *e, ?????????????????????????iptc_handle_t?*handle); ? /* Zeroes the counters in a chain. */ int?iptc_zero_entries(const ipt_chainlabel chain,iptc_handle_t?*handle); ? /* Creates a new chain. */ int?iptc_create_chain(const ipt_chainlabel chain,iptc_handle_t?*handle); ? /* Makes the actual changes. */ int?iptc_commit(iptc_handle_t?*handle); ? |
? ? ? ? 上面這些接口都是為IPv4定義了,同樣的IPv6的接口均定義在libip6tc.h頭文件中,都以“ip6tc_”開頭(如此說來,IPv4的頭文件應(yīng)該叫libip4tc.h才比較合適)。然后在libip4tc.c和libip6tc.c文件中分別通過宏定義的形式將IPv4和IPv6對外的接口均統(tǒng)一成“TC_”開頭的宏,并在libiptc.c中實現(xiàn)這些宏即可。如下圖所示:
?
? ? ? ? 這里我們看到iptables-v4和iptables-v6都和諧地統(tǒng)一到了libiptc.c中,后面我們分析的時候只要分析這些相關(guān)的宏定義的實現(xiàn)即可。
? ? ? 在繼續(xù)往下分析之前我們先看一下STRUCT_TC_HANDLE這個比較拉風的結(jié)構(gòu)體,它用于存儲我們和內(nèi)核所需要交換的數(shù)據(jù)。說的通俗一些,就是從內(nèi)核中取出的表的信息會存儲到該結(jié)構(gòu)體類型的變量中;當我們向內(nèi)核提交iptables變更時,也需要一個該結(jié)構(gòu)體類型的變量用于存儲我們所要提交的數(shù)據(jù)。(定義在ip_tables.h頭文件中)
| 適用于當getsockopt的參數(shù)為IPT_SO_GET_INFO,用于從內(nèi)核讀取表信息 struct ipt_getinfo???????????????#define?STRUCT_GETINFO?struct ipt_getinfo { ?????????/* Which table: caller fills this in. */????#從內(nèi)核取出的表信息會存儲在該結(jié)構(gòu)體中 ?????????char name[IPT_TABLE_MAXNAMELEN]; ?????????/* Kernel fills these in. */ ?????????unsigned int?valid_hooks; /* Which hook entry points are valid: bitmask */ ?????????unsigned int?hook_entry[NF_IP_NUMHOOKS]; // Hook entry points: one per netfilter hook. ?????????unsigned int?underflow[NF_IP_NUMHOOKS]; /* Underflow points. */ ?????????unsigned int?num_entries; /* Number of entries */ ?????????unsigned int?size; /* Size of entries. */ }; |
還有一個成員entries用保存表中的所有規(guī)則信息,每條規(guī)則都是一個ipt_entry的實例:
| /* The argument to IPT_SO_GET_ENTRIES. */ struct ipt_get_entries { ?????????/* Which table: user fills this in. */ ?????????char?name[IPT_TABLE_MAXNAMELEN]; ?????????unsigned int?size; /* User fills this in: total entry size. */ ? ?????????struct?ipt_entry?entrytable[0]; /*內(nèi)核里表示規(guī)則的結(jié)構(gòu),參見博文三. */ }; |
????????
(一)、從內(nèi)核獲取數(shù)據(jù):iptc_init()
都說“磨刀不誤砍柴工”,接下來我們繼續(xù)上一篇中do_command()函數(shù)里剩下的部分。*handle?= iptc_init(*table);?這里即根據(jù)表名table去從內(nèi)核中獲取該表的自身信息和表中的所有規(guī)則。關(guān)于表自身的一些信息存儲在handle->info成員里;表中所有規(guī)則的信息保存在handle->entries成員里。
? ? ? 如果handle獲取失敗,則嘗試加載完內(nèi)核中相應(yīng)的ko模塊后再次執(zhí)行iptc_init()函數(shù)。
? ? ? 然后,針對“ADRI”操作需要做一些合法性檢查,諸如-o選項不能用在PREROUTING和INPUT鏈中、-i選項不能用在POSTROUTING和OUTPUT鏈中。
| if (target &&?iptc_is_chain(jumpto, *handle)) { ???????????????????fprintf(stderr,"Warning: using chain %s, not extension\n",jumpto); ?????????????if (target->t) ????????????????????????????free(target->t); ??????????????????????????? ???????????????????printf("Target is a chain,but we have gotten a target,then free it!\n"); ? ???????????????????target = NULL; } |
如果-j XXX?后面的XXX是一條用戶自定義規(guī)則鏈,但是之前卻解析出了標準target,那么需要將target的空間釋放掉。很明顯,目前我們的-j ACCEPT不會執(zhí)行到這里。
| if (!target??#如果沒有指定target。同樣,我們的規(guī)則也不會執(zhí)行到這里 && (strlen(jumpto) == 0|| iptc_is_chain(jumpto, *handle)) #或者target是一條鏈或為空 ) { ?????????size_t size; ????… …???????? } |
? ? ? ? 因為我們的target為ACCEPT,已經(jīng)被完全正確解析,即target!=NULL。后面我們會執(zhí)行else條件分子如下的代碼:
e = generate_entry(&fw, matches, target->t);
用于生成一條iptables的規(guī)則,它首先會為e去申請一塊大小n*match+target的空間,其中n為用戶輸入的命令行中的match個數(shù),target為最后的動作。這里很明顯,我們的命令只有一個tcp的match,target是標準target,即ACCEPT。將已經(jīng)解析的fw賦給e,并對結(jié)構(gòu)體e中其他的成員進行初始化,然后將相應(yīng)的match和target的數(shù)據(jù)拷貝到e中對應(yīng)的成員中。
| size = sizeof(struct ipt_entry); for (matchp = matches; matchp; matchp = matchp->next) ?????????size += matchp->match->m->u.match_size; ? e = fw_malloc(size + target->u.target_size); *e = *fw; e->target_offset = size; e->next_offset = size + target->u.target_size; ? size = 0; for (matchp = matches; matchp; matchp = matchp->next) { ?????????memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size); ?????????size += matchp->match->m->u.match_size; } memcpy(e->elems + size, target, target->u.target_size); |
? ? ? ? 最后所生成的規(guī)則e,其內(nèi)存結(jié)構(gòu)如下圖所示:
?
? ? ? ? 這里再聯(lián)系我們對內(nèi)核中netfilter的分析就很容易理解了,一旦我們獲取一條規(guī)則ipt_entry的首地址,那么我們能通過target_offset很快獲得這條規(guī)則的target地址,同時也可以通過next_offset獲得下一條ipt_entry規(guī)則的起始地址,很方便我們到時候做數(shù)據(jù)包匹配的操作。
?緊接著就是對解析出來的command命令進行具體操作,這里我們是-A命令,因此最后command命令就是CMD_APPEND,這里則執(zhí)行append_entry()函數(shù)。
ret =?append_entry(chain,??#鏈名,這里為INPUT
e,??????#將用戶的命令解析出來的最終的規(guī)則對象
nsaddrs,??#-s?后面源地址的個數(shù)
saddrs,???#用于保存源地址的數(shù)組
ndaddrs,??#-d?后面的目的地址的個數(shù)
daddrs,???#用于保存目的地址的數(shù)組
options&OPT_VERBOSE, #iptables命令是否有-v參數(shù)
handle???#從內(nèi)核中取出來的規(guī)則表信息
);
?在append_entry內(nèi)部調(diào)用了iptc_append_entry(chain, fw, handle),其實就是由宏即TC_APPEND_ENTRY所表示的那個函數(shù)。該函數(shù)內(nèi)部有兩個值得注意的結(jié)構(gòu)體類型struct chain_head{}和struct rule_head{},分別用于保存我們所要操作的鏈以及鏈中的規(guī)則:
| struct chain_head { ?????????struct list_head list; ?????????char name[TABLE_MAXNAMELEN]; ?????????unsigned int hooknum;?????????????/* hook number+1 if builtin */ ?????????unsigned int references;?/*?有多少-j?指定了我們的名字?*/ ?????????int verdict;??????????????????????????/* verdict if builtin */ ?????????STRUCT_COUNTERS counters;????????/* per-chain counters */ ?????????struct counter_map counter_map; ?????????unsigned int num_rules;???????????/*?本鏈中的規(guī)則數(shù)*/ ?????????struct list_head rules;?????????????/*?本鏈中所有規(guī)則的入口點?*/ ? ?????????unsigned int index;???????????/* index (needed for jump resolval) */ ?????????unsigned int head_offset;????????/* offset in rule blob */ ?????????unsigned int foot_index;?/* index (needed for counter_map) */ ?????????unsigned int foot_offset;??????????/* offset in rule blob */ }; ? struct rule_head { ?????????struct list_head list; ?????????struct chain_head *chain; ?????????struct counter_map counter_map; ?????????unsigned int index;???????????/* index (needed for counter_map) */ ?????????unsigned int offset;??????????/* offset in rule blob */ ? ?????????enum iptcc_rule_type type; ?????????struct chain_head?*jump;??????/* jump target, if IPTCC_R_JUMP */ ? ?????????unsigned int size;??????????????/* size of entry data */ ?????????STRUCT_ENTRY entry[0];???#真正的規(guī)則入口點?sizeof計算時不會包含這個字段 }; |
TC_APPEND_ENTRY的函數(shù)實現(xiàn):
| int TC_APPEND_ENTRY(const?IPT_CHAINLABEL?chain, ???????????????????const?STRUCT_ENTRY?*e, ???????????????????TC_HANDLE_T?*handle)????#注意:這里的handle是個二級指針 { ?????????struct chain_head?*c; ?????????struct rule_head?*r; ? ?????????iptc_fn = TC_APPEND_ENTRY; ?????????if (!(c =?iptcc_find_label(chain, *handle))) {?????? #根據(jù)鏈名查找真正的鏈地址賦給c,此時c就指向了INPUT鏈的內(nèi)存, #包括INPUT中的所有規(guī)則和它的policy等 ???????????????????DEBUGP("unable to find chain `%s'\n", chain); ???????????????????errno = ENOENT; ???????????????????return 0; ?????????} ? ?????????if (!(r =?iptcc_alloc_rule(c,?e->next_offset))) { #ipt_entry的next_offset即指明了下一條規(guī)則的起始地址,同時這個值也說明了本條規(guī)則所占了存儲空間的大小。這里所申請的空間大小=sizeof(rule_head)+當前規(guī)則所占的空間大小。 ???????????????????DEBUGP("unable to allocate rule for chain `%s'\n", chain); ???????????????????errno = ENOMEM; ???????????????????return 0; ?????????} ? ?????????memcpy(r->entry, e, e->next_offset);???????#把規(guī)則拷貝到柔性數(shù)組entry中去 ?????????r->counter_map.maptype = COUNTER_MAP_SET; ? ?????????if (!iptcc_map_target(*handle, r)) {???#主要是設(shè)置規(guī)則r的target,后面分析。 ???????????????????DEBUGP("unable to map target of rule for chain `%s'\n", chain); ???????????????????free(r); ???????????????????return 0; ?????????} ? ?????????list_add_tail(&r->list, &c->rules); #將新規(guī)則r添加在鏈c的末尾 ?????????c->num_rules++;??????????????#同時將鏈中的規(guī)則計數(shù)增加 ? ?????????set_changed(*handle);????#因為INPUT鏈中的規(guī)則已經(jīng)被改變,則handle->changed=1; ?????????return 1; } |
? ? ? ? ?接下來分析一下設(shè)置target時其函數(shù)內(nèi)部流程:
| static int iptcc_map_target(const TC_HANDLE_T handle, ????????????struct rule_head *r) { ?????????STRUCT_ENTRY?*e = r->entry;?????????????????#取規(guī)則的起始地址 ?????????STRUCT_ENTRY_TARGET?*t = GET_TARGET(e);????#取規(guī)則的target ? ?????????/* Maybe it's empty (=> fall through) */ ?????????if (strcmp(t->u.user.name, "") == 0) { #如果沒有指定target,則將規(guī)則類型設(shè)為“全放行” ???????????????????r->type = IPTCC_R_FALLTHROUGH; ???????????????????return 1; ?????????} ? ?????????/* Maybe it's a standard target name... */ #因為都是標準target,因此將target中用戶空間的user.name都置為空,設(shè)置verdict, #并將rule_head中的type字段為IPTCC_R_STANDARD ?????????else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) ???????????????????return?iptcc_standard_map(r, -NF_ACCEPT - 1); ?????????else if (strcmp(t->u.user.name, LABEL_DROP) == 0) ???????????????????return?iptcc_standard_map(r, -NF_DROP - 1); ?????????else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) ???????????????????return?iptcc_standard_map(r, -NF_QUEUE - 1); ?????????else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) ???????????????????return?iptcc_standard_map(r, RETURN); ?????????else if (TC_BUILTIN(t->u.user.name, handle)) { ???????????????????/* Can't jump to builtins. */ ???????????????????errno = EINVAL; ???????????????????return 0; ?????????} else { ???????????????????/*?如果跳轉(zhuǎn)的目標是一條用戶自定義鏈,則執(zhí)行下列操作*/ ???????????????????struct chain_head *c; ???????????????????DEBUGP("trying to find chain `%s': ", t->u.user.name); ???????????????????c =?iptcc_find_label(t->u.user.name, handle); #找到要跳轉(zhuǎn)的目的鏈的入口地址 ???????????????????if (c) { ????????????????????????????DEBUGP_C("found!\n"); ????????????????????????????r->type =?IPTCC_R_JUMP;??#將rule_head結(jié)構(gòu)的type字段置為“跳轉(zhuǎn)” ????????????????????????????r->jump = c;?????????????#跳轉(zhuǎn)的目標為t->u.user.name所指示的鏈 ????????????????????????????c->references++;?????????#跳轉(zhuǎn)到的目的鏈因此而被引用了一次,則計數(shù)器++ ????????????????????????????return 1; ???????????????????} ???????????????????DEBUGP_C("not found :(\n"); ?????????} ? ?????????/*?如果不是用戶自定義鏈,它一定一個用戶自定義開發(fā)的target模塊,比如SNAT、LOG等。If not, kernel will reject... */ ?????????/* memset to all 0 for your memcmp convenience: don't clear version */ ?????????memset(t->u.user.name + strlen(t->u.user.name), ????????????????0, ????????????????FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name)); ?????????r->type =?IPTCC_R_MODULE;??#比如SNAT,LOG等會執(zhí)行到這里 ?????????set_changed(handle); ?????????return 1; } |
?在append_entry()函數(shù)最后,將執(zhí)行的執(zhí)行結(jié)果返回給ret,1表示成功;0表示失敗。然后在做一下善后清理工作,如果命令行中有-v則將內(nèi)核中表的快照dump一份詳細信息出來顯示給用戶看:
if (verbose > 1)
?????dump_entries(*handle);
clear_rule_matches(&matches);?//釋放matches所占的存儲空間
?由struct ipt_entry e;所存儲的規(guī)則信息已經(jīng)被提交給了handle對象對應(yīng)的成員,因此將e所占的存儲空間也釋放:
if (e != NULL) {
??????????????free(e);
??????????????e = NULL;
}
?將全局變量opts復(fù)位,初始化時opts=original_opts。因為在解析--syn時tcp的解析參數(shù)被加進來了:
static struct option?original_opts[] = {
??????????????????{ "append", 1, NULL, 'A' },
??????????????????{ "delete", 1, NULL,??'D' },
??????????????????… …
}
至此,do_command()函數(shù)的執(zhí)行就算全部完成了。
?
(二)、向內(nèi)核提交變更:iptc_commit()
執(zhí)行完do_command()解析完命令行參數(shù)后,用戶所作的變更僅被提交給了handle這個結(jié)構(gòu)體變量,這個變量里的所有數(shù)據(jù)在執(zhí)行iptc_commit()函數(shù)前都駐留在內(nèi)存里。因此,在iptables-standalone.c里有如下的代碼語句:
| ret =?do_command(argc, argv, &table, &handle); if (ret) ?????????ret =?iptc_commit(&handle); |
? 當do_command()執(zhí)行成功后才會去執(zhí)行iptc_commit()函數(shù),將handle里的數(shù)據(jù)提交給Netfilter內(nèi)核。
? ? ? ? iptc_commit()的實現(xiàn)函數(shù)為int TC_COMMIT(TC_HANDLE_T *handle),我們只分析IPv4的情形,因此專注于libiptc.c文件中該函數(shù)的實現(xiàn)。
? ? ? ? 在TC_COMMIT()函數(shù)中,又出現(xiàn)了我們在分析Netfilter中filter表時所見到的一些重要結(jié)構(gòu)體STRUCT_REPLACE?*repl;STRUCT_COUNTERS_INFO?*newcounters;還有前面出現(xiàn)的struct chain_head?*c;結(jié)構(gòu)體。
new_number =?iptcc_compile_table_prep(*handle, &new_size);
iptcc_compile_table_prep()該函數(shù)主要做的工作包含幾個方面:
a.初始化handle里每個struct chain_head{}結(jié)構(gòu)體成員中的head_offset、foot_index和foot_offset。
b.對每個鏈(struct chain_head{})中的每條規(guī)則,再分別計算它們的offset和index。
c.計算handle所指示的表中所有規(guī)則所占的存儲空間的大小new_size,以及規(guī)則的總條數(shù)new_number。
?接下來,為指針repl;申請存儲空間,所申請的大小為sizeof(struct ipt_replace)+new_size。因為struct ipt_replace{}結(jié)構(gòu)的末尾有一個柔性數(shù)組struct ipt_entry entries[0];?它是不計入sizeof的計算結(jié)果的。因此,iptables的所有規(guī)則實際上是存儲在struct ipt_entry entries[0]柔性數(shù)組中的,這里所有規(guī)則所占大小已經(jīng)得到:new_size。
? 因為,每條規(guī)則entry都一個計數(shù)器,用來記錄該規(guī)則處理了多少數(shù)據(jù)包,注意結(jié)構(gòu)體STRUCT_COUNTERS_INFO{}的末尾也有一個柔性數(shù)組struct xt_counters?counters[0];其中struct xt_counters{}才是真正的用于統(tǒng)計數(shù)據(jù)包的計數(shù)器。
然后開始初始化repl結(jié)構(gòu):
| strcpy(repl->name, (*handle)->info.name); repl->num_entries = new_number; repl->size = new_size; ? repl->num_counters = (*handle)->info.num_entries; repl->valid_hooks = (*handle)->info.valid_hooks; |
? ? ? ? 緊接著對repl結(jié)構(gòu)體中剩下的成員進行初始化,hook_entry[]、underflow[]等。對于用戶自定義鏈,其末尾的target.verdict=RETURN。
setsockopt(sockfd, TC_IPPROTO,?SO_SET_REPLACE,?repl,sizeof(*repl) + repl->size);
會觸發(fā)內(nèi)核去執(zhí)行前面我們看到的do_ipt_set_ctl()函數(shù),如下:
| static struct nf_sockopt_ops ipt_sockopts = { ?????????.pf??????????????= PF_INET, ?????????.set_optmin?????= IPT_BASE_CTL,???? ? .set_optmax????= IPT_SO_SET_MAX+1, ?????????.set???????????=?do_ipt_set_ctl, ?????????.get_optmin?????= IPT_BASE_CTL, ?????????.get_optmax????= IPT_SO_GET_MAX+1, ?????????.get???????????=?do_ipt_get_ctl, }; |
? ? ? ? 在do_ipt_set_ctl()中其核心還是執(zhí)行do_replace()函數(shù):
| static int do_replace(void __user *user, unsigned int len) { ?????????int ret; ?????????struct ipt_replace tmp; ?????????struct xt_table_info *newinfo; ?????????void *loc_cpu_entry; ? ?????????if (copy_from_user(&tmp, user, sizeof(tmp)) != 0) ???????????????????return -EFAULT; ? ?????????/* Hack: Causes ipchains to give correct error msg --RR */ ?????????if (len != sizeof(tmp) + tmp.size) ???????????????????return -ENOPROTOOPT; … … } |
? ? ? ?其中copy_from_user()負責將用戶空間的repl變量中的內(nèi)容拷貝到內(nèi)核中的tmp中去。然后設(shè)置規(guī)則計數(shù)器newcounters,通過setsockopt系統(tǒng)調(diào)用將newcounters設(shè)置到內(nèi)核:
setsockopt(sockfd, TC_IPPROTO,?SO_SET_ADD_COUNTERS,?newcounters, counterlen);
?此時,在do_ipt_set_ctl()中執(zhí)行的是do_add_counters()函數(shù)。至此,iptables用戶空間的所有代碼流程就算分析完了。命令:
iptables –A INPUT –i eth0 –p tcp --syn –s?10.0.0.0/8 –d 10.1.28.184 –j ACCEPT
即被設(shè)置到內(nèi)核的Netfilter規(guī)則中去了。
未完,待續(xù)…
?
轉(zhuǎn)載于:https://www.cnblogs.com/masterpanda/p/5700491.html
總結(jié)
以上是生活随笔為你收集整理的(十二)洞悉linux下的Netfilteramp;iptables:iptables命令行工具源码解析【下】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电商中的品类和属性设计(非原创)
- 下一篇: Javaagent技术及Instrume