- 新增 WinTun TUN 设备实现(tun_windows.go),无需安装驱动, wintun.dll 嵌入 exe 运行时自动释放 - 新增 Windows 路由管理(route_windows.go),使用 route/netsh 命令 - 新增 Windows 路径(paths_windows.go),使用 %APPDATA%/%LOCALAPPDATA% - 拆分 daemon 启动逻辑:launch_unix.go(Setsid) / launch_windows.go (CREATE_NEW_PROCESS_GROUP),公共代码提取到 launch_common.go - 拆分提权逻辑:elevation_darwin.go(osascript) / elevation_windows.go (ShellExecute UAC) / elevation_other.go(pkexec) - IPCSocketPath 按平台分散,Windows 用 %TEMP%\lmvpn.sock - daemon 二进制名用编译时常量(daemonBinaryName),按平台选定 .exe 后缀 - Makefile 新增 build-windows 目标 - 重新标记 !darwin 文件为 !darwin && !windows
154 lines
4.0 KiB
Go
154 lines
4.0 KiB
Go
//go:build windows
|
|
|
|
// Package tun — Windows implementation using WinTun.
|
|
//
|
|
// WinTun is a userspace TUN driver for Windows developed by the
|
|
// WireGuard project. It requires only a single wintun.dll (MIT
|
|
// licensed) placed next to the executable — no driver installation
|
|
// is needed.
|
|
//
|
|
// The DLL is embedded at compile time via //go:embed and extracted
|
|
// to the executable's directory on first use.
|
|
package tun
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/windows"
|
|
"golang.zx2c4.com/wintun"
|
|
)
|
|
|
|
//go:embed wintun.dll
|
|
var wintunDLL []byte
|
|
|
|
type wintunDevice struct {
|
|
adapter *wintun.Adapter
|
|
session wintun.Session
|
|
readWait windows.Handle
|
|
name string
|
|
}
|
|
|
|
func createTUN(name string) (Device, error) {
|
|
if err := ensureWintunDLL(); err != nil {
|
|
return nil, fmt.Errorf("extract wintun.dll: %w", err)
|
|
}
|
|
if name == "" {
|
|
name = "LMVPN"
|
|
}
|
|
adapter, err := wintun.CreateAdapter(name, "Wintun", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create wintun adapter: %w", err)
|
|
}
|
|
session, err := adapter.StartSession(0x800000) // 8 MiB ring
|
|
if err != nil {
|
|
adapter.Close()
|
|
return nil, fmt.Errorf("start wintun session: %w", err)
|
|
}
|
|
return &wintunDevice{
|
|
adapter: adapter,
|
|
session: session,
|
|
readWait: session.ReadWaitEvent(),
|
|
name: name,
|
|
}, nil
|
|
}
|
|
|
|
func (d *wintunDevice) Name() string { return d.name }
|
|
func (d *wintunDevice) Close() error { d.session.End(); return d.adapter.Close() }
|
|
|
|
func (d *wintunDevice) Read(p []byte) (int, error) {
|
|
for {
|
|
packet, err := d.session.ReceivePacket()
|
|
if err == nil {
|
|
n := copy(p, packet)
|
|
d.session.ReleaseReceivePacket(packet)
|
|
return n, nil
|
|
}
|
|
if err == windows.ERROR_NO_MORE_ITEMS {
|
|
windows.WaitForSingleObject(d.readWait, windows.INFINITE)
|
|
continue
|
|
}
|
|
if err == windows.ERROR_HANDLE_EOF {
|
|
return 0, fmt.Errorf("wintun closed")
|
|
}
|
|
return 0, fmt.Errorf("wintun read: %w", err)
|
|
}
|
|
}
|
|
|
|
func (d *wintunDevice) Write(p []byte) (int, error) {
|
|
buf, err := d.session.AllocateSendPacket(len(p))
|
|
if err != nil {
|
|
if err == windows.ERROR_HANDLE_EOF {
|
|
return 0, fmt.Errorf("wintun closed")
|
|
}
|
|
return 0, fmt.Errorf("wintun allocate: %w", err)
|
|
}
|
|
copy(buf, p)
|
|
d.session.SendPacket(buf)
|
|
return len(p), nil
|
|
}
|
|
|
|
func (d *wintunDevice) Configure(localIP net.IP, prefix int, peerIP net.IP) error {
|
|
if localIP == nil {
|
|
return execCmd("netsh", "interface", "ip", "set", "address", "name="+d.name, "source=dhcp")
|
|
}
|
|
if localIP.To4() == nil {
|
|
return d.configureIPv6Addr(localIP, prefix)
|
|
}
|
|
mask := net.IP(net.CIDRMask(prefix, 32))
|
|
return execCmd("netsh", "interface", "ip", "set", "address",
|
|
"name="+d.name, "source=static",
|
|
"addr="+localIP.String(), "mask="+mask.String())
|
|
}
|
|
|
|
func (d *wintunDevice) ConfigureIPv6(localIP6 net.IP, prefix6 int) error {
|
|
if localIP6 == nil {
|
|
return nil
|
|
}
|
|
return d.configureIPv6Addr(localIP6, prefix6)
|
|
}
|
|
|
|
func (d *wintunDevice) configureIPv6Addr(ip net.IP, prefix int) error {
|
|
addr := fmt.Sprintf("%s/%d", ip.String(), prefix)
|
|
return execCmd("netsh", "interface", "ipv6", "add", "address",
|
|
"name="+d.name, "address="+addr)
|
|
}
|
|
|
|
func (d *wintunDevice) SetMTU(mtu int) error {
|
|
return execCmd("netsh", "interface", "ipv4", "set", "subinterface",
|
|
"name="+d.name, fmt.Sprintf("mtu=%d", mtu))
|
|
}
|
|
|
|
// ensureWintunDLL extracts the embedded wintun.dll to the executable's
|
|
// directory if it does not already exist. The wintun Go package loads
|
|
// the DLL via LoadLibrary which searches the exe's directory first.
|
|
func ensureWintunDLL() error {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := filepath.Join(filepath.Dir(exe), "wintun.dll")
|
|
if _, err := os.Stat(path); err == nil {
|
|
return nil
|
|
}
|
|
return os.WriteFile(path, wintunDLL, 0o644)
|
|
}
|
|
|
|
// execCmd runs a command, forwarding stdout/stderr.
|
|
func execCmd(name string, arg ...string) error {
|
|
cmd := exec.Command(name, arg...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("%s %s: %w", name, strings.Join(arg, " "), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|