摘要:本文将以太网数据包经过压缩、加密后利用UDP传送,实现一种直接在以太网层将园区网通过Internet网络扩展的L2VPN功能。使用rawsocket机制接收和发送以太网数据包,避免了其他实现方式的网桥处理步骤;使用PING/PONG机制在主备2个UDP连接间实现自动故障发现和主备自动切换,避免使用传统的生成树机制,简化了配置并能提高系统运行稳定性。使用高速的LZ4压缩算法,使得本文的实现可以在高带宽时尽量少影响性能。实现中还支持一方处于NAT环境,支持VLAN号的重新映射以避免VLAN冲突,适应更加灵活的应用场景。
通过Internet网络扩展园区网最简单的做法是建立L2VPN,即通过VPN隧道将以太网数据包经过加密后利用Internet网络传递,从而完成双方的通信。
常见的L2VPN或能实现类似L2隧道功能的程序对比见表1。
VxLAN主要用于数据中心内部解决VLAN扩展性问题,OpenVPN用于远程用户的连接,不适合用来建立隧道扩展网络。
如图1所示,VTun和Tinc可以利用Internet网络建立隧道,在tap接口间转发数据包。将以太网接口和tap接口加入同一个网桥,由Linux的网桥处理逻辑实现网卡与tap接口间数据包的转发,从而可以完整实现以太网透传隧道功能。如果使用两个UDP连接(如不同的ISP线路)实现线路备用,需要配置两条隧道,并启用生成树协议以避免环路。由于公网的连通性不能实时反映到tap虚拟接口up/down状态,公网连接不稳定时会无法避免形成短时的环路,引起网络广播风暴,这对以太网是致命的。并且在生成树状态切换时会有若干秒网络处于中断状态。
本文实现的EthernetOverUDP[5](简称EthUDP,代码在https://github.com/bg6cq/ethudp),支持使用IPv4和IPv6建立隧道,支持一方使用动态地址或NAT环境后的连接,并可以验证对方IP地址。
RAW SOCKET接收和发送数据包
在Linux中,使用raw socket[6]可以跳过TCP/IP处理逻辑,直接在以太网接口接收和发送数据包。
使用rawsocket机制从网卡接收以太网数据包并利用UDP发送给远端,同时从UDP接收远端发来的数据包并通过网卡直接发出,可以直接实现桥接功能,避免了VTun、Tinc两种实现方式中的网桥处理步骤。
从特定以太网接口接收数据包时,需要建立PF_PACKET类型的raws ocket,将接口设置为混杂模式,并绑定到该接口。将来使用recvmsg调用即可接收数据包。接收到的数据包不含通过rawsocket发送的数据包,正好避免了环路重复包。
int32_tifindex,fdraw,val=1;structifreqifr;
structsockaddr_llsll;
fdraw=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));strncpy(ifr.ifr_name,ifname,IFNAMSIZ);
ioctl(fdraw,SIOCGIFINDEX,&ifr);ifindex=ifr.ifr_ifindex;
ioctl(fdraw,SIOCGIFFLAGS,&ifr);ifr.ifr_flags|=IFF_PROMISC;ioctl(fdraw,SIOCSIFFLAGS,&ifr);sll.sll_family=AF_PACKET;sll.sll_protocol=htons(ETH_P_ALL);sll.sll_ifindex=ifindex;
bind(fdraw,(structsockaddr*)&sll,sizeof(sll);
向特定接口发送数据包时,只要提供接口索引调用sendto即可将完整的以太网数据包发送出去:
structsockaddr_llsll;sll.sll_family=AF_PACKET;
sll.sll_protocol=htons(ETH_P_ALL);sll.sll_ifindex=ifindex;
sendto(fdraw,buf,len,0,(structsockaddr*)&sll,sizeof(sll));
PING/PONG机制
EthUDP每隔1秒钟利用UDP向对方发送PING消息,每一方收到PING消息后会立即应答PONG消息。
主备两个UDP连接的情况下,如果一方在连续的5秒钟内未从主连接收到对方的PONG消息,会认为主连接中断,立即将备用连接设定为当前在用连接,将来需要转发的以太网数据包将通过备用连接发送。一旦再次从主连接接收到对方的PONG消息,立即切换回主连接。这个简单的机制可以在主备2个UDP连接间实现自动故障发现和主备自动切换。
发送以太网数据包时,发送方只会通过主备UDP连接中的一个发送,不需要使用生成树协议也能从根本上确保不会发生环路情况,彻底避免了广播风暴问题,能大大提高系统运行的稳定性。
LZ4压缩/AES加密
为了适应高速网络,EthUDP可选使用LZ4[7]高速算法对发送数据包进行压缩,在高带宽环境下尽量少影响性能。
启用压缩后,EthUDP在每个数据包后增加一个字节作为是否压缩的标志。当原始数据包使用LZ4压缩无效(压缩后的长度没有减少)时,该标志为0xaa;有效压缩时为0xff。通过这样的机制,一个数据包即便无法有效压缩,也仅仅多传输1个字节。
EthUDP可选使用openssl[8]提供的AESCBC算法对发送数据包进行简单加密。加密处理中使用openssl标准填充方式填充,每个数据包加密后可能会增加1-16字节。
VLAN映射
将两个局域网相连时,经常会碰到vlan编号冲突问题。通常的解决办法是切换到某一方不使用的vlan,以解决冲突问题。但这样对于双方已经在用的vlan比较麻烦。EthUDP增加了可选的vlan编号映射步骤,在发送前将本地的一个vlan编号转换成另一个,接收时再转换回来,方便解决vlan冲突,适应更加灵活的应用场景。
EthUDP进程设计与实现
EthUDP是运行在用户态空间的多线程进程。进程启动初始化时会建立相关的UDP连接(如果指明的UDP端口为0,则为NAT模式,暂时不建立连接),打开指定网卡的rawsocket,然后启动以下3种线程。
1.以太网接口数据包接收转发线程
处于如下循环:
从以太网接口接收数据包;
如果有MSS调整,对tcpsyn包修改MSS;
如果有VLAN映射,将以太网包头的VLAN编号改为对方VLAN编号;
如果需要,进行压缩和加密处理;通过在用的UDP连接发送给对方。
2.UDP数据包接收转发线程
每个UDP连接对应一个接收线程,因此主备两个连接时启动2个线程。线程处于如下循环:
从UDP端口接收数据包;
如果需要,进行解密和解压缩处理;
如果是NAT环境,判断是否需要更新对方地址和端口号信息;如果是PING消息,发送PONG消息应答;
如果是PONG消息,更新最后接收时间;
如果有VLAN映射,将以太网包头的VLAN编号改为我方VLAN编号;
如果有MSS调整,对tcpsyn包修改MSS;通过rawsocket发送数据包。
3.ping/pong消息处理线程线程处于如下循环:
向UDP连接发送PING消息;
如果主连接最后接收PONG消息是5秒前,设定备用连接为在用UDP连接;
每间隔1小时,输出收发数据包/字节/压缩效率等统计信息;等待1秒。
EthUDP进程的每个线程仅仅处理一个方向的数据包转发,这样的设计可以充分发挥多核CPU的作用。正常运行时,上述5.1和5.2线程会占用CPU时间,因此建议运行EthUDP的机器或虚拟机至少能允许2个线程同时执行。
特别声明:本站注明稿件来源为其他媒体的文/图等稿件均为转载稿,本站转载出于非商业性的教育和科研之目的,并不意味着赞同其观点或证实其内容的真实性。如转载稿涉及版权等问题,请作者在两周内速来电或来函联系。