入门

TUN/TAP是操作系统内核中的虚拟网络设备,可以完成用户空间与内核空间的数据的交互。网络协议栈中的数据通过该设备可以进入到用户空间中,而用户空间中的程序通过该设备空间进入到内核空间的网络协议栈。

TUN模拟的是三层设备,操作三层的数据包,而TAP模拟的二层设备,操作二层的数据包。

物理网卡与虚拟网卡的区别是,物理网卡是外界与内核空间的网络协议栈数据交互的门户,而虚拟网卡是用户空间和内核空间交互的门户。

/dev/net/tun​是linux提供的字符设备,写入该设备的数据会发送到虚拟网卡中,而发送到虚拟网卡中的数据也会出现在字符设备中。

应用程序通过tun设备获取ping数据包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQ4lwibx-1685070382243)(https://gcore.jsdelivr.net/gh/tenqaz/BLOG-CDN@main/无标题-2023-05-13-2359-20230514000046-x1gjzuq.png)]

app程序通过打开tun字符设备创建出tun虚拟网卡。然后通过ping命令发送ICMP数据包到网络协议栈中,这个过程是从用户空间到内核空间,再通过路由将数据包转发到tun虚拟网卡中,因为tun网卡特性,会进入到打开该tun设备用户空间app程序中。

app程序代码如下:

import os
import struct
from fcntl import ioctlBUFFER_SIZE = 4096# 完成虚拟网卡的注册
TUNSETIFF = 0x400454ca# 设备模式
IFF_TUN = 0x0001
IFF_TAP = 0x0002def create_tunnel(tun_name='tun%d', tun_mode=IFF_TUN):# 以读写的方式打开字符设备tun,获取到设备描述符tun_fd = os.open("/dev/net/tun", os.O_RDWR)# 对该设备进行配置,设备名称和设备模式。ifn = ioctl(tun_fd, TUNSETIFF, struct.pack(b"16sH", tun_name.encode(), tun_mode))# 获取到设备名称tun_name = ifn[:16].decode().strip("\x00")return tun_fd, tun_namedef main():tun_fd, tun_name = create_tunnel()while True:data = os.read(tun_fd, BUFFER_SIZE)print(f"get data from tun. data size = {len(data)}")if __name__ == '__main__':main()

运行后输出:

# python3 tun_demo.py
Open tun/tap device: tun0 for reading...

通过ip a​命令发现tun设备已经创建,但其状态为DOWN

# ip a | grep -C tun
45: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 500link/none

对其设置一个ip并将它状态设置为UP

ip a add 192.37.1.2/24 dev tun0
ip link set tun0 up

配置好ip后,会发现自动配置了如下路由:

...
192.37.1.0/24 dev tun0 proto kernel scope link src 192.37.1.2
...

再次查看tun设备,发现已经配置好

# ip a | grep tun0
45: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500inet 192.37.1.1/24 scope global tun0

并且这时会发现tun0已经接收到了3个数据包

# python3 tun_demo.py
Open tun/tap device: tun0 for reading...
get data from tun. data size = 52
get data from tun. data size = 52
get data from tun. data size = 52

这时候使用tcpdump监听tun0,执行ping 192.37.1.2,是有回包的,但是tcpdump却没有抓到任何包。

ping命令会根据目标IP地址和子网掩码来判断数据包的目的地,如果目的地在本地网络中,ping命令会直接将数据包发送到本地网络,而不是通过TUN设备发送。

# tcpdump -i tun0 -n
...# ping 192.37.1.2 -c 3
PING 192.37.1.2 (192.37.1.2) 56(84) bytes of data.
64 bytes from 192.37.1.2: icmp_seq=1 ttl=64 time=0.148 ms
64 bytes from 192.37.1.2: icmp_seq=2 ttl=64 time=0.114 ms
64 bytes from 192.37.1.2: icmp_seq=3 ttl=64 time=0.316 ms--- 192.37.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.114/0.192/0.316/0.089 ms

改为ping 192.37.1.3,可以看到程序收到了收到了数据包,tcpdump也抓到了包,但是因为没有做任何的处理也没有回包,所以ping命令看到不到回包。

# python3 tun_demo.py
Open tun/tap device: tun0 for reading...
get data from tun. data size = 52
get data from tun. data size = 52
get data from tun. data size = 52get data from tun. data size = 88
get data from tun. data size = 88
get data from tun. data size = 88# tcpdump -i tun0 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
22:56:41.018149 IP 192.37.1.2 > 192.37.1.3: ICMP echo request, id 22559, seq 1, length 64
22:56:42.018871 IP 192.37.1.2 > 192.37.1.3: ICMP echo request, id 22559, seq 2, length 64
22:56:43.022732 IP 192.37.1.2 > 192.37.1.3: ICMP echo request, id 22559, seq 3, length 64

使用tun设备完成基于UDP的容器跨节点通信

使用tun设备基于UDP完成容器跨节点通信。如下图所示:

通信流程是,在Node1中的NS1进行ping Node2中NS2的veth0网卡的IP,ICMP的IP包会通过veth0到达veth1中,并进入到宿主机的网络协议栈,通过路由配置达到tun设备,这时app服务从tun设备中读取到IP包数据,然后将其封装在UDP包中,并通过eth0网卡发送到Node2的eth0网卡上,通过网络协议栈解包达到app程序中,拿到里面的IP包,将其写入到tun设备中,进入到网络协议栈中,通过路由达到veth1中,然后到达net ns1的veth0网卡。

app程序简单实现如下:

import os
import socket
import struct
import threading
from fcntl import ioctl
import clickBIND_ADDRESS = ('0.0.0.0', 7000)
BUFFER_SIZE = 4096TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002def create_tunnel(tun_name='tun%d', tun_mode=IFF_TUN):tun_fd = os.open("/dev/net/tun", os.O_RDWR)ifn = ioctl(tun_fd, TUNSETIFF, struct.pack(b"16sH", tun_name.encode(), tun_mode))tun_name = ifn[:16].decode().strip("\x00")return tun_fd, tun_namedef start_tunnel(tun_name):os.popen(f"ip link set {tun_name} up")def udp_server(udp_socket, tun_fd):while True:data, addr = udp_socket.recvfrom(2048)print("get data from udp.")if not data:breakos.write(tun_fd, data)@click.command()
@click.option("--peer_node_ip", "-p", required=True, help="对端节点IP")
def main(peer_node_ip):peer_node_addr = (peer_node_ip, 7000)tun_fd, tun_name = create_tunnel()start_tunnel(tun_name)udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)udp_socket.bind(BIND_ADDRESS)t = threading.Thread(target=udp_server, args=(udp_socket, tun_fd))t.daemon = Truet.start()while True:data = os.read(tun_fd, BUFFER_SIZE)print(f"get data from tun. data size = {len(data)}")udp_socket.sendto(data, peer_node_addr)if __name__ == '__main__':main()

