TAP/TUN Vnet veth

TAP 设备与 VETH 设备

    TUN/TAP 设备是一种让用户态程序向内核协议栈注入数据的设备,一个工作在三层,一个工作在二层,使用较多的是 TAP 设备。VETH 设备出现较早,它的作用是反转通讯数据的方向,需要发送的数据会被转换成需要收到的数据重新送入内核网络层进行处理,从而间接的完成数据的注入。

图 3 .TAP 设备和 VETH 设备工作过程

    图 3 .TAP 设备和 VETH 设备工作过程

     如图所示,当备一个 TAP 设被创建时,在 Linux 设备文件目录下将会生成一个对应 char 设备,用户程序可以像打开普通文件一样打开这个文件进行读写。当执行 write()操作时,数据进入 TAP 设备,此时对于 Linux 网络层来说,相当于 TAP 设备收到了一包数据,请求内核接受它,如同普通的物理网卡从外界收到一包数据一样,不同的是其实数据来自 Linux 上的一个用户程序。Linux 收到此数据后将根据网络配置进行后续处理,从而完成了用户程序向 Linux 内核网络层注入数据的功能。当用户程序执行 read()请求时,相当于向内核查询 TAP 设备上是否有需要被发送出去的数据,有的话取出到用户程序里,完成 TAP 设备的发送数据功能。针对 TAP 设备的一个形象的比喻是:使用 TAP 设备的应用程序相当于另外一台计算机,TAP 设备是本机的一个网卡,他们之间相互连接。应用程序通过 read()/write()操作,和本机网络核心进行通讯。

    VETH 设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接受的形式出现。该设备不能被用户程序直接操作,但使用起来比较简单。创建并配置正确后,向 其一端输入数据,VETH 会改变数据的方向并将其送入内核网络核心,完成数据的注入。在另一端能读到此数据


TAP口的应用场景:

 

     这是比较标准的网络流程图,报文从网卡被接收上来,当然这里的网卡是被内核给托管了。 也就是说网卡接收的报文都交给内核

进行了处理。内核收到报文后依次走preRoute,LocalIn将报文进行转发或者发到LocalIn。再到用户态被应用程序进行处理。当然,如

果是发送报文的话,那就是将报文走LocalOut,然后如果目的地址是本机就转发到本机,否则走路由,然后postRoute.再进入网卡。

 

这是在PC机上的流程,但是现在的转发设备如交换机,路由器,那就有点不一样了。假如用的是DPDK来接发数据报文。情况就有点不样了。

DPDK对网卡进行了托管,内核没有直接管理网卡。

 如图所以,网卡被DPDK进行管理,DPDK接收和发送网卡的报文。然后报文的转发等处理流程,在用户态进行处理。

当然在这种情况下,如果报文不是从本机发出(指的是,不是由本机的内核态发出),而且接收的时候,也不是由

本机进行接收(意思是目的地址不是本机)。那么这套流程走起来完全是可以的。

    但是你们平时用网页管理那个路由器是怎么个实现法,例如,你访问192.168.1.1,然后就会出现一个路由器配

置的页面(这里是指家用路由器哈,转发用的不是DPDK。。。。但是打个比方)。很明显,路由器上apache 的流量

是从路由器发出,也会接收流量。那这个如何实现。

    这里就会用到TAP口了。


于是就变成了这个样子,也就是说,当用DPDK把这些报文都收上来,然后对目的IP进行判断(这部分在用户态)。

如是到本机的报文,那么它就交给TAP口,然后TAP收到这个报文后,就会走preRoute -----> LocalIn----->用户态。

然后用户态对应的socket就会收到你发的报文里面的内容。

     如果是Apache产生的信息,那么它当然会调用socket进行发送了,也就是会先进入内核,但是无法直接进入网卡了。

