利用nf_conntrack机制存储路由,省去每包路由查找
IP是无连接的,因此IP路由是每包一路由的,数据包通过查找路由表获取路由,这是现代操作协议协议栈IP路由的默认处理方式。但是如果协议栈具有流识别能力,是不是可以基于流来路由呢?答案无疑是肯定的。
设计思想
在Linux的实现中,nf_conntrack可以做到基于流的IP路由,大致思想就是,仅仅针对一个流的第一个正向包和第一个反向包查找标准的IP路由表,将结果保存在conntrack项中,后续的属于同一流的数据包直接取出路由项来使用。背后的思想是:这可以省去查找路由表的开销,是这样吗?也不全是!关键是,将一个数据包对应到一个数据流,这本身就需要一个查找匹配的过程,如果能将路由保存在conntrack里面,那么conntrack查找和路由查找就可以合并成一次查找。因此,查找是免不了的,只是换了地方而已,如果有了conntrack,仍然进行标准的基于包的IP路由查找过程,那就是平白多了一次查找。
实现思想
在实现上,很简单,那就是 尽量在数据包离开协议栈的地方设置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
有了以上的理解,代码就很简单了
#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_release, 需要到注册ACCT 调用destroy方法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]);
而这个非常容易,使用内核的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,才信任流路由,并且设置给skbif (flag == 1) {skb_dst_set(skb, dst_info->dst[dir]);}}return NF_ACCEPT;
}
然而,把这件事交给用户态或许更好些。毕竟内核态发生的所有事情,用户态都有办法监控到,我认为用一个procfs的可写文件来通知flag变为1或者变为0可能更好,即flag的值由用户来设置,这样用户就可以在任意时刻启用,停用流路由机制,比如使用iproute2的monitor机制监控到了路由的改变,如果是无关路由改变了,那么就不更新flag,只有是相关的路由改变了,才更新,何其灵活。
利用nf_conntrack机制存储路由,省去每包路由查找相关推荐
- 图十字链表并求度c语言,利用十字链表存储树结构(便于同时求出某一点的入度与出度)------C语言实现...
#include #include #include /* 利用十字链表存储有向图,可用于同时查找某个顶点的出度与入度: */ typedef struct edge{//顶点表 int headve ...
- c语言建立并存储树,利用十字链表存储树结构(便于同时求出某一点的入度与出度)------C语言实现...
#include #include #include /* 利用十字链表存储有向图,可用于同时查找某个顶点的出度与入度: */ typedef struct edge {//顶点表 int headv ...
- iOS开发:沙盒机制以及利用沙盒存储字符串、数组、字典等数据
iOS开发:沙盒机制以及利用沙盒存储字符串.数组.字典等数据 1.初识沙盒:(1).存储在内存中的数据,程序关闭,内存释放,数据就会丢失,这种数据是临时的.要想数据永久保存,将数据保存成文件,存储到程 ...
- C++利用线性探查实现存储机制hash table的算法(附完整源码)
C++利用线性探查实现存储机制的算法 C++利用线性探查实现存储机制的算法完整源码(定义,实现,main函数测试) C++利用线性探查实现存储机制的算法完整源码(定义,实现,main函数测试) #in ...
- NBT:利用细胞甘油三酯存储提高链霉菌中聚酮类化合物的效价
利用细胞内甘油三酯存储提高链霉菌中聚酮类化合物的效价 靶向代谢流分析(Glycosis, TCAcycle, beta-oxidation)由中科脂典平台完成 原文链接: https://www.na ...
- 利用单臂路由实现VLAN间路由
实验名称: 利用单臂路由实现VLAN间路由. 实验目的: 掌握如何在路由器端口上划分子接口.封装Dot1q (IEEE 802.1q)协议,实现VLAN间的路由. 背景描述: 假设某企业有两个主要部门 ...
- java判断对象无数据_java利用反射机制判断对象的属性是否为空以及获取和设置该属性的值...
1.java利用反射机制判断对象的属性是否为空: Map validateMap = new LinkedHashMap(); validateMap.put("serial", ...
- 利用反射机制创建新类的两种方式及比较
[0]README 0.1) 本文描述+源代码均 转自 http://blog.csdn.net/fenglibing/article/details/4531033 , 旨在深入理解 如何利用反射机 ...
- 利用链式存储结构实现线性表
本图文主要介绍了如何利用链式存储结构实现线性表.
最新文章
- php.ini 配快捷方式,求高手解答!界面保存的网页快捷方式图标默认恢复.
- Erdaicms旅游网站系统微信和手机端分销系统正式上线发布啦
- spring源码学习之路---深入AOP(终)
- DevExpress的TextEdit、RadioGroup、ColorPickEdit设置默认值
- VTK:PolyData之TriangleArea
- 只允许运行一个实例的方法
- 用JUnit框架实现Java单元测试
- MySQL基础部分总结
- 张左峰的歪理邪说 之 大话Redmine插件
- 关于Viual Studio 改变编辑器背景背景及背景图片(转)
- mysql 清空或删除表数据后,控制表自增列值的方法
- Flask源码解析:从第一个版本开始阅读Flask源码
- [转载] Python基础:什么是字符串?字符串是用来做什么的?
- [linux]记录内核编译日志
- 树莓派 Linux 操作系统大全
- 林达华推荐有关机器学习的数学书籍
- 前沿关注 | 5G和边缘计算将如何改变AR和VR?
- python爬虫实践之爬取hao123音乐音乐导航
- 通过宏快速破解EXCEL文件密码
- (2019.2) Anki2.1 自建服务器临时解决方案及一些想法