TUN设备简述和使用方式
之前用easytier被公司检测出来,遂起意自己搓一个IP转TCP的工具,用于在异地组网
首先,什么是TUN设备
TUN(Network TUNnel)设备是一种虚拟网络设备,工作在OSI模型的第三层(网络层)。与TAP设备(工作在第二层,处理以太网帧)不同,TUN设备处理的是IP数据包。
你就知道是操作系统开的一个口子,让你在用户空间程序和内核空间网络协议栈之间进行数据交互。
数据流转过程
发送流程(用户空间 -> 内核 -> 网络)
复制代码 应用程序 ↓ 系统调用(socket) ↓ 内核网络协议栈 ↓ 路由查找(匹配到TUN设备) ↓ IP数据包封装 ↓ 写入TUN设备文件
接收流程(网络 -> 内核 -> 用户空间)
复制代码 TUN设备接收数据包 ↓ 内核网络协议栈处理 ↓ 路由查找/转发 ↓ 发送到网络或本地应用
详细数据流转示例
假设有一个TUN设备 tun0,配置如下:
- IP地址:10.0.0.1/24
- 点对点对端:10.0.0.2
发送数据包
- 应用程序发送数据 bash 复制代码 ping 10.0.0.2
- 内核处理 创建ICMP Echo Request数据包 源IP:10.0.0.1,目标IP:10.0.0.2 查找路由表,发现10.0.0.2通过tun0可达
- 写入TUN设备 内核将IP数据包写入tun0的文件描述符 用户空间程序通过read()读取数据包
- 用户空间处理 读取完整的IP数据包(包含IP头和载荷) 根据目标IP进行转发或其他处理
接收数据包
- 用户空间构造数据包 构造完整的IP数据包 包含IP头、协议类型、载荷等
- 写入TUN设备 通过write()将数据包写入tun0文件描述符
- 内核处理 内核接收数据包 根据IP头信息进行路由 如果目标IP是本机,传递给相应协议栈 如果需要转发,查找路由表并转发
Go语言相关包介绍
github.com/songgao/water
这是一个流行的TUN/TAP设备操作库
安装
go get github.com/songgao/water基本使用
package main
import (
"fmt"
"log"
"net"
"os/exec"
"github.com/songgao/water"
"github.com/vishvananda/netlink"
)
func setupTunDevice(ip string, mask int) *water.Interface {
// 创建 TUN 设备
ifce, err := water.New(water.Config{
DeviceType: water.TUN,
})
if err != nil {
log.Fatalln("create tun dev failed", err.Error())
}
// 解析 IP 地址
addr, err := netlink.ParseAddr(fmt.Sprintf("%s/%d", ip, mask))
if err != nil {
log.Fatalln("parse addr failed", err.Error())
}
// 获取 TUN 设备
tunDev, err := netlink.LinkByName(ifce.Name())
if err != nil {
log.Fatalln("get tun dev failed: ", err.Error())
}
// 添加 IP 地址
if err = netlink.AddrAdd(tunDev, addr); err != nil {
log.Fatalln("add address to tun dev failed", err.Error())
}
// 启用设备
cmd := exec.Command("ip", "link", "set", ifce.Name(), "up")
if err = cmd.Run(); err != nil {
log.Fatal(err)
}
return ifce
}实际项目使用示例
参考 client/client.go 中的实际使用方式:
package main import ( "fmt" "log" "net" "os/exec" "git.dengqn.com/dqn/gotun/config" "github.com/songgao/water" "github.com/vishvananda/netlink" ) type Client struct { tunDev *water.Interface } func initTunDev() *water.Interface { // 创建TUN设备 ifce, err := water.New(water.Config{ DeviceType: water.TUN, PlatformSpecificParams: water.PlatformSpecificParams{}, }) if err != nil { log.Fatalln("create tun dev failed", err.Error()) } // 配置IP地址 addr, _ := netlink.ParseAddr(fmt.Sprintf( "%s/%d", config.INSTANCE.Client.Ip, config.INSTANCE.Client.Mask), ) // 获取TUN设备 if tunDev, err := netlink.LinkByName(ifce.Name()); err != nil { log.Fatalln("get tun dev failed: ", err.Error()) } else { // 添加IP地址到TUN设备 if err = netlink.AddrAdd(tunDev, addr); err != nil { log.Fatalln("add address to tun dev failed", err.Error()) } else { // 启用TUN设备 cmd := exec.Command("ip", "link", "set", ifce.Name(), "up") err = cmd.Run() if err != nil { log.Fatal(err) } } } return ifce }数据读写示例
package main import ( "log" "net" "github.com/songgao/water" ) func TunDataToServer(tunDev *water.Interface, tcpConn net.Conn) { buf := make([]byte, 1500) for { n, err := tunDev.Read(buf) if err != nil { log.Printf("Error reading from TUN: %v", err) return } // 将TUN设备读取的数据发送到TCP连接 _, err = tcpConn.Write(buf[:n]) if err != nil { log.Printf("Error writing to TCP: %v", err) return } } } func ServerDataToTun(tcpConn net.Conn, tunDev *water.Interface) error { buf := make([]byte, 1500) for { n, err := tcpConn.Read(buf) if err != nil { return err } // 将TCP连接读取的数据写入TUN设备 _, err = tunDev.Write(buf[:n]) if err != nil { log.Printf("Error writing to TUN: %v", err) return err } } }github.com/vishvananda/netlink
用于网络设备配置和路由管理的库,常与water配合使用。
安装
bash 复制代码 go get github.com/vishvananda/netlink
配置TUN设备
package main
import (
"fmt"
"log"
"net"
"os/exec"
"github.com/songgao/water"
"github.com/vishvananda/netlink"
)
func setupTunDevice(ip string, mask int) *water.Interface {
// 创建 TUN 设备
ifce, err := water.New(water.Config{
DeviceType: water.TUN,
})
if err != nil {
log.Fatalln("create tun dev failed", err.Error())
}
// 解析 IP 地址
addr, err := netlink.ParseAddr(fmt.Sprintf("%s/%d", ip, mask))
if err != nil {
log.Fatalln("parse addr failed", err.Error())
}
// 获取 TUN 设备
tunDev, err := netlink.LinkByName(ifce.Name())
if err != nil {
log.Fatalln("get tun dev failed: ", err.Error())
}
// 添加 IP 地址
if err = netlink.AddrAdd(tunDev, addr); err != nil {
log.Fatalln("add address to tun dev failed", err.Error())
}
// 启用设备
cmd := exec.Command("ip", "link", "set", ifce.Name(), "up")
if err = cmd.Run(); err != nil {
log.Fatal(err)
}
return ifce
}
配置路由
package main import ( "log" "net" "github.com/vishvananda/netlink" ) func setupRoute(tunName string, network string, mask int) error { // 获取TUN设备 tunDev, err := netlink.LinkByName(tunName) if err != nil { return err } // 添加路由 route := &netlink.Route{ LinkIndex: tunDev.Attrs().Index, Scope: netlink.SCOPE_UNIVERSE, Dst: &net.IPNet{ IP: net.ParseIP(network), Mask: net.CIDRMask(mask, 32), }, } if err := netlink.RouteAdd(route); err != nil { return err } log.Printf("Route added: %s/%d via %s", network, mask, tunName) return nil }完整示例
简单的TUN设备客户端
package main import ( "fmt" "log" "net" "os/exec" "time" "github.com/songgao/water" "github.com/vishvananda/netlink" ) type Client struct { tcpConn net.Conn tunDev *water.Interface } func (c *Client) Init(serverAddr string, localIP string, mask int) error { c.tunDev = initTunDev(localIP, mask) conn, err := net.Dial("tcp", serverAddr) if err != nil { return fmt.Errorf("connect to server failed: %w", err) } c.tcpConn = conn return nil } func (c *Client) Run() error { go c.tunToTcp() return c.tcpToTun() } func (c *Client) tunToTcp() { buf := make([]byte, 1500) for { n, err := c.tunDev.Read(buf) if err != nil { log.Printf("Error reading from TUN: %v", err) return } _, err = c.tcpConn.Write(buf[:n]) if err != nil { log.Printf("Error writing to TCP: %v", err) return } } } func (c *Client) tcpToTun() error { buf := make([]byte, 1500) for { n, err := c.tcpConn.Read(buf) if err != nil { return err } _, err = c.tunDev.Write(buf[:n]) if err != nil { log.Printf("Error writing to TUN: %v", err) return err } } } func initTunDev(ip string, mask int) *water.Interface { ifce, err := water.New(water.Config{ DeviceType: water.TUN, }) if err != nil { log.Fatalln("create tun dev failed", err.Error()) } addr, _ := netlink.ParseAddr(fmt.Sprintf("%s/%d", ip, mask)) if tunDev, err := netlink.LinkByName(ifce.Name()); err != nil { log.Fatalln("get tun dev failed: ", err.Error()) } else { if err = netlink.AddrAdd(tunDev, addr); err != nil { log.Fatalln("add address to tun dev failed", err.Error()) } else { cmd := exec.Command("ip", "link", "set", ifce.Name(), "up") err = cmd.Run() if err != nil { log.Fatal(err) } } } return ifce } func main() { client := &Client{} err := client.Init("server.example.com:8080", "10.0.0.2", 24) if err != nil { log.Fatal(err) } for { err := client.Run() if err != nil { log.Printf("Connection error: %v", err) time.Sleep(5 * time.Second) err = client.Init("server.example.com:8080", "10.0.0.2", 24) if err != nil { log.Fatal(err) } } } }