在Node1中运行该程序,设置Node2 IP

python3 tun_app.py -p 10.65.132.187

可以看到已经创建了tun设备

# ip link show tun0
...
109: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500link/none inet6 fe80::8e98:91a4:6537:d77a/64 scope link flags 800 valid_lft forever preferred_lft forever
...

在Node1中创建Network Namespace命名为net1,使用它来完成模拟容器网络。

ip netns add net1

然后创建veth pair,它们是一对网卡,分别为命名为veth0和veth1

ip link add veth0 type veth peer name veth1

将其一端接入到net1中,并设置好其IP地址为10.1.1.2/24

ip link set dev veth0 netns net1
ip netns exec net1 ip addr add 10.1.1.2/24 dev veth0
ip netns exec net1 ip link set dev veth0 up

开启在宿主机上的veth1网卡,并设置其IP为10.1.1.1/24

ip a add 10.1.1.1/24 dev veth1
ip link set dev veth1 up

再将net1中的默认路由设置成都走veth0,这样,ping Node2中net2的网络包可以到veth1中,也就进入到了宿主机的网络协议栈中。

ip netns exec net1 ip r add default via 10.1.1.1 dev veth0

在宿主机上还需要添加路由,访问Node2中net2时都路由到tun0设备

ip r add 10.1.2.0/24 dev tun0

这时,在Node1 net1中ping Node2 net2时,正常来说是可以在app中看到从tun收到IP包的,虽然没有回包,那是因为app程序收到包后没有做任何回包操作。

