Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user