所有下载源码放在G:\实验室项目\源码
详细配置见官网quick start
1、服务器:
安装Open*** server  open***-as-1.8.3-Ubuntu10.i386.deb
(直接双击安装)
2、客户端:
windows客户端安装open***-2.2.2-install.exe
linux客户端安装open***-2.2.2.tar.gz
这个软件什么作用?
3、服务端:
服务器开启其open***  :  /etc/init.d/open***as start   (stop关闭)
需要先在服务器终端输入passwd open***
https://yourserverIP/admin (服务器Public Address)
用户名是open***,密码yourpasswd
4、客户端:
登录网页https://**.**.**.**  (服务器Public Address)
5、客户端:
下载Open*** for Linux证书,
运行,此时桌面上会有一个图标Open*** Connect
如果下载不成功,需要修改hosts(open***官网被墙掉了)
如果仍不能下载,使用以前下载的client.o***(存在download文件夹中),注意修改其中服务器的ip
然后在客户端终端输入open*** --config [路径]/client.o***
用户名open***密码open***
6、连接
客户端安装出错解决
1、错误:LZO headers were not found
按提示:LZO library available from http://www.oberhumer.com/opensource/lzo/
configure: error: Or try ./configure --disable-lzo
2、
configure: error: OpenSSL Crypto headers not found.
解决:apt-get install libssl-dev
参考链接http://www.linuxdiyf.com/viewarticle.php?id=108414
在apt-get install libssl-dev时,提示网址找不到,可先apt-get update再install
下面我将介绍Open***的实现方式,基本上所有的***都是这么实现的。    
1.   更改路由表,通过应用程序调用API函数更改和添加路由表将数据包都先发送到虚拟网卡。    
2.   虚拟网卡得到数据包,先判断该数据包是什么类型的数据包。因为本身是一个物理的虚拟网卡,它必须去处理ARP,DHCP等一些数据包,用于欺骗上层网络,否则该网卡将不能工作,这一点比IMD的驱动要麻烦一些。    
3.   虚拟网卡判断得到的数据包是普通的Tcp和Udp数据包后,将该数据包压入一个堆栈。该堆栈是一个和应用程序(通常这个应用程序被称为守护程序)共享的内存块。然后驱动Set一个Event。上层应用程序Wait这个Event。并从这个堆栈中取得这个数据包。    
4.   上层应用程序得到这个数据包后,修改修改这个数据包的内容,然后建立一个Socket把该数据包重新封装成TCP或者UDP包从物理网卡发送到***的服务器上,由服务器再进行处理。转发到最终的目的IP。    
假设我们要打开一个IE,那么数据包的路线如下所示。    
ie-vnic-*** client-tcp/udp-***网关-route-web服务器      
 接收的时候正好相反。
总结一下,open***原理如下:    
1 open***驱动部分实现了网卡处理和字符设备。网卡处理网络数据字符设备完成与应用层的数据交互。    
2 使用open***必须修改路由表

