152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
//go:build windows
|
|
|
|
package platform
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"net"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// WindowsNetworkDiscoverer discovers network configuration using netsh commands.
|
|
// It does not require elevated privileges or PowerShell.
|
|
type WindowsNetworkDiscoverer struct{}
|
|
|
|
var ipv4Re = regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
|
|
var dnsLineRe = regexp.MustCompile(`^\s*(?:\S.*:\s+)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*$`)
|
|
|
|
// Discover returns the gateway IP and DNS servers for the default IPv4 route.
|
|
func (d *WindowsNetworkDiscoverer) Discover() (NetworkInfo, error) {
|
|
info := NetworkInfo{}
|
|
|
|
// Step 1: Find default route — extract interface index and gateway IP.
|
|
ifIdx, gateway, err := windowsDefaultRoute()
|
|
if err != nil {
|
|
return info, fmt.Errorf("reading default IPv4 route: %w", err)
|
|
}
|
|
info.Gateway = gateway
|
|
|
|
// Step 2: Map interface index to name.
|
|
ifName, err := windowsInterfaceName(ifIdx)
|
|
if err != nil {
|
|
return info, fmt.Errorf("resolving interface name for index %d: %w", ifIdx, err)
|
|
}
|
|
info.Interface = ifName
|
|
|
|
// Step 3: Enumerate DNS servers for that interface.
|
|
servers, err := windowsDNSServers(ifName)
|
|
if err != nil {
|
|
return info, fmt.Errorf("reading DNS servers for interface %q: %w", ifName, err)
|
|
}
|
|
info.DNSServers = servers
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// windowsDefaultRoute runs "netsh interface ipv4 show route" and returns the
|
|
// interface index and gateway IP for the best (lowest-metric) 0.0.0.0/0 route.
|
|
func windowsDefaultRoute() (ifIdx int, gateway string, err error) {
|
|
out, err := exec.Command("netsh", "interface", "ipv4", "show", "route").Output()
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("netsh interface ipv4 show route: %w", err)
|
|
}
|
|
|
|
bestMetric := -1
|
|
bestIdx := 0
|
|
bestGateway := ""
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if !strings.Contains(line, "0.0.0.0/0") {
|
|
continue
|
|
}
|
|
// Format: <Publish> <Type> <Metric> <Prefix> <Idx> <Gateway or Iface Name...>
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 6 {
|
|
continue
|
|
}
|
|
metric, err := strconv.Atoi(fields[2])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
idx, err := strconv.Atoi(fields[4])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
// Gateway field is field 5+ — join and extract first IPv4.
|
|
gatewayField := strings.Join(fields[5:], " ")
|
|
m := ipv4Re.FindString(gatewayField)
|
|
if m == "" {
|
|
continue
|
|
}
|
|
if bestMetric < 0 || metric < bestMetric {
|
|
bestMetric = metric
|
|
bestIdx = idx
|
|
bestGateway = m
|
|
}
|
|
}
|
|
|
|
if bestIdx == 0 {
|
|
return 0, "", fmt.Errorf("no default IPv4 route found")
|
|
}
|
|
return bestIdx, bestGateway, nil
|
|
}
|
|
|
|
// windowsInterfaceName runs "netsh interface ipv4 show interfaces" and maps
|
|
// the given interface index to the interface name.
|
|
func windowsInterfaceName(idx int) (string, error) {
|
|
out, err := exec.Command("netsh", "interface", "ipv4", "show", "interfaces").Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("netsh interface ipv4 show interfaces: %w", err)
|
|
}
|
|
|
|
idxStr := strconv.Itoa(idx)
|
|
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
|
// Skip two header lines.
|
|
for i := 0; i < 2; i++ {
|
|
scanner.Scan()
|
|
}
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 5 {
|
|
continue
|
|
}
|
|
if fields[0] != idxStr {
|
|
continue
|
|
}
|
|
// Name is fields[4:] joined — may contain spaces.
|
|
return strings.Join(fields[4:], " "), nil
|
|
}
|
|
return "", fmt.Errorf("interface index %d not found", idx)
|
|
}
|
|
|
|
// windowsDNSServers runs "netsh interface ipv4 show dnsservers name=<ifName>"
|
|
// and returns all IPv4 DNS server addresses in order.
|
|
func windowsDNSServers(ifName string) ([]string, error) {
|
|
out, err := exec.Command("netsh", "interface", "ipv4", "show", "dnsservers",
|
|
"name="+ifName).Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("netsh interface ipv4 show dnsservers: %w", err)
|
|
}
|
|
|
|
var servers []string
|
|
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
|
for scanner.Scan() {
|
|
m := dnsLineRe.FindStringSubmatch(scanner.Text())
|
|
if len(m) < 2 {
|
|
continue
|
|
}
|
|
ip := m[1]
|
|
if net.ParseIP(ip) != nil {
|
|
servers = append(servers, ip)
|
|
}
|
|
}
|
|
return servers, nil
|
|
}
|