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

发送数据包

  1. 应用程序发送数据 bash 复制代码 ping 10.0.0.2
  2. 内核处理 创建ICMP Echo Request数据包 源IP:10.0.0.1,目标IP:10.0.0.2 查找路由表,发现10.0.0.2通过tun0可达
  3. 写入TUN设备 内核将IP数据包写入tun0的文件描述符 用户空间程序通过read()读取数据包
  4. 用户空间处理 读取完整的IP数据包(包含IP头和载荷) 根据目标IP进行转发或其他处理

接收数据包

  1. 用户空间构造数据包 构造完整的IP数据包 包含IP头、协议类型、载荷等
  2. 写入TUN设备 通过write()将数据包写入tun0文件描述符
  3. 内核处理 内核接收数据包 根据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) } } } }