现在只能通过TAP口进入用户态,然后在用户态进行转发,再用DPDK发送到对应的网卡。

    使用 TAP 设备的应用程序相当于另外一台计算机,TAP 设备是本机的一个网卡,他们之间相互连接。应用程序通过 read()/write()操作,和本机网络核心进行通讯

那么我们直接用TAP口转发报文和TAP口绑定的虚拟网卡上用socket上发报文有什么区别呢?

    

 我上图,我们可以看到, Virtual NIC 就是我们的虚拟网卡,那么如果从TAP口进行报文发送的时候,

我们说了,TAP的使用者就相当于远程机器的网卡,也就是说报文会从虚拟网卡进入到协议栈,然后走内核

的转发流程进行转发(如果是本机就再去LocalIn点),否则就通过forward 到postRoute节点再次进入到TAP口,被

TAP接收后到应用层进行再次转发出去。

但是如果是通过Virtual NIC的socket发送消息就不同了,会先进入到内核,然后进入到转发面(LocalOut->forward),如果是本机发给本机的,报文就会进入到LocalIn节点,再被socket接收。

    如果是非本机的,就会通过postRoute发出去,再进入到TAP口被应用层转发面走转发流程。

一 图例

二 图解
1 将vm的网卡和host OS的网卡连接在一起。
2 通过tun/tap adapter,会在host OS上生成虚拟网卡tap。
3 tun是点对点的网络设备,使得vm的网卡和tap虚拟网卡成为一对。
4 从vm网卡发出的所有网络包,host OS的tap0都能收到,最终通过eth0发出。
5 虚拟机将网络包通过字符设备写入/dev/net/tun(host OS上的一个文件)。
6 字符设备驱动将数据包写入虚拟网卡驱动。
7 虚拟网卡驱动将包通过tcp/ip协议栈写入host OS上的虚拟网卡tap0.
8 在host OS通过路由规则(通过网桥(东西向流量)、网桥和路由器(南北向流量)),包从ech0出去。

三 原理
TAP/TUN是Linux内核实现的一对虚拟网络设备,TAP工作在二层,TUN工作在三层,Linux内核通过TAP/TUN设备向绑定该设备的用户空间程序发送数据,反之,用户空间程序也可以像操作硬件网络设备那样,通过TAP/TUN设备发送数据。
基于TAP驱动,即可以实现虚拟网卡的功能,虚拟机的每个vNIC都与hypervisor中的一个TAP设备相连。当一个TAP设备被创建时,在Linux设备文件目录下生会生成一个对应的字符设备文件,用户程序可以打开普通文件一样打开这个文件进行读写。
当对这个TAP设备文件执行write()操作时,对于Linux网络子系统来说,相对于TAP设备收到了数据,并请求内核接受他,Linux内核收到此数据后将根据网络配置进行后续处理,处理过程类似于普通的物理网卡从外界收到数据。(VM向外发数据)。
当用户程序执行read()请求时,相对于向内核查询TAP设备上是否有数据需要被发送,有的话则取出到用户程序里,从而完成TAP设备发送数据的功能。(VM接收数据)。
在这个过程中,TAP设备可以被当做本机的一个网卡,而操作TAP设备应用程序相对于另外一台VM,它通过read/write系统调用,和本机进行网络通信。
VETH设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接收的形式出现。创建并配置正确后,向其一端输入数据,VETH会改变数据的方向并将其送入内核网络子系统,完成数据的注入,而在另一端则能读到此数据。

四 veth、tun、tap比对
1 图例

2 图解
tun:点对点的设备,tun设备模拟网络层设备,处理三层报文,如IP报文。tun设备完全不需要物理地址的。它收到和发出的包不需要arp,也不需要有数据链路层的头。
tap:是一个普通的以太网设备,tap设备模拟链路层设备,处理二层报文,比如以太网帧。tap设备需要有完整的物理地址和完整的以太网帧
TUN用于路由,而TAP用于创建网桥。
--------------------- 
作者:cakincqm 
来源:CSDN 
原文:https://blog.csdn.net/chengqiuming/article/details/79840092 
版权声明:本文为博主原创文章,转载请附上博文链接!

