Files
navi/cmd/vpnclient/main.go
T

251 lines
6.4 KiB
Go

package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"vpnclient/internal/config"
"vpnclient/internal/core"
"vpnclient/internal/protocols/naive"
)
func main() {
if len(os.Args) < 2 {
printUsage()
os.Exit(2)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "connect":
os.Exit(runConnect(args))
case "disconnect":
fmt.Fprintln(os.Stderr, "disconnect: use Ctrl+C on a running connect session")
os.Exit(2)
case "status":
fmt.Fprintln(os.Stderr, "status: only available while connect is running in this process")
os.Exit(2)
case "install-core":
os.Exit(runInstallCore(args))
case "init":
os.Exit(runInit(args))
case "probe":
os.Exit(runProbe(args))
case "set-proxy":
os.Exit(runSetProxy(args))
case "help", "-h", "--help":
printUsage()
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
printUsage()
os.Exit(2)
}
}
func printUsage() {
fmt.Fprintf(os.Stderr, `VPN client (NaiveProxy) for Windows
Usage:
vpnclient init [-config configs/config.json]
vpnclient install-core [-config configs/config.json]
vpnclient set-proxy [-config configs/config.json] <share-or-proxy-uri>
vpnclient connect [-config configs/config.json] [-profile name] [-no-sysproxy] [-probe]
vpnclient probe [-config configs/config.json] [-profile name] [-url URL]
Share links like naive+https://user:pass@host:443#name are accepted.
Architecture:
This client runs the official naive.exe from klzgrad/naiveproxy (Chromium
network stack) and optionally sets the Windows system HTTP proxy so apps
use the tunnel. That is a real NaiveProxy session, not a simulation.
`)
}
func defaultConfigPath() string {
return filepath.Join("configs", "config.json")
}
func runInit(args []string) int {
fs := flag.NewFlagSet("init", flag.ExitOnError)
cfgPath := fs.String("config", defaultConfigPath(), "path to write example config")
_ = fs.Parse(args)
if _, err := os.Stat(*cfgPath); err == nil {
fmt.Fprintf(os.Stderr, "refusing to overwrite existing %s\n", *cfgPath)
return 1
}
if err := config.WriteExample(*cfgPath); err != nil {
fmt.Fprintf(os.Stderr, "init: %v\n", err)
return 1
}
fmt.Printf("wrote %s — edit proxy user/pass/host, then: vpnclient install-core && vpnclient connect\n", *cfgPath)
return 0
}
func runInstallCore(args []string) int {
fs := flag.NewFlagSet("install-core", flag.ExitOnError)
cfgPath := fs.String("config", defaultConfigPath(), "config path (for bin_dir)")
binDirFlag := fs.String("bin-dir", "", "override bin directory")
_ = fs.Parse(args)
binDir := *binDirFlag
if binDir == "" {
if cfg, err := config.Load(*cfgPath); err == nil {
resolved, err := config.ResolveBinDir(*cfgPath, cfg.BinDir)
if err != nil {
fmt.Fprintf(os.Stderr, "install-core: %v\n", err)
return 1
}
binDir = resolved
} else {
binDir = "bin"
}
}
path, err := naive.EnsureBinary(binDir)
if err != nil {
fmt.Fprintf(os.Stderr, "install-core: %v\n", err)
return 1
}
fmt.Printf("naive core ready: %s\n", path)
return 0
}
func loadManager(cfgPath string) (*core.Manager, *config.Config, error) {
cfg, err := config.Load(cfgPath)
if err != nil {
return nil, nil, err
}
mgr, err := core.NewManager(cfgPath, cfg, os.Stderr)
if err != nil {
return nil, nil, err
}
return mgr, cfg, nil
}
func runConnect(args []string) int {
fs := flag.NewFlagSet("connect", flag.ExitOnError)
cfgPath := fs.String("config", defaultConfigPath(), "config path")
profile := fs.String("profile", "", "profile name (default: config.active)")
noSys := fs.Bool("no-sysproxy", false, "do not change Windows system proxy")
doProbe := fs.Bool("probe", false, "verify tunnel with an HTTP request after connect")
_ = fs.Parse(args)
mgr, cfg, err := loadManager(*cfgPath)
if err != nil {
fmt.Fprintf(os.Stderr, "connect: %v\n", err)
return 1
}
if *noSys {
cfg.SystemProxy = false
}
// Ensure official core is present.
if _, err := naive.EnsureBinary(mgr.BinDir()); err != nil {
fmt.Fprintf(os.Stderr, "connect: %v\n", err)
return 1
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := mgr.Connect(ctx, *profile); err != nil {
fmt.Fprintf(os.Stderr, "connect: %v\n", err)
return 1
}
defer func() {
if err := mgr.Disconnect(); err != nil {
fmt.Fprintf(os.Stderr, "disconnect: %v\n", err)
}
}()
st := mgr.Status()
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
_ = enc.Encode(st)
fmt.Fprintln(os.Stderr, "connected — press Ctrl+C to disconnect and restore system proxy")
if *doProbe {
pctx, cancel := context.WithTimeout(ctx, 45*time.Second)
err := mgr.Probe(pctx, "")
cancel()
if err != nil {
fmt.Fprintf(os.Stderr, "probe failed: %v\n", err)
} else {
fmt.Fprintln(os.Stderr, "probe ok")
}
}
<-ctx.Done()
fmt.Fprintln(os.Stderr, "shutting down...")
return 0
}
func runProbe(args []string) int {
fs := flag.NewFlagSet("probe", flag.ExitOnError)
cfgPath := fs.String("config", defaultConfigPath(), "config path")
profile := fs.String("profile", "", "profile name")
testURL := fs.String("url", "https://www.google.com/generate_204", "URL to fetch via tunnel")
_ = fs.Parse(args)
mgr, cfg, err := loadManager(*cfgPath)
if err != nil {
fmt.Fprintf(os.Stderr, "probe: %v\n", err)
return 1
}
cfg.SystemProxy = false
if _, err := naive.EnsureBinary(mgr.BinDir()); err != nil {
fmt.Fprintf(os.Stderr, "probe: %v\n", err)
return 1
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := mgr.Connect(ctx, *profile); err != nil {
fmt.Fprintf(os.Stderr, "probe: connect: %v\n", err)
return 1
}
defer mgr.Disconnect()
pctx, cancel := context.WithTimeout(ctx, 45*time.Second)
defer cancel()
if err := mgr.Probe(pctx, *testURL); err != nil {
fmt.Fprintf(os.Stderr, "probe failed: %v\n", err)
return 1
}
fmt.Println("probe ok")
return 0
}
func runSetProxy(args []string) int {
fs := flag.NewFlagSet("set-proxy", flag.ExitOnError)
cfgPath := fs.String("config", defaultConfigPath(), "config path")
_ = fs.Parse(args)
rest := fs.Args()
if len(rest) < 1 {
fmt.Fprintln(os.Stderr, "usage: vpnclient set-proxy <uri>")
return 2
}
mgr, _, err := loadManager(*cfgPath)
if err != nil {
fmt.Fprintf(os.Stderr, "set-proxy: %v\n", err)
return 1
}
if err := mgr.UpdateProxyURI(rest[0]); err != nil {
fmt.Fprintf(os.Stderr, "set-proxy: %v\n", err)
return 1
}
fmt.Println("proxy saved")
return 0
}