期望连接的建立过程 nat helper tftp

内核首先注册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  创建一个 tuple以及对应struct nf_conn  *ct 并赋值给skb

/* 根据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 --->init_conntrack逻辑可知:

  逻辑中有在全局的期望连接链表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];
}

 nf_ct_try_assign_helper  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 tuple信息是源IP,目的IP,目的端口号,L3协议类型,L4协议类型)

 

 

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);
    }

 后续执行 init_conntrack  中resolve_normal_ct时 其状态为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)


  并不完全颠倒,就是说不能直接匹配初始数据包的tuple的反防向的reply_tuple,在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

posted @ 2023-03-09 14:36  codestacklinuxer  阅读(116)  评论(0编辑  收藏  举报