diff --git a/internal/registry/hostname.go b/internal/registry/hostname.go new file mode 100644 index 000000000..83d58eeb5 --- /dev/null +++ b/internal/registry/hostname.go @@ -0,0 +1,67 @@ +package registry + +import ( + "errors" + "fmt" + "net" + "os" + "strings" +) + +func isFQDN(host string) bool { + return strings.Count(host, ".") > 1 +} + +func chooseIP(ips []net.IP) net.IP { + for _, ip := range ips { + if !ip.IsLoopback() && !ip.IsInterfaceLocalMulticast() && !ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast() { + return ip + } + } + return nil +} + +// getViaLookup tries to lookup whether short hostname may be resolved into IP and back into a longer one +func getViaLookup(host string) (string, error) { + addrs, err := net.LookupIP(host) + if err != nil { + return "", err + } + if len(addrs) == 0 { + return "", errors.New("address lookup failed") + } + for _, addr := range addrs { + hosts, err := net.LookupAddr(addr.String()) + if err != nil { + continue + } + for _, h := range hosts { + h = strings.TrimSuffix(h, ".") + if isFQDN(h) { + return h, nil + } + } + } + + if ip := chooseIP(addrs); ip != nil { + return ip.String(), nil + } + return "", errors.New("lookup failed") +} + +// getHostOrIP tries to fetch a publicly accessible IP address for the current host/container +func getHostOrIP() (string, error) { + host, err := os.Hostname() + if err != nil { + return "", fmt.Errorf("hostname: %w", err) + } + if isFQDN(host) { + return host, nil + } + + if h, err := getViaLookup(host); err == nil { + return h, nil + } + + return host, nil +} diff --git a/internal/registry/reporter.go b/internal/registry/reporter.go index b57c3cddd..bc25e73fe 100644 --- a/internal/registry/reporter.go +++ b/internal/registry/reporter.go @@ -81,9 +81,22 @@ func getReportedServices(cfg *config.Config) ([]*pb.Service, error) { } func metricsURL(o config.Options) (*url.URL, error) { + host, port, err := net.SplitHostPort(o.MetricsAddr) + if err != nil { + return nil, fmt.Errorf("invalid metrics address %q: %w", o.MetricsAddr, err) + } + if port == "" { + return nil, fmt.Errorf("invalid metrics value %q: port is required", o.MetricsAddr) + } + if host == "" { + if host, err = getHostOrIP(); err != nil { + return nil, fmt.Errorf("could not guess hostname: %w", err) + } + } + u := url.URL{ Scheme: "http", - Host: o.MetricsAddr, + Host: net.JoinHostPort(host, port), Path: defaultMetricsPath, } @@ -107,19 +120,6 @@ func metricsURL(o config.Options) (*url.URL, error) { return nil, fmt.Errorf("no metrics address provided") } - host, port, err := net.SplitHostPort(o.MetricsAddr) - if err != nil { - return nil, fmt.Errorf("invalid metrics address %q: %w", o.MetricsAddr, err) - } - - if port == "" { - return nil, fmt.Errorf("invalid metrics value %q: port is required", o.MetricsAddr) - } - - if host == "" { - return nil, fmt.Errorf("invalid metrics value %q: either host or IP address is required", o.MetricsAddr) - } - return &u, nil } diff --git a/internal/registry/reporter_test.go b/internal/registry/reporter_test.go index e27bfcd3e..63d97c626 100644 --- a/internal/registry/reporter_test.go +++ b/internal/registry/reporter_test.go @@ -24,7 +24,6 @@ func TestMetricsURL(t *testing.T) { for _, opt := range []config.Options{ {MetricsAddr: "my.host:"}, {MetricsAddr: "my.host:9090", MetricsBasicAuth: "SMTH"}, - {MetricsAddr: ":9090"}, {MetricsAddr: "my.host"}, } { _, err := metricsURL(opt)