启动时首先在ip_conntrack_standalone.c中调用
static int __init ip_conntrack_standalone_init(void) //proc相关部分省略
{
......
int ret = ; ret = ip_conntrack_init(); //大部分初始化工作
if (ret < )
return ret; ......
//注册hook函数,添加到二围数组连表中, ip_conntrack_ops 定义看下面
ret = nf_register_hooks(ip_conntrack_ops, ARRAY_SIZE(ip_conntrack_ops));
if (ret < ) {
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+,
.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) / ) / sizeof(struct list_head));
if (num_physpages > ( * * / PAGE_SIZE)) //内存大于1G
ip_conntrack_htable_size = ; if (ip_conntrack_htable_size < )
ip_conntrack_htable_size = ;
} ip_conntrack_max = * 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 != ) {
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), , , 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 = ; 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, );
//它还是一个被证实的连接
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 = -,
NF_IP_PRI_RAW = -,
NF_IP_PRI_SELINUX_FIRST = -,
NF_IP_PRI_CONNTRACK = -,
NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD = -,
NF_IP_PRI_MANGLE = -,
NF_IP_PRI_NAT_DST = -,
NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT = -,
NF_IP_PRI_FILTER = ,
NF_IP_PRI_NAT_SRC = ,
NF_IP_PRI_SELINUX_LAST = ,
NF_IP_PRI_CONNTRACK_HELPER = INT_MAX - ,
NF_IP_PRI_NAT_SEQ_ADJUST = INT_MAX - ,
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, )) == )\
__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 ;
#ifndef CONFIG_NETFILTER_DEBUG
if (list_empty(&nf_hooks[pf][hook]))
return ;
#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 = ; 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 = ;
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 = ;
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)) <= ) {
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 < ) {
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*, &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 = ;
} 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 = ;
}
//设置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 ;
}
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 * < 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, , &_hdr);
if (hp == NULL)
return ; tuple->src.u.tcp.port = hp->source;
tuple->dst.u.tcp.port = hp->dest;
return ;
}
端口互换
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 ;
}
判断数据包是否合法,并调整相应连接的信息,也就是实现各协议的状态检测,
对于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 * , 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, , 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, , 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, , 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, , 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 * , sizeof(_tcph), &_tcph);
//初始化一个最新的状态
new_state = tcp_conntracks[][get_conntrack_index(th)][TCP_CONNTRACK_NONE];
if (new_state >= TCP_CONNTRACK_MAX) { //新状态无效
return ;
}
if (new_state == TCP_CONNTRACK_SYN_SENT) { //syn包
conntrack->proto.tcp.seen[].td_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th); //记录结束序号
conntrack->proto.tcp.seen[].td_maxwin = ntohs(th->window); //记录窗口
if (conntrack->proto.tcp.seen[].td_maxwin == )
conntrack->proto.tcp.seen[].td_maxwin = ;
conntrack->proto.tcp.seen[].td_maxend = conntrack->proto.tcp.seen[].td_end;
//记录tcp选项
tcp_options(skb, iph, th, &conntrack->proto.tcp.seen[]);
conntrack->proto.tcp.seen[].flags = ;
conntrack->proto.tcp.seen[].loose =
conntrack->proto.tcp.seen[].loose = ;
} else if (ip_ct_tcp_loose == ) { //连接跟踪丢失,不再试着追踪
return ;
} else { //试图去重新追踪一个已经建立好的连接
conntrack->proto.tcp.seen[].td_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th);
conntrack->proto.tcp.seen[].td_maxwin = ntohs(th->window);
if (conntrack->proto.tcp.seen[].td_maxwin == )
conntrack->proto.tcp.seen[].td_maxwin = ; conntrack->proto.tcp.seen[].td_maxend = conntrack->proto.tcp.seen[].td_end + conntrack->proto.tcp.seen[].td_maxwin;
conntrack->proto.tcp.seen[].td_scale = ; conntrack->proto.tcp.seen[].flags = conntrack->proto.tcp.seen[].flags = IP_CT_TCP_FLAG_SACK_PERM;
conntrack->proto.tcp.seen[].loose = conntrack->proto.tcp.seen[].loose = ip_ct_tcp_loose;
}
//初始化另一个方向
conntrack->proto.tcp.seen[].td_end = ;
conntrack->proto.tcp.seen[].td_maxend = ;
conntrack->proto.tcp.seen[].td_maxwin = ;
conntrack->proto.tcp.seen[].td_scale = ; /// tcp_packet 会修改这些设置
conntrack->proto.tcp.state = TCP_CONNTRACK_NONE;
conntrack->proto.tcp.last_index = TCP_NONE_SET;
return ;
}
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 * ;
u_int8_t tcpflags; th = skb_header_pointer(skb, iph->ihl * , sizeof(_tcph), &_tcph); //获取tcp头
if (th == NULL) {
if (LOG_INVALID(IPPROTO_TCP))
nf_log_packet(PF_INET, , skb, NULL, NULL, NULL, "ip_ct_tcp: short packet ");
return -NF_ACCEPT;
}
if (th->doff* < sizeof(struct tcphdr) || tcplen < th->doff*) { //tcp头不完整或
if (LOG_INVALID(IPPROTO_TCP))
nf_log_packet(PF_INET, , 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 * , IPPROTO_TCP)) {
if (LOG_INVALID(IPPROTO_TCP))
nf_log_packet(PF_INET, , skb, NULL, NULL, NULL, "ip_ct_tcp: bad TCP checksum ");
return -NF_ACCEPT;
}
//检测tcp标志
tcpflags = (((u_int8_t *)th)[] & ~(TH_ECE|TH_CWR));
if (!tcp_valid_flags[tcpflags]) {
if (LOG_INVALID(IPPROTO_TCP))
nf_log_packet(PF_INET, , 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 ,,,,,
客户端<-服务器端: PORT command successful.
在上面的例子中192,,,1构成IP地址,,176构成端口号(*+)。
Pasv模式:
当客户端C与服务端S建立控制连接后,若使用Pasv模式,那么客户端C会向服务端S发送一条Pasv命令,服务端S会对该命令发送回应信息,
这个信息是:服务端S在本地打开了一个端口M,你现在去连接我吧.当客户端C收到这个信息后,就可以向服务端S的M端口进行连接,连接成功后,数据连接也就建立了。
例:客户端->服务器端:PASV
客户端<-服务器端: Entering Passive Mode (,,,,,). 从上面的解释中,可以看到两种模式主要的不同是数据连接建立的不同.
对于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(, GFP_KERNEL);
if (!ftp_buffer)
return -ENOMEM; if (ports_c == )
ports[ports_c++] = FTP_PORT; // #define FTP_PORT 21 //正常情况现在 ports_c = 1
for (i = ; 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 = ; //看上面结构中说明
ftp[i].timeout = * ; // 5分钟
ftp[i].me = THIS_MODULE;
ftp[i].help = help; //关键函数 tmpname = &ftp_names[i][]; 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 ;
}
下面我们看关键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[] = { };
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 = , 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*, sizeof(_tcph), &_tcph);
if (th == NULL)
return NF_ACCEPT; dataoff = (*pskb)->nh.iph->ihl* + th->doff*; //数据开始位置
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 - ] == '\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[] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> ) & 0xFF;
array[] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> ) & 0xFF;
array[] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> ) & 0xFF;
array[] = ntohl(ct->tuplehash[dir].tuple.src.ip) & 0xFF;
//解析ftp命令
for (i = ; 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 == -) {
ret = NF_DROP;
goto out;
} else if (found == ) { //不匹配
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[] << ) | (array[] << ) | (array[] << ) | array[]) != ct->tuplehash[dir].tuple.src.ip) {
if (!loose) {
ret = NF_ACCEPT;
goto out_put_expect;
}
exp->tuple.dst.ip = htonl((array[] << ) | (array[] << ) | (array[] << ) | array[]);
} exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip;
exp->tuple.dst.u.tcp.port = htons(array[] << | array[]); //port = x * 256 + y
exp->tuple.src.u.tcp.port = ; /* Don't care. */
exp->tuple.dst.protonum = IPPROTO_TCP;
exp->mask = ((struct ip_conntrack_tuple) { 0xFFFFFFFF, { } , { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }});
exp->expectfn = NULL;
exp->flags = ; 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) != ) //注册这个期望
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, );
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 = ;
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 = ;
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 ;
}
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 != ; port++) {
//获取一个新端口然后检查是否可以用此端口替代原来的端口
exp->tuple.dst.u.tcp.port = htons(port);
if (ip_conntrack_expect_related(exp) == )
break;
}
if (port == )
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) < )
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 = ; i < info->seq_aft_nl_num[dir]; i++)
//如果当前数据包的序列号和期待的序列号中的任一个相同返回1
if (info->seq_aft_nl[dir][i] == seq)
return ;
//否则返回0表示失败,失败后虽然不解析包内容了,但仍然会调用下面的函数来调整期待的序列号
return ;
}
这个函数更新主连接所期待的序列号, 更换最老的一个(代码有错误).
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 = ; 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);
}
}
[/序号处理解释]

