设为首页 收藏本站
查看: 553|回复: 0

[经验分享] 实现一个做双向NAT的虚拟网卡

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-7-21 08:51:53 | 显示全部楼层 |阅读模式
问题描述与解决方案
还是老问题,Linux系统中通过iptables配置的NAT无法在双向通信环境中使用,你无法配置一条NAT规则实现对两个方向主动发起的流量做NAT,解决这个问题的方案有好几种:
1.配置两条NAT规则
iptables的NAT配置本身就是先match再执行一个target,因此一条规则只能表示一种转换策略,要想实现“来自x的数据包的源地址转换为y,去往y的数据包的目标地址转为x”这样的逻辑,必须使用两条规则。那么为何不使用两条规则呢?因为iptables的nat配置是基于数据流的,它只对一个创建ip_conntrack结构体的那个数据包进行规则查找,因此在一个流已经创建并在数据传输的时候,加入一条nat配置是无效的。
       xtables-addons中有一个RAWNAT,不再基于ip_conntrack了,也就是它是基于数据包而不是数据流的NAT,即时生效问题解决了,但是由于它还是一个match-target规则,因此要想实现双向的NAT,还是要配置两条规则。
2.编写一个Netfilter HOOK
编写一个Netfilter HOOK模块不是什么难事,我自己写过好几个,但是,Netfilter框架是在协议栈的处理路径上拦截数据包进行检查-匹配/动作的,它对每一个经过协议栈的数据包都要进行检查,也就是说每一个数据包都要经过HOOK函数的过滤,在Netfilter HOOK过多的时候,大大降低了效率。
3.使用专门的虚拟设备
这是一种全新的理念,实现一个虚拟网卡,其xmit函数是这样的:


    static netdev_tx_t sdnat_net_xmit(struct sk_buff *skb, struct net_device *dev)  
    {  
        struct sdnat_struct *sdnat = netdev_priv(dev);  
        unsigned int flags = sdnat->flags;  
        struct nat_entry *entry;  
      
        entry = find_sdnat_policy(skb, flags);  
        if (unlikely(!entry)) {  
            goto xmit;  
        }  
        if (flags & SNAT) {  
            do_trans_src(entry, skb);  
        } else if (flags & DNAT) {  
            do_trans_dst(entry, skb);  
        }  
        // 此时skb的dst为将数据包导入NAT设备的dst_entry,  
        // 为了防止循环路由,将其drop,NAT已经完成,已经没有用了  
        skb_dst_drop(skb);  
        // 清除mark,因为一般通过mark策略路由将数据包导入NAT设备  
        // 这也是为了防止循环路由  
        skb->mark = 0;  
    xmit:  
        netif_rx_ni(skb);  
    drop:  
        kfree_skb(skb);  
        return NETDEV_TX_OK;  
    }  


do_trans_src/dst完全可以通过一个函数实现,此处是为了使接口更加清晰。具体的转换就不多说了,很简单,修改掉IP报头的源地址或者目标地址,然后重新计算L3,L4的校验码。
       关键是如何组织nat规则。我使用一个nat_entry来保存每一条规则:


    struct nat_entry {  
        struct hlist_node hash_list;  
        __be32 key1;  //对于SNAT即原始IP地址,对于DNAT即要转换到的IP地址  
        __be32 key2;  //对于SNAT即要转换到的IP地址,对于DNAT即原始IP地址  
        __be32 hash;  /数据包源IP或者目标IP的jhash_2words值  
        int flags;  
    };  


hash的计算如下:


    static u32 keys_get_hash(__be32 key)  
    {  
        return jhash_2words(key, 0x01, 0x0);  
    }  


模块加载的时候,会创建两个虚拟网卡,一个负责SNAT,一个负责DNAT,同时系统中也会有两个sdnat_struct结构体,一个负责SNAT,一个负责DNAT:


    struct sdnat_struct {  
        int flags;  
        struct net_device   *dev;  
        struct hlist_head entrys[1024];  
    };  


Linux上要配置就是两条策略路由:
a.从内网口进入往外发的数据包导入到SNAT网卡设备进行SANT;
b.从外网口进入到内网口的数据包导入到DNAT网卡设备进行DNAT。
这样就可以双向自动转换了,不管数据是从哪个首先发起的,实现了“来自x的数据包的源地址转换为y,去往y的数据包的目标地址转为x”。是不是和Cisco的static NAT有些类似呢?定义出入设备而不是靠iptables的match来过滤数据包。我比较喜欢使用procfs作为用户接口,因为它方便shell操作:
echo +192.168.1.1 9.24.100.1 >/proc/net/nat
上面的命令执行后,将会在两块网卡共享的hash表中添加一个nat_entry,key1为192.168.1.1,key2为9.24.100.1,在SNAT网卡设备中,将会用skb的iph->saddr做hash后查表匹配其key1,取出key2作为要转换的IP地址,在DNAT网卡设备中,将会用skb的iph->daddr做hash后查表匹配key2,取出key1作为要转换到的IP地址。如果想删除一条规则,那么就执行:
echo -192.168.1.1 9.24.100.1 >/proc/net/nat
策略路由规则如下:
ip rule add iif $内网口 table snat
ip rule add iif $外网口 table dnat
ip route add 0.0.0.0/0 dev snat0 table snat
ip route add 0.0.0.0/0 dev dnat0 table dnat
依靠路由来做是否要进行NAT的判断,是不是更加高效些呢?而不再需要通过Netfilter模块去匹配每一个数据包了,也不需要折腾低效率的ip_conntrack了!值得注意的是,sdnat设备的xmit函数最终执行了一个netif_rx_ni这相当于将数据包重新注入其本身,此时数据包的iif将不再是内网口或者外网口了,而是实实在在的sdant虚拟网卡设备,因此数据包再次到达路由模块的时候将不会再次进入sdnat设备。
引申出来的思想和含义
除了Netfilter框架之外,我们也可以使用Linux的网卡设备模型来构建另一套数据包过滤系统,是的,其思想就是上面展示的。我曾经写过几篇关于在路由项中保存信息,然后通过查路由表的方式获取信息的技巧,其中使用了自己定义的“路由表”,查询方式依然是最长前缀匹配法,只是路由项中保存的东西变了。在本文中,我给出的是使用Linux原生的路由表(不是自己定义的)+自定义的虚拟网卡设备实现数据包过滤的思想,按照这种思想,iptables的每一个target就是一个虚拟网卡设备,每一系列的matches就是一条路由,该路由的路由项就是将数据包导入对应的虚拟网卡设备,路由的方式来匹配数据包将比Netfilter的方式高效,因为它使用了hash/trie这类高效的数据结构,而不是像Netfilter那样遍历好几层的链表。
       事实上,这种思想很新吗?不!路由项不是有unreachable或者blackhole吗?它们不正是iptables的REJECT和DROP么?

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-22396-1-1.html 上篇帖子: 快速搭建Ubuntu更新源服务器 下篇帖子: Linux下设置定期执行脚本 网卡
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表