工作过程
发送数据:      
1 应用程序发送网络数据    
2 网络数据根据修改后的路由表把数据路由到虚拟网卡    
3 虚拟网卡把数据放到数据队列中    
4 字符设备从数据队列中取数据,然后送给应用层    
5 应用层把数据转发给物理网卡
6 物理网卡发送数据
接收过程:      
1 物理网卡接受到数据,并传到应用空间(指***客户端)    
2 应用守护程序通过字符设备,把数据传给驱动网卡    
3 数据通过虚拟网卡重新进入网络堆栈    
4 网络堆栈把数据传给上层真实的应用程序。
?网络数据指的什么,含有IP和端口号吗?
imageimageimage
TAP以太设备,桥接     TUN (tunnel)网络设备,路由
Collapse this Answer
Okay, here is a brief summary of the principle behind Open***:
Imagine you had a direct physical wire (i.e. a long cable) connecting two computers (A and B) at different locations. On each computer there would be a /dev/longcable which would be a network device. You could route IP traffic over it, and do everything you could normally do with a network device.
Basically a tun device is like having a /dev/longcable except the Open*** daemon is the program that connects the /dev/longcable on computer A with the /dev/longcable on computer B so that you can use the internet rather than a real physical cable. But in this case it is called /dev/tun or whatever your OS prefers to call them.
Now the mechanism by which Open*** connects /dev/tun on computer A with /dev/tun on computer B is this: It simply creates an encrypted UDP connection over the internet between A and B and forwards traffic between /dev/tun on A with /dev/tun on B. Because of the clever way in which the tun and tap drivers were designed, it is possible for a program running entirely in user-space to effect this link, allowing Open*** to be a portable cross-platform daemon (like SSH), rather than an OS-specific kernel module (like IPSec).
The difference between a tun and tap device is this: a tun device is a virtual IP point-to-point device and a tap device is a virtual ethernet device. So getting back to the "long cable" analogy, using a tun device would be like having a T1 cable connecting the computers and using a tap device would be like having an ethernet network connecting the two computers. People who are running applications that need the special features of ethernet (which won't work on an IP-only network) will often bridge their physical local ethernet with a tap device (using a utility such as brctl on Linux), then *** the tap device to another similar setup at the other end. This allows Open*** to route ethernet broadcasts and non-IP protocols such as Windows NetBios over the ***. If you don't need the special features of ethernet (such as bridging capability), it's better to use a tun device.
Tun and tap devices can be interconnected to create a complex routing topology. Some people have created multi-node WAN networks over tap devices and actually run DHCP over the *** so that clients can log into the virtual ethernet and request an IP address. I've even heard of people using Linux advanced routing to run OSPF (a kind of dynamic routing protocol) over the *** WAN to allow for dynamic, fault-tolerant routing. They sky is the limit as far as the complexity of network you can build, but the basic building block is a *** daemon such as Open*** connecting tun or tap devices on two different machines.
Open***使用SSL(Secure Sockets Layer 安全套接层)加密
HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议
skb(Socket buffer)套接口缓存
image
imageimageimageimage
2.1.2虚拟网卡技术    
对于操作系统而言虚拟网卡就像是一块真正的物理网卡,操作系统可以对其    
进行所有真正物理网卡可以进行的控制,如收发数据包,禁用,启用,绑定TCP/IP    
协议,IPx协议等[5]。虚拟网卡也被相应地分配了一个IP地址,但与主机物理网    
卡的真实正地址不同。虚拟网卡只在系统内部起作用,实际的数据传输还是通    
过物理网卡发送到物理链路上的。    
TUN/TAP驱动程序是一个开源项目,它实现了虚拟网卡的功能。TUN表示    
虚拟对象是点对点设备,TAP表示虚拟对象是以太网设备。TUN/TAP驱动包括    
字符设备驱动和虚拟网卡驱动两部分[6]。字符设备驱动部分以字符设备的方式连    
接用户态和核心态,将网络分包在内核与用户态之间传送,模拟物理链路的数据    
接收和发送。虚拟网卡驱动部分接收来自TCP/IP协议栈的网络分包并发送或者    
反过来将接收到的网络分包传给协议栈处理。其工作原理如图2一5所示,利用    
Tl)N/TAP驱动程序可以将TCP/IP协议栈处理好的网络数据包传给任何一个使用    
TUN/TAP驱动程序的进程,由该进程重新处理后再通过协议栈发到物理链路。    
协议栈和虚拟网卡对数据包的处理顺序是固定的,发送的数据包先经过协议栈处    
理在交给虚拟网卡,接收的数据包是先经过虚拟网卡再交给协议栈处理。
image
图2一5虚拟网卡工作原理[5]    
在启动虚拟网卡之前,必须为虚拟网卡配置相应的IP地址,称虚拟IP地址,    
子网掩码等参数,在***应用中还需要添加虚拟路由信息。2.2.3.2节介绍的SSL    
***中提到的SSL隧道***支持的两种隧道方式,即全隧道方式和分离隧道方    
式,就是通过设置路由表来实现的。SSL***程序可以根据用户的选择的方式    
来配置路由表。    
实现SSL***的一个开源工程OPen***就是利用虚拟网卡技术来实现的数    
据隧道传输。其典型的应用场景就是远程用户访问受保护的内网资源,在基于虚    
拟网卡的SSL***系统中,网络数据包从SSL***客户端到内网主机的处理过    
程如下[71:
(l)客户端先与SSL***网关建立TCP连接,并进行SSL/TLS握手过程,    
其中客户端会对SSL***网关的身份进行验证,与其协商好通信的加密算法及    
密钥等。SSL***网关为客户端虚拟网卡分配一个虚拟IP地址,子网掩码等,    
分配的IP地址与内网IP地址的分配策略是一致的。客户端在系统的路由表中添    
加相应的虚拟路由,如发送到受保护内网主机的数据包都要经过虚拟网卡。    
(2)连接建立好之后,当上层应用程序需要通过SSL***访问受保护内网    
中的主机时,应用数据先经过TCP/IP协议栈的处理,根据匹配的路由,将封装    
好的IP包发给虚拟网卡,其中IP包的源IP地址就是虚拟网卡的IP地址,目的    
地址是受保护内网中主机的IP地址。    
(3)虚拟网卡将收到的IP包交给SSL***处理程序,处理程序根据在握手    
过程中协商好的加密算法,哈希算法,密钥等信息,对数据进行分片,压缩,计
算哈希值,加密等处理并添加SSL***头部,然后交给TCP/IP协议栈,通过物    
理网卡将数据包发送给SSL***网关,这里发送的报文的源地址为真实网卡的    
JP地址,目的地址为SSL***网关的lP地址。    
(4)SSL***网关接收到来自用户的数据后,将数据包先交给协议栈处理,    
协议栈根据数据包的目的地址和端口号将载荷部分交给SSL***处理程序,程    
序根据之前协商好的算法及密钥对此数据包进行解密,解压等处理后,得知此数    
据包的目的地为内网中的主机,然后通过字符驱动模块传递到虚拟网卡,虚拟网    
卡再由TCP/IP协议栈通过真实网卡转发出去,发送到内网的目的主机。
52    
4.6 虚拟网卡    
4.6.1 工作原理简介    
虚拟网卡    
[10]    
在 Linux 中是一个通用 TUN/TAP 设备驱动,原作者为马克西姆53    
(Maxim Krasnyansky)。在 Linux 2.4 内核版本及后来的版本中,TUN/TAP 驱动是作为    
系统默认预先编译进内核中的。其中 TUN 表示点到点设备,TAP 表示虚拟以太网设    
备。TUN 只转发 IP 数据包,而 TAP 设备则转发整个以太网帧。该设备驱动向应用    
程序提供了在用户态发送和接收网络数据包的功能。    
TUN/TAP 设备驱动在内核中建立了两个接口。一是与虚拟网卡交换数据的字符    
设备/dev/net/tun,字符设备 tun 作为用户态和内核态交换数据的接口。二是虚拟网卡    
与真实网卡交换数据的接口,该接口由 TUN/TAP 设备驱动负责维护。应用程序通过    
字符设备可以建立与虚拟网卡关联的文件描述字,通过该文件描述字可以发送或接    
受以太网帧或 IP 数据包。    
对虚拟网卡更多的关注请参阅相关资料,本文主要介绍通过 Linux 函数 ioctl()    
对虚拟网卡进行配置    
[53]    
。    
4.6.2 IOCTL 编程    
1. 虚拟网卡 I/O 编程    
虚拟网卡一般由两种工作方式,持续模式(PERSIST)和非持续模式,默认情    
况下为非持续模式。两种模式下虚拟网卡的状态都为非活动(DOWN)。在应用程序中,    
配置虚拟网卡的参数和状态一般通过系统命令调用或 I/O 控制完成。系统命令调用通    
过函数族系列 exel()进行,编程一般采用并发程序模式。在并发程序模式下,通    
过 fork() 子进程,函数族系列 exel()调用系统命令 ifconfig 完成接口的配置。VTUN    
和 TINC 软件包就采用的这种方法。如果子进程完成接口配置结束时,在父进程中动    
态分配的内存变量指针也被子进程释放,父进程内存变量指针变为 NULL,程序产    
生无法预期的错误。同时当子进程同时并发调用 ifconfig 命令时,会产生复杂的信号    
处理过程,增加父进程的编程复杂性。VTUN 软件包采用延时的措施,没有更为好    
的解决办法。    
通用 TUN/TAP 设备驱动支持通过函数 ioctl()获取接口信息和对接口进行配置。    
函数原型如下:    
int ioctl(int fd, int cmd, void *arg);    
其中 fd 为打开的文件描述符,cmd 为要执行的命令, arg 为不定指针参数变量。    
调用成功函数返回 0,失败返回小于 0 的值。通用 TUN/TAP 设备驱动支持虚拟网卡    
的字符设备文件描述符(用 fd1 表示)和套接字描述符(用 fd2 表示)。通过 fd1 调用的    
ioctl()由通用 TUN/TAP 设备驱动内部相应的函数来执行。通过 fd2 调用的 ioctl()由操    
作系统内核接口驱动完成。    
字符设备文件描述符创建主要由两个函数完成:open() 和 ioctl()。open()创建文    
件描述符,ioctl()函数建立接口与文件描述符的关联,并设定设备类型 TUN/TAP 和    
参数。如果要使用虚拟网卡,则必须创建字符设备文件描述符。54    
通用 TUN/TAP 设备驱动版本为 v1.15 支持的 cmd 参数如下:    
1. TUNSETNOCSUM 设置检验和    
2. TUNSETDEBUG 设置调试    
3. TUNSETIFF 字符设备和网络设备关联    
4. TUNSETPERSIST 设置网络设备为持续模式    
5. TUNSETOWNER 设置网络设备的属主    
6. TUNSETLINK 设置网络设备的链路类型(接口状态必须 DOWN)    
7. TUNSETGROUP 设置所属组    
8. SIOCGIFFLAGS 获取接口标志    
9. SIOCSIFFLAGS 设置接口标志(目前仅支持标志 IFF_PROMISC    
和 IFF_ALLMULTI,其它标志通过 fd2 调用 ioctl()实现)    
10. SIOCGIFHWADDR 获取接口硬件地址    
11. SIOCSIFHWADDR 设置接口硬件地址    
12SIOCADDMULTI 增加多播地址    
13SIOCDELMULTI 删除多播地址    
通用 TUN/TAP 设备驱动支持的参数 arg 使用结构为 struct ifreq,其定义如下    
struct ifreq    
{    
#define IFHWADDRLEN 6    
union    
{    
char ifrn_name[IFNAMSIZ]; /*定义虚拟网卡设备名称*/    
} ifr_ifrn;    
union {    
structsockaddr ifru_addr; /*接口网络地址*/    
structsockaddr ifru_dstaddr;    
structsockaddr ifru_broadaddr; /*广播地址*/    
structsockaddr ifru_netmask; /*子网掩码*/    
struct sockaddr ifru_hwaddr; /*硬件地址*/    
short ifru_flags; /*接口标志*/    
int ifru_ivalue;    
int ifru_mtu; /*最大传输单元*/    
struct ifmap ifru_map;    
char ifru_slave[IFNAMSIZ]; /* Just fits the size */    
char ifru_newname[IFNAMSIZ];    
void * ifru_data;    
structif_settings ifru_settings;    
} ifr_ifru;    
};    
TUNSETIFF 命令支持的参数有:    
IFF_TUN、IFF_TAP、IFF_NO_PI 和 IFF_ONE_QUEUE。    
参数的含义如下。    
IFF_TUN: 创建一个点对点设备。    
IFF_TAP: 创建一个以太网设备 。    
IFF_NO_PI: 不包含包信息,默认的每个数据包当传到用户空间时,都将包含一    
个附加的包头来保存包信息。55    
IFF_ONE_QUEUE: 采用单一队列模式,即当数据包队列满的时候,由虚拟网络    
设备自已丢弃以后的数据包直到数据包队列再有空闲。    
配置的时候,IFF_TUN 和 IFF_TAP 必须择一,其他选项则可任意组合。其中    
IFF_NO_PI 没有开启时所附加的包信息头如下:    
struct tun_pi {    
unsigned short flags;    
unsigned short proto;    
};    
结构struct ifreq支持的接口标志为16位的短整数,每位代表接口的一种标志,    
从低到高各位的含义如下:    
1. IFF_UP 接口 UP    
2. IFF_BROADCAST 广播地址合法,默认值为 1 不能改变    
3. IFF_DEBUG 允许调试    
4 IFF_LOOPBACK 回环地址,默认值为 0,不能改变    
5. IFF_POINTOPOINT TUN/TAP,0 为 TAP, 1 为 TUN,由命令 TUNSETIFF 设置    
6. IFF_NOTRAILERS 防止跟踪    
7. IFF_RUNNING RFC2863 接口操作实现    
8 IFF_NOARP 禁用 ARP 协议, 默认值为 0    
9. IFF_PROMISC 接收所有包,默认值为 1    
10. IFF_ALLMULTI 允许接受多播,默认值为 0    
11. IFF_MASTER 负载均衡主用,默认值为 0,不能改变    
12. IFF_SLAVE 负载均衡备用,默认值为 0,不能改变    
13. IFF_MULTICAST 支持多播,默认值为 1    
14. IFF_PORTSEL 允许设置链路类型    
15. IFF_AUTOMEDIA 允许自动选择链路类型    
16. IFF_DYNAMIC 拨号设备改变地址    
2. 设置网络设备接口 MAC 地址    
函数 Set_MAC()实现了对网络接口硬件地址的设置,其中参数 fd1 是字符设备文    
件描述符,参数 name 为虚拟网卡的名称,参数 mac_addr 为要设置的 MAC 地址,    
参数格式为网络字节序,可以使用 Linux 函数 ether_aton()转换合法 MAC 地址 ASCII    
表示为网络地址序表示。函数调用成功返回 0,失败返回-1。    
1 /* SET MAC ADDRESS */    
2 int Set_MAC(int fd1, unsigned char *name, unsigned char *mac_addr) {    
3 struct ifreq ifr;    
4 memset(&ifr, 0, sizeof(ifr));    
5 strncpy(ifr.ifr_name, name, IFNAMSIZ);    
6 memcpy(ifr.ifr_hwaddr.sa_data, mac_addr, 6);    
7 ifr.ifr_hwaddr.sa_family = AF_LOCAL;    
8 if (ioctl(fd1, SIOCSIFHWADDR, (void *) &ifr) < 0)    
9 {56    
10 perror("ioctl(SIOCSIFHWADDR)");    
11 return -1;    
12 }    
13 return 0;    
14}    
函数第 3 行定义了结构 ifr。第 5、6、7 行分别填写结构 ifr 的字段设备名称、硬    
件地址和协议族的值,其中协议族可以是 AF_LOCAL 或 AF_UNIX。第 8 行调用了    
ioctl()函数,函数使用了设置硬件地址命令 SIOCSIFHWADDR,在 SIOCSIFHWADDR    
中第 5 个字母 S 代表 SET,HW 代表 HARDWARE,ADDR 代表 ADDRESS。如果函数    
ioctl()调用失败,第 10 行调用函数 perror()输出错误信息。    
3. 启动网络设备接口    
函数interface_up()实现了启动网络设备接口,其中参数name为虚拟网卡的名称。    
函数调用成功返回 0,失败返回-1。    
1 /* interface up */    
2 int interface_up( unsigned char *name) {    
3 struct ifreq ifr;    
4 int fd2;    
5 unsigned short st;    
6 fd2=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);    
7 memset(&ifr, 0, sizeof(ifr));    
8 strncpy(ifr.ifr_name, name, IFNAMSIZ);    
9 if (ioctl(fd2, SIOCGIFFLAGS, (void *)&ifr) &lt; 0) {    
10 perror("ioctl(SIOGSIFFLAGS)"); return -1;    
11 }    
12 st = ifr.ifr_flags;    
13 memset(&ifr, 0, sizeof(ifr));    
14 strncpy(ifr.ifr_name, tap_name, IFNAMSIZ);    
15 ifr.ifr_flags=IFF_UP|st;    
16 if (ioctl(fd2, SIOCSIFFLAGS, (void *)&ifr) &lt; 0) {    
17 perror("ioctl(SIOCSIFFLAGS)"); return -1;    
18 }    
19 close(fd2);    
20 return 0;    
21}    
函数第 6 行建立一个套接口描述符。第 7~12 行获取网络设备接口当前标志,函    
数 ioctl()使用了获取接口标志命令 SIOCGIFFLAGS。第 13~18 行根据前面获取的标    
志设置网络设备接口标志 IFF_UP,其中函数 ioctl()使用了设置接口标志命令    
SIOCSIFFLAGS。如果函数 ioctl()调用失败,调用函数 perror()输出错误信息。第 19    
行关闭打开的套接口描述符。    
对于其它的命令调用这里就不再赘述。57    
4.6.3 虚拟网卡 IOCTL 功能测试    
对虚拟网卡接口性能的了解是使用的前提,本文在接口默认状态下从两个个方    
面对其进行了测试。一是接口标志全为 0,二是接口标志全为 1。测试环境为:操作    
系统 Fedora 8.0,TUN/TAP 设备驱动 V1.15,TUN/TAP 设备驱动工作在虚拟网卡方    
式,模式为持续模式。    
测试结果见虚拟网卡接口标志测试表。从表中可以得到:广播地址标志位不能    
更改,回环地址不能设置,内核和 TUN/TAP 设备驱动不支持负载均衡主备用方式。    
表 4-2 虚拟网卡接口标志测试    
Table4-2 Virtual Network Adapter Interface Flag Test    
1 2    
标志位 默认值    
设置值 结果 设置值 结果    
IFF_UP 0 0 0 1 1    
IFF_BROADCAST 1 0 1 1 1    
IFF_DEBUG 0 0 0 1 1    
IFF_LOOPBACK 0 0 0 1 0    
IFF_POINTOPOINT 0 0 0 1 0    
IFF_NOTRAILERS 0 0 0 1 1    
IFF_RUNNING 0 0 0 1 1    
IFF_NOARP 0 0 0 1 1    
IFF_PROMISC 0 0 0 1 1    
IFF_ALLMULTI 0 0 0 1 1    
IFF_MASTER 0 0 0 1 0    
IFF_SLAVE 0 0 0 1 0    
IFF_MULTICAST 1 0 0 1 1    
IFF_PORTSE 0 0 0 1 1    
IFF_AUTOMEDIA 0 0 0 1 1    
IFF_DYNAMIC 0 0 0 1 1    
4.6.4 虚拟网卡配置    
在 IPv6 网络中,节点要根据网卡硬件地址生成节点链路本地地址,再使用邻节    
点报文协议来检测节点地址在链路本地范围的惟一性。如果有一个算法能保证生成    
MAC 地址在节点本地范围内是惟一的,那么就可以不用进行节点链路地址惟一性检    
测。    
在 IPv6 虚拟网络中,所有从 IPv4 网络中接入虚拟隧道路由器的客户端遇到的第    
一个邻节点就是虚拟隧道路由器本身。正常情况下,虚拟隧道路由器需要处理邻节    
点请求报文,来判断是否与所有已经接入虚拟隧道路由器客户端链路本地地址存在    
冲突。如果存在冲突,则发送邻节点公告报文给发送邻节点请求报文的客户端。    
在本文研究的方案中,虚拟隧道路由器拥有公有 IPv4 地址,部署在 IPv4 因特网    
的边缘。任何从 IPv4网络中接入的客户端有两种情况,一是本身拥有合法的公有IPv4    
地址,二是本身拥有一个私有 IPv4 地址。在第一种情况下,由于公有 IPv4 地址在全    
球是唯一的,因此使用该地址来生成客户端虚拟网卡的 MAC 地址,可以保证在虚拟隧道路由器 IPv6 虚拟网络中链路本地地址是惟一的。在第二种情况下,客户端通过    
NAT 接入 IPv4 网络中,再接入虚拟隧道路由器,客户端使用的地址也是公有 IPv4    
地址。在客户端所在的私有网络中,如果只有一个客户端接入虚拟隧道路由器,则    
地址是惟一的;如果多个客户端接入虚拟隧道路由器,则多个客户端可能公用一个    
公有 IPv4 地址,特别是使用 ADSL 技术的私有网络中。这种情况下,可以考虑使用    
一个二元组{公有 IPv4 地址,端口号}来生成客户端虚拟网卡的 MAC 地址。其中 IPv4    
地址网络字节序表示占四个字节,端口号网络字节序占用 2 个字节,一共 6 字节 48    
位,与以太网 MAC 地址占用的位数相等,因此存在满射关系。但是合法的 MAC 地    
址要考虑以太网 MAC 的单播/多播位和全球/本地位,这样的话,{公有 IPv4 地址,    
端口号}生成的合法 MAC 地址可能会产生冲突。客户端登录虚拟隧道路由器时,可    
以通过报文交换来确认客户端使用地址和端口号的情况,如果存在冲突,要求客户    
端改用其它的端口号来保证生成 MAC 地址的惟一性。    
虚拟网卡 MAC 地址的正确配置保证了生成链路本地地址的惟一性,减少了虚拟    
隧道路由器处理地址重复检测带来的消耗,同时为快速转发 IPv6 报文算法带来了便    
利。
源码包名称    tuntap【按照网页说明试验成功】
1. 我的应用目标:
   为了把MPE封装的数据重新转化为网络UDP流,实现应用只要通过Socket接口即可实现数据接收和分析。
