TAP与TUN模式简介
在OpenVPN中有两种工作模式,一种称为 TUN(tunnel)模式,即通道的意思;另一种是TAP(Terminal Access Point)模式,即一种网络设备或软件虚拟设备的意思。
TUN模式是一种虚拟点对点的网络设备模式。通常用于实现点对点(即主机到主机)虚拟专用网络(VPN)。在TUN模式下,OpenVPN会创建一个虚拟网络接口(通常称为tun0),通过该接口发送和接收IP包。TUN模式通常用于路由网络流量,因此其只能够处理IP层(OSI模型中的第3层)数据包,不能处理链路层(OSI模型中的第2层)数据包。
TAP模式是一种以太网桥设备模式。通常用于实现一点对多点(即主机到局域网)虚拟专用网络(VPN)。在TAP模式下,OpenVPN同样会创建一个虚拟网络接口(通常称为tap0),不过该接口与NAT模式下的接口不同,它可以接收和发送以太网帧,就像一个物理的以太网接口一样。TAP模式可以处理链路层数据包,因此它可以传输多种类型的网络包,如IP、ARP、NetBIOS等。
一般来说,TUN模式更适合用于远程访问,因为它可以更好地控制网络流量,并且可以提供更好的性能。而TAP模式更适合用于网络扩展,因为它可以模拟真实的局域网,并且可以传输多种类型的网络包。
tap模式下的OpenVPN节点可以被看作一个虚拟的以太网,节点之间的通信可以通过以太网协议来完成;
tun模式是三层模式,也就是说,tun模式下的OpenVPN节点可以被看作一个虚拟的IP网络,节点之间的通信完全靠路由来完成;
如此,OpenVPN可以既是以太网模型又可以是IP网络模型,但是同一时间只能是一种。那么在每一种模型下,OpenVPN就扮演了该网络模型的核心设备的角色,对于以太网,它是交换机,对于IP网络,它是路由器,当然这种设备是虚拟出来的,是软件实现,是由OpenVPN的代码实现的。前文总是说OpenVPN是一个虚拟的交换机,又说OpenVPN是一个虚拟的路由器,那么它到底如何做到的的呢?
TAP模式实现原理
以太网交换机内部有一个简单的映射表,映射MAC地址和端口信息。注意,这个映射表的容量是有限的,这也常常成为一种攻击向量…(注意,最美丽的天使是断了翅膀再也回不了天国的天使),只要有数据帧经过交换机,哪怕是一个广播,交换机就记录下该数据帧的源地址和进入的端口,将之做成一个映射项,存入这个以太网交换机的“路由表”,隔一段时间,这个映射会失效。以上就是以太网的简单的“路由表维护机制”。
在OpenVPN中,如果它真的要实现一个虚拟的以太网交换机,那么它就应该实现一个类似的路由表,保存MAC地址和端口的映射项。在OpenVPN中,其实并不存在真正的“端口”,所谓的端口都是虚拟出来的,在OpenVPN中表现为一个结构体multi_instance,所谓的MAC地址还是MAC,这个并没有变。那么在OpenVPN中,如果想实现一个虚拟的交换机,那么就是要实现一个MAC地址和multi_instance的映射表,对于映射表之类的,用软件实现要比用硬件实现简单得多,C语言可以很简单的实现类似table,hash之类的数据结构。
由于tap模式下,所有的VPN节点构成了一个虚拟的以太网,那么这个虚拟的交换机的MAC地址-multi_instance映射表就要起码包容所有的这些VPN节点,至于是否要包容其它的MAC地址,那就看你的网络拓扑了,基本上tap模式下的拓扑有三类:
全网桥接
这种拓扑几乎不用任何路由,整个网络都是桥接的,拓扑图如下:
这样子,OpenVPN虚拟出来的交换机就和各个VPN网段内部的物理交换机是完全并列的关系。这种拓扑下,OpenVPN的虚拟交换机需要学习所有VPN节点后面物理网段的所有的主机的MAC地址信息。
仅VPN节点桥接,其它路由
这种拓扑最简单。只需要考虑接入的VPN节点即可。拓扑图如下:
这样子,OpenVPN虚拟交换机只需要学习VPN节点的MAC地址即可。
一些VPN节点桥接,另一些路由
这种拓扑其实上上述两种的综合。拓扑图就不再画了吧。 这样子,OpenVPN虚拟交换机需要学习的MAC地址信息完全取决于各个VPN节点所在的主机是怎么配置的。
理解了上述的拓扑,继续下去就简单了。我们知道在OpenVPN中有一个multi.c文件,里面有一个multi_process_incoming_link函数,该函数中有两个逻辑,其中之一是处理TUN模式的逻辑,这个一会儿再说,另一个就是处理TAP模式,这个正是我们现在要分析的。该函数处理网络上过来的使用OpenVPN协议封装的加密流量,将之解密,然后写入到虚拟网卡或者经由其它multi_instance加密发送给其它的OpenVPN客户端。
在分析代码之前,首先要明白的是,对于TAP模式,OpenVPN实现的虚拟设备主要处理虚拟交换机的MAC-“端口(multi_instance)”映射,而对于TUN模式,OpenVPN主要处理路由缓存,其实就是内部路由所体现的一张虚拟的路由表。不管是tap模式还是tun模式,路由信息都是保存在一个hash表中的,在OpenVPN的server模式中,这个hash表为multi_context的vhash字段。在multi_process_incoming_link中,处理TAP模式的逻辑如下(不考虑内部包过滤):
else if (TUNNEL_TYPE (m->top.c1.tuntap) == DEV_TYPE_TAP) {
//从解密后的载荷包中解析中源MAC地址和目标MAC地址
mroute_flags = mroute_extract_addr_from_packet (&src,
&dest,
NULL,
NULL,
&c->c2.to_tun,
DEV_TYPE_TAP);
if (mroute_flags & MROUTE_EXTRACT_SUCCEEDED) {
//虚拟交换机的学习,和物理交换机的学习机制一样
if (multi_learn_addr (m, m->pending, &src, 0) == m->pending) {
if (m->enable_c2c) {
if (mroute_flags & (需要广播的标识)) {
multi_bcast (m, &c->c2.to_tun, m->pending, NULL);
} else {
mi = multi_get_instance_by_virtual_addr (m, &dest, false);
/* 如果目的地址对应另外一个multi_instance,那么就直接将数据“路由”到该客户端,不再往虚拟网卡中写 */
if (mi) {
multi_unicast (m, &c->c2.to_tun, mi);
register_activity (c, BLEN(&c->c2.to_tun));
c->c2.to_tun.len = 0;
}
}
}
} else {
msg (bad source address);
c->c2.to_tun.len = 0;
}
} else {
c->c2.to_tun.len = 0;
}
}
在mroute_extract_addr_from_packet中解析出该数据包的目的地址(此时数据包已经被解密了),将其保存到dest变量,接下来就要地址学习了,和物理交换机一样,此时虚拟交换机学习的是发送该数据包的源地址,所谓的学习过程很简单:
- 在vhash中查找这个src,如果没有找到则添加,同时和该src的multi_instance关联;
- 在vhash中查找这个src,如果找到了,则更新vhash的对应项。 在地址学习之后,接下来调用multi_get_instance_by_virtual_addr试图找到目的地址对应的multi_instance,注意,这个对应关系也是虚拟交换机学习得到的,在multi_get_instance_by_virtual_addr函数中,实际上是在同一个vhash中进行查找的,就是刚才虚拟交换机地址学习时更新的那个vhash。如果找到了,则直接将数据包路由给对应的multi_instance,也就不再往虚拟网卡中写了,否则就写入虚拟网卡。
值得注意的是,前一段的一句话“如果找到了,则直接将数据包路由给对应的multi_instance”,为什么不是“对应的OpenVPN客户端”而是“对应的multi_instance”呢?这是因为在tap模式下,如果有虚拟网卡和物理网卡桥接的情况,那么实际上OpenVPN节点后面的节点也处于同一个虚拟以太网中,此时对应的目的地址就不再是OpenVPN客户端本身了,而是该OpenVPN客户端后面的一台主机。在tap模式下,主要有两个函数调用值得注意,一个是multi_learn_addr,另一个是multi_get_instance_by_virtual_addr,前者学习MAC地址和multi_instance的映射,以src作为输入,后者根据学者学习的结果,从OpenVPN的虚拟交换机的映射表中取出和dest对应的multi_instance,以dest作为输入,learn函数将一个项插入这个映射表,而get函数则试图从这个表中取出一项,其实就是查表的过程。
TAP模式配置
dev tun:工作在tun模式
dev tap:工作在TAP模式