Linux 网络设备 - TUN/TAP

2023-12-26 12:04:32

转自:https://zhuanlan.zhihu.com/p/661035195
我们今天学习一下 Linux 上常见的网络设备 TUN/TAP,这两个设备经常被放到一起讨论,因为它们的功能其实非常类似,都是 Linux 内核提供的虚拟网络设备,就像一块真实的网卡,但它的一端连着操作系统协议栈,另一端连着用户空间的程序。如下图所示:
在这里插入图片描述
我们假设:右侧的应用程序代表 ping 命令,左侧的应用程序代表绑定了 TUN/TAP 设备的用户空间程序。 右侧的应用进程想尝试执行ping 172.1.1.100,那经过 ping 命令构建的 ICMP 的请求包会通过调用 Socket API 将数据发送到内核的 TCP/IP 协议栈,此时协议栈会查询系统路由表,根据路由策略来决定 ICMP 请求包会投递到哪张网卡,如果: - 被投递到物理网卡,那此时包会发往 Remote PC; - 被投递到 TUN/TAP 虚拟网卡,那此时包会发往绑定该 TUN/TAP 设备的用户空间程序;

当 ping 命令发送完请求在等待 ICMP 回包时:

Remote PC 接收到 ICMP 的请求,构造 ICMP 响应,通过物理网线发送回 Local PC的物理网卡,并投递到内核协议栈;
用户空间程序接受到 ICMP 的请求,构造 ICMP 响应,通过 TUN/TAP 设备投递回内核协议栈;
所以,逻辑上来说,TUN/TAP 设备类似一块真实的物理网卡,而绑定 TUN/TAP 设备的用户空间程序则类似一台仅处理网络数据包的 Remote PC。

接下来,我们来编写用户空间程序,验证下刚才的分析是否正确,也顺便看一下 TUN 和 TAP 设备两者的区别:

分别创建 TUN 和 TAP 设备,为设备绑定 IP 10.1.1.100/24;
绑定 TUN/TAP 设备,接收 ICMP 的请求并回包;
我们的实验环境是Ubuntu22.04,编程语言使用的golang-1.20,为了方便讲解,文章里没有贴完整的代码,需要的可以在 Github 自取:TUN/TAP

TAP 设备
我们先来创建一个名为tap0的 TAP 设备,这里使用的是github.com/songgao/water,API 可以参考官方文档:

config := water.Config{
  DeviceType: water.TAP,
  PlatformSpecificParams: water.PlatformSpecificParams{
    Name: "tap0",
  },
}
iface, _ := water.New(config)

这段代码执行完成后,tap0设备就创建成功并绑定到我们的用户空间程序,从详细信息里可以看到tun type tap,代表这个设备是 TAP 类型,符合预期。此外,我们应该注意到tap0设备是包含 MAC 地址的 ba:6b:1b:72:62:45,这表示它可以在二层工作(后面介绍的 TUN 设备仅工作在三层,没有 MAC 地址)。

 ip -d link show dev tap0
4: tap0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether ba:6b:1b:72:62:45 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65521
    tun type tap pi off vnet_hdr off persist off addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

我们给tap0设备绑定 IP 10.1.1.100,并且将其设置为启用状态:

cmd := exec.Command("ip", "addr", "add", "10.1.1.100/24", "dev", ifaceName)
_ = cmd.Run()

cmd = exec.Command("ip", "link", "set", "dev", "tap0", "up")
_ = cmd.Run()

再查看一下tap0设备,已经绑定 IP,并且处于 UP 状态:

ip a
...
10: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether ba:6b:1b:72:62:45 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.100/24 scope global tap0
       valid_lft forever preferred_lft forever
    inet6 fe80::b86b:1bff:fe72:6245/64 scope link
       valid_lft forever preferred_lft forever

当我们为tap0绑定 IP 并启用时,内核会自动生成指向该设备的路由,我们查看一下路由表:

$ ip route list
default via 192.168.31.1 dev enp1s0 proto dhcp metric 100
10.1.1.0/24 dev tap0 proto kernel scope link src 10.1.1.100
169.254.0.0/16 dev enp1s0 scope link metric 1000
192.168.31.0/24 dev enp1s0 proto kernel scope link src 192.168.31.92 metric 100

里面多了一条10.1.1.0/24 dev tap0 proto kernel scope link src 10.1.1.100规则,我们来分析一下重点信息:

proto kernel代表该规则是内核根据网络配置自动生成的;
10.1.1.0/24 dev tap0代表匹配该网段的数据包将会通过tap0设备传输;
src 10.1.1.100代表数据包的源 IP 会被设置为该 IP,同时我们应该也能推断出,源 MAC 会被设置为tap0设备的 MAC ba:6b:1b:72:62:45;
OK,到这里我们再梳理一下流程,现在用户空间的程序已经有了(就是我们正在编写的创建并绑定 TAP 设备的程序),tap0 TAP 设备有了,路由规则有了,如果现在通过 ping 命令来执行ping 10.1.1.200(与路由规则同网段,会投递到tap0设备),那用户空间程序应该能接收到数据包? 我们来试一下,先调整下程序,打印出接收到的数据包:

buffer := make([]byte, 1500)
for {
  n, err := iface.Read(buffer)
  if err != nil {
    log.Printf("iface read failed: %v", err)
    continue
  }

  printPacketInHex("Received: ", buffer[:n])
}

好,我们来尝试下执行ping 10.1.1.200:

# 执行 ping 命令
$ ping -c1 -i1 10.1.1.200
PING 10.1.1.101 (10.1.1.101) 56(84) bytes of data.
From 10.1.1.100 icmp_seq=1 Destination Host Unreachable
--- 10.1.1.101 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

# 用户空间程序日志
$ ./tap
2023-10-10 10:19:34: Received: ff ff ff ff ff ff ba 6b 1b 72 62 45 08 06 00 01 08 00 06 04 00 01 ba 6b 1b 72 62 45 0a 01 01 64 00 00 00 00 00 00 0a 01 01 65
2023-10-10 10:19:35: Received: ff ff ff ff ff ff ba 6b 1b 72 62 45 08 06 00 01 08 00 06 04 00 01 ba 6b 1b 72 62 45 0a 01 01 64 00 00 00 00 00 00 0a 01 01 65
2023-10-10 10:19:36: Received: ff ff ff ff ff ff ba 6b 1b 72 62 45 08 06 00 01 08 00 06 04 00 01 ba 6b 1b 72 62 45 0a 01 01 64 00 00 00 00 00 00 0a 01 01 65
...

成功了?100% packet loss告诉我们 ping 命令没有响应,但是,我们同时也注意到在用户空间程序的日志中,打印出了我们接收到的数据包,说明至少数据包经过路由匹配后被发送到tap0设备,进而转发到连接tap0设备的用户空间程序,按照正常思路,只要我们接收到数据包并且按照规范处理,那 ping 命令收到响应只是迟早的事情。

好,我们分析下这个数据包:

ff ff ff ff ff ff ba 6b 1b 72 62 45 08 06 00 01 08 00 06 04 00 01 ba 6b 1b 72 62 45 0a 01 01 64 00 00 00 00 00 00 0a 01 01 65

后面的不想复制了 看原贴吧
https://zhuanlan.zhihu.com/p/661035195

文章来源:https://blog.csdn.net/qq_38350702/article/details/135216101
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。