前言:
  为了实现这个功能,我在网络上搜索了相关信息,都没有具体的实现过程和应用,主要找到理论性的介绍tun/tap驱动的原理,而且很难让人理解它的功能。对于做实际开发的人员来说更需要的是一个实际实现的样例,这样更能说明问题,切实的解决应用实现问题。
2. tun/tap设备功能说明: 我实际应用时的一些理解,若有原理上的错误请多多指教
tun设备原理图
   我这里着重说明Tun设备,从上图可以看出虚拟网卡主要作为一个中转设备。Write的内容如果是tcp或单播的udp时,由于必须有一个目标地址和端口,且只能有eth0来接收,Socket的数据其实都是eth0上接收的。当发的是广播或组播时,数据直接可以从虚拟网卡接收。
  虽然我在上图示例性的写上了10.10.10.2的ip地址,但其实不是使用ifconfig配置,就算你配置了也是无法实际使用的,而是使用route add 10.10.10.2 dev tun0配置的路由。
3. 下面举个实际的例子,详细的说明一下上述原理。
     Ping示例:该示例我是网上搜索到的,它主要功能是创建Tun0设备,然后一直读/dev/net/tun设备,并把read到的数据修改后write回去。运行该程序,配置上述所得route命令,然后运行ping 10.10.10.2。就可以发现示例不停的收到数据并写数据,而且ping也能正常的收到回馈。这样就形成了一个 tun设备read、write与socket收发的转化。
   ping   首先发送一个icmp包,该包的IP源地址是 192.168.203.132,目的地址是10.10.10.2,这样tun的read接口就可以读到一个IP包(45 00 开头),然后把IP包的源和目的ip进行交换后,write出去。这样就可以从真实网卡接收到icmp反馈的包。
