之前大致寫過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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
static int nf_conntrack_net_init(struct net *net) { int ret; ret = nf_conntrack_init(net); if (ret < 0) goto out_init; ret = nf_conntrack_standalone_init_proc(net); if (ret < 0) goto out_proc; net->ct.sysctl_checksum = 1; net->ct.sysctl_log_invalid = 0; ret = nf_conntrack_standalone_init_sysctl(net); if (ret < 0) goto out_sysctl; return 0; out_sysctl: nf_conntrack_standalone_fini_proc(net); out_proc: nf_conntrack_cleanup(net); out_init: return ret; } static void nf_conntrack_net_exit(struct net *net) { nf_conntrack_standalone_fini_sysctl(net); nf_conntrack_standalone_fini_proc(net); nf_conntrack_cleanup(net); } static struct pernet_operations nf_conntrack_net_ops = { .init = nf_conntrack_net_init, .exit = nf_conntrack_net_exit, }; static int __init nf_conntrack_standalone_init(void) { return register_pernet_subsys(&nf_conntrack_net_ops); } |
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
static struct nf_hook_ops ipt_ops[] __read_mostly = { { .hook = ipt_local_in_hook, .owner = THIS_MODULE, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP_PRI_FILTER, }, { .hook = ipt_hook, .owner = THIS_MODULE, .pf = NFPROTO_IPV4, .hooknum = NF_INET_FORWARD, .priority = NF_IP_PRI_FILTER, }, { .hook = ipt_local_out_hook, .owner = THIS_MODULE, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_FILTER, }, }; |
很明顯filter只在local_in /local_out/ forward三個鉤子,並且它們的鉤子函式最終都呼叫了
1 2 3 4 5 6 7 |
/* Returns one of the generic firewall policies, like NF_ACCEPT. */ unsigned int ipt_do_table(struct sk_buff *skb, unsigned int hook, const struct net_device *in, const struct net_device *out, 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static unsigned int iptable_filter_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { const struct net *net; if (hook == NF_INET_LOCAL_OUT && (skb->len < sizeof(struct iphdr) || ip_hdrlen(skb) < sizeof(struct iphdr))) /* root is playing with raw sockets. */ return NF_ACCEPT; net = dev_net((in != NULL) ? in : out); return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter); } |
對於ipt_do_table最需要我們關心的就是net->ipv4.iptable_filter這個東西從哪裡來的,當然我們知道它就是存放rules的地方
從搜尋的核心程式碼來看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static int __net_init iptable_filter_net_init(struct net *net) { struct ipt_replace *repl; repl = ipt_alloc_initial_table(&packet_filter); if (repl == NULL) return -ENOMEM; /* Entry 1 is the FORWARD hook */ ((struct ipt_standard *)repl->entries)[1].target.verdict = forward ? -NF_ACCEPT - 1 : -NF_DROP - 1; net->ipv4.iptable_filter = ipt_register_table(net, &packet_filter, repl); kfree(repl); return PTR_RET(net->ipv4.iptable_filter); } |
上述程式碼的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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* Furniture shopping... */ struct xt_table { struct list_head list; /* What hooks you will enter on */ unsigned int valid_hooks; /* Man behind the curtain... */ struct xt_table_info *private; /* Set this to THIS_MODULE if you are a module, otherwise NULL */ struct module *me; u_int8_t af; /* address/protocol family */ int priority; /* hook order */ /* A unique name... */ const char name[XT_TABLE_MAXNAMELEN]; }; |
還有另外一個重要的結構體: struct xt_table_info *private;
當然關於tables的rules需要使用者空間來下發配置。以及註冊match和target相關的東西.關於rules的結構和表的關係後續我們會講到.
關於全域性表的維護:通過API介面註冊的表都掛到了net->xt.tables中.
xt的型別是struct netns_xt:
1 2 3 4 5 6 7 8 9 10 |
struct netns_xt { struct list_head tables[NFPROTO_NUMPROTO]; bool notrack_deprecated_warning; #if defined(CONFIG_BRIDGE_NF_EBTABLES) || \ defined(CONFIG_BRIDGE_NF_EBTABLES_MODULE) struct ebt_table *broute_table; struct ebt_table *frame_filter; struct ebt_table *frame_nat; #endif }; |
還有另外一個全域性變數Struct xt_af xt;
1 2 3 4 5 6 7 8 9 10 |
struct netns_xt { struct list_head tables[NFPROTO_NUMPROTO]; bool notrack_deprecated_warning; #if defined(CONFIG_BRIDGE_NF_EBTABLES) || \ defined(CONFIG_BRIDGE_NF_EBTABLES_MODULE) struct ebt_table *broute_table; struct ebt_table *frame_filter; struct ebt_table *frame_nat; #endif }; |
有了表,我們看看match和target的註冊:match和target放到了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 2 3 4 5 6 7 8 9 10 11 |
xt_ematch_foreach(ematch, e) { acpar.match = ematch->u.kernel.match; acpar.matchinfo = ematch->data; if (!acpar.match->match(skb, &acpar)) goto no_match; } |
那麼match函式從哪裡來?Ipt_entry又在什麼賦值的呢?後續我們會看到答案.
如果match匹配那麼呼叫相應的target,這裡不要把類似struct xt_match 和struct xt_entry_match搞混了。
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
struct xt_entry_match { union { struct { __u16 match_size; /* Used by userspace */ char name[XT_EXTENSION_MAXNAMELEN]; __u8 revision; } user; struct { __u16 match_size; /* Used inside the kernel */ struct xt_match *match; } kernel; /* Total length */ __u16 match_size; } u; unsigned char data[0]; }; |
對於現在來說,它通過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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
static const struct subcommand multi_subcommands[] = { #ifdef ENABLE_IPV4 {"iptables", iptables_main}, {"main4", iptables_main}, {"iptables-save", iptables_save_main}, {"save4", iptables_save_main}, {"iptables-restore", iptables_restore_main}, {"restore4", iptables_restore_main}, #endif {"iptables-xml", iptables_xml_main}, {"xml", iptables_xml_main}, #ifdef ENABLE_IPV6 {"ip6tables", ip6tables_main}, {"main6", ip6tables_main}, {"ip6tables-save", ip6tables_save_main}, {"save6", ip6tables_save_main}, {"ip6tables-restore", ip6tables_restore_main}, {"restore6", ip6tables_restore_main}, #endif {NULL}, }; int main(int argc, char **argv) { return subcmd_main(argc, argv, multi_subcommands); } |
編譯過iptables後,檢視sbin下iptables命令都軟連線到了xtables-multi。
先看主函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
int iptables_main(int argc, char *argv[]) { int ret; char *table = "filter"; struct xtc_handle *handle = NULL; iptables_globals.program_name = "iptables"; ret = xtables_init_all(&iptables_globals, NFPROTO_IPV4); if (ret < 0) { fprintf(stderr, "%s/%s Failed to initialize xtables\n", iptables_globals.program_name, iptables_globals.program_version); exit(1); } #if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) init_extensions(); init_extensions4(); #endif ret = do_command4(argc, argv, &table, &handle, false); if (ret) { ret = iptc_commit(handle); iptc_free(handle); } ... |
我們看到命令列解析是在do_command4裡
1 2 3 4 5 6 7 |
opts = xt_params->orig_opts; while ((cs.c = getopt_long(argc, argv, "-: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", opts, NULL)) != -1) { |
而opts是什麼?
#define opts iptables_globals.opts
還有在之前初始化的時候:xt_params = &iptables_globals;
1 |
struct xtables_globals *xt_params = NULL; |
這個結構體包含什麼呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct xtables_globals { unsigned int option_offset; const char *program_name, *program_version; struct option *orig_opts; struct option *opts; void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); }; |
關鍵點就是struct option,既然上面orig_opts和opts指向相同的地方,而xt_params又指向iptables_globals
1 2 3 4 5 6 7 8 9 10 |
static struct option original_opts[] = { {.name = "append", .has_arg = 1, .val = 'A'}, {.name = "delete", .has_arg = 1, .val = 'D'}, {.name = "check", .has_arg = 1, .val = 'C'}, {.name = "insert", .has_arg = 1, .val = 'I'}, ... |
1 2 3 4 5 6 7 8 9 10 11 |
struct xtables_globals iptables_globals = { .option_offset = 0, .program_version = IPTABLES_VERSION, .orig_opts = original_opts, .exit_err = iptables_exit_error, }; |
那麼可能好奇struct option了是什麼玩意,我們來看一下:
原型在getopt.h中
1 2 3 4 5 6 7 8 9 10 11 |
struct option { const char *name; int has_arg; int *flag; int val; }; |
因為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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
static struct xtables_match tcp_match = { .family = NFPROTO_UNSPEC, .name = "tcp", .version = XTABLES_VERSION, .size = XT_ALIGN(sizeof(struct xt_tcp)), .userspacesize = XT_ALIGN(sizeof(struct xt_tcp)), .help = tcp_help, .init = tcp_init, .parse = tcp_parse, .print = tcp_print, .save = tcp_save, .extra_opts = tcp_opts, //... }; |
在註冊match的時候xtables_register_match
/* place on linked list of matches pending
full registration */
me->next= xtables_pending_matches;
xtables_pending_matches= me;
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
static void tcp_help(void) { printf( "tcp match options:\n" "[!] --tcp-flags mask comp match when TCP flags & mask == comp\n" " (Flags: SYN ACK FIN RST URG PSH ALL NONE)\n" "[!] --syn match when only SYN flag set\n" " (equivalent to --tcp-flags SYN,RST,ACK,FIN SYN)\n" "[!] --source-port port[:port]\n" " --sport ...\n" " match source port(s)\n" "[!] --destination-port port[:port]\n" " --dport ...\n" " match destination port(s)\n" "[!] --tcp-option number match if TCP option set\n"); } static const struct option tcp_opts[] = { {.name = "source-port", .has_arg = true, .val = '1'}, {.name = "sport", .has_arg = true, .val = '1'}, /* synonym */ {.name = "destination-port", .has_arg = true, .val = '2'}, {.name = "dport", .has_arg = true, .val = '2'}, /* synonym */ {.name = "syn", .has_arg = false, .val = '3'}, {.name = "tcp-flags", .has_arg = true, .val = '4'}, {.name = "tcp-option", .has_arg = true, .val = '5'}, XT_GETOPT_TABLEEND, }; |
這就是引數的解析過程,不論什麼引數都是這樣解析,那麼解析完如何把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 2 3 4 5 6 7 8 9 |
case 'A': add_command(&command, CMD_APPEND, CMD_NONE, cs.invert); chain = optarg; break; |
add_command即把command賦值為CMD_APPEND
cs.invert為0
Chain 為 OUTPUT
或許我們需要回顧下getopt_long
1 2 |
static struct option original_opts[] = { {.name = "append", .has_arg = 1, .val = 'A'}, |
根據上面的初始化值來解析引數A,明顯has_arg=1表示A後面跟一個引數即OUTPUT
2>第二個命令-p tcp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
/* * Option selection */ case 'p': set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags, cs.invert); /* Canonicalize into lower case */ for (cs.protocol = optarg; *cs.protocol; cs.protocol++) *cs.protocol = tolower(*cs.protocol); // tolower把字串轉換成小寫. cs.protocol = optarg; cs.fw.ip.proto = xtables_parse_protocol(cs.protocol); if (cs.fw.ip.proto == 0 && (cs.fw.ip.invflags & XT_INV_PROTO)) xtables_error(PARAMETER_PROBLEM, "rule would never match protocol"); 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const struct xtables_pprot xtables_chain_protos[] = { {"tcp", IPPROTO_TCP}, {"sctp", IPPROTO_SCTP}, {"udp", IPPROTO_UDP}, {"udplite", IPPROTO_UDPLITE}, {"icmp", IPPROTO_ICMP}, {"icmpv6", IPPROTO_ICMPV6}, {"ipv6-icmp", IPPROTO_ICMPV6}, {"esp", IPPROTO_ESP}, {"ah", IPPROTO_AH}, {"ipv6-mh", IPPROTO_MH}, {"mh", IPPROTO_MH}, {"all", 0}, {NULL}, }; |
Cs.fw.ip.proto值為IPPROTO_TCP
3>. 第三個命令–dport 31337
進入command_default流程後
首先判斷cs->target是否為null,
然後判斷cs->matches是否為null;點選(此處)摺疊或開啟
1 2 3 |
for (matchp = cs->matches; matchp; matchp = matchp->next) { 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 2 3 |
find_proto(cs->protocol, XTF_TRY_LOAD, cs->options & OPT_NUMERIC, &cs->matches); |
在xtables_find_match中分兩個部分:
1 2 3 4 5 6 7 8 9 10 11 |
/* Second and subsequent clones */ clone = xtables_malloc(sizeof(struct xtables_match)); memcpy(clone, ptr, sizeof(struct xtables_match)); clone->udata = NULL; clone->mflags = 0; /* This is a clone: */ clone->next = clone; ptr = clone; break; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (ptr && matches) { struct xtables_rule_match **i; struct xtables_rule_match *newentry; newentry = xtables_malloc(sizeof(struct xtables_rule_match)); for (i = matches; *i; i = &(*i)->next) { if (strcmp(name, (*i)->match->name) == 0) (*i)->completed = true; } newentry->match = ptr; newentry->completed = false; newentry->next = NULL; *i = newentry; } |
完成了cs.matchs初始化為tcp match(cloned)。
找到tcp註冊的match之後 ,申請xt_entry_match節點(xtables_calloc),呼叫xs_init_match(m);
對於tcp match就是:
1 2 3 4 5 |
static void tcp_init(struct xt_entry_match *m) { struct xt_tcp *tcpinfo = (struct xt_tcp *)m->data; tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF; } |
然後呼叫xtables_merge_options把tcp match的ext_opt複製到全域性的opts中。optind– ,回退重新處理–dport。
重新進入command_default:由於cs.matchs不為null
1 2 3 4 5 6 7 8 9 10 11 12 |
for (matchp = cs->matches; matchp; matchp = matchp->next) { m = matchp->match; if (matchp->completed || (m->x6_parse == NULL && m->parse == NULL)) continue; if (cs->c < matchp->match->option_offset || cs->c >= matchp->match->option_offset + XT_OPTION_OFFSET_SCALE) continue; xtables_option_mpcall(cs->c, cs->argv, cs->invert, m, &cs->fw); return 0; } |
進入xtables_option_mpcall解析
1 2 3 4 5 6 |
if (m->x6_parse == NULL) { if (m->parse != NULL) m->parse(c - m->option_offset, argv, invert, &m->mflags, fw, &m->m); return; } |
m->parse即之前已經註冊tcp_match中的tcp_parse
由於之前已經把tcp擴充套件的options新增到了全域性表中,所以重新解析後cs.c值為2,optarg即我們傳遞的埠號
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
static int tcp_parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_match **match) { struct xt_tcp *tcpinfo = (struct xt_tcp *)(*match)->data; switch (c) { case '1': if (*flags & TCP_SRC_PORTS) xtables_error(PARAMETER_PROBLEM, "Only one `--source-port' allowed"); parse_tcp_ports(optarg, tcpinfo->spts); if (invert) tcpinfo->invflags |= XT_TCP_INV_SRCPT; *flags |= TCP_SRC_PORTS; break; case '2': if (*flags & TCP_DST_PORTS) xtables_error(PARAMETER_PROBLEM, "Only one `--destination-port' allowed"); parse_tcp_ports(optarg, tcpinfo->dpts); if (invert) tcpinfo->invflags |= XT_TCP_INV_DSTPT; *flags |= TCP_DST_PORTS; 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
static void standard_help(void) { printf( "standard match options:\n" "(If target is DROP, ACCEPT, RETURN or nothing)\n"); } static struct xtables_target standard_target = { .family = NFPROTO_UNSPEC, .name = "standard", .version = XTABLES_VERSION, .size = XT_ALIGN(sizeof(int)), .userspacesize = XT_ALIGN(sizeof(int)), .help = standard_help, }; void _init(void) { xtables_register_target(&standard_target); } |
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 www.baidu.com -j DROP
則會對Dhostnetworkmask賦值處理和解析。
主要是根據主機名解析所有的ip地址,使用了gethostbyname函式. 這只是一個小插曲.
到這裡需要注意:
1 2 |
1. if (!*handle) *handle = iptc_init(*table); |
系統初始化時handle為null,*table為“filter”:
1 2 |
1. if (!*handle) *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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* The argument to IPT_SO_GET_INFO */ struct ipt_getinfo { /* Which table: caller fills this in. */ char name[XT_TABLE_MAXNAMELEN]; /* Kernel fills these in. */ /* Which hook entry points are valid: bitmask */ unsigned int valid_hooks; /* Hook entry points: one per netfilter hook. */ unsigned int hook_entry[NF_INET_NUMHOOKS]; /* Underflow points. */ unsigned int underflow[NF_INET_NUMHOOKS]; /* Number of entries */ unsigned int num_entries; /* Size of entries. */ unsigned int size; }; |
它和核心的struct xt_table_info很相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* The table itself */ struct xt_table_info { /* Size per table */ unsigned int size; /* Number of entries: FIXME. --RR */ unsigned int number; /* Initial number of entries. Needed for module usage count */ unsigned int initial_entries; /* Entry points and underflows */ unsigned int hook_entry[NF_INET_NUMHOOKS]; unsigned int underflow[NF_INET_NUMHOOKS]; /* * Number of user chains. Since tables cannot have loops, at most * @stacksize jumps (number of user chains) can possibly be made. */ unsigned int stacksize; unsigned int __percpu *stackptr; void ***jumpstack; /* ipt_entry tables: one per CPU */ /* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */ void *entries[1]; }; |
關於getinfo的操作,它下發到核心獲取資訊:
1 2 3 4 5 6 7 8 9 10 11 12 |
static int do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) { int ret; if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; switch (cmd) { case IPT_SO_GET_INFO: ret = get_info(sock_net(sk), user, len, 0); break; |
4.建立handle,並初始化。
5.getsockopt(h->sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,&tmp) 獲取entires資訊
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* The argument to IPT_SO_GET_ENTRIES. */ struct ipt_get_entries { /* Which table: user fills this in. */ char name[XT_TABLE_MAXNAMELEN]; /* User fills this in: total entry size. */ unsigned int size; /* The entries. */ struct ipt_entry entrytable[0]; }; |
一開始認識info 和entries應該為空,其實錯了,在初始化filter表的時候,即在iptable_filter_net_init裡面做了重要工作:
1 2 |
struct ipt_replace *repl; repl = ipt_alloc_initial_table(&packet_filter); |
1 2 3 4 |
void *ipt_alloc_initial_table(const struct xt_table *info) { return xt_alloc_initial_table(ipt, IPT); } |
很有意思的是上面這個函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#define xt_alloc_initial_table(type, typ2) ({ \ unsigned int hook_mask = info->valid_hooks; \ unsigned int nhooks = hweight32(hook_mask); \ unsigned int bytes = 0, hooknum = 0, i = 0; \ struct { \ struct type##_replace repl; \ struct type##_standard entries[nhooks]; \ struct type##_error term; \ } *tbl = kzalloc(sizeof(*tbl), GFP_KERNEL); \ if (tbl == NULL) \ return NULL; \ strncpy(tbl->repl.name, info->name, sizeof(tbl->repl.name)); \ tbl->term = (struct type##_error)typ2##_ERROR_INIT; \ tbl->repl.valid_hooks = hook_mask; \ tbl->repl.num_entries = nhooks + 1; \ tbl->repl.size = nhooks * sizeof(struct type##_standard) + \ sizeof(struct type##_error); \ for (; hook_mask != 0; hook_mask >>= 1, ++hooknum) { \ if (!(hook_mask & 1)) \ continue; \ tbl->repl.hook_entry[hooknum] = bytes; \ tbl->repl.underflow[hooknum] = bytes; \ tbl->entries[i++] = (struct type##_standard) \ typ2##_STANDARD_INIT(NF_ACCEPT); \ bytes += sizeof(struct type##_standard); \ } \ tbl; \ }) |
為什麼這麼說,還記得iptcc_find_label這個函式嗎?它去查詢handle->chains 而在TC_INIT的時候,從核心獲取info和entries之後通過parse_table賦值給handle.
補充幾個結構體:
1 2 3 4 5 |
/* Standard entry. */ struct ipt_standard { struct ipt_entry entry; struct xt_standard_target target; }; |
1 2 3 4 5 |
/* Standard entry. */ struct ipt_standard { struct ipt_entry entry; struct xt_standard_target target; }; |
1 2 3 4 |
struct xt_standard_target { struct xt_entry_target target; int verdict; }; |
下面看看filter表初始化的時候做了什麼工作:
1 2 3 4 5 6 7 |
#define IPT_STANDARD_INIT(__verdict) \ { \ .entry = IPT_ENTRY_INIT(sizeof(struct ipt_standard)), \ .target = XT_TARGET_INIT(XT_STANDARD_TARGET, \ sizeof(struct xt_standard_target)), \ .target.verdict = -(__verdict) - 1, \ } |
和
1 2 3 4 5 6 7 |
#define XT_TARGET_INIT(__name, __size) \ { \ .target.u.user = { \ .target_size = XT_ALIGN(__size), \ .name = __name, \ }, \ } |
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 2 3 4 5 6 7 |
else { e= generate_entry(&cs.fw, cs.matches, cs.target->t); free(cs.target->t); } |
因為它用程式碼說明了rules的組成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
static struct ipt_entry * generate_entry(const struct ipt_entry *fw, struct xtables_rule_match *matches, struct xt_entry_target *target) { unsigned int size; struct xtables_rule_match *matchp; struct ipt_entry *e; size = sizeof(struct ipt_entry); for (matchp = matches; matchp; matchp = matchp->next) size += matchp->match->m->u.match_size; e = xtables_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); return e; } |
Ipt_entry+ xt_entry_match+…+xt_entry_target (…表示match可以不止一個,但target只有一個)
申請新的ipt_entry *e空間,這裡把ipt_entry->ip 賦值,複製entry_matchs和entry_target.
接著是對command的處理,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
switch (command) { case CMD_APPEND: ret = append_entry(chain, e, nsaddrs, saddrs, smasks, ndaddrs, daddrs, dmasks, cs.options&OPT_VERBOSE, *handle); break; |
append_entry的引數的傳遞這裡就不解釋了,前面都分析完了.
1.ip初始化 2.iptc_append_entry. 這個函式很好理解,就是附加entry.
處理完命令 ,然後釋放掉原來的空間.
在完成do_comand4後,自然是要提交我們的東西到核心裡
即通過ipt_commit函式
原理是利用Setsockopt下發配置,關於setsockopt這裡就不詳細講解了.