conntrack机制存储路由,省去每包路由查找

IP是无连接的,因此IP路由是每包一路由的,数据包通过查找路由表获取路由,这是现代操作协议协议栈IP路由的默认处理方式。但是如果协议栈具有流识别能力,是不是可以基于流来路由呢?答案无疑是肯定的。

设计思想

在Linux的实现中,nf_conntrack可以做到基于流的IP路由,大致思想就是,仅仅针对一个流的第一个正向包和第一个反向包查找标准的IP路由表,将结果保存在conntrack项中,后续的属于同一流的数据包直接取出路由项来使用。背后的思想是:这可以省去查找路由表的开销,是这样吗?也不全是!关键是,将一个数据包对应到一个数据流,这本身就需要一个查找匹配的过程,如果能将路由保存在conntrack里面,那么conntrack查找和路由查找就可以合并成一次查找。因此,查找是免不了的,只是换了地方而已,如果有了conntrack,仍然进行标准的基于包的IP路由查找过程,那就是平白多了一次查找。

实现思想

在实现上,很简单,那就是尽量在数据包离开协议栈的地方设置skb的路由到conntrack。之所以可以这么做是因为不管是POSTROUTING还是INPUT,都是在路由之后,如果前面进行了基于包的IP路由查找,此时skb上一定绑定了dst_entry,将其绑到conntrack里面即可。另外,在数据包刚进入协议栈的地方试图从conntrack项中取出路由,然后直接将其设置到skb上。整个处理过程类似skb-mark和conntrack mark的处理方式:
-A PREROUTING -m mark --mark 100 -j ACCEPT
-A PREROUTING -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
-A PREROUTING -m mark ! --mark 0x0 -j ACCEPT
...... 慢速匹配过程
-A PREROUTING ..... -j MARK --set-mark 100
.....慢速匹配过程
-A POSTROUTING -m mark ! --mark 0x0 -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff

有了以上的理解,代码就很简单了

#include <linux/ip.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/dst.h>
#include <net/netfilter/nf_conntrack_acct.h>


MODULE_AUTHOR("xtt");
MODULE_DESCRIPTION("gll");
MODULE_LICENSE("GPL");
MODULE_ALIAS("XTT and GLL");

struct nf_conn_priv {
        struct nf_conn_counter ncc[IP_CT_DIR_MAX];
        struct dst_entry *dst[IP_CT_DIR_MAX];
};

static unsigned int ipv4_conntrack_getdst (unsigned int hooknum,
                                      struct sk_buff *skb,
                                      const struct net_device *in,
                                      const struct net_device *out,
                                      int (*okfn)(struct sk_buff *))
{
        struct nf_conn *ct;
        enum ip_conntrack_info ctinfo;
        struct nf_conn_counter *acct;
        struct nf_conn_priv *dst_info;
        ct = nf_ct_get(skb, &ctinfo);
        if (!ct || ct == &nf_conntrack_untracked)
                return NF_ACCEPT;
        acct = nf_conn_acct_find(ct);
        if (acct) {
                int dir = CTINFO2DIR(ctinfo);
                dst_info = (struct nf_conn_priv *)acct;
                if (dst_info->dst[dir] == NULL) {
                        dst_hold(skb_dst(skb));
                        dst_info->dst[dir] = skb_dst(skb);
                }
        }
        return NF_ACCEPT;
}

static unsigned int ipv4_conntrack_setdst (unsigned int hooknum,
                                      struct sk_buff *skb,
                                      const struct net_device *in,
                                      const struct net_device *out,
                                      int (*okfn)(struct sk_buff *))
{
        struct nf_conn *ct;
        enum ip_conntrack_info ctinfo;
        struct nf_conn_counter *acct;
        struct nf_conn_priv *dst_info;
        ct = nf_ct_get(skb, &ctinfo);
        if (!ct || ct == &nf_conntrack_untracked)
                return NF_ACCEPT;
        acct = nf_conn_acct_find(ct);
        if (acct) {
                int dir = CTINFO2DIR(ctinfo);
                dst_info = (struct nf_conn_priv *)acct;
                if (dst_info->dst[dir] != NULL) {
                      // 如果在此设置了skb的dst,那么在ip_rcv_finish中就不会再去查找路由表了
                        skb_dst_set(skb, dst_info->dst[dir]);
                }
        }
        return NF_ACCEPT;
}
static struct nf_hook_ops ipv4_conn_dst_info[] __read_mostly = {
        {
                .hook          = ipv4_conntrack_getdst,
                .owner          = THIS_MODULE,
                .pf            = NFPROTO_IPV4,
                .hooknum        = NF_INET_POST_ROUTING,
                .priority      = NF_IP_PRI_CONNTRACK + 1,
        },
        {
                .hook          = ipv4_conntrack_getdst,
                .owner          = THIS_MODULE,
                .pf            = NFPROTO_IPV4,
                .hooknum        = NF_INET_LOCAL_IN,
                .priority      = NF_IP_PRI_CONNTRACK + 1,
        },
        {
                .hook          = ipv4_conntrack_setdst,
                .owner          = THIS_MODULE,
                .pf            = NFPROTO_IPV4,
                .hooknum        = NF_INET_PRE_ROUTING,
                .priority      = NF_IP_PRI_CONNTRACK + 1,
        },
};

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/b60d82360034a1bbf5a91524ceec49bc.html