Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
//go:build darwin
|
||||
|
||||
package platform
|
||||
|
||||
// NewNetworkDiscoverer returns the macOS-specific NetworkDiscoverer.
|
||||
func NewNetworkDiscoverer() NetworkDiscoverer {
|
||||
return &DarwinNetworkDiscoverer{}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//go:build linux
|
||||
|
||||
package platform
|
||||
|
||||
// NewNetworkDiscoverer returns the Linux-specific NetworkDiscoverer.
|
||||
func NewNetworkDiscoverer() NetworkDiscoverer {
|
||||
return &LinuxNetworkDiscoverer{}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//go:build windows
|
||||
|
||||
package platform
|
||||
|
||||
// NewNetworkDiscoverer returns the Windows-specific NetworkDiscoverer.
|
||||
func NewNetworkDiscoverer() NetworkDiscoverer {
|
||||
return &WindowsNetworkDiscoverer{}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package platform
|
||||
|
||||
// NetworkInfo holds discovered local network configuration from the
|
||||
// system's default-route network adapter.
|
||||
type NetworkInfo struct {
|
||||
DNSServers []string // Ordered DNS server IPs from the default-route adapter
|
||||
Gateway string // Default gateway IP (may be empty if not discoverable)
|
||||
Interface string // Adapter name holding the default route (informational)
|
||||
}
|
||||
|
||||
// NetworkDiscoverer discovers the local network configuration.
|
||||
type NetworkDiscoverer interface {
|
||||
Discover() (NetworkInfo, error)
|
||||
}
|
||||
|
||||
// FakeNetworkDiscoverer returns predetermined NetworkInfo for testing.
|
||||
// It is exported so packages outside of platform can use it in tests.
|
||||
type FakeNetworkDiscoverer struct {
|
||||
Info NetworkInfo
|
||||
Err error
|
||||
}
|
||||
|
||||
// Discover implements NetworkDiscoverer. Returns the pre-configured Info and Err.
|
||||
func (f *FakeNetworkDiscoverer) Discover() (NetworkInfo, error) {
|
||||
return f.Info, f.Err
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
//go:build darwin
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DarwinNetworkDiscoverer discovers network configuration using route(8) and
|
||||
// scutil(8). Both commands are available on every macOS version since 10.5.
|
||||
type DarwinNetworkDiscoverer struct{}
|
||||
|
||||
// Discover returns the default gateway IP and DNS server list for this host.
|
||||
func (d *DarwinNetworkDiscoverer) Discover() (NetworkInfo, error) {
|
||||
info := NetworkInfo{}
|
||||
|
||||
// Step 1: Determine default gateway and interface via "route -n get default".
|
||||
gateway, iface, err := darwinDefaultRoute()
|
||||
if err != nil {
|
||||
return info, fmt.Errorf("reading default route: %w", err)
|
||||
}
|
||||
info.Gateway = gateway
|
||||
info.Interface = iface
|
||||
|
||||
// Step 2: Extract DNS servers from scutil --dns, matching the interface.
|
||||
servers, err := darwinDNSServers(iface)
|
||||
if err != nil {
|
||||
// Non-fatal — return what we have.
|
||||
return info, nil
|
||||
}
|
||||
info.DNSServers = servers
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// darwinDefaultRoute runs "route -n get default" and returns the gateway IP
|
||||
// and interface name for the default route.
|
||||
func darwinDefaultRoute() (gateway, iface string, err error) {
|
||||
out, err := exec.Command("route", "-n", "get", "default").Output()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("route -n get default: %w", err)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "gateway:") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
ip := fields[1]
|
||||
if net.ParseIP(ip) != nil {
|
||||
gateway = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(line, "interface:") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
iface = fields[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gateway == "" {
|
||||
return "", "", fmt.Errorf("no default gateway found")
|
||||
}
|
||||
return gateway, iface, nil
|
||||
}
|
||||
|
||||
// darwinDNSServers runs "scutil --dns" and extracts DNS server IPs from the
|
||||
// resolver block that matches iface. Falls back to resolver #1 if no match.
|
||||
func darwinDNSServers(iface string) ([]string, error) {
|
||||
out, err := exec.Command("scutil", "--dns").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scutil --dns: %w", err)
|
||||
}
|
||||
|
||||
type resolverBlock struct {
|
||||
nameservers []string
|
||||
ifaceMatch bool
|
||||
isMDNS bool
|
||||
number int
|
||||
}
|
||||
|
||||
var blocks []resolverBlock
|
||||
var current *resolverBlock
|
||||
inScopedSection := false
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
stripped := strings.TrimSpace(line)
|
||||
|
||||
// Detect the "scoped queries" section — stop parsing there.
|
||||
if strings.Contains(line, "DNS configuration (for scoped queries)") {
|
||||
inScopedSection = true
|
||||
}
|
||||
if inScopedSection {
|
||||
continue
|
||||
}
|
||||
|
||||
// Start of a new resolver block.
|
||||
if strings.HasPrefix(stripped, "resolver #") {
|
||||
if current != nil {
|
||||
blocks = append(blocks, *current)
|
||||
}
|
||||
num := 0
|
||||
fmt.Sscanf(stripped, "resolver #%d", &num)
|
||||
current = &resolverBlock{number: num}
|
||||
continue
|
||||
}
|
||||
|
||||
if current == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse nameserver entries.
|
||||
if strings.HasPrefix(stripped, "nameserver[") {
|
||||
// Format: "nameserver[0] : 8.8.8.8"
|
||||
idx := strings.Index(stripped, ": ")
|
||||
if idx >= 0 {
|
||||
ip := strings.TrimSpace(stripped[idx+2:])
|
||||
if net.ParseIP(ip) != nil {
|
||||
current.nameservers = append(current.nameservers, ip)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for interface match via if_index line: "if_index : 5 (en0)"
|
||||
if strings.HasPrefix(stripped, "if_index") && iface != "" {
|
||||
if strings.Contains(stripped, "("+iface+")") {
|
||||
current.ifaceMatch = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Detect mDNS blocks: domain + options lines.
|
||||
if strings.HasPrefix(stripped, "domain") && strings.Contains(stripped, ": local") {
|
||||
current.isMDNS = true
|
||||
}
|
||||
if strings.HasPrefix(stripped, "options") && strings.Contains(stripped, "mdns") {
|
||||
current.isMDNS = true
|
||||
}
|
||||
}
|
||||
if current != nil {
|
||||
blocks = append(blocks, *current)
|
||||
}
|
||||
|
||||
// Find the best block: interface-matching, non-mDNS.
|
||||
for _, b := range blocks {
|
||||
if b.ifaceMatch && !b.isMDNS && len(b.nameservers) > 0 {
|
||||
return b.nameservers, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: use resolver #1 if it's not mDNS.
|
||||
for _, b := range blocks {
|
||||
if b.number == 1 && !b.isMDNS && len(b.nameservers) > 0 {
|
||||
return b.nameservers, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no usable DNS resolver block found in scutil output")
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//go:build linux
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LinuxNetworkDiscoverer discovers network configuration using ip(8) and
|
||||
// /etc/resolv.conf. On systemd-resolved systems it falls back to resolvectl(1)
|
||||
// when all nameservers are stub addresses.
|
||||
type LinuxNetworkDiscoverer struct{}
|
||||
|
||||
// stubAddrs are local stub resolver addresses used by systemd-resolved.
|
||||
var stubAddrs = map[string]bool{
|
||||
"127.0.0.53": true,
|
||||
"127.0.0.1": true,
|
||||
"127.0.1.1": true,
|
||||
}
|
||||
|
||||
// Discover returns the default gateway and DNS server list for this host.
|
||||
func (d *LinuxNetworkDiscoverer) Discover() (NetworkInfo, error) {
|
||||
info := NetworkInfo{}
|
||||
|
||||
// Step 1: Determine default gateway and interface via "ip route show default".
|
||||
gateway, iface, err := linuxDefaultRoute()
|
||||
if err != nil {
|
||||
return info, fmt.Errorf("reading default route: %w", err)
|
||||
}
|
||||
info.Gateway = gateway
|
||||
info.Interface = iface
|
||||
|
||||
// Step 2: Parse /etc/resolv.conf for nameserver lines.
|
||||
servers, err := linuxResolvConf()
|
||||
if err != nil {
|
||||
// A missing resolv.conf is not fatal — return empty list.
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Step 3: If all servers are stub addresses, try resolvectl for upstream IPs.
|
||||
if len(servers) > 0 && allStubs(servers) && iface != "" {
|
||||
if upstream, err := linuxResolvectl(iface); err == nil && len(upstream) > 0 {
|
||||
servers = upstream
|
||||
}
|
||||
}
|
||||
|
||||
info.DNSServers = servers
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// linuxDefaultRoute parses "ip route show default" and returns the gateway IP
|
||||
// and interface name for the best (lowest metric) default route.
|
||||
func linuxDefaultRoute() (gateway, iface string, err error) {
|
||||
out, err := exec.Command("ip", "route", "show", "default").Output()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("ip route show default: %w", err)
|
||||
}
|
||||
|
||||
bestMetric := -1
|
||||
bestGW := ""
|
||||
bestIface := ""
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 || fields[0] != "default" {
|
||||
continue
|
||||
}
|
||||
// Parse by keyword: "via <ip>" and "dev <iface>" and optional "metric <N>".
|
||||
var gw, dev string
|
||||
metric := 0
|
||||
for i := 1; i < len(fields)-1; i++ {
|
||||
switch fields[i] {
|
||||
case "via":
|
||||
gw = fields[i+1]
|
||||
case "dev":
|
||||
dev = fields[i+1]
|
||||
case "metric":
|
||||
if n, err := parseInt(fields[i+1]); err == nil {
|
||||
metric = n
|
||||
}
|
||||
}
|
||||
}
|
||||
if gw == "" || net.ParseIP(gw) == nil {
|
||||
continue
|
||||
}
|
||||
if bestMetric < 0 || metric < bestMetric {
|
||||
bestMetric = metric
|
||||
bestGW = gw
|
||||
bestIface = dev
|
||||
}
|
||||
}
|
||||
|
||||
if bestGW == "" {
|
||||
return "", "", fmt.Errorf("no default route found")
|
||||
}
|
||||
return bestGW, bestIface, nil
|
||||
}
|
||||
|
||||
// linuxResolvConf reads /etc/resolv.conf and returns the nameserver IPs.
|
||||
func linuxResolvConf() ([]string, error) {
|
||||
f, err := os.Open("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var servers []string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(line, "nameserver") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
ip := fields[1]
|
||||
if net.ParseIP(ip) != nil {
|
||||
servers = append(servers, ip)
|
||||
}
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// linuxResolvectl runs "resolvectl status <iface>" and returns the upstream DNS
|
||||
// servers. Returns an error if resolvectl is not available.
|
||||
func linuxResolvectl(iface string) ([]string, error) {
|
||||
path, err := exec.LookPath("resolvectl")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolvectl not found: %w", err)
|
||||
}
|
||||
out, err := exec.Command(path, "status", iface).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolvectl status %s: %w", iface, err)
|
||||
}
|
||||
|
||||
// Parse "DNS Servers: <ip> <ip>..." and continuation lines.
|
||||
var servers []string
|
||||
inDNS := false
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
stripped := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(stripped, "DNS Servers:") {
|
||||
inDNS = true
|
||||
// Extract IPs from the same line after the label.
|
||||
rest := strings.TrimPrefix(stripped, "DNS Servers:")
|
||||
for _, f := range strings.Fields(rest) {
|
||||
if net.ParseIP(f) != nil {
|
||||
servers = append(servers, f)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if inDNS {
|
||||
// Continuation lines are indented; stop at a non-indented line.
|
||||
if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
|
||||
break
|
||||
}
|
||||
for _, f := range strings.Fields(stripped) {
|
||||
if net.ParseIP(f) != nil {
|
||||
servers = append(servers, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// allStubs reports whether every address in servers is a known stub resolver.
|
||||
func allStubs(servers []string) bool {
|
||||
for _, s := range servers {
|
||||
if !stubAddrs[s] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func parseInt(s string) (int, error) {
|
||||
n := 0
|
||||
for _, c := range s {
|
||||
if c < '0' || c > '9' {
|
||||
return 0, fmt.Errorf("not an int: %q", s)
|
||||
}
|
||||
n = n*10 + int(c-'0')
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package platform_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"dns-helper/platform"
|
||||
)
|
||||
|
||||
func TestFakeNetworkDiscoverer_ReturnsInfo(t *testing.T) {
|
||||
fake := &platform.FakeNetworkDiscoverer{
|
||||
Info: platform.NetworkInfo{
|
||||
DNSServers: []string{"10.0.0.1", "10.0.0.2"},
|
||||
Gateway: "10.0.0.1",
|
||||
Interface: "eth0",
|
||||
},
|
||||
}
|
||||
|
||||
info, err := fake.Discover()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(info.DNSServers) != 2 {
|
||||
t.Errorf("expected 2 DNS servers, got %d", len(info.DNSServers))
|
||||
}
|
||||
if info.DNSServers[0] != "10.0.0.1" {
|
||||
t.Errorf("DNSServers[0]: got %q, want %q", info.DNSServers[0], "10.0.0.1")
|
||||
}
|
||||
if info.DNSServers[1] != "10.0.0.2" {
|
||||
t.Errorf("DNSServers[1]: got %q, want %q", info.DNSServers[1], "10.0.0.2")
|
||||
}
|
||||
if info.Gateway != "10.0.0.1" {
|
||||
t.Errorf("Gateway: got %q, want %q", info.Gateway, "10.0.0.1")
|
||||
}
|
||||
if info.Interface != "eth0" {
|
||||
t.Errorf("Interface: got %q, want %q", info.Interface, "eth0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeNetworkDiscoverer_ReturnsError(t *testing.T) {
|
||||
fake := &platform.FakeNetworkDiscoverer{
|
||||
Err: errors.New("discovery failed"),
|
||||
}
|
||||
|
||||
_, err := fake.Discover()
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if err.Error() != "discovery failed" {
|
||||
t.Errorf("error message: got %q, want %q", err.Error(), "discovery failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeNetworkDiscoverer_EmptyInfo(t *testing.T) {
|
||||
fake := &platform.FakeNetworkDiscoverer{}
|
||||
|
||||
info, err := fake.Discover()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(info.DNSServers) != 0 {
|
||||
t.Errorf("expected empty DNSServers, got %v", info.DNSServers)
|
||||
}
|
||||
if info.Gateway != "" {
|
||||
t.Errorf("expected empty Gateway, got %q", info.Gateway)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user