wswg/route.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
}