package configreader import ( "fmt" "net" "os" "os/exec" "path/filepath" "strings" "networktool/internal/model" "gopkg.in/yaml.v3" ) type Service struct{} type configuredInterfaceValues struct { DHCP4 bool Addresses []model.InterfaceAddressConfig Routes []model.InterfaceRouteConfig 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.Addresses = configured.Addresses config.Routes = configured.Routes if configured.DNS != nil { config.DNS = configured.DNS } if config.Dhcp4 || len(config.Addresses) == 0 { addresses := readInterfaceIPv4(iface) if len(addresses) > 0 { config.Addresses = addresses } } if len(config.Addresses) > 0 { config.IP = config.Addresses[0].IP config.Prefix = config.Addresses[0].Prefix } if config.Dhcp4 || len(config.Routes) == 0 { config.Routes = readRoutes(interfaceName) } for _, route := range config.Routes { if route.To == "default" { config.Gateway = route.Via break } } return config, nil } func readInterfaceIPv4(iface *net.Interface) []model.InterfaceAddressConfig { addrs, err := iface.Addrs() if err != nil { return nil } result := make([]model.InterfaceAddressConfig, 0) for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok || ipNet.IP.To4() == nil { continue } prefix, _ := ipNet.Mask.Size() result = append(result, model.InterfaceAddressConfig{IP: ipNet.IP.String(), Prefix: prefix}) } return result } func readRoutes(interfaceName string) []model.InterfaceRouteConfig { cmd := exec.Command("ip", "route", "show", "dev", interfaceName) output, err := cmd.Output() if err != nil { return nil } result := make([]model.InterfaceRouteConfig, 0) for _, line := range strings.Split(string(output), "\n") { line = strings.TrimSpace(line) if line == "" { continue } parts := strings.Fields(line) if len(parts) >= 3 && parts[0] == "default" && parts[1] == "via" { result = append(result, model.InterfaceRouteConfig{To: "default", Via: parts[2]}) continue } if len(parts) >= 3 && strings.Contains(parts[0], "/") && parts[1] == "via" { result = append(result, model.InterfaceRouteConfig{To: parts[0], Via: parts[2]}) } } 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.Addresses = append(values.Addresses, model.InterfaceAddressConfig{IP: ip.String(), Prefix: prefix}) } } 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) to = strings.TrimSpace(to) via = strings.TrimSpace(via) if to != "" && via != "" { values.Routes = append(values.Routes, model.InterfaceRouteConfig{To: to, Via: via}) } } } if !hasDefaultRoute(values.Routes) { if gateway4, ok := entry["gateway4"].(string); ok { gateway4 = strings.TrimSpace(gateway4) if gateway4 != "" { values.Routes = append(values.Routes, model.InterfaceRouteConfig{To: "default", Via: gateway4}) } } } return values, values.DHCP4 || len(values.Addresses) > 0 || len(values.Routes) > 0 || len(values.DNS) > 0 } func hasDefaultRoute(routes []model.InterfaceRouteConfig) bool { for _, route := range routes { if route.To == "default" { return true } } return false } 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 }