内核首先注册tftp help
将tftp_help 以及熟知的端口号69 注册到nf_ct_helper_hash链表中
static int __init nf_conntrack_tftp_init(void)
{
for (i = 0; i < ports_c; i++) {
tftp[i][0].tuple.src.l3num = AF_INET;
tftp[i][1].tuple.src.l3num = AF_INET6;
for (j = 0; j < 2; j++) {
tftp[i][j].tuple.dst.protonum = IPPROTO_UDP;
tftp[i][j].tuple.src.u.udp.port = htons(69);
tftp[i][j].expect_policy = &tftp_exp_policy;
tftp[i][j].me = THIS_MODULE;
tftp[i][j].help = tftp_help;
ret = nf_conntrack_helper_register(&tftp[i][j]);
//----> hlist_add_head_rcu(&tftp[i][j]->hnode, &nf_ct_helper_hash[h]);
}
}
return 0;
}
样例分析:
PC--->SERVER Client主动发起连接。报文元组信息如下
Dip 1.1.1.5 Sip.1.1.1.6 Dport:69 Sport:1116 Protonum:udp L3num:Inet
其中流量拓扑为: PC----->AF----->server (AF有netfilter防火墙)
Netfilter入口,conntrack连接报文
其处理逻辑为:在NF_INET_PRE_ROUTING处理执行 nf_conntrack_in; 其会调用 resolve_normal_ct
/* 根据skb L3和L4层的信息 得到一个nf_conn结构 */
ct = resolve_normal_ct(net, tmpl, skb, dataoff, pf, protonum, l3proto, l4proto, &set_reply, &ctinfo);
建立正反向连接,和一般连接处理一样。
新建连接的过程中,会根据连接的反向连接B中的sport,protonum,L3num来在Netfilter中查找已注册的helper函数。找到已注册的tftp的helper函数,并把该函数指针赋给链接nf_conn *ct
分析resolve_normal_ct
逻辑中有在全局的期望连接链表expect_hash中查找是否有匹配新建tuple的期望连接。第一次过来的数据包肯定是没有的,于是走else分支,__nf_ct_try_assign_helper()函数去nf_ct_helper_hash哈希表中匹配当前tuple,
由于我们在本节开头提到nf_conntrack_tftp_init()已经把tftp的helper extension添加进去了,所以可以匹配成功,于是把找到的helper赋值给nfct_help(ct)->helper,而这个helper的help方法就是tftp_help()
/* Allocate a new conntrack: we return -ENOMEM if classification
failed due to stress. Otherwise it really is unclassifiable. */
static struct nf_conntrack_tuple_hash *
init_conntrack(struct net *net, struct nf_conn *tmpl,
const struct nf_conntrack_tuple *tuple,
struct nf_conntrack_l3proto *l3proto,
struct nf_conntrack_l4proto *l4proto,
struct sk_buff *skb,
unsigned int dataoff, u32 hash)
{
struct nf_conn *ct;
struct nf_conn_help *help;
struct nf_conntrack_tuple repl_tuple;
struct nf_conntrack_ecache *ecache;
struct nf_conntrack_expect *exp = NULL;
const struct nf_conntrack_zone *zone;
struct nf_conn_timeout *timeout_ext;
struct nf_conntrack_zone tmp;
unsigned int *timeouts;
/* 根据tuple制作一个repl_tuple。主要是调用L3和L4的invert_tuple方法 */
if (!nf_ct_invert_tuple(&repl_tuple, tuple, l3proto, l4proto)) {
pr_debug("Can't invert tuple.\n");
return NULL;
}
/* 在cache中申请一个nf_conn结构,把tuple和repl_tuple赋值给ct的tuplehash[]数组,
并初始化ct.timeout定时器函数为death_by_timeout(),但不启动定时器。 *
*/
zone = nf_ct_zone_tmpl(tmpl, skb, &tmp);
ct = __nf_conntrack_alloc(net, zone, tuple, &repl_tuple, GFP_ATOMIC,
-------------------------/* 为acct和ecache两个ext分配空间。不过之后一般不会被初始化,所以用不到 */
nf_ct_acct_ext_add(ct, GFP_ATOMIC);
nf_ct_tstamp_ext_add(ct, GFP_ATOMIC);
nf_ct_labels_ext_add(ct);
ecache = tmpl ? nf_ct_ecache_find(tmpl) : NULL;
nf_ct_ecache_ext_add(ct, ecache ? ecache->ctmask : 0,
ecache ? ecache->expmask : 0,
GFP_ATOMIC);
local_bh_disable();
/*
会在全局的期望连接链表expect_hash中查找是否有匹配新建tuple的期望连接。第一次过来的数据包肯定是没有的,
于是走else分支,__nf_ct_try_assign_helper()函数去nf_ct_helper_hash哈希表中匹配当前tuple,
由于我们在本节开头提到nf_conntrack_tftp_init()已经把tftp的helper extension添加进去了,
所以可以匹配成功,于是把找到的helper赋值给nfct_help(ct)->helper,而这个helper的help方法就是tftp_help()。
当tftp请求包走到ipv4_confirm的时候,会去执行这个help方法,即tftp_help(),也就是建立一个期望连接
当后续tftp传输数据时,在nf_conntrack_in里面,新建tuple后,在expect_hash表中查可以匹配到新建tuple的期望连接(因为只根据源端口来匹配),
因此上面代码的if成立,所以ct->master被赋值为exp->master,并且,还会执行exp->expectfn()函数,这个函数上面提到是指向nf_nat_follow_master()的,
该函数根据ct的master来给ct做NAT,ct在经过这个函数处理前后的tuple分别为:
*/
/* 在helper 函数中 回生成expect 并加入全局链表 同时 expect_count++*/
if (net->ct.expect_count) {
/* 如果在期望连接链表中 */
spin_lock(&nf_conntrack_expect_lock);
exp = nf_ct_find_expectation(net, zone, tuple);
/* 如果在期望连接链表中 */
if (exp) {
pr_debug("expectation arrives ct=%p exp=%p\n",
ct, exp);
/* Welcome, Mr. Bond. We've been expecting you... */
__set_bit(IPS_EXPECTED_BIT, &ct->status);
/* conntrack的master位指向搜索到的expected,而expected的sibling位指向conntrack……..解释一下,这时候有两个conntrack,
一个是一开始的初始连接(比如69端口的那个)也就是主连接conntrack1,
一个是现在正在处理的连接(1002)子连接conntrack2,两者和expect的关系是:
1. expect的sibling指向conntrack2,而expectant指向conntrack1,
2. 一个主连接conntrack1可以有若干个expect(int expecting表示当前数量),这些
expect也用一个链表组织,conntrack1中的struct list_head sibling_list就是该
链表的头。
3. 一个子连接只有一个主连接,conntrack2的struct nf_conntrack_expect *master
指向expect
通过一个中间结构expect将主连接和子连接关联起来 */
/* exp->master safe, refcnt bumped in nf_ct_find_expectation */
ct->master = exp->master;
if (exp->helper) {/* helper的ext以及help链表分配空间 */
help = nf_ct_helper_ext_add(ct, exp->helper,
GFP_ATOMIC);
if (help)
rcu_assign_pointer(help->helper, exp->helper);
}
#ifdef CONFIG_NF_CONNTRACK_MARK
ct->mark = exp->master->mark;
#endif
#ifdef CONFIG_NF_CONNTRACK_SECMARK
ct->secmark = exp->master->secmark;
#endif
NF_CT_STAT_INC(net, expect_new);
}
spin_unlock(&nf_conntrack_expect_lock);
}
/*在全局的期望连接链表expect_hash中查找是否有匹配新建tuple的期望连接。
第一次过来的数据包肯定是没有的,于是走else分支,__nf_ct_try_assign_helper()
函数去nf_ct_helper_hash哈希表中匹配当前tuple,由于我们在本节开头提到nf_conntrack_tftp_init()
已经把tftp的helper extension添加进去了,所以可以匹配成功,于是把找到的helper赋值给nfct_help(ct)->helper,
而这个helper的help方法就是tftp_help() */
if (!exp) {// 如果不存在 从新赋值 ct->ext->...->help->helper = helper
__nf_ct_try_assign_helper(ct, tmpl, GFP_ATOMIC);
NF_CT_STAT_INC(net, new);
}
/* Now it is inserted into the unconfirmed list, bump refcount */
nf_conntrack_get(&ct->ct_general);
/* 将这个tuple添加到unconfirmed链表中,因为数据包还没有出去,
所以不知道是否会被丢弃,所以暂时先不添加到conntrack hash中 */
nf_ct_add_to_unconfirmed_list(ct);
local_bh_enable();
if (exp) {
if (exp->expectfn)
exp->expectfn(ct, exp);
nf_ct_expect_put(exp);
}
return &ct->tuplehash[IP_CT_DIR_ORIGINAL];
}
conntrack关联相关的helper处理
// conntrack关联相关的helper处理
int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl,
gfp_t flags)
{
struct nf_conntrack_helper *helper = NULL;
struct nf_conn_help *help;
struct net *net = nf_ct_net(ct);
int ret = 0;
/* We already got a helper explicitly attached. The function
* nf_conntrack_alter_reply - in case NAT is in use - asks for looking
* the helper up again. Since now the user is in full control of
* making consistent helper configurations, skip this automatic
* re-lookup, otherwise we'll lose the helper.
*/
if (test_bit(IPS_HELPER_BIT, &ct->status))
return 0;
if (tmpl != NULL) {
help = nfct_help(tmpl);
if (help != NULL) {
helper = help->helper;
set_bit(IPS_HELPER_BIT, &ct->status);
}
}
help = nfct_help(ct);
if (net->ct.sysctl_auto_assign_helper && helper == NULL) {
//从 nf_ct_helper_hash hash 中找到 helper(ftp tftp) 对比tuple
helper = __nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
if (unlikely(!net->ct.auto_assign_helper_warned && helper)) {
pr_info("nf_conntrack: automatic helper "
"assignment is deprecated and it will "
"be removed soon. Use the iptables CT target "
"to attach helpers instead.\n");
net->ct.auto_assign_helper_warned = true;
}
}
if (helper == NULL) {
if (help)
RCU_INIT_POINTER(help->helper, NULL);
goto out;
}
if (help == NULL) {
help = nf_ct_helper_ext_add(ct, helper, flags);
if (help == NULL) {
ret = -ENOMEM;
goto out;
}
} else {
/* We only allow helper re-assignment of the same sort since
* we cannot reallocate the helper extension area.
*/
struct nf_conntrack_helper *tmp = rcu_dereference(help->helper);
if (tmp && tmp->help != helper->help) {
RCU_INIT_POINTER(help->helper, NULL);
goto out;
}
}
tftp_help
rcu_assign_pointer(help->helper, helper);
out:
return ret;
}
View Code
在防火墙出口 NF_INET_POST_ROUTING 会执行ipv4_helper 处理help相关逻辑
{.hook = ipv4_helper,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_HELPER,},
连接状态设置为NEW
防火墙出口处:拦截报文后,根据报文携带的连接信息,找到连接。并执行连接带的helper函数。
/* Must be kept in sync with the classes defined by helpers */
#define NF_CT_MAX_EXPECT_CLASSES 4
/* nf_conn feature for connections that have a helper */
struct nf_conn_help {
/* Helper. if any */
struct nf_conntrack_helper __rcu *helper;//指向相应的Netfiler中注册的helper实例
struct hlist_head expectations; //如果有多个相关联的期望连接,链接起来
/* Current number of expected connections */
u8 expecting[NF_CT_MAX_EXPECT_CLASSES];
/* private helper information. */
char data[];
};
static unsigned int ipv4_helper(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
const struct nf_conn_help *help;
const struct nf_conntrack_helper *helper;
/* This is where we call the helper: as the packet goes out. */
ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
return NF_ACCEPT;
/* 获得ct->ext中的NF_CT_EXT_HELPER数据。一般都是没有的。ftp,tftp,pptp等这些协议的数据包就会有helper。 */
help = nfct_help(ct);
if (!help)
return NF_ACCEPT;
/* rcu_read_lock()ed by nf_hook_slow */
helper = rcu_dereference(help->helper);
if (!helper)
return NF_ACCEPT;
return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
ct, ctinfo);
}
Tftp的helper函数做如下处理
tftp_help 函数主要做了两件事情,首先初始化了一个nf_conntrack_expect结构,并将conntrack中应答方向的tuple结构附值给exp-〉tuple,
然后调用:nf_ct_expect_related(exp)
将这个expect结构与当前的连接状态关联起来,并把它注册到一个专门组织expect结构的全局链表nf_ct_expect_hash里。
expect结构有什么用呢?当有返回的数据包时,首先仍然是搜索hash表,如果找不到可以匹配的连接,还会在全局链表里搜索匹配的expect结构,然后找到相关的连接状态。
初始化结果:
<1>创建一个期望连接。初始化如下:
*(期望连接使用的 IP_CT_DIR_REPLY
static int tftp_help(struct sk_buff *skb,
unsigned int protoff,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo)
{
const struct tftphdr *tfh;
struct tftphdr _tftph;
struct nf_conntrack_expect *exp;
struct nf_conntrack_tuple *tuple;
unsigned int ret = NF_ACCEPT;
typeof(nf_nat_tftp_hook) nf_nat_tftp;
tfh = skb_header_pointer(skb, protoff + sizeof(struct udphdr),
sizeof(_tftph), &_tftph);
if (tfh == NULL)
return NF_ACCEPT;
switch (ntohs(tfh->opcode)) {
case TFTP_OPCODE_READ:
case TFTP_OPCODE_WRITE:
/*
在nf_ct_expect_cachep上分配一个expect连接,同时赋两个值:
exp->master = ct,
exp->use = 1。
*/
/* RRQ and WRQ works the same way */
nf_ct_dump_tuple(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
nf_ct_dump_tuple(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
exp = nf_ct_expect_alloc(ct);//exp->master = ct;
if (exp == NULL) {
nf_ct_helper_log(skb, ct, "cannot alloc expectation");
return NF_DROP;
}
/* 根据ct初始化expect */
tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
/*初始化expect exp->class=NF_CT_EXPECT_CLASS_DEFAULT
期望连接使用的 IP_CT_DIR_REPLY tuple信息是源IP,目的IP,目的端口号,L3协议类型,L4协议类型*/
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct),
&tuple->src.u3, &tuple->dst.u3,
IPPROTO_UDP, NULL, &tuple->dst.u.udp.port);
pr_debug("expect: ");
nf_ct_dump_tuple(&exp->tuple);
nf_nat_tftp = rcu_dereference(nf_nat_tftp_hook);
/* 数据包需要走NAT时,if成立,局域网传输则else成立。
如果ct做了NAT,就调用nf_nat_tftp指向的函数,这里它指向nf_nat_tftp.c中的help()函数。*/
if (nf_nat_tftp && ct->status & IPS_NAT_MASK)
ret = nf_nat_tftp(skb, ctinfo, exp);//如果ct做了NAT,就调用nf_nat_tftp指向的函数,这里它指向nf_nat_tftp.c中的help()函数
else if (nf_ct_expect_related(exp) != 0) {//调用nf_ct_expect_insert 插入 nf_ct_expect_hash 同时ct.expect_count++ 所以后面 conntrack_in的时候根据expect_cout直接调用expect处理
nf_ct_helper_log(skb, ct, "cannot add expectation");
ret = NF_DROP;
}
nf_ct_expect_put(exp);
break;
case TFTP_OPCODE_DATA:
case TFTP_OPCODE_ACK:
pr_debug("Data/ACK opcode\n");
break;
case TFTP_OPCODE_ERROR:
pr_debug("Error opcode\n");
break;
default:
pr_debug("Unknown opcode\n");
}
return ret;
}
ct和expect related关联逻辑:
nf_ct_expect_insert 插入 nf_ct_expect_hash 同时ct.expect_count++ 所以后面 conntrack_in的时候根据expect_cout直接调用expect处理
static int nf_ct_expect_insert(struct nf_conntrack_expect *exp)
{
/* 获得exp->master的help */
struct nf_conn_help *master_help = nfct_help(exp->master);
struct nf_conntrack_helper *helper;
struct net *net = nf_ct_exp_net(exp);
unsigned int h = nf_ct_expect_dst_hash(net, &exp->tuple);
/* two references : one for hash insert, one for the timer */
atomic_add(2, &exp->use);
/* 插入到help->expectations链表 */
hlist_add_head(&exp->lnode, &master_help->expectations);////如果有多个相关联的期望连接,链接起来
master_help->expecting[exp->class]++;
/* 插入到全局的expect_hash表 */
hlist_add_head_rcu(&exp->hnode, &nf_ct_expect_hash[h]);
net->ct.expect_count++;
/* 设置并启动定时器 */
setup_timer(&exp->timeout, nf_ct_expectation_timed_out,
(unsigned long)exp);
helper = rcu_dereference_protected(master_help->helper,
lockdep_is_held(&nf_conntrack_expect_lock));
if (helper) {
exp->timeout.expires = jiffies +
helper->expect_policy[exp->class].timeout * HZ;
}
add_timer(&exp->timeout);
NF_CT_STAT_INC(net, expect_create);
return 0;
}
View Code
从help函数返回后,连接跟踪的第一阶段就结束了。
PS:所以后面 conntrack_in--->init_conntrack() 的时候根据expect_cout直接调用exp处理
//init_conntrack 函数中根据 expect_count 来决定逻辑分支
/* 在helper 函数中 回生成expect 并加入全局链表 同时 expect_count++*/
if (net->ct.expect_count) {
/* 如果在期望连接链表中 */
spin_lock(&nf_conntrack_expect_lock);
exp = nf_ct_find_expectation(net, zone, tuple);
/* 如果在期望连接链表中 */
if (exp) {
pr_debug("expectation arrives ct=%p exp=%p\n",
ct, exp);
/* Welcome, Mr. Bond. We've been expecting you... */
__set_bit(IPS_EXPECTED_BIT, &ct->status);
/* exp->master safe, refcnt bumped in nf_ct_find_expectation */
ct->master = exp->master;
if (exp->helper) {/* helper的ext以及help链表分配空间 */
help = nf_ct_helper_ext_add(ct, exp->helper,
GFP_ATOMIC);
if (help)
rcu_assign_pointer(help->helper, exp->helper);
}
-----------------
NF_CT_STAT_INC(net, expect_new);
}
spin_unlock(&nf_conntrack_expect_lock);
}
对于 ipv4_confirm 逻辑就是:
把该连接的正向连接A从unconfirmed链表上摘下来,把该连接的正反向连接A和B加入到连接hash表中。并把该连接确认状态置为confirmed状态。
SERVER--->PC
SERVER回应Client的请求,报文元组信息如下
Dip 1.1.1.6 Sip.1.1.1.5 Dport:1116 Sport:1115 Protonum:udp L3num:Inet
防火墙入口处NF_INET_PRE_ROUTING
conntrack模块截获报文。在连接表中查找已建立的连接,没有找到,新建一个连接。现在整个Netfiler中建立的连接如下:
总共有A,B,C,D,E五个方向上的连接。
在新建连接的过程中,会根据连接的正方向连接D的dport,dip,protonum,L3num为hash key来在expect_hash表中查找,找到对应的期望连接C。置连接status的IPS_EXPECTED_BIT位。
设置连接的状态为RELATED。分析init_conntrack 中代码可知:
/* 在helper 函数中 回生成expect 并加入全局链表 同时 expect_count++*/
if (net->ct.expect_count) {
/* 如果在期望连接链表中 */
spin_lock(&nf_conntrack_expect_lock);
exp = nf_ct_find_expectation(net, zone, tuple);
/* 如果在期望连接链表中 */
if (exp) {
pr_debug("expectation arrives ct=%p exp=%p\n",
ct, exp);
/* Welcome, Mr. Bond. We've been expecting you... */
__set_bit(IPS_EXPECTED_BIT, &ct->status);
/* conntrack的master位指向搜索到的expected,而expected的sibling位指向conntrack……..解释一下,这时候有两个conntrack,
一个是一开始的初始连接(比如69端口的那个)也就是主连接conntrack1,
一个是现在正在处理的连接(1002)子连接conntrack2,两者和expect的关系是:
1. expect的sibling指向conntrack2,而expectant指向conntrack1,
2. 一个主连接conntrack1可以有若干个expect(int expecting表示当前数量),这些
expect也用一个链表组织,conntrack1中的struct list_head sibling_list就是该
链表的头。
3. 一个子连接只有一个主连接,conntrack2的struct ip_conntrack_expect *master
指向expect
通过一个中间结构expect将主连接和子连接关联起来 */
/* exp->master safe, refcnt bumped in nf_ct_find_expectation */
ct->master = exp->master;
if (exp->helper) {/* helper的ext以及help链表分配空间 貌似 tftp 中的exp->helper =NULL*/
help = nf_ct_helper_ext_add(ct, exp->helper,
GFP_ATOMIC);
if (help)
rcu_assign_pointer(help->helper, exp->helper);
}
NF_CT_STAT_INC(net, expect_new);
}
spin_unlock(&nf_conntrack_expect_lock);
}
IPS_EXPECTED_BIT,设置连接的状态为RELATED;
resolve_normal_ct(NULL)
{
----------------------------------
h = init_conntrack(net, tmpl, &tuple, l3proto, l4proto, skb, dataoff, hash);
ct = nf_ct_tuplehash_to_ctrack(h);/* Once we've had two way comms, always ESTABLISHED. */
if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
pr_debug("normal packet for %p\n", ct);
*ctinfo = IP_CT_ESTABLISHED;
} else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
pr_debug("related packet for %p\n", ct);
*ctinfo = IP_CT_RELATED;
} else {
pr_debug("new packet for %p\n", ct);
*ctinfo = IP_CT_NEW; //init ip_ct_new
}
*set_reply = 0;
/*在struct nf_conn结构体中,ct_general是其第一个成员,所以它的地址和整个结构体的地址相同,
所以skb->nfct的值实际上就是skb对应的conntrack条目的地址,因此通过(struct nf_conn *)skb->nfct就可以通过skb得到它的conntrack条目*/
skb->nfct = &ct->ct_general;
skb->nfctinfo = *ctinfo;
return ct;
}
其逻辑如下:
A(10.10.10.1:1001) ――> B(10.10.10.2:69)
从A的1001端口发往B的69端口,连接跟踪模块跟踪并记录了次条连接,保存在一个nf_conntrack结构里(用tuple来识别),但是我们知道,一个连接有两个方向,我们怎么确定两个方向相反的数据包是属于同一个连接的呢?最简单的判断方法就是将地址和端口号倒过来,就是说如果有下面的数据包:
B(10.10.10.2:69)――> A(10.10.10.1:1001)
虽然源/目的端口/地址全都不一样,不能匹配初始数据包的tuple,但是与对应的reply_tuple完全匹配,所以显然应该是同一个连接,所以见到这样的数据包时就可以直接确定其所属的连接了,
当然不需要什么expect然而不是所有协议都这么简单.
对于tftp协议,相应的数据包可能是
B(10.10.10.2:1002)――> A(10.10.10.1:1001)
在hash表里找不到对应的节点,但我们仍然认为它和前面第一条消息有密切的联系,
甚至我们可以明确,将所有下面形式的数据包都归属于这一连接的相关连接
B(10.10.10.2:XXX)――> A(10.10.10.1:1001)
怎么实现这一想法呢,只好再多创建一个expect了,它的tuple结构和repl_tuple完全相同,只是在mask中将源端口位置0,就是说不比较这一项,只要其他项匹配就OK。
以上就是helper和expect的作用了,但是具体的实现方法还跟协议有关。ftp 比较复杂
防火墙出口post_routing处
在post_routing位置:
- 首先执行 ipv4_helper,由于此时ct没有期望连接,所以跳过helper有关的代码;
static unsigned int ipv4_helper(void *priv,struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
const struct nf_conn_help *help;
const struct nf_conntrack_helper *helper;
/* This is where we call the helper: as the packet goes out. */
ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
return NF_ACCEPT;
/* 获得ct->ext中的NF_CT_EXT_HELPER数据。一般都是没有的。ftp,tftp,pptp等这些协议的数据包就会有helper。 */
help = nfct_help(ct);
if (!help)
return NF_ACCEPT;
-
---------------------------------------
}
- 在执行 nf_conntrack_confirm确认连接,至此,期望连接的建立和关联完毕。
以后SERVER来的每个报文在连接表中查找到已建立的连接,发现该连接的status中置了IPS_EXPECTED_BIT位,就指定该连接是期望连接,设置报文的连接状态为RELATED。
有个问题: tftp放在内核里面有啥好处啊? 屁事没做啊??
参考:http://m.blog.chinaunix.net/uid/9829088.html
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子