综合测试代码附件地址:http://download.csdn.net/source/1056909
4. 接着讲一下我的实际操作:
  --测试系统 ubuntu 8.10,默认已经安装了tun/tap
  --eth0配置:192.168.203.132,为什么用这么奇怪的ip?(呵呵vmware,nat配置后自动获取的)
  --附件中有三个测试程序、一个配置脚本及测试数据
  --tuntap.c 就是上述ping示例的代码,我做了改进,可以从文件读IP包write到tun设备。
  --udprecv.c是udp接收程序,也可以接收组播数据
  --sockraw.c是sock raw接收,可以接收icmp包,当然也可以接收原生udp包
  1). 进入目录make 生成测试程序
=测试1=============================
  2). 开启一个终端 sudo ./tuntap ping tun (ping测试,创建tun设备)
  3). 再开启一个终端 sudo sh netconfig.sh    (网络设置)
  4). ping 10.10.10.2
  5). 查看两个窗口的信息
==============================
=测试2 UDP==========================
  2). 开启一个终端 sudo ./tuntap 10.10.10.2-192.168.203.132-20000.udp tun (读对应数据,创建tun设备)
  3). 再开启一个终端 sudo sh netconfig.sh    (网络设置)
  4). ./udprecv 20000
  5). 查看窗口的信息,可以看到wwwww的打印信息
