package configreader import ( "bufio" "fmt" "net" "os" "os/exec" "path/filepath" "strings" "networktool/internal/model" "gopkg.in/yaml.v3" ) type Service struct{} type configuredInterfaceValues struct { DHCP4 bool IP string Prefix int Gateway string DNS []string } func New() *Service { return &Service{} } func (s *Service) Read(interfaceName string) (model.InterfaceConfig, error) { iface, err := net.InterfaceByName(interfaceName) if err != nil { return model.InterfaceConfig{}, err } config := model.InterfaceConfig{Interface: interfaceName, DNS: []string{}} configured := readConfiguredInterfaceValues(interfaceName) config.Dhcp4 = configured.DHCP4 config.IP = configured.IP config.Prefix = configured.Prefix config.Gateway = configured.Gateway if configured.DNS != nil { config.DNS = configured.DNS } if config.Dhcp4 || config.IP == "" { ip, prefix := readInterfaceIPv4(iface) if ip != "" { config.IP = ip config.Prefix = prefix } } if config.Dhcp4 || config.Gateway == "" { config.Gateway = readGateway(interfaceName) } if config.Dhcp4 || len(config.DNS) == 0 { config.DNS = readDNS() } return config, nil } func readInterfaceIPv4(iface *net.Interface) (string, int) { addrs, err := iface.Addrs() if err != nil { return "", 0 } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok || ipNet.IP.To4() == nil { continue } prefix, _ := ipNet.Mask.Size() return ipNet.IP.String(), prefix } return "", 0 } func readGateway(interfaceName string) string { cmd := exec.Command("ip", "route", "show", "dev", interfaceName) output, err := cmd.Output() if err != nil { return "" } for _, line := range strings.Split(string(output), "\n") { line = strings.TrimSpace(line) if !strings.HasPrefix(line, "default via ") { continue } parts := strings.Fields(line) if len(parts) >= 3 { return parts[2] } } return "" } func readDNS() []string { file, err := os.Open("/etc/resolv.conf") if err != nil { return []string{} } defer file.Close() result := make([]string, 0, 2) scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if !strings.HasPrefix(line, "nameserver ") { continue } parts := strings.Fields(line) if len(parts) >= 2 { result = append(result, parts[1]) } } return result } func readConfiguredInterfaceValues(interfaceName string) configuredInterfaceValues { files, err := filepath.Glob("/etc/netplan/*.yaml") if err != nil || len(files) == 0 { return configuredInterfaceValues{} } for _, filePath := range files { data, err := os.ReadFile(filePath) if err != nil { continue } var raw map[string]any if err := yaml.Unmarshal(data, &raw); err != nil { continue } values, ok := findInterfaceValues(raw, interfaceName) if ok { return values } } return configuredInterfaceValues{} } func findInterfaceValues(raw map[string]any, interfaceName string) (configuredInterfaceValues, bool) { network, ok := raw["network"].(map[string]any) if !ok { return configuredInterfaceValues{}, false } ethernets, ok := network["ethernets"].(map[string]any) if !ok { return configuredInterfaceValues{}, false } entry, ok := ethernets[interfaceName].(map[string]any) if !ok { return configuredInterfaceValues{}, false } values := configuredInterfaceValues{} if dhcp4, ok := entry["dhcp4"].(bool); ok { values.DHCP4 = dhcp4 } addresses, ok := entry["addresses"].([]any) if ok { for _, item := range addresses { text, ok := item.(string) if !ok || strings.TrimSpace(text) == "" { continue } ip, ipNet, err := net.ParseCIDR(text) if err != nil || ip == nil || ip.To4() == nil { continue } prefix, _ := ipNet.Mask.Size() values.IP = ip.String() values.Prefix = prefix break } } if nameservers, ok := entry["nameservers"].(map[string]any); ok { values.DNS = anyToStringSlice(nameservers["addresses"]) } if routes, ok := entry["routes"].([]any); ok { for _, item := range routes { route, ok := item.(map[string]any) if !ok { continue } to, _ := route["to"].(string) via, _ := route["via"].(string) if strings.TrimSpace(to) == "default" && strings.TrimSpace(via) != "" { values.Gateway = strings.TrimSpace(via) break } } } if values.Gateway == "" { if gateway4, ok := entry["gateway4"].(string); ok { values.Gateway = strings.TrimSpace(gateway4) } } return values, values.DHCP4 || values.IP != "" || values.Gateway != "" || len(values.DNS) > 0 } func anyToStringSlice(value any) []string { switch typed := value.(type) { case []string: return append([]string(nil), typed...) case []any: result := make([]string, 0, len(typed)) for _, item := range typed { text, ok := item.(string) if ok && strings.TrimSpace(text) != "" { result = append(result, text) } } return result default: return []string{} } } func MustRead(interfaceName string) model.InterfaceConfig { config, err := New().Read(interfaceName) if err != nil { panic(fmt.Sprintf("read interface config failed: %v", err)) } return config }