欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

通过 nat 实现 GRE/VXLAN 的一些尝试

最编程 2024-06-20 16:02:25
...

支持nat-t的tunnel要么是标准的C/S模型,能够从墙内发起建立tunnel隧道,且有保活机制使防火墙上的ct持久生效。要么像IPSec一样,能够在协议层面支持,知道tunnel是经过nat的。

linux gre 和 vxlan 作为常用的tunnel口是无法过nat的,但其性能和复杂度比ssl,ipsec这些能够过nat的tunnel要好很多。

做了些gre/vxlan过nat的测试,做了些记录:


GRE过nat

如果网关设备为ovs,直接通过流表学习到nat过的gre头即可,如果网关设备为linux bridge需要改动内核gre模块,修改量不多。

网关设备为ovs:

1. ovs上的操作

* 创建br0

ovs-vsctl add-br br0

ip link set up dev br0

ip addr add 211.1.1.1/24 dev br0

* 创建gre口

ovs-vsctl add-port br0 gre1 -- set interface gre1 type=gre options:local_ip=192.168.121.177 options:remote_ip=flow options:key=flow

* 创建流表,in_port=1是gre1的端口编号,流表主要用于学习反向nat过的流表。流表需要配置老化时间防止对端不可用后流表残留,这里只是测试没配置。

ovs-ofctl add-flow br0 "priority=1,in_port=1,actions=learn(priority=1,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[] load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],load:NXM_NX_TUN_IPV4_SRC[]->NXM_NX_TUN_IPV4_DST[],output:NXM_OF_IN_PORT[]),NORMAL"

* 配置arp表项,可以省掉,能够自动学习,工作上组网远比试验复杂,所有节点mac和ip都是分配的,arp也是静态配置的,所以这里用静态配置。

#ip neigh

211.1.1.2 dev br0 lladdr de:22:5f:f3:71:a6 PERMANENT

2. bridge上操作

* 创建bridge

brctl addbr br0

ip link set up dev br0

ip addr add 211.1.1.2/24 dev br0

* 创建gre口,加入bridge

key一定要加上,即使是0

ip link add gretap0 type gretap local 15.1.1.2 remote 192.168.121.177 key 0

brctl addif br0 gretap0

ip link set up dev gretap0

* 配置arp表项

211.1.1.1 dev br0 lladdr f2:f9:24:d1:a1:40 PERMANENT

3. nat 设备上操作

* 配置出口nat

iptables -t nat -A POSTROUTING -i eth0 -o eth1 -j MASQUERADE

* 安装gre的nat支持

modprobe nf_conntrack_pptp

modprobe nf_nat_pptp

modprobe nf_conntrack_proto_gre

modprobe nf_nat_proto_gre

4. 测试

bridge上 ping 211.1.1.1

#ping 211.1.1.1

PING 211.1.1.1 (211.1.1.1) 56(84) bytes of data.

64 bytes from 211.1.1.1: icmp_seq=1 ttl=64 time=1.10 ms

64 bytes from 211.1.1.1: icmp_seq=2 ttl=64 time=0.634 ms

64 bytes from 211.1.1.1: icmp_seq=3 ttl=64 time=0.661 ms


ovs上可以看到学习到一个流表,NXM_NX_TUN_IPV4_DST 是经过nat的nat设备的出口ip地址,而不是bridge上ip地址,这样即使nat过,回程流量的gre头也能正确封装,当然有个前提,nat内部的bridge设备需要做gre的keepalive,定时发些报文到ovs上,使nat设备能够保持连接跟踪表,ovs上能刷新回程流表:

cookie=0x0, duration=2449.653s, table=0, n_packets=635, n_bytes=62230, idle_age=0, hard_age=0, priority=1,dl_dst=de:22:5f:f3:71:a6 actions=load:0->NXM_NX_TUN_ID[],load:0xc0a87945->NXM_NX_TUN_IPV4_DST[],output:6

网关设备为bridge

正在测试中。。。。

二. vxlan过nat

