68 lines
2.5 KiB
Go
68 lines
2.5 KiB
Go
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
|
|
}
|