experimental/windivert: P2.1+P2.2 with WinDivert NETWORK+SOCKET layers
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>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
//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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user