diff --git a/server/reload.go b/server/reload.go index aff64ccd..7058417f 100644 --- a/server/reload.go +++ b/server/reload.go @@ -413,7 +413,9 @@ func (r *routesOption) Apply(server *Server) { } // Add routes. + server.mu.Lock() server.solicitRoutes(r.add) + server.mu.Unlock() server.Noticef("Reloaded: cluster routes") } diff --git a/server/route.go b/server/route.go index 0ed56a48..c37bf682 100644 --- a/server/route.go +++ b/server/route.go @@ -1305,6 +1305,7 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client { authRequired := s.routeInfo.AuthRequired tlsRequired := s.routeInfo.TLSRequired clusterName := s.info.Cluster + tlsName := s.routeTLSName s.mu.Unlock() // Grab lock @@ -1329,8 +1330,13 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client { tlsConfig = tlsConfig.Clone() } // Perform (server or client side) TLS handshake. - if _, err := c.doTLSHandshake("route", didSolicit, rURL, tlsConfig, _EMPTY_, opts.Cluster.TLSTimeout, opts.Cluster.TLSPinnedCerts); err != nil { + if resetTLSName, err := c.doTLSHandshake("route", didSolicit, rURL, tlsConfig, tlsName, opts.Cluster.TLSTimeout, opts.Cluster.TLSPinnedCerts); err != nil { c.mu.Unlock() + if resetTLSName { + s.mu.Lock() + s.routeTLSName = _EMPTY_ + s.mu.Unlock() + } return nil } } @@ -1897,7 +1903,24 @@ func (c *client) isSolicitedRoute() bool { return c.kind == ROUTER && c.route != nil && c.route.didSolicit } +// Save the first hostname found in route URLs. This will be used in gossip mode +// when trying to create a TLS connection by setting the tlsConfig.ServerName. +// Lock is held on entry +func (s *Server) saveRouteTLSName() { + var tlsName string + for _, u := range s.getOpts().Routes { + if tlsName == _EMPTY_ && net.ParseIP(u.Hostname()) == nil { + tlsName = u.Hostname() + } + } + s.routeTLSName = tlsName +} + +// Start connection process to provided routes. Each route connection will +// be started in a dedicated go routine. +// Lock is held on entry func (s *Server) solicitRoutes(routes []*url.URL) { + s.saveRouteTLSName() for _, r := range routes { route := r s.startGoRoutine(func() { s.connectToRoute(route, true, true) }) diff --git a/server/routes_test.go b/server/routes_test.go index ed3405ed..1c9c205e 100644 --- a/server/routes_test.go +++ b/server/routes_test.go @@ -1670,3 +1670,60 @@ func TestRouteSolicitedReconnectsEvenIfImplicit(t *testing.T) { // OK } } + +func TestRouteSaveTLSName(t *testing.T) { + c1Conf := createConfFile(t, []byte(` + port: -1 + cluster { + name: "abc" + port: -1 + tls { + cert_file: '../test/configs/certs/server-noip.pem' + key_file: '../test/configs/certs/server-key-noip.pem' + ca_file: '../test/configs/certs/ca.pem' + } + } + `)) + defer removeFile(t, c1Conf) + s1, o1 := RunServerWithConfig(c1Conf) + defer s1.Shutdown() + + c2And3Conf := createConfFile(t, []byte(fmt.Sprintf(` + port: -1 + cluster { + name: "abc" + port: -1 + routes: ["nats://localhost:%d"] + tls { + cert_file: '../test/configs/certs/server-noip.pem' + key_file: '../test/configs/certs/server-key-noip.pem' + ca_file: '../test/configs/certs/ca.pem' + } + } + `, o1.Cluster.Port))) + defer removeFile(t, c2And3Conf) + s2, _ := RunServerWithConfig(c2And3Conf) + defer s2.Shutdown() + + checkClusterFormed(t, s1, s2) + + s3, _ := RunServerWithConfig(c2And3Conf) + defer s3.Shutdown() + + checkClusterFormed(t, s1, s2, s3) + + // Do a reload of s2 and close the route connections and make sure it + // reconnects properly. + err := s2.Reload() + require_NoError(t, err) + + s2.mu.RLock() + for _, r := range s2.routes { + r.mu.Lock() + r.nc.Close() + r.mu.Unlock() + } + s2.mu.RUnlock() + + checkClusterFormed(t, s1, s2, s3) +} diff --git a/server/server.go b/server/server.go index 607958a9..c6e18980 100644 --- a/server/server.go +++ b/server/server.go @@ -152,6 +152,7 @@ type Server struct { routeInfoJSON []byte routeResolver netResolver routesToSelf map[string]struct{} + routeTLSName string leafNodeListener net.Listener leafNodeListenerErr error leafNodeInfo Info