netbirdio/netbird · error
invalid server IP
Error message
invalid server IP
What it means
substituteHost rewrites a relay URL's host to a literal IP (keeping scheme and port) and requires that IP to be a valid netip.Addr. The error means the passed Addr is the zero value (IsValid() false): resolution was skipped, failed upstream, or its error was ignored.
Source
Thrown at shared/relay/client/client.go:468
rd.WithSequential()
}
return rd.Dial(ctx)
}
// substituteHost replaces the host portion of a rel/rels URL with ip,
// preserving the scheme and port. Returns the rewritten URL and the
// original host to use as the TLS ServerName, or empty if the original
// host is itself an IP literal (SNI requires a DNS name).
func substituteHost(serverURL string, ip netip.Addr) (string, string, error) {
u, err := url.Parse(serverURL)
if err != nil {
return "", "", fmt.Errorf("parse %q: %w", serverURL, err)
}
if u.Scheme == "" || u.Host == "" {
return "", "", fmt.Errorf("invalid relay URL %q", serverURL)
}
if !ip.IsValid() {
return "", "", errors.New("invalid server IP")
}
origHost := u.Hostname()
if _, err := netip.ParseAddr(origHost); err == nil {
origHost = ""
}
ip = ip.Unmap()
newHost := ip.String()
if ip.Is6() {
newHost = "[" + newHost + "]"
}
if port := u.Port(); port != "" {
u.Host = newHost + ":" + port
} else {
u.Host = newHost
}
return u.String(), origHost, nil
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Check ip.IsValid() before calling substituteHost
- Propagate and handle resolver errors instead of continuing with the zero Addr
- Log the resolution failure that produced the empty address
Example fix
// before
u, name, err := substituteHost(relayURL, resolvedAddr)
// after
if !resolvedAddr.IsValid() {
return fmt.Errorf("relay host %q did not resolve to a usable address", host)
}
u, name, err := substituteHost(relayURL, resolvedAddr) Defensive patterns
Strategy: validation
Validate before calling
addrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
if err != nil || len(addrs) == 0 {
return fmt.Errorf("resolve relay host %q: %w", host, err)
}
ip := addrs[0].Unmap()
if !ip.IsValid() {
return fmt.Errorf("resolver returned invalid address for %q", host)
} Type guard
func isValidAddr(ip netip.Addr) bool {
return ip.IsValid()
} Try / catch
if _, _, err := substituteHost(serverURL, ip); err != nil {
if err.Error() == "invalid server IP" {
// re-run resolution and propagate its error instead of ignoring it
}
return err
} Prevention
- Always check resolver errors before using the result
- Call Unmap() on resolved addresses before passing them on
- Guard shared resolver state with a mutex or pass values, not shared fields
When it happens
Trigger: Passing netip.Addr{} because the resolver returned no addresses or its error was swallowed; a race where the address is consumed before DNS resolution completes.
Common situations: Custom integrations calling substituteHost with a lookup result not checked; races between a resolver goroutine and the URL-rewrite path.
Related errors
- %s is invalid, it should be formatted as IP:Port string or a
- record name is required
- invalid record name format
- record type is required
- invalid CNAME target format
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/f559348bb6987a29.
Report an issue: GitHub.