Browse Source

lib: Removal global connection registry (#8254)

Simon Frei 3 years ago
parent
commit
b947056e62

+ 1 - 1
cmd/stfinddevice/main.go

@@ -84,7 +84,7 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
 }
 
 func checkServer(deviceID protocol.DeviceID, server string) checkResult {
-	disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, events.NoopLogger)
+	disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, events.NoopLogger, nil)
 	if err != nil {
 		return checkResult{error: err}
 	}

+ 4 - 2
lib/connections/connections_test.go

@@ -21,6 +21,7 @@ import (
 	"github.com/thejerf/suture/v4"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/nat"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -414,7 +415,7 @@ func withConnectionPair(b *testing.B, connUri string, h func(client, server inte
 	}
 	natSvc := nat.NewService(deviceId, wcfg)
 	conns := make(chan internalConn, 1)
-	listenSvc := lf.New(uri, wcfg, tlsCfg, conns, natSvc)
+	listenSvc := lf.New(uri, wcfg, tlsCfg, conns, natSvc, registry.New())
 	supervisor.Add(listenSvc)
 
 	var addr *url.URL
@@ -433,7 +434,8 @@ func withConnectionPair(b *testing.B, connUri string, h func(client, server inte
 	if err != nil {
 		b.Fatal(err)
 	}
-	dialer := df.New(cfg.Options, tlsCfg)
+	// Purposely using a different registry: Don't want to reuse port between dialer and listener on the same device
+	dialer := df.New(cfg.Options, tlsCfg, registry.New())
 
 	// Relays might take some time to register the device, so dial multiple times
 	clientConn, err := dialer.Dial(ctx, deviceId, addr)

+ 10 - 6
lib/connections/quic_dial.go

@@ -41,6 +41,7 @@ func init() {
 
 type quicDialer struct {
 	commonDialer
+	registry *registry.Registry
 }
 
 func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL) (internalConn, error) {
@@ -58,7 +59,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
 	// Given we always pass the connection to quic, it assumes it's a remote connection it never closes it,
 	// So our wrapper around it needs to close it, but it only needs to close it if it's not the listening connection.
 	var createdConn net.PacketConn
-	listenConn := registry.Get(uri.Scheme, packetConnUnspecified)
+	listenConn := d.registry.Get(uri.Scheme, packetConnUnspecified)
 	if listenConn != nil {
 		conn = listenConn.(net.PacketConn)
 	} else {
@@ -96,7 +97,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
 
 type quicDialerFactory struct{}
 
-func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
+func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config, registry *registry.Registry) genericDialer {
 	// So the idea is that we should probably try dialing every 20 seconds.
 	// However it would still be nice if this was adjustable/proportional to ReconnectIntervalS
 	// But prevent something silly like 1/3 = 0 etc.
@@ -104,10 +105,13 @@ func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Confi
 	if quicInterval < 10 {
 		quicInterval = 10
 	}
-	return &quicDialer{commonDialer{
-		reconnectInterval: time.Duration(quicInterval) * time.Second,
-		tlsCfg:            tlsCfg,
-	}}
+	return &quicDialer{
+		commonDialer: commonDialer{
+			reconnectInterval: time.Duration(quicInterval) * time.Second,
+			tlsCfg:            tlsCfg,
+		},
+		registry: registry,
+	}
 }
 
 func (quicDialerFactory) Priority() int {

+ 15 - 13
lib/connections/quic_listen.go

@@ -40,11 +40,12 @@ type quicListener struct {
 
 	onAddressesChangedNotifier
 
-	uri     *url.URL
-	cfg     config.Wrapper
-	tlsCfg  *tls.Config
-	conns   chan internalConn
-	factory listenerFactory
+	uri      *url.URL
+	cfg      config.Wrapper
+	tlsCfg   *tls.Config
+	conns    chan internalConn
+	factory  listenerFactory
+	registry *registry.Registry
 
 	address *url.URL
 	laddr   net.Addr
@@ -100,8 +101,8 @@ func (t *quicListener) serve(ctx context.Context) error {
 
 	go svc.Serve(ctx)
 
-	registry.Register(t.uri.Scheme, conn)
-	defer registry.Unregister(t.uri.Scheme, conn)
+	t.registry.Register(t.uri.Scheme, conn)
+	defer t.registry.Unregister(t.uri.Scheme, conn)
 
 	listener, err := quic.Listen(conn, t.tlsCfg, quicConfig)
 	if err != nil {
@@ -217,13 +218,14 @@ func (f *quicListenerFactory) Valid(config.Configuration) error {
 	return nil
 }
 
-func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
+func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service, registry *registry.Registry) genericListener {
 	l := &quicListener{
-		uri:     fixupPort(uri, config.DefaultQUICPort),
-		cfg:     cfg,
-		tlsCfg:  tlsCfg,
-		conns:   conns,
-		factory: f,
+		uri:      fixupPort(uri, config.DefaultQUICPort),
+		cfg:      cfg,
+		tlsCfg:   tlsCfg,
+		conns:    conns,
+		factory:  f,
+		registry: registry,
 	}
 	l.ServiceWithError = svcutil.AsService(l.serve, l.String())
 	l.nat.Store(stun.NATUnknown)

+ 0 - 16
lib/connections/registry/registry.go

@@ -15,10 +15,6 @@ import (
 	"github.com/syncthing/syncthing/lib/sync"
 )
 
-var (
-	Default = New()
-)
-
 type Registry struct {
 	mut       sync.Mutex
 	available map[string][]interface{}
@@ -85,15 +81,3 @@ func (r *Registry) Get(scheme string, preferred func(interface{}) bool) interfac
 	}
 	return best
 }
-
-func Register(scheme string, item interface{}) {
-	Default.Register(scheme, item)
-}
-
-func Unregister(scheme string, item interface{}) {
-	Default.Unregister(scheme, item)
-}
-
-func Get(scheme string, preferred func(interface{}) bool) interface{} {
-	return Default.Get(scheme, preferred)
-}

+ 2 - 1
lib/connections/relay_dial.go

@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/dialer"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/relay/client"
@@ -68,7 +69,7 @@ func (d *relayDialer) Dial(ctx context.Context, id protocol.DeviceID, uri *url.U
 
 type relayDialerFactory struct{}
 
-func (relayDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
+func (relayDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config, _ *registry.Registry) genericDialer {
 	return &relayDialer{commonDialer{
 		trafficClass:      opts.TrafficClass,
 		reconnectInterval: time.Duration(opts.RelayReconnectIntervalM) * time.Minute,

+ 2 - 1
lib/connections/relay_listen.go

@@ -16,6 +16,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/dialer"
 	"github.com/syncthing/syncthing/lib/nat"
 	"github.com/syncthing/syncthing/lib/relay/client"
@@ -177,7 +178,7 @@ func (t *relayListener) NATType() string {
 
 type relayListenerFactory struct{}
 
-func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
+func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service, _ *registry.Registry) genericListener {
 	t := &relayListener{
 		uri:     uri,
 		cfg:     cfg,

+ 6 - 3
lib/connections/service.go

@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/discover"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/nat"
@@ -160,6 +161,7 @@ type service struct {
 	limiter              *limiter
 	natService           *nat.Service
 	evLogger             events.Logger
+	registry             *registry.Registry
 
 	dialNow           chan struct{}
 	dialNowDevices    map[protocol.DeviceID]struct{}
@@ -170,7 +172,7 @@ type service struct {
 	listenerTokens map[string]suture.ServiceToken
 }
 
-func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger) Service {
+func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger, registry *registry.Registry) Service {
 	spec := svcutil.SpecWithInfoLogger(l)
 	service := &service{
 		Supervisor:              suture.New("connections.Service", spec),
@@ -187,6 +189,7 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
 		limiter:              newLimiter(myID, cfg),
 		natService:           nat.NewService(myID, cfg),
 		evLogger:             evLogger,
+		registry:             registry,
 
 		dialNowDevicesMut: sync.NewMutex(),
 		dialNow:           make(chan struct{}, 1),
@@ -655,7 +658,7 @@ func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg con
 			continue
 		}
 
-		dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg)
+		dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg, s.registry)
 		nextDialAt.set(deviceID, addr, now.Add(dialer.RedialFrequency()))
 
 		// For LAN addresses, increase the priority so that we
@@ -755,7 +758,7 @@ func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
 
 	l.Debugln("Starting listener", uri)
 
-	listener := factory.New(uri, s.cfg, s.tlsCfg, s.conns, s.natService)
+	listener := factory.New(uri, s.cfg, s.tlsCfg, s.conns, s.natService, s.registry)
 	listener.OnAddressesChanged(s.logListenAddressesChangedEvent)
 
 	// Retrying a listener many times in rapid succession is unlikely to help,

+ 3 - 2
lib/connections/structs.go

@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/nat"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/stats"
@@ -139,7 +140,7 @@ func (c internalConn) String() string {
 }
 
 type dialerFactory interface {
-	New(config.OptionsConfiguration, *tls.Config) genericDialer
+	New(config.OptionsConfiguration, *tls.Config, *registry.Registry) genericDialer
 	Priority() int
 	AlwaysWAN() bool
 	Valid(config.Configuration) error
@@ -162,7 +163,7 @@ type genericDialer interface {
 }
 
 type listenerFactory interface {
-	New(*url.URL, config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
+	New(*url.URL, config.Wrapper, *tls.Config, chan internalConn, *nat.Service, *registry.Registry) genericListener
 	Valid(config.Configuration) error
 }
 

+ 12 - 7
lib/connections/tcp_dial.go

@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/dialer"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
@@ -28,6 +29,7 @@ func init() {
 
 type tcpDialer struct {
 	commonDialer
+	registry *registry.Registry
 }
 
 func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL) (internalConn, error) {
@@ -35,7 +37,7 @@ func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL)
 
 	timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
 	defer cancel()
-	conn, err := dialer.DialContextReusePort(timeoutCtx, uri.Scheme, uri.Host)
+	conn, err := dialer.DialContextReusePortFunc(d.registry)(timeoutCtx, uri.Scheme, uri.Host)
 	if err != nil {
 		return internalConn{}, err
 	}
@@ -62,12 +64,15 @@ func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL)
 
 type tcpDialerFactory struct{}
 
-func (tcpDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
-	return &tcpDialer{commonDialer{
-		trafficClass:      opts.TrafficClass,
-		reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
-		tlsCfg:            tlsCfg,
-	}}
+func (tcpDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config, registry *registry.Registry) genericDialer {
+	return &tcpDialer{
+		commonDialer: commonDialer{
+			trafficClass:      opts.TrafficClass,
+			reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
+			tlsCfg:            tlsCfg,
+		},
+		registry: registry,
+	}
 }
 
 func (tcpDialerFactory) Priority() int {

+ 10 - 8
lib/connections/tcp_listen.go

@@ -32,11 +32,12 @@ type tcpListener struct {
 	svcutil.ServiceWithError
 	onAddressesChangedNotifier
 
-	uri     *url.URL
-	cfg     config.Wrapper
-	tlsCfg  *tls.Config
-	conns   chan internalConn
-	factory listenerFactory
+	uri      *url.URL
+	cfg      config.Wrapper
+	tlsCfg   *tls.Config
+	conns    chan internalConn
+	factory  listenerFactory
+	registry *registry.Registry
 
 	natService *nat.Service
 	mapping    *nat.Mapping
@@ -69,8 +70,8 @@ func (t *tcpListener) serve(ctx context.Context) error {
 	t.notifyAddressesChanged(t)
 	defer t.clearAddresses(t)
 
-	registry.Register(t.uri.Scheme, tcaddr)
-	defer registry.Unregister(t.uri.Scheme, tcaddr)
+	t.registry.Register(t.uri.Scheme, tcaddr)
+	defer t.registry.Unregister(t.uri.Scheme, tcaddr)
 
 	l.Infof("TCP listener (%v) starting", tcaddr)
 	defer l.Infof("TCP listener (%v) shutting down", tcaddr)
@@ -213,7 +214,7 @@ func (t *tcpListener) NATType() string {
 
 type tcpListenerFactory struct{}
 
-func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
+func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service, registry *registry.Registry) genericListener {
 	l := &tcpListener{
 		uri:        fixupPort(uri, config.DefaultTCPPort),
 		cfg:        cfg,
@@ -221,6 +222,7 @@ func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.C
 		conns:      conns,
 		natService: natService,
 		factory:    f,
+		registry:   registry,
 	}
 	l.ServiceWithError = svcutil.AsService(l.serve, l.String())
 	return l

+ 24 - 22
lib/dialer/public.go

@@ -104,32 +104,34 @@ func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
 // DialContextReusePort tries dialing via proxy if a proxy is configured, and falls back to
 // a direct connection reusing the port from the connections registry, if no proxy is defined, or connecting via proxy
 // fails. It also in parallel dials without reusing the port, just in case reusing the port affects routing decisions badly.
-func DialContextReusePort(ctx context.Context, network, addr string) (net.Conn, error) {
-	// If proxy is configured, there is no point trying to reuse listen addresses.
-	if proxy.FromEnvironment() != proxy.Direct {
-		return DialContext(ctx, network, addr)
-	}
+func DialContextReusePortFunc(registry *registry.Registry) func(ctx context.Context, network, addr string) (net.Conn, error) {
+	return func(ctx context.Context, network, addr string) (net.Conn, error) {
+		// If proxy is configured, there is no point trying to reuse listen addresses.
+		if proxy.FromEnvironment() != proxy.Direct {
+			return DialContext(ctx, network, addr)
+		}
 
-	localAddrInterface := registry.Get(network, func(addr interface{}) bool {
-		return addr.(*net.TCPAddr).IP.IsUnspecified()
-	})
-	if localAddrInterface == nil {
-		// Nothing listening, nothing to reuse.
-		return DialContext(ctx, network, addr)
-	}
+		localAddrInterface := registry.Get(network, func(addr interface{}) bool {
+			return addr.(*net.TCPAddr).IP.IsUnspecified()
+		})
+		if localAddrInterface == nil {
+			// Nothing listening, nothing to reuse.
+			return DialContext(ctx, network, addr)
+		}
 
-	laddr, ok := localAddrInterface.(*net.TCPAddr)
-	if !ok {
-		return nil, errUnexpectedInterfaceType
-	}
+		laddr, ok := localAddrInterface.(*net.TCPAddr)
+		if !ok {
+			return nil, errUnexpectedInterfaceType
+		}
 
-	// Dial twice, once reusing the listen address, another time not reusing it, just in case reusing the address
-	// influences routing and we fail to reach our destination.
-	dialer := net.Dialer{
-		Control:   ReusePortControl,
-		LocalAddr: laddr,
+		// Dial twice, once reusing the listen address, another time not reusing it, just in case reusing the address
+		// influences routing and we fail to reach our destination.
+		dialer := net.Dialer{
+			Control:   ReusePortControl,
+			LocalAddr: laddr,
+		}
+		return dialTwicePreferFirst(ctx, dialer.DialContext, (&net.Dialer{}).DialContext, "reuse", "non-reuse", network, addr)
 	}
-	return dialTwicePreferFirst(ctx, dialer.DialContext, (&net.Dialer{}).DialContext, "reuse", "non-reuse", network, addr)
 }
 
 type dialFunc func(ctx context.Context, network, address string) (net.Conn, error)

+ 2 - 1
lib/discover/cache_test.go

@@ -14,6 +14,7 @@ import (
 	"time"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
@@ -23,7 +24,7 @@ func setupCache() *manager {
 	cfg.Options.LocalAnnEnabled = false
 	cfg.Options.GlobalAnnEnabled = false
 
-	return NewManager(protocol.LocalDeviceID, config.Wrap("", cfg, protocol.LocalDeviceID, events.NoopLogger), tls.Certificate{}, events.NoopLogger, nil).(*manager)
+	return NewManager(protocol.LocalDeviceID, config.Wrap("", cfg, protocol.LocalDeviceID, events.NoopLogger), tls.Certificate{}, events.NoopLogger, nil, registry.New()).(*manager)
 }
 
 func TestCacheUnique(t *testing.T) {

+ 10 - 2
lib/discover/global.go

@@ -14,12 +14,14 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"net/url"
 	"strconv"
 	stdsync "sync"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/dialer"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
@@ -71,7 +73,7 @@ func (e *lookupError) CacheFor() time.Duration {
 	return e.cacheFor
 }
 
-func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
+func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLogger events.Logger, registry *registry.Registry) (FinderService, error) {
 	server, opts, err := parseOptions(server)
 	if err != nil {
 		return nil, err
@@ -88,10 +90,16 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLo
 	// The http.Client used for announcements. It needs to have our
 	// certificate to prove our identity, and may or may not verify the server
 	// certificate depending on the insecure setting.
+	var dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
+	if registry != nil {
+		dialContext = dialer.DialContextReusePortFunc(registry)
+	} else {
+		dialContext = dialer.DialContext
+	}
 	var announceClient httpClient = &contextClient{&http.Client{
 		Timeout: requestTimeout,
 		Transport: &http.Transport{
-			DialContext: dialer.DialContextReusePort,
+			DialContext: dialContext,
 			Proxy:       http.ProxyFromEnvironment,
 			TLSClientConfig: &tls.Config{
 				InsecureSkipVerify: opts.insecure,

+ 8 - 5
lib/discover/global_test.go

@@ -16,6 +16,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/tlsutil"
@@ -56,15 +57,17 @@ func TestGlobalOverHTTP(t *testing.T) {
 	// is only allowed in combination with the "insecure" and "noannounce"
 	// parameters.
 
-	if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil, events.NoopLogger); err == nil {
+	registry := registry.New()
+
+	if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil, events.NoopLogger, registry); err == nil {
 		t.Fatal("http is not allowed without insecure and noannounce")
 	}
 
-	if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil, events.NoopLogger); err == nil {
+	if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil, events.NoopLogger, registry); err == nil {
 		t.Fatal("http is not allowed without noannounce")
 	}
 
-	if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil, events.NoopLogger); err == nil {
+	if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil, events.NoopLogger, registry); err == nil {
 		t.Fatal("http is not allowed without insecure")
 	}
 
@@ -185,7 +188,7 @@ func TestGlobalAnnounce(t *testing.T) {
 	go func() { _ = http.Serve(list, mux) }()
 
 	url := "https://" + list.Addr().String() + "?insecure"
-	disco, err := NewGlobal(url, cert, new(fakeAddressLister), events.NoopLogger)
+	disco, err := NewGlobal(url, cert, new(fakeAddressLister), events.NoopLogger, registry.New())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -210,7 +213,7 @@ func TestGlobalAnnounce(t *testing.T) {
 }
 
 func testLookup(url string) ([]string, error) {
-	disco, err := NewGlobal(url, tls.Certificate{}, nil, events.NoopLogger)
+	disco, err := NewGlobal(url, tls.Certificate{}, nil, events.NoopLogger, registry.New())
 	if err != nil {
 		return nil, err
 	}

+ 5 - 2
lib/discover/manager.go

@@ -19,6 +19,7 @@ import (
 	"github.com/thejerf/suture/v4"
 
 	"github.com/syncthing/syncthing/lib/config"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/svcutil"
@@ -44,12 +45,13 @@ type manager struct {
 	cert          tls.Certificate
 	evLogger      events.Logger
 	addressLister AddressLister
+	registry      *registry.Registry
 
 	finders map[string]cachedFinder
 	mut     sync.RWMutex
 }
 
-func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate, evLogger events.Logger, lister AddressLister) Manager {
+func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate, evLogger events.Logger, lister AddressLister, registry *registry.Registry) Manager {
 	m := &manager{
 		Supervisor:    suture.New("discover.Manager", svcutil.SpecWithDebugLogger(l)),
 		myID:          myID,
@@ -57,6 +59,7 @@ func NewManager(myID protocol.DeviceID, cfg config.Wrapper, cert tls.Certificate
 		cert:          cert,
 		evLogger:      evLogger,
 		addressLister: lister,
+		registry:      registry,
 
 		finders: make(map[string]cachedFinder),
 		mut:     sync.NewRWMutex(),
@@ -257,7 +260,7 @@ func (m *manager) CommitConfiguration(_, to config.Configuration) (handled bool)
 			if _, ok := m.finders[identity]; ok {
 				continue
 			}
-			gd, err := NewGlobal(srv, m.cert, m.addressLister, m.evLogger)
+			gd, err := NewGlobal(srv, m.cert, m.addressLister, m.evLogger, m.registry)
 			if err != nil {
 				l.Warnln("Global discovery:", err)
 				continue

+ 4 - 2
lib/syncthing/syncthing.go

@@ -26,6 +26,7 @@ import (
 	"github.com/syncthing/syncthing/lib/build"
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/connections"
+	"github.com/syncthing/syncthing/lib/connections/registry"
 	"github.com/syncthing/syncthing/lib/db"
 	"github.com/syncthing/syncthing/lib/db/backend"
 	"github.com/syncthing/syncthing/lib/discover"
@@ -276,8 +277,9 @@ func (a *App) startup() error {
 	// Create a wrapper that is then wired after they are both setup.
 	addrLister := &lateAddressLister{}
 
-	discoveryManager := discover.NewManager(a.myID, a.cfg, a.cert, a.evLogger, addrLister)
-	connectionsService := connections.NewService(a.cfg, a.myID, m, tlsCfg, discoveryManager, bepProtocolName, tlsDefaultCommonName, a.evLogger)
+	connRegistry := registry.New()
+	discoveryManager := discover.NewManager(a.myID, a.cfg, a.cert, a.evLogger, addrLister, connRegistry)
+	connectionsService := connections.NewService(a.cfg, a.myID, m, tlsCfg, discoveryManager, bepProtocolName, tlsDefaultCommonName, a.evLogger, connRegistry)
 
 	addrLister.AddressLister = connectionsService