ip_conntrack 实现
启动时首先在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); } } [/序号处理解释]
posted on 2013-08-30 09:49 SuperKing 阅读(1980) 评论(0) 编辑 收藏 举报