==============================
=测试3 ICMP==========================
  2). 开启一个终端 sudo ./tuntap 10.10.10.2-192.168.203.132.icmp tun (读对应数据,创建tun设备)
  3). 再开启一个终端 sudo sh netconfig.sh    (网络设置)
  4). sudo ./sockraw icmp
  5). 查看窗口的信息,可以看到乱码及123456 的打印因为是raw接口会接收到包头,有一些不可打印字符
  你可以重定向到文件中,然后查看文件内容
==============================
5. 测试数据包源及目标地址的修改
  修改前请先查看一下IP包及UDP包的结构
00000000h: 45 00 00 60 73 46 00 00 80 11 27 0E 0A 0A 0A 02 ; E..`sF.. .'.....
00000010h: C0 A8 CB 84 EA 60 4E 20 00 4C D1 1F 77 77 77 77 ; 括藙阘N .L?wwww
00000020h: 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 ; wwwwwwwwwwwwwwww
00000030h: 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 ; wwwwwwwwwwwwwwww
00000040h: 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 ; wwwwwwwwwwwwwwww
00000050h: 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 77 ; wwwwwwwwwwwwwwww
27 0E 是IP包有累加和
0A 0A 0A 02 源地址10.10.10.2
C0 A8 CB 84 目标地址192.168.203.132
修改了ip地址后,累加和会发生变化,需要重新计算,tuntap中已经包含了该代码,
修改完ip,请先把累加和清零
然后运行 sudo ./tuntap 10.10.10.2-192.168.203.132-20000.udp tun
会打印ip head checksum=abcd (不到四位前面补零)
然后以cdab的顺序填入,重新运行,看到 checksum=0即可成功发送
6. 对于组播的补充
    最近又确认了一下,可以接收组播包了,需要增加路由设置
    route add -net 224.0.0.0 netmask 224.0.0.0 dev tun0
7. 嵌入式平台和ubuntu上的对比测试的补充
   嵌入式系统,uclinux 2.4.26 arm7,编译时只要选择了tun/tap设备就可以了。
   有一个很奇怪的现象,相同的代码编译到两个平台运行,主要是组播的测试:
   嵌入式: tun设备无法使用
   ubuntu:tap设备无法使用
8. tun和tap的区别
  原理是一致的,主要就是Write的内容有所区别,但接收完全保持不变,
  tap设备基于以太层,所有包头包括以太帧,就在ip包前面加上14字节,目的mac+源mac+“0800”
  在嵌入式平台进行的组播测试中,发现Write的内容中两个mac地址和源ip地址可以不管,
  可以在任何网络上使用抓包软件保存的组播包不经任何修改就可以Write到tap设备使用了。
  所以虚拟网卡的ip地址也可以不用设置。只要执行
  ifconfig tap0 0.0.0.0 up
  route add -net 224.0.0.0 netmask 224.0.0.0 dev tun0
  上述两句后网络就配置完成了。
注:
netconfig.sh
#!/bin/sh
#设置tun0的IP,启动tun0虚拟网卡    
ifconfig tun0 0.0.0.0 up
#该route命令指明目标为10.10.10.2的包由网卡tun0路由出去
route add 10.10.10.2 dev tun0
sockraw.c
#include    "stdio.h"  
#include    "fcntl.h"  
#include    "errno.h"  
#include    "signal.h"
#include    "string.h"  
#include    "sys/types.h"  
#include    "sys/socket.h"  
#include    "sys/time.h"  
#include    "netinet/in.h"  
#include    "arpa/inet.h"  
#include    "netdb.h"
/*    
原始套接字(SOCK_RAW):原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据包套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。    
*/
/*    
所在头文件:string.h    
函数原型:extern char *strstr(char *str1, char *str2);    
函数功能:找出str2字符串在str1字符串中第一次出现的位置(不包括str2的串结束符)    
入口参数:2个字符串    
出口参数:返回位置的指针,如找不到,返回空指针    
*/
int main(int argc, char *argv[])
{
   int sockfd;
   struct sockaddr_in source_addr;
   char * strprotocol;int protocol;
   char buf[2048];
   int addr_size;
   /*参数个数是否满足*/
   if (argc != 2)
   {
       fprintf(stderr, "usage:%s protocl\n", argv[0]);
       return 0;
   }
   /*参数个数是否满足*/
   strprotocol = argv[1];//argv[1]即第一个参数为协议,argv[0]是程序运行的全路径名
   /*判断输入何种协议*/
   if (strstr(strprotocol,"udp"))
   {
       protocol = IPPROTO_UDP;
   }
   else if (strstr(strprotocol,"icmp"))
   {
       protocol = IPPROTO_ICMP;
   }
   else
   {
       fprintf(stderr, "invalid protocol, try 'upd' or 'icmp'");
   }  
     /*判断输入何种协议*/    
   /*创建原始套接字*/
   if((sockfd = socket(AF_INET, SOCK_RAW, protocol)) &lt; 0) {
            perror("socket()");
            return(-1);
   }    
   /*创建原始套接字*/
   addr_size = sizeof(source_addr);
   for(;;)    
   {
        int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&source_addr, &addr_size);
       if(n == -1)
       perror("recvfrom()");
       else
        {
            fprintf(stderr, "recv %d bytes\n", n);
            fwrite(buf, n, 1, stdout);
             fflush(stdout);
        }
   }
   return 0;
}
tuntap.c
#include &lt;stdio.h>    
#include <stdlib.h>    
#include <string.h>    
#include <sys/socket.h>    
#include <arpa/inet.h>    
#include <net/if.h>    
#include <fcntl.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <linux/if_tun.h>    
#include <sys/ioctl.h>
/*    
所在头文件:c中 <memory.h> or <string.h>  c++中 <cstring>    
函数原型:void *memset(void *s, int ch, size_t n);    
函数功能:将s中前n个字节替换为ch并返回s    
        作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。    
入口参数:    
出口参数:    
*/
/*    
所在头文件:#include<unistd.h>    
函数原型:int write(int handle, void *buf, int nbyte);    
函数功能: 从buf中提取nbyte个字符写入到参数handle所指的文件    
出口参数:正确,返回写入字节数;错误,返回-1    
*/
/*    
所在头文件:#include <stdio.h>    
函数原型:size_t fread(void *buffer,size_t size,size_t count,FILE *stream);    
函数功能:从一个流中读数据    
入口参数:buffer--接收数据的指针,size--单个元素的大小,count--元素个数,stream--提供数据的文件指针    
出口参数:读取的元素个数    
*/
/*    
原型:extern void *memcpy(void *dest, void *src, unsigned int count);    
用法:#include <string.h>    
功能:由src所指内存区域复制count个字节到dest所指内存区域。    
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。    
*/
/*    
ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。    
其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。    
ifreq包含在ifconf结构中。而ifconf结构通常是用来保存所有接口的信息的。    
*/
unsigned   short   ChkSum(unsigned   short   *addr,   int   len)  
{  
   register   int   nleft   =   len;  //register寄存器变量,提高运算速度    
   register   unsigned   short   *w   =   addr;  
   register   int   sum   =   0;  
     unsigned   short   answer   =0;  
     while   (nleft   &gt;   1)  
   {  
         sum   +=   *w++;  
         nleft   -=   2;  
   }  
   if(nleft   ==   1)  
   {              
     *(unsigned   char   *)(&answer)   =   *(unsigned   char   *)w;  
     sum   +=   answer;  
   }  
   sum   =   (sum   &gt;&gt;   16)   +   (sum   &   0xffff);  
   sum   +=   (sum   &gt;&gt;   16);  
   answer   =   ~sum;  
   return(answer);  
}
//tun 工作在三层 ip层  tap工作在以太层    
/*打开tuntap文件描述,返回文件描述符号*/    
int tuntap_create (const char *dev)  //由下面的main函数知输入参数为tun,返回文件描述符号    
{    
  struct ifreq ifr;//ifreq存放接口信息    
  int fd;    
  char *device = "/dev/net/tun";    
  if ((fd = open (device, O_RDWR)) < 0) //创建描述符    
   fprintf(stderr, "Cannot open TUN/TAP dev %s", device);    
  memset (&ifr,0, sizeof (ifr));    
  ifr.ifr_flags = IFF_NO_PI;    
   /*判断参数dev是何种设备*/    
  if (!strncmp (dev, "tun", 3))//比较dev与tun的前三个字符    
  {  
         ifr.ifr_flags |= IFF_TUN;    
  }    
  else if (!strncmp (dev, "tap", 3))    
  {    
       ifr.ifr_flags |= IFF_TAP;    
  }    
  else    
  {    
       fprintf(stderr, "I don't recognize device %s as a TUN or TAP device",dev);    
  }    
   /*判断参数dev是何种设备*/    
 if (strlen (dev) > 3)/* unit number specified? */    
   strncpy (ifr.ifr_name, dev, IFNAMSIZ);
 if(ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)//打开虚拟网卡    
     fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);
  if(ioctl(fd, TUNSETNOCSUM, (void *)&ifr) &lt; 0)//不校验和    
     fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);
 fprintf(stderr, "TUN/TAP device %s opened\n", ifr.ifr_name);    
 return fd;    
}    
/*打开tuntap文件描述,返回文件描述符号*/
//main给的参数ping, buf, sizeof(buf),返回读取元素个数;注意调用玩该参数后buf的值改变来!    
int read_ip_data(char * filepath,char * data, int size)    
{    
   FILE * fh;    
   int rsize;    
   u_short checksum;    
   /*打开ping文件?*/    
   fh = fopen(filepath, "rb");    
   if (fh == NULL)    
   {    
       perror("fopen:");    
       return 0;    
   }    
   /*打开ping文件?*/    
   /*读取ping文件到data中*/    
   rsize = fread(data,1, size, fh);    
   printf("read file %s ,bufsize=%d, readsize=%d\n",filepath, size, rsize);    
   /*读取ping文件到data中*/    
   checksum = ChkSum((u_short *)data, 20);    
   printf("ip head checksum = %x\n",checksum);    
   fclose(fh);    
   return rsize;    
}
int main(int argc, char * argv[])    
{    
   int i;    
   int tun, ret, rsize;    
   unsigned char buf[4096]={'f','y','s'};    
   /*判断次函数的参数个数*/    
   if(argc != 3)    
   {    
       printf("usage:%s inputfile dev\n", argv[0]);    
       return 0;    
   }    
   /*判断次函数的参数个数*/
   tun = tuntap_create(argv[2]);//argv[2]是设备名,这里实验为tun,返回文件描述符号    
   /*文件描述正确否,是否打开?*/    
   if (tun &lt; 0)    
   {    
       perror("tun_create");    
       return -1;    
   }    
   /*文件描述正确否,是否打开?*/    
   /*判断该函数的第一个输入参数以进行不同的实验,即判断是ping实验还是UDP/ICMP实验*/    
   if (!strstr(argv[1],"ping"))//若该函数第一个参数不是ping则执行if内操作    
   {    
       rsize = read_ip_data(argv[1], buf, sizeof(buf));//rsize为读取元素个数    
       while(rsize > 0)    
       {    
           ret = write(tun, buf, rsize);//将buf中数据写到tun中(调用完read_ip_data后buf中有数据!)    
           printf("write %d bytes\n", ret);    
           sleep(1);    
       }    
   }    
   else     //ping实验:即一个终端ping,另一个终端看到tun接收ping数据    
   {    
       while (1)    
       {    
           unsigned char ip[4];    
           ret = read(tun, buf, sizeof(buf));    
           if (ret < 0) break;    
           /*输出格式*/    
           for(i = 0 ; i &lt; ret; i++)    
           {    
               if ( i % 16 == 0 ) printf("\n");    
               printf("%02x ",buf[i]);//按16进制输出,长度不足2,补零    
           }    
           printf("\n");    
           /*输出格式*/    
           /*字符串交换,没看懂*/    
           memcpy(ip, &buf[12], 4);    
           memcpy(&buf[12], &buf[16], 4);    
           memcpy(&buf[16], ip, 4);    
           /*字符串交换,没看懂*/
           buf[20] = 0;    
           *((unsigned short*)&buf[22]) += 8;    
           printf("read %d bytes\n", ret);    
           ret = write(tun, buf, ret);    
           printf("write %d bytes\n", ret);    
       }    
   }    
   /*判断该函数的第一个输入参数以进行不同的实验,即判断是ping实验还是UDP/ICMP实验*/
   return 0;    
}
udprecv.c
#include &lt;string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define MAX_SECTION_LEN 0x1000
/*接收多播数据*/
static int MultiCastRecv(int port, char * multi_ip)
{
   int socket_fd = -1;
   struct sockaddr_in local_addr,source_addr;
   struct ip_mreq mreq;
   int flags,packet_len=0,source_addr_len;
   fd_set  rset;//文件描述符集合,接收多播数据,因此需要多个文件描述符
   struct timeval timeout;
   static char buffer[MAX_SECTION_LEN];
   /*创建无连接套接字*/
   socket_fd = socket(AF_INET,SOCK_DGRAM,0);
   if (socket_fd < 0)
   {
       perror("open socket error!\n");  
       return -1;
   }    
   fprintf(stderr, "Create client socket successfully.\n");    
   /*创建无连接套接字*/
/*
   memset(&source_addr,0,sizeof(source_addr));
     source_addr.sin_family = AF_INET;
     source_addr.sin_addr.s_addr = inet_addr("192.168.203.12"); //htonl(INADDR_ANY);
     source_addr.sin_port = htons(port);    
*/  
   /*绑定本机地址*/
   memset(&local_addr,0,sizeof(local_addr));
     local_addr.sin_family = AF_INET;
     local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
     local_addr.sin_port = htons(port);
   if (bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) &lt; 0)
   {
       perror("bind broadcast address error.");
       return -1;
   }
   fprintf(stderr, "Socket bind successfully.\n");
   /*绑定本机地址*/
   /*加入组播*/
   if (multi_ip)
   {    
       /*转换地址格式*/
       inet_aton(multi_ip, &mreq.imr_multiaddr);//将一个字符串IP地址转换为一个32位的网络序列IP 地址
       mreq.imr_interface.s_addr = htonl(INADDR_ANY);    
       /*转换地址格式*/    
       /*setsockopt允许地址重用*/
       if (setsockopt(socket_fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))&lt; 0)
       {
           perror("setsockopt error.");
           //return -1;
       }
       fprintf(stderr, "Add group successfully.\n");    
       /*setsockopt允许地址重用*/
   }
   /*设置socket为非阻塞模式*/
   flags = fcntl(socket_fd, F_GETFL, 0);
   fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
   fprintf(stderr, "Start while loop for receiving data.\n");
   while(1)
   {
       while(1)
       {
           timeout.tv_sec=0;
           timeout.tv_usec=10000;
           FD_ZERO(&rset);    
           FD_SET(socket_fd, &rset);
           if (select(socket_fd+1, &rset, NULL, NULL, &timeout) &lt; 0)
            {
               if (errno == EINTR)
               {
                   continue;
               }
               else
               {
                   perror("select");
                   return -1;
               }
           }  
           break;
       }
       if (FD_ISSET(socket_fd, &rset))
       {
           source_addr_len = sizeof(source_addr);
           packet_len = recvfrom(socket_fd, buffer, MAX_SECTION_LEN, 0, (struct sockaddr *)&source_addr, &source_addr_len);
//            packet_len = recvfrom(socket_fd, buffer, MAX_SECTION_LEN, 0, NULL, NULL);
           if (packet_len &lt; 0)
           {
               if( (errno != EAGAIN) && (errno != EWOULDBLOCK) )
               {
                   perror("recvfrom");
                   return -1;
               }
           }
           else if (packet_len > 0)
           {
               fprintf(stderr, "src ip=%x,src_port=%d\n", ntohl(source_addr.sin_addr.s_addr), ntohs(source_addr.sin_port));
               fwrite(buffer, packet_len, 1, stdout);
               fflush(stdout);
           }
       }
 }
}
int main(int argc, char * argv[])
{
   /*判断函数参数个数,以执行不同动作*/    
   if (argc == 3)
   {
       MultiCastRecv(atoi(argv[1]), argv[2]);
   }
   else if (argc == 2)
   {
       MultiCastRecv(atoi(argv[1]), NULL);
   }
   else
   {
       fprintf(stderr, "usage:%s port [multi_ip]\n", argv[0]);
   }    
   /*判断函数参数个数,以执行不同动作*/
   return 0;  
}
此实验的几点说明
(1)测试2 UDP
需要将eth0的IP改为192.168.203.132
即ifconfig eth0 192.168.203.132
(2)tuntap.c主要用于创建虚拟网卡,打开tun文件。
(3)udprecv.c主要用于多播实验,因此创建了多个文件描述符。
(4)sockraw.c用于ICMP实验,因为协议类型为ICMP,故需要用原始套接字,SOCK_STREAM和SOCK_DGRAM为TCP和UDP类型的。
VTun 工作原理详解
写在前面:开源项目VTun 短小精悍,涉及到了Linux下网络编程的几乎所有的东西,包括守护程序、信号的处理、服务创建子进程等,实现了虚拟LAN的功能。个人感觉 VTun 唯一的缺憾是在认证方面,没有基于X509证书,而是简陋的在配置文件进行了密钥的设置。虽然在舍弃CA方面,做到了“轻型化”,但留下来极大的安全隐患,这也限制了它的应用,使得其只适合进行一些实验和测试。
我参考了 VTun 源代码和 麻利辉在ibm的developerworks 文章,把自己对 VTun 工作原理做了一个简要的叙述。限于水平,文章难免谬误,仅供参考。欢迎指正!
1. 初始化
这一个阶段主要是服务器和客户端的为下一阶段的通信做各种初始化和准备工作,依次包括建立连接、挑战 / 握手认证、设置 TAP/TUN 设备等。
初始化阶段
初始化阶段
如图可以看出,这一阶段还没有用到虚拟网卡 TAP/TUN 设备,服务器和客户端所有通信均通过物理网卡 eth0 进行,双方会开启 socket 网络套接字进行挑战 / 握手认证双方合法性。注意 :挑战 / 握手所需要的密钥在配置文件 vtund.conf 给出(字段是 pass XXXX ),还有我们会注意到在源代码中的认证还需要 host (在 auth.c 中)
if( !(h = find_host(host)) )
      break;
此处的 host 就是在 vtund.conf 中的服务器端给客户定义的名字(服务器端可以任意取)。
接下来,服务器和客户会根据各自的配置文件设置虚拟网卡的特征,比如是 TUN 还是 TAP ,或者 PIPE 、 TTY ,配置虚拟网卡的网络地址和内核的路由等。
好了,我们假设以上一切顺利了。现在,万事俱备、只欠东风。下面就是重头戏,主角虚拟网卡出场了。
2. 虚拟隧道通信
这一阶段基本进入了正题,服务器端和客户端的应用程序可以使用虚拟网卡进行加密通信了。
虚拟通信
虚拟隧道通信
下面就上图的做一个说明。这里我们假设:
VTun 服务器: IP : eth0 192.168.1.11            tun0 10.0.1.1
VTun 客户端: IP : eth0 192.168.2.22            tun0 10.0.1.2
1. VTun 客户端 的应用程序要和 VTun 服务器的应用程序 进行通信,发往 服务器 的数据报的出口为 tun0 设备。数据包的源 IP 10.0.1.2 ,目的 IP 192.168.1.11 (目的地址也可能为 10.0.1.1,这要看应用程序说提供服务的IP 。如果是 IP 192.168.1.11 ,那么还要有网关gw 10.0.1.1帮忙转发,才可以滴 );
2. 虚线表示 VTun 进程 read 读取 tun0 设备的数据,不走TCP/IP协议栈。而读取的数据为IP包(另,在TAP的是以太网帧 ) ;
3. 隧道封装。VTun 进程对读到的数据进行对称加密,然后 write 写到 socket 套接字描述符,通过物理网卡,发送到通信对端。(数据包的源 IP 192.168.2.22 ,目的 IP 192.168.1.11 ) 。这样一来,就形成了加密隧道包裹的情形了。TUN的是IP-in-IP,TAP的是ETH-in-IP ;
4. 数据包正常路由;
5. 隧道解封装。 在 VTun 服务器, VTun 进程 read 读取 socket 套接字描述符,然后进行对称解密;
6. 虚线表示 VTun 进程将解密后的数据 write 写到 tun0 设备;
7. tun0 设备将数据包交给TCP/IP协议栈处理。或者转发,或者递交到上层应用,比如 VTun 客户端的应用程序收到数据。
总结
   VTun 的一个优点就是在用户区,即使崩溃了也不会影响系统内核。注意:由于封装隧道的缘故,数据包在整个过程中, IP 报头和 TCP 或 UDP 头都没有被修改,因此不惧 NAT 和源路由过滤。因为只是添加了一条到 tun0 设备路由而已,可以说是以最小的代价完成了数据报的 “ 乾坤大挪移 ” 。