# ip netns exec net1 ping 10.1.2.2 -c 3
PING 10.1.2.2 (10.1.2.2) 56(84) bytes of data.
--- 10.1.2.2 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2001ms

我们通过tcpdump抓取veth1网卡,可以看到收到了ARP请求,想要获取10.1.2.2的MAC地址,但是一直获取不到,所以导致IP包无法通过路由达到TUN设备

# tcpdump -i veth1 -n
00:45:13.988076 ARP, Request who-has 10.1.2.2 tell 10.1.1.2, length 28

这个时候需要开启veth1的arp代理,将veth1的MAC地址作为ARP的回复。

echo 1 >  /proc/sys/net/ipv4/conf/veth1/proxy_arp

再次ping Node2 net2时,可以看到tcpdump看到ARP中回复的MAC地址为veth1的地址。

# tcpdump -i veth1 -n
00:45:13.988076 ARP, Request who-has 10.1.2.2 tell 10.1.1.2, length 28
00:45:13.988100 ARP, Reply 10.1.2.2 is-at 4e:7c:bf:fe:4d:0f, length 28# ip a | grep -C 3 veth1
...
107: veth1@if108: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000link/ether 4e:7c:bf:fe:4d:0f brd ff:ff:ff:ff:ff:ff link-netnsid 3inet 10.1.1.1/24 scope global veth1valid_lft forever preferred_lft forever
...

并且app程序中也从tun设备中获取到了IP包。

# python3 tun_app.py -p 10.65.132.187
get data from tun. data size = 52
get data from tun. data size = 52
get data from tun. data size = 52
get data from tun. data size = 88
get data from tun. data size = 88
get data from tun. data size = 88

到这一步,Node1的基本配置完成,接下来配置Node2,配置的方法与Node1一致,在Node2执行命令如下:

# 开启app程序
python3 tun_app.py -p 10.61.74.37# 新增network namespace net2
ip netns add net2# 新增veth pair设备
ip link add veth0 type veth peer name veth1# 配置veth pair设备
ip link set dev veth0 netns net2
ip netns exec net2 ip addr add 10.1.2.2/24 dev veth0
ip netns exec net2 ip link set dev veth0 upip a add 10.1.2.1/24 dev veth1
ip link set dev veth1 up# 添加默认路由
ip netns exec net2 ip r add default via 10.1.2.1 dev veth0# 添加tun0设备路由
ip r add 10.1.1.0/24 dev tun0# 开启arp代理
echo 1 >  /proc/sys/net/ipv4/conf/veth1/proxy_arp

配置完成后,在Node1的net1中ping Node2的net2,可以ping通有回包。

# ip netns exec net1 ping 10.1.2.2
PING 10.1.2.2 (10.1.2.2) 56(84) bytes of data.
64 bytes from 10.1.2.2: icmp_seq=1 ttl=62 time=5.46 ms
64 bytes from 10.1.2.2: icmp_seq=2 ttl=62 time=4.67 ms
64 bytes from 10.1.2.2: icmp_seq=3 ttl=62 time=5.52 ms

巨人的肩膀

  • Linux Tun/Tap 介绍

  • 理解 Linux 虚拟网卡设备 tun/tap 的一切

  • 基于tun设备实现在用户空间可以ping通外部节点(golang版本)

  • 一起动手写一个VPN

欢迎关注,互相学习,共同进步~

我的个人博客
公众号:编程黑洞

