博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编写一个Linux虚拟网卡来实现类NVI
阅读量:5926 次
发布时间:2019-06-19

本文共 2903 字,大约阅读时间需要 9 分钟。

我们可以在Linux上使用loopback接口来模拟两个阶段的路由抉择,第一个阶段是走一遍PRE/POST ROUTING流程,将NAT实施完毕,第二阶段完成单纯路由转发。然而需要在Netfilter上挂钩子,以便取消关联在skb上的路由项,并且取消关联在skb上的conntrack信息,因为在第二阶段的单纯路由流程里面,我不希望再有什么基于conntrack的动作,因此如果需要有基于conntrack的操作,务必在第一阶段内和NAT一并完成。

回过头来看loopback的实现,不是那么完美,因为像在Netfilter上挂载钩子完成的这种事完全可以在虚拟网卡的xmit操作中完成,因此有必要重新写一个虚拟网卡,之所以最终还是考虑重新写,是因为这个模块超级简单,基本可以照搬loopback.c的实现,所不同的是xmit的操作:

static netdev_tx_t nvi_xmit(struct sk_buff *skb,                                 struct net_device *dev){    int len;    //注意,我把原始的数据包入接口写在了skb的mark中了,为何能这么做呢?因为...    struct net_device * real_dev = dev_get_by_index(dev_net(dev), skb->mark);    skb_orphan(skb);    skb->protocol = eth_type_trans(skb, real_dev);    //取消关联的路由项,以便可以在ip_input的时候重新policy routing    skb_dst_drop(skb);    //取消conntrack,因为它的任务在第一阶段已经完成了    skb->nfct = &nf_conntrack_untracked.ct_general;    skb->nfctinfo = IP_CT_NEW;    nf_conntrack_get(skb->nfct);        len = skb->len;        if (likely(netif_rx(skb) == NET_RX_SUCCESS)) {        ...//做点什么好呢?统计?    } else {        ...//...    }    return NETDEV_TX_OK;}

对NVI接口的注册也非常简单:

dev = alloc_netdev(0, "nvi", nvi_setup);
为何可以使用skb的mark来保存入接口index呢?实际上在32位的机器上它完全可以保存原始入网卡dev的地址,强转成net_device类型指针即可。我并不是一开始就直接把入网卡的index保存在mark中了,因为可能Netfilter钩子还要用这个mark,我也没有使用mark的掩码掩去一些位来保存index,因为不知情者可能会误用。我采用的方式是在“确认不可能有Netfilter钩子使用mark的时候再将其覆盖成入网卡的index,那么何时合适呢?精通Netfilter的都知道,在POSTROUTING的最后做这件事比较合适,因此我就把这个HOOK安置于POSTROUTING的nf_confirm之后。是否会有流控用到mark我不管,毕竟流控是在物理网卡上做的,和第一轮的路由无关。然而问题是,到了POSTROUTING的时候我还能取到原始的入网卡的index吗?Oh,NO!:

int ip_output(struct sk_buff *skb){    struct net_device *dev = skb_dst(skb)->dev;    IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);    //在此处,POSTROUTING前替换了skb的dev...    skb->dev = dev;    skb->protocol = htons(ETH_P_IP);    return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,                ip_finish_output,                !(IPCB(skb)->flags & IPSKB_REROUTED));}
所以,到了POSTROUTING,就再也取不到原始网卡的index了!那么变通的办法就是将Linux协议栈的这段代码改了:

int ip_output(struct sk_buff *skb){    struct net_device *orig_dev    struct net_device *dev = skb_dst(skb)->dev;    IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);    //保存原始入网卡的dev(反正都是forward包...)    orig_dev = skb->dev    skb->dev = dev;    skb->protocol = htons(ETH_P_IP);    //在HOOK调用的时候传入indev    if (orig_dev && orig_dev->flags & IFF_LOOPBACK) {        orig_dev = NULL;    }    return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, orig_dev, dev,                ip_finish_output,                !(IPCB(skb)->flags & IPSKB_REROUTED));}
我个人觉得,对于将Linux作为路由BOX来用的人来讲,对于FORWARDING的数据包,在POSTROUTING的时候能取到数据包从哪个网卡进入,可以实施更多的控制策略,这难道不更好吗?好吧,如果非要说这样改不好,我还有更加标准的做法,那就是在conntrack结构体中注册一个新的extend,其实就是一个结构体,将原始的入网卡作为一个字段放进去,在NVI接口的xmit中,conntrack重置为nf_conntrack_untracked之前,取出这个网卡,调用eth_type_trans接口即可,这样好了吧,我没有触动Linux kernel的主协议栈,还是基于Netfilter来做扩展!事实上,Netfilter的扩展能力是无限的!
 本文转自 dog250 51CTO博客,原文链接:
http://blog.51cto.com/dog250/1304515

转载地址:http://kcovx.baihongyu.com/

你可能感兴趣的文章
分享一个CSS3的网格系统架构 - ResponsiveAeon
查看>>
如何使用VIM的Help
查看>>
以软件推动工业进步 -嵌入式学习网站
查看>>
mvc control 请求两次问题
查看>>
视图加载时自动执行铵钮事件
查看>>
【原】python中文文本挖掘资料集合
查看>>
【转】学习apicloud和IOS之间的模块化使用
查看>>
solrcloud线上创建collection,修改默认配置
查看>>
JSON
查看>>
DCOS实践分享(6):基于DCOS的大数据应用分享
查看>>
MongoDB 权限认证
查看>>
java jar包收集
查看>>
做Adsense的一些经验
查看>>
私活利器,docker快速部署node.js应用
查看>>
2018第51周日
查看>>
Process of knowledge discovery in databases
查看>>
C#中字符串的内存分配与驻留池
查看>>
Cowboy 源码分析(十八)
查看>>
SharePoint 2007 "Select People and Groups"中搜索不到其他Domain账户的问题[已解决]
查看>>
cas 资源
查看>>