176 lines
3.6 KiB
Go
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
|
|
}
|