4074e68715
WIP snapshot before pivot to sing-box+TUN. Reached: - TCP redirect via streamdump pattern (swap+Outbound=0+reinject) - SOCKET layer for SYN-stage flow detection (avoids FLOW Establish-too-late race) - Lazy PID→name resolution (catches Update.exe inside procscan tick) - UDP forward via SOCKS5 UDP ASSOCIATE relay + manual reinject - Result: chat works, voice times out (Discord IP discovery / RTC handshake fails) Reason for pivot: WinDivert NAT-reinject pattern has subtle layer-3 semantics issues that DLL-injection / TUN-based proxies sidestep entirely. Going with embedded sing-box + wintun as the engine — proven path for Discord voice through SOCKS5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
81 lines
2.3 KiB
Go
81 lines
2.3 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.okcu.io/root/drover-go/internal/engine"
|
|
)
|
|
|
|
// runProxy is the body of the `drover proxy` subcommand. It builds an
|
|
// engine.Engine from the supplied flags, calls Start, and blocks until
|
|
// the process receives SIGINT (Ctrl+C) or SIGTERM. On signal, it
|
|
// gracefully Stops the engine and exits.
|
|
//
|
|
// All output is mirrored to stderr (visible when launched from a
|
|
// console session) AND %LOCALAPPDATA%\Drover\debug.log. setupDebugLog
|
|
// in main.go has already wired the log package to write to both.
|
|
func runProxy(parent context.Context, host string, port int, auth bool, login, password string) error {
|
|
if host == "" || port == 0 {
|
|
return fmt.Errorf("--host and --port are required")
|
|
}
|
|
|
|
ctx, cancel := signal.NotifyContext(parent, os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
cfg := engine.Config{
|
|
ProxyAddr: fmt.Sprintf("%s:%d", host, port),
|
|
UseAuth: auth,
|
|
Login: login,
|
|
Password: password,
|
|
Targets: []string{"Discord.exe", "DiscordCanary.exe", "DiscordPTB.exe", "Update.exe"},
|
|
}
|
|
|
|
log.Printf("proxy: building engine (proxy=%s auth=%v targets=%v)", cfg.ProxyAddr, cfg.UseAuth, cfg.Targets)
|
|
e, err := engine.New(cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("engine.New: %w", err)
|
|
}
|
|
startCtx, startCancel := context.WithTimeout(ctx, 15*time.Second)
|
|
defer startCancel()
|
|
if err := e.Start(startCtx); err != nil {
|
|
log.Printf("proxy: Start failed: %v", err)
|
|
return fmt.Errorf("engine.Start: %w", err)
|
|
}
|
|
log.Printf("proxy: engine status=%s — press Ctrl+C to stop", e.Status())
|
|
|
|
// Periodic status ping so the user sees the engine is alive.
|
|
statusTk := time.NewTicker(10 * time.Second)
|
|
defer statusTk.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Printf("proxy: signal received, shutting down")
|
|
if err := e.Stop(); err != nil {
|
|
log.Printf("proxy: Stop returned: %v", err)
|
|
}
|
|
log.Printf("proxy: bye")
|
|
return nil
|
|
case <-statusTk.C:
|
|
if le := e.LastError(); le != nil {
|
|
log.Printf("proxy: heartbeat status=%s lastErr=%v", e.Status(), le)
|
|
} else {
|
|
log.Printf("proxy: heartbeat status=%s", e.Status())
|
|
}
|
|
if e.Status() == engine.StatusFailed {
|
|
log.Printf("proxy: engine entered Failed state, exiting")
|
|
_ = e.Stop()
|
|
return fmt.Errorf("engine failed: %v", e.LastError())
|
|
}
|
|
}
|
|
}
|
|
}
|