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,
},
};
static int __init test_info_init(void)
{
int err;
err = nf_register_hooks(ipv4_conn_dst_info, ARRAY_SIZE(ipv4_conn_dst_info));
if (err) {
return err;
}
return err;
}
static void __exit test_info_exit(void)
{
nf_unregister_hooks(ipv4_conn_dst_info, ARRAY_SIZE(ipv4_conn_dst_info));
}
module_init(test_info_init);
module_exit(test_info_exit);
在以上的實現思想的文字描述中,我使用了盡量和試圖兩個不那麼明確的詞,這就牽扯到了流路由的老化機制。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-04/100910p2.htm