diff --git a/internal_test.go b/internal_test.go index 4a655cff0..8ba92722a 100644 --- a/internal_test.go +++ b/internal_test.go @@ -383,3 +383,38 @@ var _ = Describe("ClusterClient", func() { }) }) }) + +var _ = Describe("isLoopback", func() { + DescribeTable("should correctly identify loopback addresses", + func(host string, expected bool) { + result := isLoopback(host) + Expect(result).To(Equal(expected)) + }, + // IP addresses + Entry("IPv4 loopback", "127.0.0.1", true), + Entry("IPv6 loopback", "::1", true), + Entry("IPv4 non-loopback", "192.168.1.1", false), + Entry("IPv6 non-loopback", "2001:db8::1", false), + + // Well-known loopback hostnames + Entry("localhost lowercase", "localhost", true), + Entry("localhost uppercase", "LOCALHOST", true), + Entry("localhost mixed case", "LocalHost", true), + + // Docker-specific loopbacks + Entry("host.docker.internal", "host.docker.internal", true), + Entry("HOST.DOCKER.INTERNAL", "HOST.DOCKER.INTERNAL", true), + Entry("custom.docker.internal", "custom.docker.internal", true), + Entry("app.docker.internal", "app.docker.internal", true), + + // Non-loopback hostnames + Entry("redis hostname", "redis-cluster", false), + Entry("FQDN", "redis.example.com", false), + Entry("docker but not internal", "redis.docker.com", false), + + // Edge cases + Entry("empty string", "", false), + Entry("invalid IP", "256.256.256.256", false), + Entry("partial docker internal", "docker.internal", false), + ) +}) diff --git a/osscluster.go b/osscluster.go index 0f678e602..87c1f096e 100644 --- a/osscluster.go +++ b/osscluster.go @@ -770,12 +770,25 @@ func replaceLoopbackHost(nodeAddr, originHost string) string { return net.JoinHostPort(originHost, nodePort) } +// isLoopback returns true if the host is a loopback address. +// For IP addresses, it uses net.IP.IsLoopback(). +// For hostnames, it recognizes well-known loopback hostnames like "localhost" +// and Docker-specific loopback patterns like "*.docker.internal". func isLoopback(host string) bool { ip := net.ParseIP(host) - if ip == nil { + if ip != nil { + return ip.IsLoopback() + } + + if strings.ToLower(host) == "localhost" { return true } - return ip.IsLoopback() + + if strings.HasSuffix(strings.ToLower(host), ".docker.internal") { + return true + } + + return false } func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) {