vxlan封装在udp中,但是两端是独立建socket的,而不是c/s模型,需要做些修改支持nat。vxlan在bridge实现较为简单,linux原生的vxlan实现直接使用vxlan接口上配置的dstport作为udp的目的端口号,实际上如果过nat-t,nat内部的设备需要先主动从vxlan上发一些数据包,在nat设备上建立连接跟踪表,nat外部的设备用转换过的src ip和src port作为返向流量的dst ip和dst port,linux的fdb表项学习已基本支持这个功能,唯一需要修改的地方是src port的学习,修改量10行之内就有很好的效果;

     ovs类似,基本原理也是nat外部设备通过入方向的报文学习到出方向的vxlan的封装信息,通过流表的learn功能实现添加流表,同样缺少tun_port的支持,修改量稍大,过了一下ovs的源码,感觉修改量在可控范围内,300行左右的修改;

大概看了一下vxlan的协议栈,感觉可以通过修改vxlan模块达成目的,特别如果在dpdk上实现更简单一点。今天在原生的linux上试了一下,只修改了vxlan.ko 模块不到10行代码,搞定了,效果非常好。

bridge协议栈,每次收到报文的时候都会学习 fdb表项,就是mac---ip地址映射,回来的报文走这个表项封装vxlan报文以及转发,vxlan的表项特殊之处全在下面这个结构里了,有remote_ip,remoteport,remote_vni,内核只根据接收报文对remote_ip做了赋值,其它两个都取的接口上的配置,所以只需要对这两个成员赋下值就ok了,这样即使nat过的ip和port,也能学到fdb表中,回程报文使用这两个值封装vxlan,到了防火墙也能通过。

注意需要在cpe设备上做保活,防止防火墙的连接跟踪表项老化。

struct vxlan_rdst {

union vxlan_addr remote_ip;

__be16 remote_port;

__be32 remote_vni;

u32 remote_ifindex;

struct list_head list;

struct rcu_head rcu;

struct dst_cache dst_cache;

};

vxlan_xmit_one:

。。。。

if (rdst) {

dst_port = rdst->remote_port ? rdst->remote_port : vxlan->cfg.dst_port;

vni = rdst->remote_vni;

dst = &rdst->remote_ip;

local_ip = vxlan->cfg.saddr;

dst_cache = &rdst->dst_cache;

}

。。。。

udp_tunnel_xmit_skb(rt, sk, skb, local_ip.sin.sin_addr.s_addr,

    dst->sin.sin_addr.s_addr, tos, ttl, df,

    src_port, dst_port, xnet, !udp_sum);

。。。。

修改点:

static bool vxlan_snoop(struct net_device *dev,

union vxlan_addr *src_ip, __be16 src_port, __be32 vni,const u8 *src_mac)  // 增加port和vni赋值

{

struct vxlan_dev *vxlan = netdev_priv(dev);

struct vxlan_fdb *f;

f = vxlan_find_mac(vxlan, src_mac);

if (likely(f)) {

struct vxlan_rdst *rdst = first_remote_rcu(f);

if (likely(vxlan_addr_equal(&rdst->remote_ip, src_ip)))

return false;

/* Don't migrate static entries, drop packets */

if (f->state & NUD_NOARP)

return true;

if (net_ratelimit())

netdev_info(dev,

    "%pM migrated from %pIS to %pIS\n",

    src_mac, &rdst->remote_ip, &src_ip);

rdst->remote_ip = *src_ip;

rdst->remote_port = src_port;    // 增加port和vni赋值

rdst->remote_vni = vni;

f->updated = jiffies;

vxlan_fdb_notify(vxlan, f, RTM_NEWNEIGH);

} else {

/* learned new entry */

spin_lock(&vxlan->hash_lock);

/* close off race between vxlan_flush and incoming packets */

if (netif_running(dev))

vxlan_fdb_create(vxlan, src_mac, src_ip,

NUD_REACHABLE,

NLM_F_EXCL|NLM_F_CREATE,

//vxlan->dst_port,

//vxlan->default_dst.remote_vni,

src_port, vni,    // 增加port和vni赋值

0, NTF_SELF);

spin_unlock(&vxlan->hash_lock);

}

return false;

}

调用这个函数的地方赋下值即可。

推荐阅读