3.  Tap/TunDevice在libvirt中的应用
•  将guest system的网络和hostsystem的网络连在一起。

•  通过TUN/TAPadapter,会生成一个在host system上的虚拟网卡tap

•  tun建立了point to point的网络设备,使得guest system的网卡和tap虚拟网卡成为一对

•  从而guestsystem的所有网络包,host system都能收到。

Tun/tap驱动程序中包含两个部分,一部分是字符设备驱动,还有一部分是网卡驱动部分

•       虚拟机将网络包通过字符设备写入/dev/net/tun(Host上);

•       字符设备驱动将数据包写入虚拟网卡驱动;

•       虚拟网卡驱动将包通过TCP/IP协议栈写给Host上的虚拟网卡tap0;

•       在HOST上通过路由规则(通过网桥(东西向流量)、网桥和路由器(南北向流量)),包从eth0出去。

4.  Linux全虚拟机网络桥接模型
KVM 客户机网络连接有两种方式:

用户网络(User Networking):让虚拟机访问主机、互联网或本地网络上的资源的简单方法,但是不能从网络或其他的客户机访问客户机,性能上也需要大的调整。NAT方式。
    虚拟网桥(Virtual Bridge):这种方式要比用户网络复杂一些,但是设置好后客户机与互联网,客户机与主机之间的通信都很容易。Bridge方式。  
    Bridge方式即虚拟网桥的网络连接方式,是客户机和子网里面的机器能够互相通信。可以使虚拟机成为网络中具有独立IP的主机。桥接网络(也叫物理设备共享)被用作把一个物理设备复制到一台虚拟机。网桥多用作高级设置,特别是主机多个网络接口的情况。

在Linux中的虚拟机的网卡都包含前半段和后半段,前半段在虚拟机上,后半段在宿主机上。上图eth0为虚拟机上的网卡,对应的后半段为vnet0,vnet0为tap设备。在虚拟机上所有发往eth0的数据就直接发往vnet0了,也可以将vnet0看作一块网卡。

  在宿主机中创建一个桥设备,把宿主机的eth0放在桥上,这样虚拟机上的eth0将报文发给vnet0,再直接发给宿主机上的eth0,将源地址改为宿主机上的eth0的地址

当响应报文到达物理机上的eth0时如何判断此响应报文是发给虚拟机的还是物理机自己的?

物理机会先创建一个虚拟网卡,在物理机上打开混杂模式(无论mac地址是不是自己的都将接收响应报文),如果mac地址是自己的则转发给虚拟网卡,如果不是自己的则转发给vnet0,这就是桥接模型,因为物理机的网卡具有桥的功能所以叫做桥接模型。

5.  Qemu全虚拟化网桥模式下的VM的收发包的流程

如图中所示,红色箭头表示数据报文的入方向,步骤:

网络数据从 Host 上的物理网卡接收,到达网桥;
由于 eth0 与 tap1 均加入网桥中,根据二层转发原则,br0 将数据从 tap1 口转发出去,即数据由 Tap设备接收;
Tap 设备通知对应的 fd 数据可读;
fd 的读动作通过 tap 设备的字符设备驱动将数据拷贝到用户空间,完成数据报文的前端接收。
6.  准虚拟化(Para-virtualizaiton) I/O 驱动 virtio
KVM/QEMU 的 vitio 实现采用在 Guest OS 内核中安装前端驱动 (Front-end driver)和在 QEMU 中实现后端驱动(Back-end)的方式。前后端驱动通过 vring 直接通信,这就绕过了经过 KVM 内核模块的过程,达到提高 I/O 性能的目的。纯软件模拟的设备和 Virtio 设备的区别:virtio 省去了纯模拟模式下的异常捕获环节,Guest OS 可以和 QEMU 的I/O 模块直接通信。Vitio需要让客户机知道自己运行在虚拟化环境中。

