啓動時首先在ip_conntrack_standalone.c中調用 static int __init ip_conntrack_standalone_init(void) //proc相關部分省略 { ...... int ret = 0; ret = ip_conntrack_init(); //大部分初始化工做 if (ret < 0) return ret; ...... //註冊hook函數,添加到二圍數組連表中, ip_conntrack_ops 定義看下面 ret = nf_register_hooks(ip_conntrack_ops, ARRAY_SIZE(ip_conntrack_ops)); if (ret < 0) { printk("ip_conntrack: can't register hooks.\n"); goto cleanup_proc_stat; } ...... return ret; } struct nf_sockopt_ops是在系統調用get/set sockopt中引用的數據結構, 實現用戶空間對規則的添加,刪除,修改,查詢等動做.以上的結構在使用之 前必須先註冊到系統中才能被引用 static struct nf_sockopt_ops so_getorigdst = { .pf = PF_INET, .get_optmin = SO_ORIGINAL_DST, .get_optmax = SO_ORIGINAL_DST+1, .get = &getorigdst, }; int __init ip_conntrack_init(void) { unsigned int i; int ret; if (!ip_conntrack_htable_size) { //全局變量,開始爲0 //根據內存大小計算hash table大小 ip_conntrack_htable_size = (((num_physpages << PAGE_SHIFT) / 16384) / sizeof(struct list_head)); if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE)) //內存大於1G ip_conntrack_htable_size = 8192; if (ip_conntrack_htable_size < 16) ip_conntrack_htable_size = 16; } ip_conntrack_max = 8 * ip_conntrack_htable_size; //打印一些信息 printk("ip_conntrack version %s (%u buckets, %d max) - %Zd bytes per conntrack\n", IP_CONNTRACK_VERSION, ip_conntrack_htable_size, ip_conntrack_max, sizeof(struct ip_conntrack)); ret = nf_register_sockopt(&so_getorigdst); //添加結構到全局連表中 if (ret != 0) { printk(KERN_ERR "Unable to register netfilter socket option\n"); return ret; } //分配hash table內存,若是使用了vmalloc那麼ip_conntrack_vmalloc置1 ip_conntrack_hash = alloc_hashtable(ip_conntrack_htable_size, &ip_conntrack_vmalloc); if (!ip_conntrack_cachep) { printk(KERN_ERR "Unable to create ip_conntrack slab cache\n"); goto err_free_hash; } //expect高速緩存初始化 ip_conntrack_expect_cachep = kmem_cache_create("ip_conntrack_expect", sizeof(struct ip_conntrack_expect), 0, 0, NULL, NULL); if (!ip_conntrack_expect_cachep) { printk(KERN_ERR "Unable to create ip_expect slab cache\n"); goto err_free_conntrack_slab; } //conntrack對每種協議數據的處理,都有不一樣的地方,例如,tuple中提取的內容,TCP的與ICMP的確定不一樣的,由於ICMP連端口的概念也沒有, //因此,對於每種協議的一些特殊處理的函數,須要進行封裝,struct ip_conntrack_protocol 結構就實現了這一封裝,這樣,在之後的數據包處理後, //就能夠根據包中的協議值,使用ip_ct_protos[協議值],找到註冊的協議節點,調用協議對應的處理函數了 write_lock_bh(&ip_conntrack_lock); for (i = 0; i < MAX_IP_CT_PROTO; i++) //所有設置成初始協議 ip_ct_protos[i] = &ip_conntrack_generic_protocol; //初始化主要協議 ip_ct_protos[IPPROTO_TCP] = &ip_conntrack_protocol_tcp; ip_ct_protos[IPPROTO_UDP] = &ip_conntrack_protocol_udp; ip_ct_protos[IPPROTO_ICMP] = &ip_conntrack_protocol_icmp; write_unlock_bh(&ip_conntrack_lock); ip_ct_attach = ip_conntrack_attach; //ipt_REJECT使用 //設置僞造的conntrack,從不刪除,也再也不任何hash table atomic_set(&ip_conntrack_untracked.ct_general.use, 1); //它仍是一個被證明的鏈接 set_bit(IPS_CONFIRMED_BIT, &ip_conntrack_untracked.status); return ret; ...... } NF_IP_PRE_ROUTING,在報文做路由之前執行; NF_IP_FORWARD,在報文轉向另外一個NIC之前執行; NF_IP_POST_ROUTING,在報文流出之前執行; NF_IP_LOCAL_IN,在流入本地的報文做路由之後執行; NF_IP_LOCAL_OUT,在本地報文作流出路由前執行; #define INT_MAX ((int)(~0U>>1)) #define INT_MIN (-INT_MAX - 1) enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD = -175, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT = -50, NF_IP_PRI_FILTER = 0, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_HELPER = INT_MAX - 2, NF_IP_PRI_NAT_SEQ_ADJUST = INT_MAX - 1, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX, }; NF_ACCEPT :繼續正常的報文處理; NF_DROP :將報文丟棄; NF_STOLEN :由鉤子函數處理了該報文,不要再繼續傳送; NF_QUEUE :將報文入隊,一般交由用戶程序處理; NF_REPEAT :再次調用該鉤子函數。 NF_STOP :中止檢測,再也不進行下一個Hook函數 static struct nf_hook_ops ip_conntrack_ops[] = { { .hook = ip_conntrack_defrag, //處理函數 .owner = THIS_MODULE, .pf = PF_INET, //協議 .hooknum = NF_IP_PRE_ROUTING, //5個hook類型之一 .priority = NF_IP_PRI_CONNTRACK_DEFRAG, //權限,調用順序 }, { .hook = ip_conntrack_in, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_CONNTRACK, }, { .hook = ip_conntrack_defrag, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_OUT, .priority = NF_IP_PRI_CONNTRACK_DEFRAG, }, { .hook = ip_conntrack_local, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_OUT, .priority = NF_IP_PRI_CONNTRACK, }, { .hook = ip_conntrack_help, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_POST_ROUTING, .priority = NF_IP_PRI_CONNTRACK_HELPER, }, { .hook = ip_conntrack_help, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_IN, .priority = NF_IP_PRI_CONNTRACK_HELPER, }, { .hook = ip_confirm, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_POST_ROUTING, .priority = NF_IP_PRI_CONNTRACK_CONFIRM, }, { .hook = ip_confirm, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_IN, .priority = NF_IP_PRI_CONNTRACK_CONFIRM, }, }; 協議 hook類型 struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]; #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN) #define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh) \ ( {int __ret; \ if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh, 1)) == 1)\ __ret = (okfn)(skb); \ __ret;}) static inline int nf_hook_thresh(int pf, unsigned int hook, struct sk_buff **pskb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int thresh, int cond) { if (!cond) return 1; #ifndef CONFIG_NETFILTER_DEBUG if (list_empty(&nf_hooks[pf][hook])) return 1; #endif return nf_hook_slow(pf, hook, pskb, indev, outdev, okfn, thresh); } int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh) { struct list_head *elem; unsigned int verdict; int ret = 0; rcu_read_lock(); elem = &nf_hooks[pf][hook]; next_hook: //定位hook連表 verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev, outdev, &elem, okfn, hook_thresh); if (verdict == NF_ACCEPT || verdict == NF_STOP) { //容許經過 ret = 1; goto unlock; } else if (verdict == NF_DROP) { //丟棄 kfree_skb(*pskb); ret = -EPERM; } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) { //入隊發送到用戶空間 if (!nf_queue(pskb, elem, pf, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS)) goto next_hook; } unlock: rcu_read_unlock(); return ret; } unsigned int nf_iterate(struct list_head *head, struct sk_buff **skb, int hook, const struct net_device *indev, const struct net_device *outdev, struct list_head **i, int (*okfn)(struct sk_buff *), int hook_thresh) { unsigned int verdict; list_for_each_continue_rcu(*i, head) { //循環連表 struct nf_hook_ops *elem = (struct nf_hook_ops *)*i; //指向元素 if (hook_thresh > elem->priority) //若是元素中的權限小於參數的權限,忽略這個hook函數 continue; verdict = elem->hook(hook, skb, indev, outdev, okfn); //調用相關hook函數 if (verdict != NF_ACCEPT) { //不在繼續處理 if (verdict != NF_REPEAT) //不重複 return verdict; *i = (*i)->prev; //在此調用相同的hook函數 } } } 下面咱們就來一個一個看相關的hook函數.首先 static unsigned int ip_conntrack_defrag(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { #if !defined(CONFIG_IP_NF_NAT) && !defined(CONFIG_IP_NF_NAT_MODULE) //已經看見過這個數據包 if ((*pskb)->nfct) return NF_ACCEPT; #endif if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) { //在這個函數中最主要就是調用 skb = ip_defrag(skb, user); 進行ip重組,參考個人ip重組文章. *pskb = ip_ct_gather_frags(*pskb, hooknum == NF_IP_PRE_ROUTING ? IP_DEFRAG_CONNTRACK_IN : IP_DEFRAG_CONNTRACK_OUT); if (!*pskb) //數據包已經由hook處理 return NF_STOLEN; } return NF_ACCEPT; } 重組完成後 unsigned int ip_conntrack_in(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct ip_conntrack *ct; enum ip_conntrack_info ctinfo; struct ip_conntrack_protocol *proto; int set_reply = 0; int ret; //已經看見過這個數據包 if ((*pskb)->nfct) { CONNTRACK_STAT_INC(ignore); return NF_ACCEPT; } //又看見ip碎片包?應該從不發生 if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) { if (net_ratelimit()) { printk(KERN_ERR "ip_conntrack_in: Frag of proto %u (hook=%u)\n", (*pskb)->nh.iph->protocol, hooknum); } return NF_DROP; } //根據ip報文所攜帶數據的協議號,獲取相應的協議封裝,該數據結構封裝了對協議私有數據處理的函數和屬性 proto = __ip_conntrack_proto_find((*pskb)->nh.iph->protocol); //實現爲return ip_ct_protos[protocol]; //調用該協議相應的error處理函數,檢查報文是否正確(長度,校驗和之類的比較簡單) if (proto->error != NULL && (ret = proto->error(*pskb, &ctinfo, hooknum)) <= 0) { CONNTRACK_STAT_INC(error); CONNTRACK_STAT_INC(invalid); return -ret; } //在全局鏈接表中,查找與該報文相應的鏈接狀態,返回的是ip_conntrack的指針,用於描述和記錄鏈接的狀態; //若該鏈接尚不存在,則建立相應的結構,並進行初始化 if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo))) { CONNTRACK_STAT_INC(invalid); return NF_ACCEPT; } if (IS_ERR(ct)) { /* Too stressed to deal. */ CONNTRACK_STAT_INC(drop); return NF_DROP; } //調用相應協議的packet處理函數,判斷報文是否屬於有效鏈接,並更新鏈接狀態;返回值若不爲NF_ACCEPT,則報文不合法 ret = proto->packet(ct, *pskb, ctinfo); if (ret < 0) { nf_conntrack_put((*pskb)->nfct); (*pskb)->nfct = NULL; CONNTRACK_STAT_INC(invalid); return -ret; } //設置應答位,ip_conntrack_event_cache,用戶要求對鏈接跟蹤進行更詳細的控制(數據包是REPLY),使用event_cache機制 if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status)) ip_conntrack_event_cache(IPCT_STATUS, *pskb); return ret; } 如今咱們假設是tcp協議 struct ip_conntrack_protocol ip_conntrack_protocol_tcp = { .proto = IPPROTO_TCP, .name = "tcp", .pkt_to_tuple = tcp_pkt_to_tuple, //其指向函數的做用是將協議的端口信息加入到ip_conntrack_tuple的結構中 .invert_tuple = tcp_invert_tuple, //其指向函數的做用是將源和目的多元組中協議部分的值進行互換,包括端口等 .print_tuple = tcp_print_tuple, //打印多元組中的協議信息 .print_conntrack = tcp_print_conntrack, //打印整個鏈接記錄 .packet = tcp_packet, //判斷數據包是否合法,並調整相應鏈接的信息,也就是實現各協議的狀態檢測, //對於UDP等自己是無鏈接的協議 的判斷比較簡單,netfilter創建一個虛擬鏈接, //每一個新發包都是合法包,只等待迴應包到後鏈接都結束;但對於TCP之類的有 //狀態協議必須檢查數據是 否符合協議的狀態轉換過程,這是靠一個狀態轉換數組實現的. //返回數據報的verdict值 .new = tcp_new, //當此協議的一個新鏈接發生時,調用其指向的這個函數,調用返回true時再繼續調用packet()函數 .error = tcp_error, //判斷數據包是否正確,長度,校驗和等 #if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \ defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE) .to_nfattr = tcp_to_nfattr, .from_nfattr = nfattr_to_tcp, .tuple_to_nfattr = ip_ct_port_tuple_to_nfattr, .nfattr_to_tuple = ip_ct_port_nfattr_to_tuple, #endif }; enum ip_conntrack_dir { IP_CT_DIR_ORIGINAL, IP_CT_DIR_REPLY, IP_CT_DIR_MAX }; struct ip_conntrack_tuple 結構僅僅用來標識一個鏈接,並非描述一條完整的鏈接狀態,netfilter將數據包轉換成tuple結構, 並根據其計算hash,在相應的鏈表上查詢,獲取相應的鏈接狀態,若是沒有查到,則表示是一個新的鏈接. ip_conntrack_in -> static inline struct ip_conntrack * resolve_normal_ct(struct sk_buff *skb, struct ip_conntrack_protocol *proto, int *set_reply, unsigned int hooknum, enum ip_conntrack_info *ctinfo) { struct ip_conntrack_tuple tuple; struct ip_conntrack_tuple_hash *h; struct ip_conntrack *ct; //將數據包的內容轉化成相應的tuple,對於和協議相關的部分,如端口、ip,調用相關協議的處理函數pkt_to_tuple if (!ip_ct_get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4, &tuple, proto)) return NULL; //在全局鏈接表中查找和tuple相同的hash項,全局鏈接表以tuple計算出相應的hash值,每個hash項所保存的元素也是相應的tuple h = ip_conntrack_find_get(&tuple, NULL); if (!h) { //若在全局鏈接表中沒法查到tuple所對應的hash項,即相應的鏈接狀態不存在, //系統調用init_conntrack建立並初始化ip_conntrack,並返回其相應的tuple結構指針 h = init_conntrack(&tuple, proto, skb); if (!h) return NULL; if (IS_ERR(h)) return (void *)h; } //根據全局鏈接表所得到tuple_hash,獲取其對應的ip_conntrack結構,由於tuple_hash結構嵌入在ip_conntrack中因此使用container_of就能夠了 ct = tuplehash_to_ctrack(h); //判斷鏈接方向,如果reply方向,設置相應的應答標識和數據包狀態標識 if (DIRECTION(h) == IP_CT_DIR_REPLY) { *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY; //syn+ack迴應 *set_reply = 1; } else { //如果origin方向,根據ip_conntrack中的status,設置相應的應答標識和數據包狀態標識 if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) { *ctinfo = IP_CT_ESTABLISHED; //最後的ack到來時 } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) { *ctinfo = IP_CT_RELATED; } else { *ctinfo = IP_CT_NEW; //當syn包來時 } *set_reply = 0; } //設置skb的對應成員,數據包對應的鏈接狀態結構和數據包鏈接狀態標記 skb->nfct = &ct->ct_general; skb->nfctinfo = *ctinfo; return ct; } resolve_normal_ct-> int ip_ct_get_tuple(const struct iphdr *iph, const struct sk_buff *skb, unsigned int dataoff, struct ip_conntrack_tuple *tuple, const struct ip_conntrack_protocol *protocol) { //不該該看到ip碎片 if (iph->frag_off & htons(IP_OFFSET)) { printk("ip_conntrack_core: Frag of proto %u.\n", iph->protocol); return 0; } tuple->src.ip = iph->saddr; tuple->dst.ip = iph->daddr; tuple->dst.protonum = iph->protocol; tuple->dst.dir = IP_CT_DIR_ORIGINAL; //記錄端口 return protocol->pkt_to_tuple(skb, dataoff, tuple); } resolve_normal_ct-> static struct ip_conntrack_tuple_hash * init_conntrack(struct ip_conntrack_tuple *tuple, struct ip_conntrack_protocol *protocol, struct sk_buff *skb) { struct ip_conntrack *conntrack; struct ip_conntrack_tuple repl_tuple; struct ip_conntrack_expect *exp; //獲取tuple的反向信息,就是把源,目的端口和地址相反的保存到repl_tuple中 if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) { return NULL; } //分配,並用相關參數初始化conntrack,其中最主要的就是 //conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *orig; //conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = *repl; conntrack = ip_conntrack_alloc(tuple, &repl_tuple); if (conntrack == NULL || IS_ERR(conntrack)) return (struct ip_conntrack_tuple_hash *)conntrack; //調用協議相關的new函數進一步初始化conntrack, 對於tcp來講就是tcp_new函數 if (!protocol->new(conntrack, skb)) { ip_conntrack_free(conntrack); return NULL; } write_lock_bh(&ip_conntrack_lock); //查找但願,看下面ftp實現 exp = find_expectation(tuple); if (exp) { //找到 __set_bit(IPS_EXPECTED_BIT, &conntrack->status); conntrack->master = exp->master;//指向主conntrack ...... nf_conntrack_get(&conntrack->master->ct_general); //增長引用計數 CONNTRACK_STAT_INC(expect_new); } esle { //沒有找到 conntrack->helper = __ip_conntrack_helper_find(&repl_tuple); //在全局helpers連表中查找helper, 參看ftp實現 CONNTRACK_STAT_INC(new); } //添加到未證明連表中 list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed); //static LIST_HEAD(unconfirmed); write_unlock_bh(&ip_conntrack_lock); if (exp) { //若是找到但願, 參看ftp實現 if (exp->expectfn) exp->expectfn(conntrack, exp); ip_conntrack_expect_put(exp); } //返回相應的tuple_hash結構 return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL]; } static unsigned int ip_conntrack_local(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { //處理raw sockets if ((*pskb)->len < sizeof(struct iphdr) || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) { if (net_ratelimit()) printk("ipt_hook: happy cracking.\n"); return NF_ACCEPT; } //進入in return ip_conntrack_in(hooknum, pskb, in, out, okfn); } 判斷報文所屬的模式ip_conntrack是否已經存在系統哈希中,不然加入到系統的hash中 static unsigned int ip_confirm(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { return ip_conntrack_confirm(pskb); } static inline int ip_conntrack_confirm(struct sk_buff **pskb) { struct ip_conntrack *ct = (struct ip_conntrack *)(*pskb)->nfct; int ret = NF_ACCEPT; if (ct) { if (!is_confirmed(ct)) //測試IPS_CONFIRMED_BIT是否置位 ret = __ip_conntrack_confirm(pskb); ip_ct_deliver_cached_events(ct); } return ret; } int __ip_conntrack_confirm(struct sk_buff **pskb) { unsigned int hash, repl_hash; struct ip_conntrack *ct; enum ip_conntrack_info ctinfo; ct = ip_conntrack_get(*pskb, &ctinfo); //獲取conntrack結構 if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) return NF_ACCEPT; //計算hash值 hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); write_lock_bh(&ip_conntrack_lock); //ip_conntrack_hash中查找,參看上面初始化過程 if (!LIST_FIND(&ip_conntrack_hash[hash], conntrack_tuple_cmp, struct ip_conntrack_tuple_hash *, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL) && !LIST_FIND(&ip_conntrack_hash[repl_hash], conntrack_tuple_cmp, struct ip_conntrack_tuple_hash*, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) { //從unconfirmed連表中刪除,參看上面 list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list); //根據兩個hash值插入相應的hash連表,當相反方向的數據包到來就能夠在以repl_hash爲hash值的hash連表中查找到 __ip_conntrack_hash_insert(ct, hash, repl_hash); ct->timeout.expires += jiffies; add_timer(&ct->timeout); atomic_inc(&ct->ct_general.use); set_bit(IPS_CONFIRMED_BIT, &ct->status); //設置證明位 CONNTRACK_STAT_INC(insert); write_unlock_bh(&ip_conntrack_lock); if (ct->helper) ip_conntrack_event_cache(IPCT_HELPER, *pskb); ...... #ifdef CONFIG_IP_NF_NAT_NEEDED if (test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status) || test_bit(IPS_DST_NAT_DONE_BIT, &ct->status)) ip_conntrack_event_cache(IPCT_NATINFO, *pskb); #endif ip_conntrack_event_cache(master_ct(ct) ? IPCT_RELATED : IPCT_NEW, *pskb); return NF_ACCEPT; } CONNTRACK_STAT_INC(insert_failed); write_unlock_bh(&ip_conntrack_lock); return NF_DROP; } 這部分咱們看上面列出的tcp協議實現相關部分,關於ip_conntrack_help的實現咱們看下面ftp實現 static int tcp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff, struct ip_conntrack_tuple *tuple) { struct tcphdr _hdr, *hp; /* Actually only need first 8 bytes. */ hp = skb_header_pointer(skb, dataoff, 8, &_hdr); if (hp == NULL) return 0; tuple->src.u.tcp.port = hp->source; tuple->dst.u.tcp.port = hp->dest; return 1; } 端口互換 static int tcp_invert_tuple(struct ip_conntrack_tuple *tuple, const struct ip_conntrack_tuple *orig) { tuple->src.u.tcp.port = orig->dst.u.tcp.port; tuple->dst.u.tcp.port = orig->src.u.tcp.port; return 1; } 判斷數據包是否合法,並調整相應鏈接的信息,也就是實現各協議的狀態檢測, 對於UDP等自己是無鏈接的協議 的判斷比較簡單,netfilter創建一個虛擬鏈接, 每一個新發包都是合法包,只等待迴應包到後鏈接都結束;但對於TCP之類的有 狀態協議必須檢查數據是否符合協議的狀態轉換過程,這是靠一個狀態轉換數組實現的. 返回數據報的verdict值 static int tcp_packet(struct ip_conntrack *conntrack, const struct sk_buff *skb, enum ip_conntrack_info ctinfo) { enum tcp_conntrack new_state, old_state; enum ip_conntrack_dir dir; struct iphdr *iph = skb->nh.iph; struct tcphdr *th, _tcph; unsigned long timeout; unsigned int index; th = skb_header_pointer(skb, iph->ihl * 4, sizeof(_tcph), &_tcph); //獲取tcp頭 write_lock_bh(&tcp_lock); old_state = conntrack->proto.tcp.state; dir = CTINFO2DIR(ctinfo); //取方向 index = get_conntrack_index(th); //返回相應標誌,根據協議 //static const enum tcp_conntrack tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = { //3圍數組在ip_conntrack_proto_tcp.c中 new_state = tcp_conntracks[dir][index][old_state]; //轉換狀態 switch (new_state) { case TCP_CONNTRACK_IGNORE: //好像是處理同時鏈接(syn),那麼阻止一段的syn_ack,只保持一條鏈接 if (index == TCP_SYNACK_SET && conntrack->proto.tcp.last_index == TCP_SYN_SET && conntrack->proto.tcp.last_dir != dir && ntohl(th->ack_seq) == conntrack->proto.tcp.last_end) { write_unlock_bh(&tcp_lock); if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: killing out of sync session "); if (del_timer(&conntrack->timeout)) //刪除定時器和這個鏈接 conntrack->timeout.function((unsigned long)conntrack); return -NF_DROP; } //記錄相關信息 conntrack->proto.tcp.last_index = index; conntrack->proto.tcp.last_dir = dir; conntrack->proto.tcp.last_seq = ntohl(th->seq); conntrack->proto.tcp.last_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th); //計算結束序號 write_unlock_bh(&tcp_lock); if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid packet ignored "); case TCP_CONNTRACK_MAX: //無效的數據包 write_unlock_bh(&tcp_lock); if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid state "); return -NF_ACCEPT; case TCP_CONNTRACK_SYN_SENT: if (old_state < TCP_CONNTRACK_TIME_WAIT) //原狀態有效 break; //試圖從新打開一個關閉的鏈接 if ((conntrack->proto.tcp.seen[dir].flags & IP_CT_TCP_FLAG_CLOSE_INIT) || after(ntohl(th->seq), conntrack->proto.tcp.seen[dir].td_end)) { write_unlock_bh(&tcp_lock); if (del_timer(&conntrack->timeout)) conntrack->timeout.function((unsigned long)conntrack); //刪除這個鏈接 return -NF_REPEAT; } else { //syn就無效 write_unlock_bh(&tcp_lock); if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid SYN"); return -NF_ACCEPT; } case TCP_CONNTRACK_CLOSE: if (index == TCP_RST_SET && ((test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status) && conntrack->proto.tcp.last_index == TCP_SYN_SET) || (!test_bit(IPS_ASSURED_BIT, &conntrack->status) && conntrack->proto.tcp.last_index == TCP_ACK_SET)) && ntohl(th->ack_seq) == conntrack->proto.tcp.last_end) { goto in_window; } default: //一切正常 break; } //若是tcp序號不在窗口內 if (!tcp_in_window(&conntrack->proto.tcp, dir, index, skb, iph, th)) { write_unlock_bh(&tcp_lock); return -NF_ACCEPT; } in_window: conntrack->proto.tcp.last_index = index; //記錄最後狀態索引 conntrack->proto.tcp.state = new_state; //記錄新狀態 //若是新狀態是關閉 if (old_state != new_state && (new_state == TCP_CONNTRACK_FIN_WAIT || new_state == TCP_CONNTRACK_CLOSE)) conntrack->proto.tcp.seen[dir].flags |= IP_CT_TCP_FLAG_CLOSE_INIT; //計算超時時間 timeout = conntrack->proto.tcp.retrans >= ip_ct_tcp_max_retrans && *tcp_timeouts[new_state] > ip_ct_tcp_timeout_max_retrans ? ip_ct_tcp_timeout_max_retrans : *tcp_timeouts[new_state]; write_unlock_bh(&tcp_lock); ip_conntrack_event_cache(IPCT_PROTOINFO_VOLATILE, skb); if (new_state != old_state) ip_conntrack_event_cache(IPCT_PROTOINFO, skb); if (!test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status)) { if (th->rst) { //若是應答是一個rst,刪除鏈接和定時器 if (del_timer(&conntrack->timeout)) conntrack->timeout.function((unsigned long)conntrack); return NF_ACCEPT; } } else if (!test_bit(IPS_ASSURED_BIT, &conntrack->status) && (old_state == TCP_CONNTRACK_SYN_RECV || old_state == TCP_CONNTRACK_ESTABLISHED) && new_state == TCP_CONNTRACK_ESTABLISHED) { //鏈接已經創建 set_bit(IPS_ASSURED_BIT, &conntrack->status); ip_conntrack_event_cache(IPCT_STATUS, skb); } //刷新conntrack的定時器時間 ip_ct_refresh_acct(conntrack, ctinfo, skb, timeout); return NF_ACCEPT; } tcp->packet -> //根據協議返回相應標誌 static unsigned int get_conntrack_index(const struct tcphdr *tcph) { if (tcph->rst) return TCP_RST_SET; else if (tcph->syn) return (tcph->ack ? TCP_SYNACK_SET : TCP_SYN_SET); else if (tcph->fin) return TCP_FIN_SET; else if (tcph->ack) return TCP_ACK_SET; else return TCP_NONE_SET; } static int tcp_new(struct ip_conntrack *conntrack, const struct sk_buff *skb) { enum tcp_conntrack new_state; struct iphdr *iph = skb->nh.iph; struct tcphdr *th, _tcph; //獲取tcp頭 th = skb_header_pointer(skb, iph->ihl * 4, sizeof(_tcph), &_tcph); //初始化一個最新的狀態 new_state = tcp_conntracks[0][get_conntrack_index(th)][TCP_CONNTRACK_NONE]; if (new_state >= TCP_CONNTRACK_MAX) { //新狀態無效 return 0; } if (new_state == TCP_CONNTRACK_SYN_SENT) { //syn包 conntrack->proto.tcp.seen[0].td_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th); //記錄結束序號 conntrack->proto.tcp.seen[0].td_maxwin = ntohs(th->window); //記錄窗口 if (conntrack->proto.tcp.seen[0].td_maxwin == 0) conntrack->proto.tcp.seen[0].td_maxwin = 1; conntrack->proto.tcp.seen[0].td_maxend = conntrack->proto.tcp.seen[0].td_end; //記錄tcp選項 tcp_options(skb, iph, th, &conntrack->proto.tcp.seen[0]); conntrack->proto.tcp.seen[1].flags = 0; conntrack->proto.tcp.seen[0].loose = conntrack->proto.tcp.seen[1].loose = 0; } else if (ip_ct_tcp_loose == 0) { //鏈接跟蹤丟失,再也不試着追蹤 return 0; } else { //試圖去從新追蹤一個已經創建好的鏈接 conntrack->proto.tcp.seen[0].td_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th); conntrack->proto.tcp.seen[0].td_maxwin = ntohs(th->window); if (conntrack->proto.tcp.seen[0].td_maxwin == 0) conntrack->proto.tcp.seen[0].td_maxwin = 1; conntrack->proto.tcp.seen[0].td_maxend = conntrack->proto.tcp.seen[0].td_end + conntrack->proto.tcp.seen[0].td_maxwin; conntrack->proto.tcp.seen[0].td_scale = 0; conntrack->proto.tcp.seen[0].flags = conntrack->proto.tcp.seen[1].flags = IP_CT_TCP_FLAG_SACK_PERM; conntrack->proto.tcp.seen[0].loose = conntrack->proto.tcp.seen[1].loose = ip_ct_tcp_loose; } //初始化另外一個方向 conntrack->proto.tcp.seen[1].td_end = 0; conntrack->proto.tcp.seen[1].td_maxend = 0; conntrack->proto.tcp.seen[1].td_maxwin = 1; conntrack->proto.tcp.seen[1].td_scale = 0; /// tcp_packet 會修改這些設置 conntrack->proto.tcp.state = TCP_CONNTRACK_NONE; conntrack->proto.tcp.last_index = TCP_NONE_SET; return 1; } static int tcp_error(struct sk_buff *skb, enum ip_conntrack_info *ctinfo, unsigned int hooknum) { struct iphdr *iph = skb->nh.iph; struct tcphdr _tcph, *th; unsigned int tcplen = skb->len - iph->ihl * 4; u_int8_t tcpflags; th = skb_header_pointer(skb, iph->ihl * 4, sizeof(_tcph), &_tcph); //獲取tcp頭 if (th == NULL) { if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: short packet "); return -NF_ACCEPT; } if (th->doff*4 < sizeof(struct tcphdr) || tcplen < th->doff*4) { //tcp頭不完整或 if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: truncated/malformed packet "); return -NF_ACCEPT; } //校驗和不對 if (ip_conntrack_checksum && hooknum == NF_IP_PRE_ROUTING && nf_ip_checksum(skb, hooknum, iph->ihl * 4, IPPROTO_TCP)) { if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: bad TCP checksum "); return -NF_ACCEPT; } //檢測tcp標誌 tcpflags = (((u_int8_t *)th)[13] & ~(TH_ECE|TH_CWR)); if (!tcp_valid_flags[tcpflags]) { if (LOG_INVALID(IPPROTO_TCP)) nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid TCP flag combination "); return -NF_ACCEPT; } return NF_ACCEPT; } FTP 實現 static unsigned int ip_conntrack_help(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct ip_conntrack *ct; enum ip_conntrack_info ctinfo; ct = ip_conntrack_get(*pskb, &ctinfo); //獲取conntrack if (ct && ct->helper && ctinfo != IP_CT_RELATED + IP_CT_IS_REPLY) { unsigned int ret; //調用相關的help函數 ret = ct->helper->help(pskb, ct, ctinfo); if (ret != NF_ACCEPT) return ret; } return NF_ACCEPT; } FTP協議大多數協議最大的一個不一樣是:它使用雙向的多個鏈接,並且使用的端口很難預計。 FTP鏈接包含一個控制鏈接(control connection)。這個鏈接用於傳遞客戶端的命令和服務器端對命 令的響應。它使用FTP協議衆所周知的21端口(固然也可以使用其它端口),生存期是整個FTP會話時間。 還包含幾個數據鏈接(data connection)。這些鏈接用於傳輸文件和其它數據,例如:目錄列表等。這種鏈接在須要數據傳輸時創建,而一旦數據傳輸完畢就關閉, 每次使用的端口 也不必定相同。並且,數據鏈接既多是客戶端發起的,也多是服務器端發起的。 根據創建數據鏈接發起方的不一樣,FTP可分爲兩種不一樣的模式:主動(Port Mode)模式和被動模式(Pasv Mode)。這兩種不一樣模式數據鏈接創建方式分別以下: (假設客戶端爲C,服務端爲S) Port模式: 當客戶端C與服務端S創建控制鏈接後,若使用Port模式,那麼客戶端C會發送一條命令告訴服務端S:客戶端C在本地打開了一個端口N在等着你進行數據鏈接。 當服務端S收到這個Port命令後就會向客戶端打開的那個端口N進行鏈接,這種數據鏈接就創建了。 例:客戶端->服務器端:PORT 192,168,1,1,15,176 客戶端<-服務器端:200 PORT command successful. 在上面的例子中192,168,1,1構成IP地址,15,176構成端口號(15*256+176)。 Pasv模式: 當客戶端C與服務端S創建控制鏈接後,若使用Pasv模式,那麼客戶端C會向服務端S發送一條Pasv命令,服務端S會對該命令發送迴應信息, 這個信息是:服務端S在本地打開了一個端口M,你如今去鏈接我吧.當客戶端C收到這個信息後,就能夠向服務端S的M端口進行鏈接,鏈接成功後,數據鏈接也就創建了。 例:客戶端->服務器端:PASV 客戶端<-服務器端:227 Entering Passive Mode (192,168,1,2,14,26). 從上面的解釋中,能夠看到兩種模式主要的不一樣是數據鏈接創建的不一樣. 對於Port模式,是客戶端C在本地打開一個端口等服務端S去鏈接而創建數據鏈接; 而Pasv模式則是服務端S打開一個端口等待客戶端C去創建一個數據鏈接. 在net/ipv4/netfilter/ip_conntrack_ftp.c中 static char *ftp_buffer; #define MAX_PORTS 8 static unsigned short ports[MAX_PORTS]; static int ports_c; static struct ip_conntrack_helper ftp[MAX_PORTS]; static char ftp_names[MAX_PORTS][sizeof("ftp-65535")]; struct ip_conntrack_helper { struct list_head list; //將該結構掛接到多鏈接協議跟蹤鏈表helpers中, helpers鏈表在ip_conntrack_core.c文件中定義 //static LIST_HEAD(helpers);注意是static的,只在該文件範圍有效 const char *name; //協議名稱,字符串常量 struct module *me; //指向模塊自己,統計模塊是否被使用 unsigned int max_expected; //子鏈接的數量,這只是表示主鏈接在每一個時刻所擁有的的子鏈接的數量,而不是 //主鏈接的整個生存期內總共生成的子鏈接的數量,如FTP,不論傳多少個文件,創建 //多少個子鏈接,每一個時刻主鏈接最多隻有一個子鏈接,一個子鏈接結束前按協議是 //不能再派生出第二個子鏈接的,因此初始時該值爲1 unsigned int timeout; //超時,指在多少時間範圍內子鏈接沒有創建的話子鏈接跟蹤失效 //這兩個參數用來描述子鏈接,判斷一個新來的鏈接是不是主鏈接期待的子鏈接,之因此要有mask參數,是由於 //子鏈接的某些參數不能肯定,如被動模式的FTP傳輸,只能獲得子鏈接的目的端口而不能肯定源端口,因此源端口部分要用mask來進行泛匹配 struct ip_conntrack_tuple tuple; struct ip_conntrack_tuple mask; //鏈接跟蹤基本函數,解析主鏈接的通訊內容,提取出關於子鏈接的信息,將子鏈接信息填充到一個struct ip_conntrack_expect結構中, //而後將此結構經過調用函數ip_conntrack_expect_related()把子鏈接的信息添加到系統的期待子鏈接鏈表ip_conntrack_expect_list中。 //返回值是NF_DROP或-1表示協議數據非法。 該函數在ip_conntrack_help中調用,這個函數是一個hook函數在ip_conntrack_standalone_init中註冊. int (*help)(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info conntrackinfo); int (*to_nfattr)(struct sk_buff *skb, const struct ip_conntrack *ct); }; struct ip_conntrack_expect { /* 鏈表頭,在被某鏈接引用以前,全部expect結構都由此鏈表維護 */ struct list_head list; /* 引用計數 */ atomic_t use; /* 主鏈接的預期的子鏈接的鏈表 */ struct list_head expected_list; /* 期待者,即預期鏈接對應的主鏈接,換句話說就是將此鏈接看成是其預期鏈接的鏈接... */ struct ip_conntrack *expectant; /* 預期鏈接對應的真實的子鏈接 */ struct ip_conntrack *sibling; /* 鏈接的tuple值 */ struct ip_conntrack_tuple ct_tuple; /* 定時器 */ struct timer_list timeout; /* 預期鏈接的tuple和mask,搜索預期鏈接時要用到的 */ struct ip_conntrack_tuple tuple, mask; /* 預期鏈接函數,通常是NULL,有特殊須要時才定義 */ int (*expectfn)(struct ip_conntrack *new); /* TCP協議時,主鏈接中描述子鏈接的數據起始處對應的序列號值 */ u_int32_t seq; /* 跟蹤各個多鏈接IP層協議相關的數據 */ union ip_conntrack_expect_proto proto; /* 跟蹤各個多鏈接應用層協議相關的數據 */ union ip_conntrack_expect_help help; }; struct ip_conntrack_expect { struct list_head list; //鏈表,在被某鏈接引用以前,全部expect結構都由此鏈表維護 struct ip_conntrack_tuple tuple, mask; //預期鏈接的tuple和mask,搜索預期鏈接時要用到的 //預期鏈接函數,通常是NULL,有特殊須要時才定義 void (*expectfn)(struct ip_conntrack *new, struct ip_conntrack_expect *this); struct ip_conntrack *master; //主鏈接的conntrack struct timer_list timeout; //定時器 atomic_t use; //引用計數 unsigned int id; //惟一id unsigned int flags; #ifdef CONFIG_IP_NF_NAT_NEEDED u_int32_t saved_ip; /* This is the original per-proto part, used to map the expected connection the way the recipient expects. */ union ip_conntrack_manip_proto saved_proto; enum ip_conntrack_dir dir; //主鏈接相關的方向 #endif }; static int __init ip_conntrack_ftp_init(void) { int i, ret; char *tmpname; ftp_buffer = kmalloc(65536, GFP_KERNEL); if (!ftp_buffer) return -ENOMEM; if (ports_c == 0) ports[ports_c++] = FTP_PORT; // #define FTP_PORT 21 //正常狀況如今 ports_c = 1 for (i = 0; i < ports_c; i++) { ftp[i].tuple.src.u.tcp.port = htons(ports[i]); ftp[i].tuple.dst.protonum = IPPROTO_TCP; ftp[i].mask.src.u.tcp.port = 0xFFFF; //描述子鏈接端口 ftp[i].mask.dst.protonum = 0xFF; ftp[i].max_expected = 1; //看上面結構中說明 ftp[i].timeout = 5 * 60; // 5分鐘 ftp[i].me = THIS_MODULE; ftp[i].help = help; //關鍵函數 tmpname = &ftp_names[i][0]; if (ports[i] == FTP_PORT) sprintf(tmpname, "ftp"); //標準21端口 else sprintf(tmpname, "ftp-%d", ports[i]); ftp[i].name = tmpname;//指向名字 //把這個helper添加到helpers連表中 ret = ip_conntrack_helper_register(&ftp[i]); if (ret) { ip_conntrack_ftp_fini(); return ret; } } return 0; } 下面咱們看關鍵help函數 static int help(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { unsigned int dataoff, datalen; struct tcphdr _tcph, *th; char *fb_ptr; int ret; u32 seq, array[6] = { 0 }; int dir = CTINFO2DIR(ctinfo); unsigned int matchlen, matchoff; struct ip_ct_ftp_master *ct_ftp_info = &ct->help.ct_ftp_info; struct ip_conntrack_expect *exp; unsigned int i; int found = 0, ends_in_nl; if (ctinfo != IP_CT_ESTABLISHED && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) { DEBUGP("ftp: Conntrackinfo = %u\n", ctinfo); return NF_ACCEPT; } //取出tcp頭,看下面函數說明 th = skb_header_pointer(*pskb, (*pskb)->nh.iph->ihl*4, sizeof(_tcph), &_tcph); if (th == NULL) return NF_ACCEPT; dataoff = (*pskb)->nh.iph->ihl*4 + th->doff*4; //數據開始位置 if (dataoff >= (*pskb)->len) { //沒有數據 DEBUGP("ftp: pskblen = %u\n", (*pskb)->len); return NF_ACCEPT; } datalen = (*pskb)->len - dataoff; //數據長度 spin_lock_bh(&ip_ftp_lock); fb_ptr = skb_header_pointer(*pskb, dataoff, (*pskb)->len - dataoff, ftp_buffer); //指向tcp數據開始 ends_in_nl = (fb_ptr[datalen - 1] == '\n'); //最後一個字符是不是 \n seq = ntohl(th->seq) + datalen; //結束序號 //檢查序列號是不是但願的序號,防止序列號問題, 看下面序號處理解釋 if (!find_nl_seq(ntohl(th->seq), ct_ftp_info, dir)) { ret = NF_ACCEPT; goto out_update_nl; } array[0] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 24) & 0xFF; array[1] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 16) & 0xFF; array[2] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 8) & 0xFF; array[3] = ntohl(ct->tuplehash[dir].tuple.src.ip) & 0xFF; //解析ftp命令 for (i = 0; i < ARRAY_SIZE(search[dir]); i++) { found = find_pattern(fb_ptr, (*pskb)->len - dataoff, search[dir][i].pattern, search[dir][i].plen, search[dir][i].skip, search[dir][i].term, &matchoff, &matchlen, array, search[dir][i].getnum); if (found) break; } if (found == -1) { ret = NF_DROP; goto out; } else if (found == 0) { //不匹配 ret = NF_ACCEPT; goto out_update_nl; } //分配指望 //之因此稱爲指望鏈接,是由於如今還處於控制鏈接階段,數據鏈接此時還未創建,但未來是會創建的. //如此處理以後,咱們就能夠預先獲取創建數據鏈接的信息,當真正的數據鏈接須要創建時,咱們只需 //在指望鏈接表中進行查找,保證了多鏈接協議的正確處理,同時還提升了效率. exp = ip_conntrack_expect_alloc(ct); if (exp == NULL) { ret = NF_DROP; goto out; } exp->tuple.dst.ip = ct->tuplehash[!dir].tuple.dst.ip; if (htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]) != ct->tuplehash[dir].tuple.src.ip) { if (!loose) { ret = NF_ACCEPT; goto out_put_expect; } exp->tuple.dst.ip = htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]); } exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip; exp->tuple.dst.u.tcp.port = htons(array[4] << 8 | array[5]); //port = x * 256 + y exp->tuple.src.u.tcp.port = 0; /* Don't care. */ exp->tuple.dst.protonum = IPPROTO_TCP; exp->mask = ((struct ip_conntrack_tuple) { 0xFFFFFFFF, { 0 } , { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }}); exp->expectfn = NULL; exp->flags = 0; if (ip_nat_ftp_hook) //若是註冊了NAT修改函數,在此直接調用,該hook函數在ip_nat_ftp.c中定義,咱們在下面看 ret = ip_nat_ftp_hook(pskb, ctinfo, search[dir][i].ftptype, matchoff, matchlen, exp, &seq); else { if (ip_conntrack_expect_related(exp) != 0) //註冊這個指望 ret = NF_DROP; else ret = NF_ACCEPT; } out_put_expect: ip_conntrack_expect_put(exp); out_update_nl: if (ends_in_nl) update_nl_seq(seq, ct_ftp_info,dir, *pskb); //看下面序號處理解釋 out: spin_unlock_bh(&ip_ftp_lock); return ret; } 分配一個指望 struct ip_conntrack_expect *ip_conntrack_expect_alloc(struct ip_conntrack *me) { struct ip_conntrack_expect *new; new = kmem_cache_alloc(ip_conntrack_expect_cachep, GFP_ATOMIC); if (!new) { DEBUGP("expect_related: OOM allocating expect\n"); return NULL; } new->master = me; //指向主ip conntrack atomic_set(&new->use, 1); return new; } int ip_conntrack_expect_related(struct ip_conntrack_expect *expect) { struct ip_conntrack_expect *i; int ret; write_lock_bh(&ip_conntrack_lock); list_for_each_entry(i, &ip_conntrack_expect_list, list) { if (expect_matches(i, expect)) { //有一樣的指望 if (refresh_timer(i)) { //刷新舊指望的定時器 ret = 0; goto out; } } else if (expect_clash(i, expect)) { //指望衝突 ret = -EBUSY; goto out; } } //超過個數限制 if (expect->master->helper->max_expected && expect->master->expecting >= expect->master->helper->max_expected) evict_oldest_expect(expect->master);//回收舊指望 ip_conntrack_expect_insert(expect); //插入指望到連表 ip_conntrack_expect_event(IPEXP_NEW, expect); ret = 0; out: write_unlock_bh(&ip_conntrack_lock); return ret; } ip_nat_ftp_hook函數指針初始化在 static int __init ip_nat_ftp_init(void) { ip_nat_ftp_hook = ip_nat_ftp; return 0; } static unsigned int ip_nat_ftp(struct sk_buff **pskb, enum ip_conntrack_info ctinfo, enum ip_ct_ftp_type type, unsigned int matchoff, unsigned int matchlen, struct ip_conntrack_expect *exp, u32 *seq) { u_int32_t newip; u_int16_t port; int dir = CTINFO2DIR(ctinfo); struct ip_conntrack *ct = exp->master; //獲取主鏈接 newip = ct->tuplehash[!dir].tuple.dst.ip; //目的ip exp->saved_proto.tcp.port = exp->tuple.dst.u.tcp.port; //保存解析出的端口 exp->dir = !dir;//ftp相對於主鏈接來講是反方向的 //該函數在初始化鏈接init_conntrack()函數中調用,用於爲子鏈接創建NAT信息 exp->expectfn = ip_nat_follow_master; for (port = ntohs(exp->saved_proto.tcp.port); port != 0; port++) { //獲取一個新端口而後檢查是否能夠用此端口替代原來的端口 exp->tuple.dst.u.tcp.port = htons(port); if (ip_conntrack_expect_related(exp) == 0) break; } if (port == 0) return NF_DROP; //修改ftp數據內容,包括IP端口而後調整tcp的序號,從新計算校驗和等 //mangle是一個函數指針數組 if (!mangle[type](pskb, newip, port, matchoff, matchlen, ct, ctinfo, seq)) { ip_conntrack_unexpect_related(exp); return NF_DROP; } return NF_ACCEPT; } 指望幫助函數在init_conntrack中若是找到指望會調用指望的幫助函數 void ip_nat_follow_master(struct ip_conntrack *ct, struct ip_conntrack_expect *exp) { struct ip_nat_range range; /* Change src to where master sends to */ range.flags = IP_NAT_RANGE_MAP_IPS; //ct爲子鏈接,exp->dir與主鏈接相反,因此下面取的是子鏈接的源地址 range.min_ip = range.max_ip = ct->master->tuplehash[!exp->dir].tuple.dst.ip; /* hook doesn't matter, but it has to do source manip */ ip_nat_setup_info(ct, &range, NF_IP_POST_ROUTING); //函數參考Linux網絡地址轉換分析 /* For DST manip, map port here to where it's expected. */ range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED); range.min = range.max = exp->saved_proto; //原始目的端口 //子鏈接的目的ip range.min_ip = range.max_ip = ct->master->tuplehash[!exp->dir].tuple.src.ip; /* hook doesn't matter, but it has to do destination manip */ ip_nat_setup_info(ct, &range, NF_IP_PRE_ROUTING); } [skb_header_pointer] skb:數據包struct sk_buff的指針 offset:相對數據起始頭(如IP頭)的偏移量 len:數據長度 buffer:緩衝區,大小不小於len static inline unsigned int skb_headlen(const struct sk_buff *skb) { return skb->len - skb->data_len; } 其中skb->len是數據包長度,在IPv4中就是單個完整IP包的總長,但這些數據並不必定都在當前內存頁;skb->data_len表示在其餘頁的數據長度, 所以skb->len - skb->data_len表示在當前頁的數據大小. 若是skb->data_len不爲0,表示該IP包的數據分屬不一樣的頁,該數據包也就被成爲非線性化的, 函數skb_is_nonlinear()就是經過該參數判斷,通常剛進行完碎片重組的skb包就屬於此類. 那麼這函數就是先判斷要處理的數據是否都在當前頁面內,若是是,則返回能夠直接對數據處理,返回所求數據指針,不然用skb_copy_bits()函數進行拷貝. static inline void *skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer) { int hlen = skb_headlen(skb); if (hlen - offset >= len) return skb->data + offset; if (skb_copy_bits(skb, offset, buffer, len) < 0) return NULL; return buffer; } [/skb_header_pointer] [序號處理解釋] #define NUM_SEQ_TO_REMEMBER 2 FTP主鏈接中記錄相關信息的結構, 主要是記錄期待的序列號 struct ip_ct_ftp_master { //每一個方向各保存2個序列號值, 能夠容排序錯誤一次 u_int32_t seq_aft_nl[IP_CT_DIR_MAX][NUM_SEQ_TO_REMEMBER]; //每一個方向記錄的序列號的數量 int seq_aft_nl_num[IP_CT_DIR_MAX]; }; 這個函數判斷當前數據包的序列號是不是正在期待的序列號, 若是不是則跳過內容解析操做 static int find_nl_seq(u32 seq, const struct ip_ct_ftp_master *info, int dir) { unsigned int i; //循環次數爲該方向上記錄的序列號的的數量 for (i = 0; i < info->seq_aft_nl_num[dir]; i++) //若是當前數據包的序列號和期待的序列號中的任一個相同返回1 if (info->seq_aft_nl[dir][i] == seq) return 1; //不然返回0表示失敗,失敗後雖然不解析包內容了,但仍然會調用下面的函數來調整期待的序列號 return 0; } 這個函數更新主鏈接所期待的序列號, 更換最老的一個(代碼有錯誤). nl_seq是要期待的序列號 static void update_nl_seq(u32 nl_seq, struct ip_ct_ftp_master *info, int dir, struct sk_buff *skb) { unsigned int i, oldest = NUM_SEQ_TO_REMEMBER; //循環次數爲該方向上記錄的序列號的的數量 for (i = 0; i < info->seq_aft_nl_num[dir]; i++) { if (info->seq_aft_nl[dir][i] == nl_seq)//若是當前數據包的序列號和期待的序列號相同則不用更新 return; //第一個比較條件有問題, 當info->seq_aft_nl_num[dir]達到最大值(2)後 oldest將永遠賦值爲0, 也就是兩邊各發出2個包後oldest就不變了 //第二個比較條件也幾乎沒有意義, oldest最大也就是2, 而info->seq_aft_nl表示序列號幾乎不可能小於2, 只有 //初始狀況info->seq_aft_nl[dir][i]還爲0是纔可能爲真, 其餘基本永遠爲假 if (oldest == info->seq_aft_nl_num[dir] || before(info->seq_aft_nl[dir][i], oldest)) oldest = i; } //調整期待的序列號 if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) { info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq; ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb); } else if (oldest != NUM_SEQ_TO_REMEMBER) { info->seq_aft_nl[dir][oldest] = nl_seq; ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb); } } [/序號處理解釋]