ip_conntrack 实现的更多相关文章

  1. ip_conntrack table full dropping packet错误的解决方法

    ip_conntrack表满导致的,iptables开启后会加载ip_conntrack模块,来跟踪包.默认情况下ip_conntrack_max大小为65536. 查看ip_conntrack最大大 ...

  2. 我和ip_conntrack不得不说的一些事

    面对让人无语的ip_conntrack,我有一种说不出的感觉!自从接触它到现在,已经两年多了,其间我受到过它的恩惠,也被它蹂躏过,被它玩过,但是又不忍心舍弃它,因为我找不到更好的替代.工作中,学习中, ...

  3. ip_conntrack缓存neighbour

    在我的ip_conntrack版本中,它目前已经可以缓存路由,filter规则等,还可以平滑生效最新配置的NAT,它越来越像真正的SDN了,唯一有待完善的就是将5元组的tuple进化成N元组的tupl ...

  4. 无状态TCP的ip_conntrack

    Linux的ip_conntrack实现得过于沉重和精细.而实际上有时候,根本不需要在conntrack中对TCP的状态进行跟踪,只把它当UDP好了,我们的需求就是让系统可以将一个数据包和一个五元组标 ...

  5. ip_conntrack参数

    ip_conntrack就是linux NAT的一个跟踪连接条目的模块,ip_conntrack模块会使用一个哈希表记录 tcp 通讯协议的 established connection记录,当这个哈 ...

  6. ip_conntrack table full dropping packet解决方案

    在一台繁忙的服务器上,建议关闭ip_conntrack模块的加载: 当我们开启iptables后,会有这么个现象发生,丢包.ping的话会断断续续的丢包,ifconfig 会看到网卡dropped:X ...

  7. ip_conntrack or nf_conntrack : table full, dropping packet

    nf_conntrack: table full, dropping packet ip_conntrack or nf_conntrack : table full, dropping packet ...

  8. 内核、中断和网络 $ sysctl -a | grep ...$ cat /proc/interrupts$ cat /proc/net/ip_conntrack /* may take some time on busy servers */$ netstat$ ss -s

    你的中断请求是否是均衡地分配给CPU处理,还是会有某个CPU的核因为大量的网络中断请求或者RAID请求而过载了? SWAP交换的设置是什么?对于工作站来说swappinness 设为 60 就很好, ...

  9. CentOS(5.8/6.7)linux生产环境若干优化实战

    CentOS系统安装之后并不能立即投入生产环境使用,往往需要先经过我们运维人员的优化才行.在此讲解几点关于Linux系统安装后的基础优化操作.注意:本次优化都是基于CentOS(5.8/6.7). 下 ...