使用 Virtio 的完整虚拟机 I/O流程: 

 

Host 数据发到 Guest:

1. KVM 通过中断的方式通知 QEMU 去获取数据,放到 virtio queue 中

2. KVM 再通知 Guest 去 virtio queue 中取数据。

7.  Vhost-net驱动:
virtio在宿主机中的后端处理程序(backend)一般是由用户空间的QEMU提供的,然而如果对于网络 I/O 请求的后端处理能够在在内核空间来完成,则效率会更高,会提高网络吞吐量和减少网络延迟。在比较新的内核中有一个叫做 “vhost-net” 的驱动模块,它是作为一个内核级别的后端处理程序,将virtio-net的后端处理任务放到内核空间中执行(在Host Kernel中直接实现了virtio设备的模拟),减少内核空间到用户空间的切换,从而提高效率。

Virtio-net和vhost-net的比较:

 

备注:当客户机通过DMA (Direct Memory Access)访问大块I/O时,QEMU 模拟程序将不会把结果放进共享页中,而是通过内存映射的方式将结果直接写到客户机的内存中,然后通知KVM模块告诉客户机DMA操作已经完成。

8.  结论:
综上,虚拟机网卡eth0存在于虚拟机上(即guest),网桥tap设备则存在于宿主机(即host)上,是两个设备。这两个设备是一对设备,同时出现、同时消失,组合在一起完成虚拟机和外部的通信。两个设备通过host上的/dev/net/tun设备、内核网卡驱动、共享队列实现数据包的通信。
--------------------- 
作者:xiakewudixl 
来源:CSDN 
原文:https://blog.csdn.net/xiakewudi/article/details/76851076 
版权声明:本文为博主原创文章,转载请附上博文链接!

发布了25 篇原创文章 · 获赞 130 · 访问量 74万+
展开阅读全文

如何在Tun / Tap接口上读取TCP数据包?

04-22

<div class="post-text" itemprop="text"> <p>I'm working on a simple project that listens on a tun interface and modified the packets then re-sends them to the real interface.</p> <p>I have tried <a href="https://github.com/songgao/water" rel="nofollow noreferrer"><code>songgao/water</code></a>, <a href="https://github.com/pkg/taptun" rel="nofollow noreferrer"><code>pkg/tuntap</code></a> and even writing my own based on some C code floating around but no matter what I tried, I can't receive TCP packets (ICMP/UDP works fine).</p> <p>I feel like i'm missing something extremely obvious but I can't figure it for the life of me...</p> <p>The <a href="https://gist.github.com/OneOfOne/5368f638f7df1035862659a7bec6ca2f" rel="nofollow noreferrer">code</a>:</p> <pre><code>package main import ( "log" "os" "os/exec" "golang.org/x/net/ipv4" "github.com/songgao/water" ) const ( // I use TUN interface, so only plain IP packet, no ethernet header + mtu is set to 1300 BUFFERSIZE = 1600 MTU = "1300" ) func main() { iface, err := water.New(water.Config{}) fatalIf(err) log.Printf("tun interface: %s", iface.Name()) runBin("/bin/ip", "link", "set", "dev", iface.Name(), "mtu", MTU) runBin("/bin/ip", "addr", "add", "10.2.0.10/24", "dev", iface.Name()) runBin("/bin/ip", "link", "set", "dev", iface.Name(), "up") buf := make([]byte, BUFFERSIZE) for { n, err := iface.Read(buf) if err != nil { log.Fatal(err) } header, _ := ipv4.ParseHeader(buf[:n]) log.Printf("isTCP: %v, header: %s", header.Protocol == 6, header) } } func fatalIf(err error) { if err != nil { log.Fatal(err) } } func runBin(bin string, args ...string) { cmd := exec.Command(bin, args...) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin fatalIf(cmd.Run()) } </code></pre> </div> 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览