package config import ( "errors" "fmt" "net" "net/netip" "net/url" "strings" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/shared/management/domain" mgmProto "github.com/netbirdio/netbird/shared/management/proto" ) var ( ErrEmptyURL = errors.New("empty URL") ErrEmptyHost = errors.New("empty host") ErrIPNotAllowed = errors.New("IP address not allowed") ) // ServerDomains represents the management server domains extracted from NetBird configuration type ServerDomains struct { Signal domain.Domain Relay []domain.Domain Flow domain.Domain Stuns []domain.Domain Turns []domain.Domain } // ExtractFromNetbirdConfig extracts domain information from NetBird protobuf configuration func ExtractFromNetbirdConfig(config *mgmProto.NetbirdConfig) ServerDomains { if config == nil { return ServerDomains{} } domains := ServerDomains{} domains.Signal = extractSignalDomain(config) domains.Relay = extractRelayDomains(config) domains.Flow = extractFlowDomain(config) domains.Stuns = extractStunDomains(config) domains.Turns = extractTurnDomains(config) return domains } // ExtractValidDomain extracts a valid domain from a URL, filtering out IP addresses func ExtractValidDomain(rawURL string) (domain.Domain, error) { if rawURL == "" { return "", ErrEmptyURL } parsedURL, err := url.Parse(rawURL) if err == nil { if domain, err := extractFromParsedURL(parsedURL); err != nil || domain != "" { return domain, err } } return extractFromRawString(rawURL) } // extractFromParsedURL handles domain extraction from successfully parsed URLs func extractFromParsedURL(parsedURL *url.URL) (domain.Domain, error) { if parsedURL.Hostname() != "" { return extractDomainFromHost(parsedURL.Hostname()) } if parsedURL.Opaque == "" || parsedURL.Scheme == "" { return "", nil } // Handle URLs with opaque content (e.g., stun:host:port) if strings.Contains(parsedURL.Scheme, ".") { // This is likely "domain.com:port" being parsed as scheme:opaque reconstructed := parsedURL.Scheme + ":" + parsedURL.Opaque if host, _, err := net.SplitHostPort(reconstructed); err == nil { return extractDomainFromHost(host) } return extractDomainFromHost(parsedURL.Scheme) } // Valid scheme with opaque content (e.g., stun:host:port) host := parsedURL.Opaque if queryIndex := strings.Index(host, "?"); queryIndex > 0 { host = host[:queryIndex] } if hostOnly, _, err := net.SplitHostPort(host); err == nil { return extractDomainFromHost(hostOnly) } return extractDomainFromHost(host) } // extractFromRawString handles domain extraction when URL parsing fails or returns no results func extractFromRawString(rawURL string) (domain.Domain, error) { if host, _, err := net.SplitHostPort(rawURL); err == nil { return extractDomainFromHost(host) } return extractDomainFromHost(rawURL) } // extractDomainFromHost extracts domain from a host string, filtering out IP addresses func extractDomainFromHost(host string) (domain.Domain, error) { if host == "" { return "", ErrEmptyHost } if _, err := netip.ParseAddr(host); err == nil { return "", fmt.Errorf("%w: %s", ErrIPNotAllowed, host) } d, err := domain.FromString(host) if err != nil { return "", fmt.Errorf("invalid domain: %v", err) } return d, nil } // extractSingleDomain extracts a single domain from a URL with error logging func extractSingleDomain(url, serviceType string) domain.Domain { if url == "" { return "" } d, err := ExtractValidDomain(url) if err != nil { log.Debugf("Skipping %s: %v", serviceType, err) return "" } return d } // extractMultipleDomains extracts multiple domains from URLs with error logging func extractMultipleDomains(urls []string, serviceType string) []domain.Domain { var domains []domain.Domain for _, url := range urls { if url == "" { continue } d, err := ExtractValidDomain(url) if err != nil { log.Debugf("Skipping %s: %v", serviceType, err) continue } domains = append(domains, d) } return domains } // extractSignalDomain extracts the signal domain from NetBird configuration. func extractSignalDomain(config *mgmProto.NetbirdConfig) domain.Domain { if config.Signal != nil { return extractSingleDomain(config.Signal.Uri, "signal") } return "" } // extractRelayDomains extracts relay server domains from NetBird configuration. func extractRelayDomains(config *mgmProto.NetbirdConfig) []domain.Domain { if config.Relay != nil { return extractMultipleDomains(config.Relay.Urls, "relay") } return nil } // extractFlowDomain extracts the traffic flow domain from NetBird configuration. func extractFlowDomain(config *mgmProto.NetbirdConfig) domain.Domain { if config.Flow != nil { return extractSingleDomain(config.Flow.Url, "flow") } return "" } // extractStunDomains extracts STUN server domains from NetBird configuration. func extractStunDomains(config *mgmProto.NetbirdConfig) []domain.Domain { var urls []string for _, stun := range config.Stuns { if stun != nil && stun.Uri != "" { urls = append(urls, stun.Uri) } } return extractMultipleDomains(urls, "STUN") } // extractTurnDomains extracts TURN server domains from NetBird configuration. func extractTurnDomains(config *mgmProto.NetbirdConfig) []domain.Domain { var urls []string for _, turn := range config.Turns { if turn != nil && turn.HostConfig != nil && turn.HostConfig.Uri != "" { urls = append(urls, turn.HostConfig.Uri) } } return extractMultipleDomains(urls, "TURN") }