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. 【Android】以BaseAdapter做适配器的ListView及其性能优化

    适配器的Java类 package com.app.adapter; import org.json.JSONArray; import org.json.JSONObject; import and ...

  2. HDU 4267-A Simple Problem with Integers(多个BIT)

    题意: 2种操作 1 a b k c 在区间[a,b]中的(i-a)%k==0的位置i上的数+c 2 a 查询位置a的值 输出每次查询的值 分析: 开始想到多维的线段树,但比较麻烦,看了题解才知道,用 ...

  3. Mobile testing基础之签名

    1. 什么是数字签名? 数字签名就是为你的程序打上一种标记,来作为你自己的标识,当别人看到签名的时候会知道它是与你相关的 2. 为什么要数字签名? 最简单直接的回答: 系统要求的. Android系统 ...

  4. DataTable转List<Model>通用类【实体转换辅助类】

    /// <summary> /// DataTable转List<Model>通用类[实体转换辅助类] /// </summary> public class Mo ...

  5. http和数据库sql分析与窃听技术

    用tunnel,tunnel是一种技术称谓,将其放到真正的服务器和客户端之间.调试阶段可以使用webcream运行tomcat作为模拟的真正的服务器. 具体:用apache axis及其项目中的工具t ...

  6. Servlet学习笔记(1)--第一个servlet&&三种状态对象(cookie,session,application)&&Servlet的生命周期

    servlet的404错误困扰了两天,各种方法都试过了,翻书逛论坛终于把问题解决了,写此博客来纪念自己的第一个servlet经历. 下面我会将自己的编写第一个servlet的详细过程提供给初学者,大神 ...

  7. MongoDB Windows 下安装部署

    下面主要是我在Windows上(Win7)安装.运行.安装Windows服务的笔记,以作备忘. 1.下载 下载地址:http://www.mongodb.org/downloads 从其下载页面就可以 ...

  8. uva 10274 Fans and Gems(隐式图搜索+模拟)

    Fans and Gems Input: Standard Input Output: Standard Output Tomy's fond of a game called 'Fans and G ...

  9. Eclipse查找类路径快捷方式

    直接ctrl+shift+t查找这个类,下面会显示类的路径,包括jar名

  10. 【Java】IO流简单分辨

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827509.html Java的IO流体系十分庞大,并且体系层次稍复杂,很容易记混或记错.在此,我把平时经常用 ...