利用nf_conntrack机制存储路由,省去每包路由查找
设计思想
在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机制存储路由,省去每包路由查找的更多相关文章
- 利用十字链表存储树结构(便于同时求出某一点的入度与出度)------C语言实现
#include <stdio.h> #include<conio.h> #include<stdlib.h> /* 利用十字链表存储有向图,可用于同时查找某个顶点 ...
- 利用LOCK机制来定位前缀劫持者
一.文章信息 作者:Tongqing Qiu, Lusheng Ji, Dan Pei等 单位:佐治亚理工学院.美国电话电报公司实验室.康奈尔大学等 来源:Conference on Usenix S ...
- Redis 利用锁机制来防止缓存过期产生的惊群现象-转载自 http://my.oschina.net/u/1156660/blog/360552
首先,所谓的缓存过期引起的“惊群”现象是指,在大并发情况下,我们通常会用缓存来给数据库分压,但是会有这么一种情况发生,那就是在一定时间 内生成大量的缓存,然后当缓存到期之后又有大量的缓存失效,导致后端 ...
- Android利用反射机制为实体类属性赋值
在做android项目时,有时会遇到从网络上获取json类型数据,赋值给实体类,实体类属性少可以一个一个的赋值,如果实体类有很多属性,赋值可能就要耗很长的功夫了,幸好Java给我们提供了反射机制.下面 ...
- java利用反射机制判断对象的属性是否为空以及获取和设置该属性的值
1.java利用反射机制判断对象的属性是否为空: Map<String,String> validateMap = new LinkedHashMap<String, String& ...
- 利用IOzone进行存储性能测试
利用IOzone进行存储性能测试 命令:1.iozone -s 10G -r 4k -i 0(0代表顺序写) -w(代表文件不删除) -+n(不测重读重写) -Rb(以某种格式生成测试文件) /t ...
- Linux数据包路由原理、Iptables/netfilter入门学习
相关学习资料 https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html http://zh.wik ...
- 利用NSUserdefaults来存储自定义的NSObject类及自定义类数组
利用NSUserdefaults来存储自定义的NSObject类及自定义类数组 1.利用NSUserdefaults来存储自定义的NSObject类 利用NSUserdefaults也可以来存储及获取 ...
- android 利用反射机制获取drawable中所有的图片资源
public List<Map<String,Object>> getGridData() { list=new ArrayList<Map<String,Obje ...
随机推荐
- SQL 的一些概念问答
1.触发器的作用? 答:触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的.它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化.可以联级运算.如, ...
- JavaScript UI技术选型
ExtJS l ExtJS(TODO:找旧版本,类似现在EasyUI插件的旧版本)简介:纯JS支持:IE6授权:GPLv3授权.商业授权($329/人) l Ext.NET简介:ExtJS的NET封装 ...
- VS中使用QT调用R脚本
一开始想直接把R编译成库然后调用R,后来查了n多资料,发现VS中是无法办到的,官方也给出了一句话,大概意思就是没可能在VS中使用R提供的C++接口,大概是涉及到了底层的ABI的原因,具体也不太清楚. ...
- list、tuple、dict、set、map
list Python内置的一种数据类型是列表. list是一种有序的集合,可以随时添加和删除其中的元素. # 创建list classmate = ['micheal', 'Bob', 'Tracy ...
- 设计模式系列 1——StaticFactory(静态工厂),AbstractFactory(抽象工厂)
本文出自 代码大湿 代码大湿 本系列持续更新,敬请关注. 1 静态工厂 静态工厂介绍: 静态工厂模式可以实现接口封装隔离的原则.在客户端只知接口而不知实现的时候可以使用静态工厂模式. 源码请点击我 角 ...
- 第三百零七天 how can I 坚持
快放假了,上班也没啥事,感觉也挺累的.明天基本都走了,收拾收拾,准备明天出发.电脑就不带了. 和她聊的还可以,小样,还想当老师,别离开济南就行,我的未来在哪里啊. 晚上炒了白菜,下了乌冬面,明天上午晚 ...
- Java设计模式---工厂方法模式(Factory-Method)
一.普通工厂模式 建立一个工厂类,对实现了同一接口的一些类进行实例的创建 实例代码: 发送短信和邮件的例子,首先创建接口: public interface Sender { public void ...
- 问题-FireDAC连接Sqlite3提示“unable to open database file”
相关资料:http://www.dfwlt.com/forum.php?mod=viewthread&tid=1497&extra= 问题现象:FireDAC连接Sqlite3在开发电 ...
- DB2 递归查询
上一篇中讲解了ORACLE中的递归查询,下面我们看一下DB2中如何使用递归查询: 同样的我们先新建一个表来存储以上信息,并插入测试数据: --建表 create table FAMILY ( pers ...
- HDU 5724 Chess (sg函数)
Chess 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5724 Description Alice and Bob are playing a s ...