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

设计思想

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

实现思想

在实现上,非常easy,那就是尽量在数据包离开协议栈的地方设置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 

有了以上的理解,代码就非常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进行路由查找了,取而代之的是直接从conntrack取出路由设置给skb,这个conntrack上的路由就是第一次的时候针对skb查找路由表的结果。那么就会引入一个问题,即什么时候再次针对skb查找路由表以便更新conntrack的路由。这个问题没法直接回答,对于路由一直稳定的网络,根本不须要又一次查找,由于针对一个流的第一个正向包和第一个反向包的路由查找结果在该流的生命周期中将一直有效,毕竟路由没有改变,可是假设在流的生命周期内一条相关的路由发生了改变,就须要又一次更新conntrack的路由结果。
       因此能够说,引入一个通知机制就能解决问题。每当路由发生改变的时候,在PREROUTING的hook中,不再运行:

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,仅仅有是相关的路由改变了,才更新,何其灵活。

利用nf_conntrack机制存储路由,省去每包路由查找的更多相关文章

  1. 利用十字链表存储树结构(便于同时求出某一点的入度与出度)------C语言实现

    #include <stdio.h> #include<conio.h> #include<stdlib.h> /* 利用十字链表存储有向图,可用于同时查找某个顶点 ...

  2. 利用LOCK机制来定位前缀劫持者

    一.文章信息 作者:Tongqing Qiu, Lusheng Ji, Dan Pei等 单位:佐治亚理工学院.美国电话电报公司实验室.康奈尔大学等 来源:Conference on Usenix S ...

  3. Redis 利用锁机制来防止缓存过期产生的惊群现象-转载自 http://my.oschina.net/u/1156660/blog/360552

    首先,所谓的缓存过期引起的“惊群”现象是指,在大并发情况下,我们通常会用缓存来给数据库分压,但是会有这么一种情况发生,那就是在一定时间 内生成大量的缓存,然后当缓存到期之后又有大量的缓存失效,导致后端 ...

  4. Android利用反射机制为实体类属性赋值

    在做android项目时,有时会遇到从网络上获取json类型数据,赋值给实体类,实体类属性少可以一个一个的赋值,如果实体类有很多属性,赋值可能就要耗很长的功夫了,幸好Java给我们提供了反射机制.下面 ...

  5. java利用反射机制判断对象的属性是否为空以及获取和设置该属性的值

    1.java利用反射机制判断对象的属性是否为空: Map<String,String> validateMap = new LinkedHashMap<String, String& ...

  6. 利用IOzone进行存储性能测试

    利用IOzone进行存储性能测试   命令:1.iozone -s 10G -r 4k -i 0(0代表顺序写) -w(代表文件不删除) -+n(不测重读重写) -Rb(以某种格式生成测试文件) /t ...

  7. Linux数据包路由原理、Iptables/netfilter入门学习

    相关学习资料 https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html http://zh.wik ...

  8. 利用NSUserdefaults来存储自定义的NSObject类及自定义类数组

    利用NSUserdefaults来存储自定义的NSObject类及自定义类数组 1.利用NSUserdefaults来存储自定义的NSObject类 利用NSUserdefaults也可以来存储及获取 ...

  9. android 利用反射机制获取drawable中所有的图片资源

    public List<Map<String,Object>> getGridData() { list=new ArrayList<Map<String,Obje ...

随机推荐

  1. replace() MySQL批量替换指定字段字符串

    mysql replace实例说明: UPDATE tb1 SET f1=REPLACE(f1, 'abc', 'def'); REPLACE(str,from_str,to_str) 在字符串 st ...

  2. Devexpress GridControl中combobox级联显示 z

    http://minmin86121.blog.163.com/blog/static/4968115720143163533356/ 在 使用GridControl时,可能会有需求要求某2列显示co ...

  3. DevExpress控件使用小结 z

    .TextEditor(barEditItem)取文本 string editValue = barEditItem1.EditValue.ToString(); //错误,返回null string ...

  4. Ye.云狐J2刷机笔记 | 完美切换内部存储卡和SD卡的改法.vold.fstab

    ================================================================================Ye.完美切换内部存储卡和SD卡成功.v ...

  5. 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇02:滚屏》

    2.滚屏 滚屏概述: 打飞机游戏场景背景设计通常很简单,因为角色敌人道具等都不与背景发生交互事件.开发者只需要根据设定的游戏类型,为游戏制作背景,模拟一个大环境即可. 滚屏原理: 材质UV动画,实现背 ...

  6. 【转】MySql数据库--mysql_real_escape_string()函数

    MySql数据库--mysql_real_escape_string()函数 unsigned long mysql_real_escape_string(MYSQL *mysql, char *to ...

  7. android sdk manager 闪退 打不开问题

    android sdk manager 闪退 打不开问题 环境 win8系统 如果访问不了  dl-ssl.google.com 网址,在C:\Windows\System32\Drivers\etc ...

  8. Context Menu on DataGrid

    应该设置  fitColumns: true 合并表头显示有问题 代码见示例

  9. 第二百八十八天 how can I坚持

    明天,就要回济南了.早上八点的票,去火车站还得一个半小时,六点就得起床啊,好早,忍了. 这两天又没法更新日志了.周一才能回来. 今天好忙,事情好杂. 其实不想请假. 算了,睡觉了,没什么可写的了.是没 ...

  10. 深入探究frame和bounds的区别以及setbounds使用

    [转自]http://blog.csdn.net/hherima/article/details/39501857 在iOS开发中经常遇到两个词Frame和bounds,本文主要阐述Frame和bou ...