Files
navi/internal/protocols/awg/engine.go
T

176 lines
3.6 KiB
Go

package awg
import (
"context"
"fmt"
"io"
"os"
"sync"
"time"
"github.com/amnezia-vpn/amneziawg-go/conn"
"github.com/amnezia-vpn/amneziawg-go/device"
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
"vpnclient/internal/config"
)
// Engine runs AmneziaWG 2.0 in userspace (gvisor netstack) and exposes local SOCKS/HTTP.
type Engine struct {
mu sync.Mutex
dev *device.Device
tnet *netstack.Net
proxies *proxyServer
profile config.Profile
stderr io.Writer
running bool
}
func New(stderr io.Writer) *Engine {
if stderr == nil {
stderr = os.Stderr
}
return &Engine{stderr: stderr}
}
func (e *Engine) Protocol() config.Protocol { return config.ProtocolAWG }
func (e *Engine) Start(ctx context.Context, profile config.Profile, _ string) error {
e.mu.Lock()
defer e.mu.Unlock()
if e.running {
return fmt.Errorf("awg: already running")
}
cfg, err := Parse(profile.Proxy)
if err != nil {
return err
}
// amneziawg-go IpcSet does not resolve hostnames reliably (Windows WSAHOST_NOT_FOUND).
if err := cfg.ResolveEndpoint(); err != nil {
return err
}
ipc, err := cfg.ToIPC()
if err != nil {
return err
}
addrs, err := cfg.LocalAddrs()
if err != nil {
return err
}
tunDev, tnet, err := netstack.CreateNetTUN(addrs, cfg.DNSAddrs(), cfg.EffectiveMTU())
if err != nil {
return fmt.Errorf("awg netstack: %w", err)
}
logger := &device.Logger{
Verbosef: func(format string, args ...any) {},
Errorf: func(format string, args ...any) {
fmt.Fprintf(e.stderr, "awg: "+format+"\n", args...)
},
}
dev := device.NewDevice(tunDev, conn.NewDefaultBind(), logger)
if err := dev.IpcSet(ipc); err != nil {
dev.Close()
return fmt.Errorf("awg configure: %w", err)
}
if err := dev.Up(); err != nil {
dev.Close()
return fmt.Errorf("awg up: %w", err)
}
socksHP, _ := profile.SOCKSListenHostPort()
httpHP, _ := profile.HTTPListenHostPort()
if socksHP == "" {
socksHP = "127.0.0.1:1080"
}
if httpHP == "" {
httpHP = "127.0.0.1:1081"
}
proxies, err := startProxies(socksHP, httpHP, tnet.DialContext)
if err != nil {
dev.Close()
return err
}
e.dev = dev
e.tnet = tnet
e.proxies = proxies
e.profile = profile
e.running = true
// Brief handshake grace; don't fail hard if peer is slow.
select {
case <-ctx.Done():
_ = e.stopLocked()
return ctx.Err()
case <-time.After(400 * time.Millisecond):
}
return nil
}
func (e *Engine) Stop() error {
e.mu.Lock()
defer e.mu.Unlock()
return e.stopLocked()
}
func (e *Engine) stopLocked() error {
if !e.running {
return nil
}
// Close device first so tun/netstack dials fail and relays exit promptly.
if e.dev != nil {
e.dev.Close()
e.dev = nil
}
e.tnet = nil
if e.proxies != nil {
_ = e.proxies.Close()
e.proxies = nil
}
e.running = false
return nil
}
func (e *Engine) Running() bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.running
}
func (e *Engine) LocalHTTPProxy() (string, bool) {
e.mu.Lock()
defer e.mu.Unlock()
if !e.running {
return "", false
}
if e.proxies != nil && e.proxies.httpAddr != "" {
return e.proxies.httpAddr, true
}
return e.profile.HTTPListenHostPort()
}
func (e *Engine) LocalSOCKSProxy() (string, bool) {
e.mu.Lock()
defer e.mu.Unlock()
if !e.running {
return "", false
}
if e.proxies != nil && e.proxies.socksAddr != "" {
return e.proxies.socksAddr, true
}
return e.profile.SOCKSListenHostPort()
}
// EnsureBinary is a no-op: AWG is embedded via amneziawg-go.
func EnsureBinary(_ string) (string, error) {
return "embedded-amneziawg-go", nil
}
// ResolveBinary reports the embedded core.
func ResolveBinary(_ string) (string, error) {
return "embedded-amneziawg-go", nil
}