有了以上的理解,代码就很简单了
#include
#include
#include
#include
#include
#include
#include
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);
skb_dst_set(skb, dst_info->dst[dir]);
而这个非常容易,使用内核的Notifier机制就可以了,在任何路由改变的时候,通知上述的流路由模块改变一个标志位,在PREROUTING的hook中,发现该标志位置位,就不执行skb_dst_set。如此一来,上述的代码就会变为下面的:
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 *))
{
...
if (acct) {
int dir = CTINFO2DIR(ctinfo);
dst_info = (struct nf_conn_priv *)acct;
// 无条件设置流的路由。skb的dst可能来自两个地方:
// 1.来自ipv4_conntrack_setdst;
// 2.来自标准的IP路由查找
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 *))
{
...
if (acct) {
int dir = CTINFO2DIR(ctinfo);
dst_info = (struct nf_conn_priv *)acct;
// 只有标志为1,才信任流路由,并且设置给skb
if (flag == 1) {
skb_dst_set(skb, dst_info->dst[dir]);
}
}
return NF_ACCEPT;
}
然而,把这件事交给用户态或许更好些。毕竟内核态发生的所有事情,用户态都有办法监控到,我认为用一个procfs的可写文件来通知flag变为1或者变为0可能更好,即flag的值由用户来设置,这样用户就可以在任意时刻启用,停用流路由机制,比如使用iproute2的monitor机制监控到了路由的改变,如果是无关路由改变了,那么就不更新flag,只有是相关的路由改变了,才更新,何其灵活。