导语 尝试配合 gpts 写一些总结, 效果似乎没我更好 😢😢😢
简述 tun/tap 设备是 Linux 内核提供的虚拟网络设备。它们允许用户空间程序像处理物理网络设备一样,处理网络数据包。其中:
tun (network TUNnel) 设备模拟了基于 IP 的网络设备,操作第三层数据包如 IP 数据包。 tap (network tap) 设备模拟了以太网设备,操作第二层数据包如以太网数据帧。 使用 tun/tap 设备,可以实现许多功能,例如:
虚拟专用网络 (VPN): 利用 tun 设备,可以实现点对点 VPN,加密并通过公网隧道传输两端的网络流量。 网络流量监控: tap 设备可以配置为混杂 (promiscuous) 模式,对网络上所有经过它的数据包进行监听和分析。 协议模拟仿真: 利用 tun/tap,可以在用户空间实现一些自定义或新的网络协议栈,如一些物联网通信协议等。 网络功能虚拟化: tun/tap 是实现 OpenFlow, Open vSwitch 等软件定义网络方案的基础。 使用 使用 tun/tap 设备,需要内核支持,并加载相应的内核模块. 一般都支持了.
命令行: ip tuntap
命令创建 tun/tap 设备
1 2 3 4 5 ip tuntap add dev tun0 mode tun ip tuntap add dev tap0 mode tap ip addr add 192.168.1.10/24 dev tap0
编程:
创建 tun/tap 设备 : open
系统调用访问 /dev/net/tun
来创建。配置设备 :使用 ioctl
系统调用来配置新创建的设备,如分配设备名、设置设备模式(tun 或 tap)等。设置网络参数 :为 tun/tap 设备分配 IP 地址、路由等,可以使用 ifconfig
或 ip
命令。数据读写 :通过读写文件描述符来接收发送数据。N 多语言都有非常良好的包装库, 使用包装库是个更好的选择.
系统调用细节 以 c 为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int tun_alloc (char *dev) { struct ifreq ifr ; int fd, err; if ((fd = open("/dev/net/tun" , O_RDWR)) < 0 ) return fd; memset (&ifr, 0 , sizeof (ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; strncpy (ifr.ifr_name, dev, IFNAMSIZ); if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) { close(fd); return err; } return fd; }
open
正常打开一个文件, 模式一般是 O_RDWR
可读可写.
ioctl
配置参数
第二个参数是命令码, 这里是 TUNSETIFF
设置 tun/tap 设备的工作模式. 最后一个是参数, TUNSETIFF
命令的参数是 struct ifreq
类型ifr_name 是 tun/tap 设备名 ifr_flags 是字符设备标志IFF_TUN / IFF_TAP 代表是 tun 还是 tap IFF_NO_PI 代表不包含附加的包头, 默认会带上额外 4 字节. 读取/写入即标准的 read 和 write;
例程 最小的 tun 设备使用程序,它使用 tun0 设备,打印收到的所有数据包,并回送它们。
C 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/if.h> #include <linux/if_tun.h> int tun_alloc (char *dev) { struct ifreq ifr ; int fd, err; if ((fd = open("/dev/net/tun" , O_RDWR)) < 0 ) return fd; memset (&ifr, 0 , sizeof (ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; strncpy (ifr.ifr_name, dev, IFNAMSIZ); if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) { close(fd); return err; } return fd; } int main (int argc, char *argv[]) { char tun_name[IFNAMSIZ]; unsigned char buf[1518 ]; int tun_fd; int nread; strcpy (tun_name, "tun0" ); tun_fd = tun_alloc(tun_name); if (tun_fd < 0 ) { perror("Could not open tun interface" ); exit (1 ); } while (1 ) { nread = read(tun_fd, buf, sizeof (buf)); if (nread < 0 ) { close(tun_fd); perror("Reading from tun interface" ); exit (1 ); } printf ("Read %d bytes from tun interface\n" , nread); nread = write(tun_fd, buf, nread); printf ("Write %d bytes to tun interface\n" , nread); } return 0 ; }
Rust rust 的 tun_tap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 use std::io;use tun_tap::{Iface, Mode};fn main () -> io::Result <()> { let mut config = tun_tap::Configuration::default(); config.name("tun0" .into()) .mode(Mode::Tun) .address((10 , 0 , 0 , 1 )) .netmask((255 , 255 , 255 , 0 )) .up(); let mut nic = Iface::from_configuration(&config)?; let mut buf = [0u8 ; 1504 ]; loop { let nbytes = nic.recv(&mut buf[..])?; eprintln!("Read {} bytes: {:?}" , nbytes, &buf[..nbytes]); nic.send(&buf[..nbytes])?; } }
上面的例子仅仅展示了 tun/tap 设备最基本的使用,实际编程中,还需要根据具体应用场景,实现复杂的网络功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 use std::ffi::CString;use std::io;use std::io::prelude::*;use std::os::unix::io::AsRawFd;use libc;fn main () -> io::Result <()> { let tun_name = CString::new("tun0" ).unwrap(); let mut buf = [0u8 ; 1504 ]; let tun_fd = unsafe { let fd = libc::open( "/dev/net/tun\0" .as_ptr() as *const libc::c_char, libc::O_RDWR, ); if fd < 0 { panic! ("failed to open /dev/net/tun" ); } let mut ifr: libc::ifreq = std::mem::zeroed(); ifr.ifr_ifru.ifru_flags = (libc::IFF_TUN | libc::IFF_NO_PI) as libc::c_short; std::ptr::copy_nonoverlapping( tun_name.as_ptr(), ifr.ifr_ifrn.ifrn_name.as_mut_ptr() as *mut libc::c_char, tun_name.as_bytes().len(), ); let ret = libc::ioctl(fd, libc::TUNSETIFF, &ifr as *const libc::ifreq); if ret < 0 { panic! ("ioctl TUNSETIFF failed" ); } fd }; let tun = unsafe { std::fs::File::from_raw_fd(tun_fd) }; loop { let nbytes = tun.read(&mut buf)?; eprintln!("Read {} bytes: {:?}" , nbytes, &buf[..nbytes]); tun.write_all(&buf[..nbytes])?; } }
Faq tun 与 tap 设备的区别是什么?
tun 设备是三层设备,操作 IP 数据包;tap 是二层设备,模拟以太网设备,操作链路层帧。
tun/tap 收发的数据包包含哪些内容?
对 tun 设备,需要自行构造 IP 头部,操作系统只处理 IP 层之上的部分。对 tap 设备,需要构造完整的以太网数据帧,包括 MAC 地址等。
可以在 tun/tap 设备上运行 TCP/IP 协议栈吗?
完全可以。事实上,一个常见的做法是,在 tap 设备上运行一个用户层的轻量级 TCP/IP 协议栈,实现可定制、易调试的虚拟网络。
tun/tap 设备是否支持多队列?
Linux 内核从 3.8 版本开始,支持多队列 tun/tap 设备。通过 TUNSETQUEUE
配置项,可以请求内核创建多个收发队列。这对于高性能应用很有帮助。
除了上面的内容,对 tun/tap 编程,还有一些值得注意的地方:
设备何时收到数据包?如果没有数据包,读取设备文件会阻塞。所以实现一个高效的事件循环很重要。epoll
或类似机制配合非阻塞 IO,是常用方法。 多线程/多进程模型: 可以利用多线程/进程,充分利用多核性能。通常一个线程用于收包,多个工作线程处理具体业务。 用户空间 TCP/IP 协议栈的性能优化: 实现用户空间协议栈时,要注意缓存、算法、数据结构的优化。一些性能关键点如 TCP/IP 校验和的计算等,可以用 SIMD 指令集如 AVX512 加速。 安全性问题:tun/tap 设备是一种特殊文件,具有较高的特权,不当使用可能带来安全隐患。比如在 VPN 场景下,用户的所有流量都会经过 tun/tap 设备,设备的访问控制和程序的安全性需要仔细设计。