package app import ( "context" "fmt" "net" "net/netip" "net/url" "sort" "strconv" "strings" "time" ) func transportResolveBootstrapIPv4(candidates []string) ([]string, error) { set := map[string]struct{}{} var warns []string for _, raw := range candidates { host := transportNormalizeBootstrapTarget(raw) if host == "" { continue } if addr, err := netip.ParseAddr(host); err == nil { if addr.Is4() { set[addr.String()] = struct{}{} } continue } ctx, cancel := context.WithTimeout(context.Background(), 2500*time.Millisecond) ipAddrs, err := net.DefaultResolver.LookupIPAddr(ctx, host) cancel() if err != nil { warns = append(warns, host+": "+strings.TrimSpace(err.Error())) continue } resolved := 0 for _, ipa := range ipAddrs { v4 := ipa.IP.To4() if v4 == nil { continue } set[v4.String()] = struct{}{} resolved++ } if resolved == 0 { warns = append(warns, host+": no IPv4 records") } } ips := make([]string, 0, len(set)) for ip := range set { ips = append(ips, ip) } sort.Strings(ips) if len(warns) == 0 { return ips, nil } if len(warns) > 3 { warns = append(warns[:3], fmt.Sprintf("... and %d more", len(warns)-3)) } return ips, fmt.Errorf("bootstrap resolve warnings: %s", strings.Join(warns, "; ")) } func transportNormalizeBootstrapTarget(raw string) string { s := strings.TrimSpace(raw) if s == "" { return "" } if strings.Contains(s, "://") { if u, err := url.Parse(s); err == nil { host := strings.TrimSpace(u.Host) if host != "" { s = host } else if strings.TrimSpace(u.Opaque) != "" { s = strings.TrimSpace(u.Opaque) } } } if at := strings.LastIndex(s, "@"); at >= 0 { s = s[at+1:] } if idx := strings.IndexAny(s, "/?#"); idx >= 0 { s = s[:idx] } s = strings.TrimSpace(s) if s == "" { return "" } if host, _, err := net.SplitHostPort(s); err == nil { s = host } else if strings.Count(s, ":") == 1 { i := strings.LastIndex(s, ":") if i > 0 { if _, err := strconv.Atoi(strings.TrimSpace(s[i+1:])); err == nil { s = s[:i] } } } s = strings.TrimSpace(strings.Trim(s, "[]")) s = strings.TrimSuffix(s, ".") if s == "" { return "" } return strings.ToLower(s) } func transportDetectMainIPv4Route() (transportMainRoute, error) { stdout, stderr, code, err := transportRunCommand(5*time.Second, "ip", "-4", "route", "show", "table", "main", "default") if err != nil || code != 0 { return transportMainRoute{}, transportCommandError("ip -4 route show table main default", stdout, stderr, code, err) } for _, line := range strings.Split(stdout, "\n") { fields := strings.Fields(strings.TrimSpace(line)) if len(fields) == 0 || fields[0] != "default" { continue } route := transportMainRoute{} for i := 0; i < len(fields)-1; i++ { switch fields[i] { case "dev": route.Dev = strings.TrimSpace(fields[i+1]) case "via": route.Via = strings.TrimSpace(fields[i+1]) } } if route.Dev != "" { return route, nil } } return transportMainRoute{}, fmt.Errorf("main default route not found") }