当前位置: 首页 > Linux

Linux虚拟网络设备之tun-tap

时间:2023-04-06 06:39:44 Linux

在现在的云时代,虚拟机和容器无处不在,其背后的网络管理离不开虚拟网络设备,所以了解虚拟网络设备有利于我们更好的理解网络结构在云时代。从本文开始,我们将介绍Linux下的虚拟网络设备。虚拟设备和物理设备的区别在Linux网络数据包的接收过程和数据包的发送过程两篇文章中,介绍了数据包的发送和接收过程。众所周知,Linux内核中有一个网络设备管理层,它位于网络设备驱动程序和协议栈之间,负责桥接它们之间的数据交互。驱动程序不需要知道协议栈的细节,协议栈也不需要知道设备驱动程序的细节。对于网络设备来说,就像管道(pipe)一样,有两端,从任何一端接收到的数据都会从另一端发送出去。例如物理网卡eth0,它的两端分别是内核协议栈(通过内核网络设备管理模块间接通信)和外部物理网络,从物理网络接收到的数据会转发给内核协议栈,而应用程序从协议栈发出的数据,会通过物理网络发送出去。那么虚拟网络设备呢?首先,它也是由内核的网络设备管理子系统管理的。对于Linux内核网络设备管理模块,虚拟设备和物理设备没有区别。它们都是网络设备,可以配置IP。来自网络设备的数据将被转发。到协议栈,来自协议栈的数据也会由网络设备发送。至于怎么发送,发送到哪里,那是设备驱动的事情,和Linux内核无关,所以说虚拟网络设备协议栈的一端也是协议栈,而另一端是什么取决于虚拟网络设备的驱动程序实现。tun/tap的另一端是什么?说话先看图:+-------------------------------------------------------------+|||+--------------------++--------------------+|||用户应用程序A||用户应用程序B|<-----+||+------------------------++----------------+||||1|5|||......................|......................................|..................................|.......||↓↓||+---------++---------+||||插座A||插座B||||+---------++---------+||||2|6|||...............|.......|.......|.......||↓↓|||+------------------------+4||||网络协议栈||||+--------------------------+||||7|3|||................|....................|.............................|.......||↓↓|||+--------------++----------------+||||eth0||tun0||||+------------------++----------------+|||10.32.0.11||192.168.3.11||||8+----------------------+||||+----------------|--------------------------------------------+↓PhysicalNetwork上图中有两个应用程序A和B,都在用户层,而其他套接字、协议栈(NewworkProtocolStack)和网络设备(eth0和tun0)部分在内核层。实际上,套接字是协议栈的一部分。这里把它分开的目的是为了更直观。tun0是一个Tun/Tap虚拟设备。从上图我们可以看出它和物理设备eth0的区别,虽然它们的一端连接的是协议栈,但是另一端就不一样了,eth0的另一端是一个物理网络,这个物理网络可能是交换机,tun0的另一端是用户层程序,协议栈发送给tun0的数据包可以被这个应用程序读取,应用程序可以直接向tun0写入数据。这里假设eth0配置的IP为10.32.0.11,tun0配置的IP为192.168.3.11。这里是一个tun/tap设备的典型应用场景,通过程序B向192.168.3.0/24网络发送数据,隧道使用10.32.0.11发送到远程网络的10.33.0.1,然后10.33.0.1转发给相应的设备,从而实现VPN。我们看一下数据包的流程:应用程序A是一个普通程序,通过socketA发送一个数据包,假设数据包的目的IP地址为192.168.3.1socket将数据包丢给协议栈根据数据包的目的IP地址,匹配本地的路由规则,知道数据包应该从tun0出去,所以把数据包交给tun0。tun0收到数据包后发现另一端被进程B打开,于是将数据包丢给进程B进程B收到数据包后做一些业务相关的处理,然后构造一个新的数据包,嵌入新数据包中的原始数据包,最后通过socketB转发数据包。此时新数据包的源地址变成了eth0的地址,目的IP地址变成了另外一个地址,比如10.33.0.1.socketB将数据包丢到协议栈。协议栈根据本地路由发现这个数据包应该是通过eth0发送出去的,于是将数据包交给eth0eth0通过物理网络发送数据包10.33.0.1收到数据包后,会打开数据包,读取里面的原始数据包,转发给本地192.168.3.1,收到192.168.3.1的响应后,构造一个新的响应包,将原来的响应包封装在里面,然后返回通过原路径到应用B,应用B取出包内的原始响应,最后返回给应用A。这里不讨论Tun/Tap设备tun0如何与用户级进程B通信。对于Linux内核,有很多方法可以让内核空间和用户空间的进程交换数据。从上面的过程可以看出,数据包选择走哪个网络设备完全是由路由表控制的,所以如果我们想让部分网络流量经过应用B的转发过程,就需要对路由表进行配置让这部分数据通过tun0。tun/tap设备有什么用?从上面描述的过程可以看出,tun/tap设备的目的是将协议栈中的一些数据包转发给用户空间的应用程序,给用户空间的程序一个处理的机会数据包。因此,常用的数据压缩、加密等功能都可以在应用B中实现。tun/tap设备最常用的场景是VPN,包括应用层的tunnel和IPSec。比较有名的项目是VTun。有兴趣的可以去了解一下。tun和tap的区别用户层程序只能通过tun设备读写IP数据包,但是可以通过tap设备读写链路层数据包,类似于普通socket和rawsocket的区别,格式处理数据包的方式不同。Samplesampleprogram下面是一个编写的程序。tun设备收到数据包后,只打印出收到了多少字节的数据包,其他什么都不做。如何编程请参考以下参考链接。#include#include#include#include#include#include#include#include#includeinttun_alloc(intflags){structifreqifr;intfd,错误;char*clonedev="/dev/net/tun";如果((fd=open(clonedev,O_RDWR))<0){返回fd;}memset(&ifr,0,sizeof(ifr));ifr.ifr_flags=标志;如果((err=ioctl(fd,TUNSETIFF,(void*)&ifr))<0){关闭(fd);返回错误;}printf("打开tun/tap设备:%s用于读取...\n",ifr.ifr_name);返回fd;}intmain(){inttun_fd,nread;字符缓冲区[1500];/*标志:IFF_TUN-TUN设备(无以太网头)*IFF_TAP-TAP设备*IFF_NO_PI-不提供数据包信息*/tun_fd=tun_alloc(IFF_TUN|IFF_NO_PI);如果(tun_fd<0){perror("Allocatinginterface");exit(1);}while(1){nread=read(tun_fd,buffer,sizeof(buffer));if(nread<0){perror("Readingfrominterface");close(tun_fd);exit(1);}printf("Read%dbytesfromtun/tapdevice\n",nread);}return0;}Demo#------------------------第一个shell窗口--------------------#将上面的程序保存为tun.c,然后编译dev@debian:~$gcctun.c-otun#启动tun程序,程序会新建一个tun设备,#程序会阻塞在这里,等待数据包的到来dev@debian:~$sudo./tunOpentun/tapdevicetun1forreading...Read84bytesfromtun/tapdeviceRead84bytesfromtun/tapdeviceRead84bytesfromtun/tapdeviceRead84bytesfromtun/tapdevice#------------------------第二个shell窗口--------------------#启动抓包程序,抓包通过tun1#tcpdump-itun1tcpdump:详细输出被抑制,使用-v或-vv在tun1上进行完整协议解码侦听,链接类型RAW(原始IP),捕获大小262144字节19:57:13.473101IP192.168.3.11>192.1268。3:ICMPechore任务,id24028,seq1,长度6419:57:14.480362IP192.168.3.11>192.168.3.12:ICMP回显请求,id24028,seq2,长度6419:57:15.488246IP6192.168.1.3.246IP68.192.31.62.请求,id24028,seq3,长度6419:57:16.496241IP192.168.3.11>192.168.3.12:ICMPecho请求,id24028,seq4,长度64#--------------------------第三个shell窗口--------------------#./tun启动,通过iplink命令你会发现系统中多了一个tun设备,#在我的测试环境中,多出来的设备名称是tun1,在你的环境中可能叫tun0#新设备没有ip,我们给tun1分配一个IP地址firstdev@debian:~$sudoipaddradd192.168.3.11/24devtun1#默认情况下tun1没有启动,使用以下命令启动tun1dev@debian:~$sudoiplinksettun1up#尝试ping192.168.3.0/24网段IP,#按照默认路由,数据包会去tun1设备,#因为我们的程序收到数据包后什么都没做,相当于丢弃数据包,#所以这里ping根本收不到返回包,#但是在前两个窗口中,可以看到这里发送了4个icmpechorequest包,#说明数据包正确发送给了应用程序,但是应用程序没有处理数据包dev@debian:~$ping-c4192.168.3.12PING192.168.3.12(192.168.3.12)56(84)bytesofdata.---192.168.3.12pingstatistics---4packetstransmitted,0received,100%丢包,时间3023ms结语通常我们使用tun/tap设备的机会不多,但是由于其结构比较简单,用它来了解虚拟网络设备也不错,后续对更复杂的虚拟网络设备的了解也不错Linux下(如网桥)参考UniversalTUN/TAPdevicedriverTun/Tap接口教程打下基础