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