有了以上的理解,代码就非常easy了
#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);
skb_dst_set(skb, dst_info->dst[dir]);而这个非常easy,使用内核的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,仅仅有是相关的路由改变了,才更新,何其灵活。