随机推荐

  1. eclipse怎么切换SVN的用户

    在用eclipse的时候会经常用到SVN来进行代码的版本控制,为了方便起见,我们会保存密码,从此之后就不会再出现输入或者修改用户名和密码的地方了,这时候想切换用户怎么办,在本地操作的一种方法是删除SV ...

  2. croppic 图片裁剪

    #region 3.1.3 保存裁剪后的图片方法 +ContentResult TemplateCropImg() /// <summary> /// 保存裁剪后的图片方法 /// < ...

  3. C#程序员整理的Unity 3D笔记(十):Unity3D的位移、旋转的3D数学模型

    遇到一个想做的功能,但是实现不了,核心原因是因为对U3D的3D数学概念没有灵活吃透.故再次系统学习之—第三次学习3D数学. 本次,希望实现的功能很简单: 如在小地图中,希望可以动态画出Player当前 ...

  4. GCD学习之dispatch_barrier_async

    iOS常见的多线程开发方式有NSThread.NSOPeration和GCD,抽象程度依次提高,GCD是最抽象的,使用起来最简单,但相对来说功能有限,比如不能cancel任务,这也算是一点遗憾吧. 今 ...

  5. 2 JavaScript应用开发实践指南

    JavaScript 语言在浏览器中的运用 HTTP请求,加载HTML后根据内容加载CSS等,大部分浏览器默认2个下载链接. HTML元素要尽可能简洁,不需要将Table元素变成多个div, css代 ...

  6. java_集合框架

    一.集合框架图 二.Collection接口     Collection中可以存储的元素间无序,可以重复的元素.     Collection接口的子接口List和Set,Map不是Collecti ...

  7. poj代码搬家啦啦啦

    我的poj代码搬家啦,大家想看可以到 blog.csdn.net/michaelysm 来看.欢迎哦

  8. How to: Signing Installers You Create with Inno Setup

    Original Link: http://revolution.screenstepslive.com/s/revolution/m/10695/l/95041-signing-installers ...

  9. OCI_ERROE - errcode[1591],errmsg[ORA-01591:

    CEASYDAO: 错误码[1591],错误信息[Error - OCI_ERROE - errcode[1591],errmsg[ORA-01591: lock held by in-doubt d ...

  10. React:用于搭建UI的JavaScript库

    React https://facebook.github.io/react/index.html 2016-08-03 先吐槽一下.看过很多博客.教程.文章,一直想不通为什么大牛们介绍一种新技术一上 ...