package main import ( "encoding/json" "errors" "fmt" "net" "os/exec" "runtime" "strings" ) func (e *Engine) pinRoute(ip string) error { if runtime.GOOS == "windows" { return e.windowsRoute(ip) } family := "-4" bits := "/32" if net.ParseIP(ip).To4() == nil { family = "-6" bits = "/128" } out, err := exec.Command("ip", family, "-j", "route", "get", ip).Output() if err != nil { return fmt.Errorf("find route: %w", err) } var routes []struct { Dev string `json:"dev"` Gateway string `json:"gateway"` } if json.Unmarshal(out, &routes) != nil || len(routes) == 0 || routes[0].Dev == "" { return errors.New("cannot determine physical route to wstunnel") } r := routes[0] // Never replace an existing host route: cleanup must only remove routes we own. existing, err := exec.Command("ip", family, "route", "show", "exact", ip+bits).Output() if err != nil { return err } if strings.TrimSpace(string(existing)) != "" { return nil } args := []string{family, "route", "add", ip + bits} if r.Gateway != "" { args = append(args, "via", r.Gateway) } args = append(args, "dev", r.Dev) if err = command("ip", args...); err != nil { return err } del := append([]string{"ip", family, "route", "del", ip + bits}, args[4:]...) e.routes = append(e.routes, del) return nil } func (e *Engine) windowsRoute(ip string) error { // Numeric IP is resolved locally before reaching PowerShell; it cannot contain script text. script := fmt.Sprintf(`$ErrorActionPreference='Stop'; $ip='%s'; $r=Find-NetRoute -RemoteIPAddress $ip | Where-Object {$_.NextHop -ne $null} | Select-Object -First 1; if (!$r) { throw 'No physical route' }; $prefix=$ip+'/32'; if ($ip.Contains(':')) {$prefix=$ip+'/128'}; $exists=Get-NetRoute -DestinationPrefix $prefix -ErrorAction SilentlyContinue; if (!$exists) { New-NetRoute -DestinationPrefix $prefix -InterfaceIndex $r.InterfaceIndex -NextHop $r.NextHop -PolicyStore ActiveStore | Out-Null; Write-Output ($prefix+'|'+$r.InterfaceIndex+'|'+$r.NextHop) }`, ip) out, err := exec.Command("powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script).CombinedOutput() if err != nil { return fmt.Errorf("route: %w: %s", err, out) } v := strings.Split(strings.TrimSpace(string(out)), "|") if len(v) == 3 { cleanup := fmt.Sprintf("Remove-NetRoute -DestinationPrefix '%s' -InterfaceIndex %s -NextHop '%s' -Confirm:$false -ErrorAction SilentlyContinue", v[0], v[1], v[2]) e.routes = append(e.routes, []string{"powershell", "-NoProfile", "-NonInteractive", "-Command", cleanup}) } return nil }