理解Linux TunTap设备相关推荐

  1. linux设备usb节点和硬件接口,所谓设备驱动即驱使硬件设备行动,带你深入理解linux的设备驱动......

    原标题:所谓设备驱动即驱使硬件设备行动,带你深入理解linux的设备驱动... 设备驱动最通俗的解释就是"驱使硬件设备行动".操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏 ...

  2. 从需求的角度去理解Linux系列:总线、设备和驱动

    <从需求的角度去理解Linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文章,也是从需求的角度去理解Linux系统软件的开篇.这是作者精心撰写的经验总结,希望嵌 ...

  3. Linux总线设备驱动框架的理解(非常棒的文章!)

    以下内容源于微信公众号:嵌入式企鹅圈.有格式内容上的修改,如有侵权,请告知删除. Linux的设备驱动框架,即某类设备对应的驱动的框架. 这里是"Linux总线设备驱动框架",应该 ...

  4. 进一步理解Linux操作系统的块设备

    在前文<理解Linux操作系统的块设备>中我们从比较高层面(Hight Level)介绍了块设备的原理和块设备的特性.但是关于Linux操作系统块设备的实现原理可能还一知半解.本文将进一步 ...

  5. 2022 年了,重新理解一波设备驱动 | Linux 驱动

    哈喽,我是老吴. 非常怀念写文章的感觉. 昨晚复习了一些 Linux 驱动的基础知识,给大家分享一下吧. 先说结论: 多年来,我接触到的 Linux 驱动教程大多都是从 0 编写,这样对初学者而言最大 ...

  6. 深入理解 Linux Cgroup 系列(二):玩转 CPU

    原文链接:深入理解 Linux Cgroup 系列(二):玩转 CPU 上篇文章主要介绍了 cgroup 的一些基本概念,包括其在 CentOS 系统中的默认设置和控制工具,并以 CPU 为例阐述 c ...

  7. 网络设备中的linux,理解linux虚拟网络设备veth

    原标题:理解linux虚拟网络设备veth 前面介绍了linux network namespace,接着介绍一下如何让一个独立的网络命名空间和主机的网络互通,这里我们需要用到linux虚拟网络设备v ...

  8. 理解Linux的性能

    项目中常遇到需要对目前运行的系统进行效率分析,或碰到客户咨询如何优化系统的效率问题.更多的情况是,在系统出现问题的时候,需要分析原因,定位系统故障或瓶颈,当然,最好是可以一并解决故障.但实际上,操作系 ...

  9. linux 统一设备模型 pci,Linux设备驱动模型摘抄

    Linux设备驱动模型摘抄Linux设备驱动模型摘抄Linux设备驱动模型摘抄Linux设备驱动模型摘抄Linux设备驱动模型摘抄 Linux设备驱动模型摘抄(1) Linux统一设备模型 简介 Li ...

最新文章

  1. jemeter多场景混合案例_Jmeter多业务混合场景如何设置各业务所占并发比例
  2. 一个关于在Fedora下安装jdk的问题
  3. mysql 分区表 归档_MySQL分区表
  4. exhaustion java_Java Exceptions
  5. html5基础知识点字体属性
  6. 昨日万圣节ABAP怪兽级代码谜团,公布答案啦
  7. java多线程内存模型_Java多线程内存模型
  8. bzoj1211: prufer序列 | [HNOI2004]树的计数
  9. DAOSquare将于今晚9点-12点进行两轮ITO
  10. 学python电脑硬件_Python实现的读取电脑硬件信息功能示例
  11. git报错之fatal: protocol error: bad line length character: No This
  12. 雅虎卖身不影响梅耶尔赚钱 她总薪酬2.2亿美元
  13. “/”应用程序中的服务器错误(System.Data.OleDb.OleDbException: 操作必须使用一个可更新的查询)
  14. 程序员头发都是怎么没的?第二个原因扎心了!
  15. 对话海尔CEO张瑞敏
  16. Python3爬虫(4)--抓取考生的四六级成绩
  17. 【老生谈算法】matlab实现功率谱密度算法源码——功率谱密度
  18. QQ浏览器9 主页无法修改成功的解决办法
  19. 计算机中的首地址是多少,物理首地址是什么
  20. 作业11 最优前缀编码

热门文章

  1. 使用静态内部类单例模式创建自定义线程池
  2. 坚果Pro 2安抚了不少人锤粉, 但用户更期待锤子T3
  3. mysql批量删除多条记录的sql语句_一次删除多条记录的sql语句
  4. 旷视科技face++ AI工程师面经·
  5. 项目中的图片跨域问题解决方式
  6. Fundebug上线了!
  7. Fundebug前端异常监控插件更新至2.4.0,支持配置breadcrumbSize
  8. python登录网站后爬取数据_需要登陆网站后才能获取数据的页面爬取
  9. HTML5语义化标签
  10. 别再推荐使用 jsoncpp 了