diff --git a/nats.go b/nats.go index 07c36f083..9faa80761 100644 --- a/nats.go +++ b/nats.go @@ -1178,18 +1178,18 @@ func (nc *Conn) createConn() (err error) { } // We will auto-expand host names if they resolve to multiple IPs - var hosts []string + hosts := map[string]struct{}{} u := nc.current.url if net.ParseIP(u.Hostname()) == nil { addrs, _ := net.LookupHost(u.Hostname()) for _, addr := range addrs { - hosts = append(hosts, fmt.Sprintf("%s:%s", addr, u.Port())) + hosts[net.JoinHostPort(addr, u.Port())] = struct{}{} } } // Fall back to what we were given. if len(hosts) == 0 { - hosts = append(hosts, u.Host) + hosts[u.Host] = struct{}{} } // CustomDialer takes precedence. If not set, use Opts.Dialer which @@ -1203,7 +1203,7 @@ func (nc *Conn) createConn() (err error) { dialer = ©Dialer } - for _, host := range hosts { + for host := range hosts { nc.conn, err = dialer.Dial("tcp", host) if err == nil { break diff --git a/nats_test.go b/nats_test.go index d6a7a1d51..626920a31 100644 --- a/nats_test.go +++ b/nats_test.go @@ -1555,3 +1555,40 @@ func TestNKeyOptionFromSeed(t *testing.T) { close(ch) wg.Wait() } + +func TestLookupHostResultIsRandomized(t *testing.T) { + orgAddrs, err := net.LookupHost("localhost") + if err != nil { + t.Fatalf("Error looking up host: %v", err) + } + if len(orgAddrs) < 2 { + t.Skip("localhost resolves to less than 2 addresses, so test not relevant") + } + + opts := gnatsd.DefaultTestOptions + // For this test, important to be able to listen on both IPv4/v6 + // because it is likely than in local test, localhost will resolve + // to ::1 and 127.0.0.1 + opts.Host = "0.0.0.0" + opts.Port = TEST_PORT + s := RunServerWithOptions(opts) + defer s.Shutdown() + + for i := 0; i < 10; i++ { + nc, err := Connect(fmt.Sprintf("localhost:%d", TEST_PORT)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + nc.mu.Lock() + host, _, _ := net.SplitHostPort(nc.conn.LocalAddr().String()) + nc.mu.Unlock() + isFirst := host == orgAddrs[0] + nc.Close() + if !isFirst { + // We used one that is not the first of the resolved addresses, + // so we consider the test good in that IPs were randomized. + return + } + } + t.Fatalf("Always used first address returned by LookupHost") +}