feat: 支持 Windows x86_64 平台编译
- 新增 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
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
//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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user