Skip to content

Do not assume that all non-IP hosts are loopbacks #3085

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
35 changes: 35 additions & 0 deletions internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
})
17 changes: 15 additions & 2 deletions osscluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading