1
0
Эх сурвалжийг харах

lib/connections: Un-deprecate relaysEnabled (fixes #3074)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3098
Jakob Borg 9 жил өмнө
parent
commit
2c1323ece6

+ 18 - 5
gui/default/syncthing/settings/settingsModalView.html

@@ -56,11 +56,24 @@
               </div>
             </div>
 
-            <div class="form-group">
-              <div class="checkbox">
-                <label>
-                  <input id="GlobalAnnEnabled" type="checkbox" ng-model="tmpOptions.globalAnnounceEnabled"> <span translate>Global Discovery</span>
-                </label>
+            <div class="row">
+              <div class="col-md-6">
+                <div class="form-group">
+                  <div class="checkbox">
+                    <label>
+                      <input id="GlobalAnnEnabled" type="checkbox" ng-model="tmpOptions.globalAnnounceEnabled"> <span translate>Global Discovery</span>
+                    </label>
+                  </div>
+                </div>
+              </div>
+              <div class="col-md-6">
+                <div class="form-group">
+                  <div class="checkbox">
+                    <label>
+                      <input id="RelaysEnabled" type="checkbox" ng-model="tmpOptions.relaysEnabled"> <span translate>Enable Relaying</span>
+                    </label>
+                  </div>
+                </div>
               </div>
             </div>
 

+ 39 - 13
lib/config/config.go

@@ -29,9 +29,9 @@ const (
 
 var (
 	// DefaultListenAddresses should be substituted when the configuration
-	// contains <listenAddress>default</listenAddress>. This is
-	// done by the "consumer" of the configuration, as we don't want these
-	// saved to the config.
+	// contains <listenAddress>default</listenAddress>. This is done by the
+	// "consumer" of the configuration as we don't want these saved to the
+	// config.
 	DefaultListenAddresses = []string{
 		"tcp://0.0.0.0:22000",
 		"dynamic+https://relays.syncthing.net/endpoint",
@@ -258,27 +258,53 @@ func convertV13V14(cfg *Configuration) {
 	// Not using the ignore cache is the new default. Disable it on existing
 	// configurations.
 	cfg.Options.CacheIgnoredFiles = false
+
+	// Migrate UPnP -> NAT options
 	cfg.Options.NATEnabled = cfg.Options.DeprecatedUPnPEnabled
+	cfg.Options.DeprecatedUPnPEnabled = false
 	cfg.Options.NATLeaseM = cfg.Options.DeprecatedUPnPLeaseM
+	cfg.Options.DeprecatedUPnPLeaseM = 0
 	cfg.Options.NATRenewalM = cfg.Options.DeprecatedUPnPRenewalM
+	cfg.Options.DeprecatedUPnPRenewalM = 0
 	cfg.Options.NATTimeoutS = cfg.Options.DeprecatedUPnPTimeoutS
-	if cfg.Options.DeprecatedRelaysEnabled {
-		cfg.Options.ListenAddresses = append(cfg.Options.ListenAddresses, cfg.Options.DeprecatedRelayServers...)
-		// Replace our two fairly long addresses with 'default' if both exist.
-		var newAddresses []string
-		for _, addr := range cfg.Options.ListenAddresses {
-			if addr != "tcp://0.0.0.0:22000" && addr != "dynamic+https://relays.syncthing.net/endpoint" {
-				newAddresses = append(newAddresses, addr)
+	cfg.Options.DeprecatedUPnPTimeoutS = 0
+
+	// Replace the default listen address "tcp://0.0.0.0:22000" with the
+	// string "default", but only if we also have the default relay pool
+	// among the relay servers as this is implied by the new "default"
+	// entry.
+	hasDefault := false
+	for _, raddr := range cfg.Options.DeprecatedRelayServers {
+		if raddr == "dynamic+https://relays.syncthing.net/endpoint" {
+			for i, addr := range cfg.Options.ListenAddresses {
+				if addr == "tcp://0.0.0.0:22000" {
+					cfg.Options.ListenAddresses[i] = "default"
+					hasDefault = true
+					break
+				}
 			}
+			break
 		}
+	}
 
-		if len(newAddresses)+2 == len(cfg.Options.ListenAddresses) {
-			cfg.Options.ListenAddresses = append([]string{"default"}, newAddresses...)
+	// Copy relay addresses into listen addresses.
+	for _, addr := range cfg.Options.DeprecatedRelayServers {
+		if hasDefault && addr == "dynamic+https://relays.syncthing.net/endpoint" {
+			// Skip the default relay address if we already have the
+			// "default" entry in the list.
+			continue
+		}
+		if addr == "" {
+			continue
 		}
+		cfg.Options.ListenAddresses = append(cfg.Options.ListenAddresses, addr)
 	}
-	cfg.Options.DeprecatedRelaysEnabled = false
+
 	cfg.Options.DeprecatedRelayServers = nil
 
+	// For consistency
+	sort.Strings(cfg.Options.ListenAddresses)
+
 	var newAddrs []string
 	for _, addr := range cfg.Options.GlobalAnnServers {
 		if addr != "default" {

+ 62 - 0
lib/config/config_test.go

@@ -12,7 +12,9 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"reflect"
 	"runtime"
+	"sort"
 	"strings"
 	"testing"
 
@@ -40,6 +42,7 @@ func TestDefaultValues(t *testing.T) {
 		MaxSendKbps:             0,
 		MaxRecvKbps:             0,
 		ReconnectIntervalS:      60,
+		RelaysEnabled:           true,
 		RelayReconnectIntervalM: 10,
 		StartBrowser:            true,
 		NATEnabled:              true,
@@ -169,6 +172,7 @@ func TestOverriddenValues(t *testing.T) {
 		MaxSendKbps:             1234,
 		MaxRecvKbps:             2341,
 		ReconnectIntervalS:      6000,
+		RelaysEnabled:           false,
 		RelayReconnectIntervalM: 20,
 		StartBrowser:            false,
 		NATEnabled:              false,
@@ -616,3 +620,61 @@ func TestRemoveDuplicateDevicesFolders(t *testing.T) {
 		t.Errorf("Incorrect number of folder devices, %d != 2", l)
 	}
 }
+
+func TestV14ListenAddressesMigration(t *testing.T) {
+	tcs := [][3][]string{
+
+		// Default listen plus default relays is now "default"
+		{
+			{"tcp://0.0.0.0:22000"},
+			{"dynamic+https://relays.syncthing.net/endpoint"},
+			{"default"},
+		},
+		// Default listen address without any relay addresses gets converted
+		// to just the listen address. It's easier this way, and frankly the
+		// user has gone to some trouble to get the empty string in the
+		// config to start with...
+		{
+			{"tcp://0.0.0.0:22000"}, // old listen addrs
+			{""}, // old relay addrs
+			{"tcp://0.0.0.0:22000"}, // new listen addrs
+		},
+		// Default listen plus non-default relays gets copied verbatim
+		{
+			{"tcp://0.0.0.0:22000"},
+			{"dynamic+https://other.example.com"},
+			{"tcp://0.0.0.0:22000", "dynamic+https://other.example.com"},
+		},
+		// Non-default listen plus default relays gets copied verbatim
+		{
+			{"tcp://1.2.3.4:22000"},
+			{"dynamic+https://relays.syncthing.net/endpoint"},
+			{"tcp://1.2.3.4:22000", "dynamic+https://relays.syncthing.net/endpoint"},
+		},
+		// Default stuff gets sucked into "default", the rest gets copied
+		{
+			{"tcp://0.0.0.0:22000", "tcp://1.2.3.4:22000"},
+			{"dynamic+https://relays.syncthing.net/endpoint", "relay://other.example.com"},
+			{"default", "tcp://1.2.3.4:22000", "relay://other.example.com"},
+		},
+	}
+
+	for _, tc := range tcs {
+		cfg := Configuration{
+			Version: 13,
+			Options: OptionsConfiguration{
+				ListenAddresses:        tc[0],
+				DeprecatedRelayServers: tc[1],
+			},
+		}
+		convertV13V14(&cfg)
+		if cfg.Version != 14 {
+			t.Error("Configuration was not converted")
+		}
+
+		sort.Strings(tc[2])
+		if !reflect.DeepEqual(cfg.Options.ListenAddresses, tc[2]) {
+			t.Errorf("Migration error; actual %#v != expected %#v", cfg.Options.ListenAddresses, tc[2])
+		}
+	}
+}

+ 6 - 6
lib/config/optionsconfiguration.go

@@ -16,6 +16,7 @@ type OptionsConfiguration struct {
 	MaxSendKbps             int      `xml:"maxSendKbps" json:"maxSendKbps"`
 	MaxRecvKbps             int      `xml:"maxRecvKbps" json:"maxRecvKbps"`
 	ReconnectIntervalS      int      `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
+	RelaysEnabled           bool     `xml:"relaysEnabled" json:"relaysEnabled" default:"true"`
 	RelayReconnectIntervalM int      `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
 	StartBrowser            bool     `xml:"startBrowser" json:"startBrowser" default:"true"`
 	NATEnabled              bool     `xml:"natEnabled" json:"natEnabled" default:"true"`
@@ -40,12 +41,11 @@ type OptionsConfiguration struct {
 	OverwriteRemoteDevNames bool     `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
 	TempIndexMinBlocks      int      `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
 
-	DeprecatedUPnPEnabled   bool     `xml:"upnpEnabled" json:"-"`
-	DeprecatedUPnPLeaseM    int      `xml:"upnpLeaseMinutes" json:"-"`
-	DeprecatedUPnPRenewalM  int      `xml:"upnpRenewalMinutes" json:"-"`
-	DeprecatedUPnPTimeoutS  int      `xml:"upnpTimeoutSeconds" json:"-"`
-	DeprecatedRelaysEnabled bool     `xml:"relaysEnabled" json:"-"`
-	DeprecatedRelayServers  []string `xml:"relayServer" json:"-"`
+	DeprecatedUPnPEnabled  bool     `xml:"upnpEnabled,omitempty" json:"-"`
+	DeprecatedUPnPLeaseM   int      `xml:"upnpLeaseMinutes,omitempty" json:"-"`
+	DeprecatedUPnPRenewalM int      `xml:"upnpRenewalMinutes,omitempty" json:"-"`
+	DeprecatedUPnPTimeoutS int      `xml:"upnpTimeoutSeconds,omitempty" json:"-"`
+	DeprecatedRelayServers []string `xml:"relayServer,omitempty" json:"-"`
 }
 
 func (orig OptionsConfiguration) Copy() OptionsConfiguration {

+ 8 - 4
lib/connections/relay_dial.go

@@ -70,10 +70,6 @@ func (d *relayDialer) RedialFrequency() time.Duration {
 	return time.Duration(d.cfg.Options().RelayReconnectIntervalM) * time.Minute
 }
 
-func (d *relayDialer) String() string {
-	return "Relay Dialer"
-}
-
 type relayDialerFactory struct{}
 
 func (relayDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
@@ -86,3 +82,11 @@ func (relayDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDi
 func (relayDialerFactory) Priority() int {
 	return relayPriority
 }
+
+func (relayDialerFactory) Enabled(cfg config.Configuration) bool {
+	return cfg.Options.RelaysEnabled
+}
+
+func (relayDialerFactory) String() string {
+	return "Relay Dialer"
+}

+ 24 - 10
lib/connections/relay_listen.go

@@ -13,23 +13,26 @@ import (
 	"sync"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/dialer"
 	"github.com/syncthing/syncthing/lib/nat"
 	"github.com/syncthing/syncthing/lib/relay/client"
 )
 
 func init() {
-	listeners["relay"] = newRelayListener
-	listeners["dynamic+http"] = newRelayListener
-	listeners["dynamic+https"] = newRelayListener
+	factory := &relayListenerFactory{}
+	listeners["relay"] = factory
+	listeners["dynamic+http"] = factory
+	listeners["dynamic+https"] = factory
 }
 
 type relayListener struct {
 	onAddressesChangedNotifier
 
-	uri    *url.URL
-	tlsCfg *tls.Config
-	conns  chan IntermediateConnection
+	uri     *url.URL
+	tlsCfg  *tls.Config
+	conns   chan IntermediateConnection
+	factory listenerFactory
 
 	err    error
 	client client.RelayClient
@@ -154,14 +157,25 @@ func (t *relayListener) Error() error {
 	return cerr
 }
 
+func (t *relayListener) Factory() listenerFactory {
+	return t.factory
+}
+
 func (t *relayListener) String() string {
 	return t.uri.String()
 }
 
-func newRelayListener(uri *url.URL, tlsCfg *tls.Config, conns chan IntermediateConnection, natService *nat.Service) genericListener {
+type relayListenerFactory struct{}
+
+func (f *relayListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan IntermediateConnection, natService *nat.Service) genericListener {
 	return &relayListener{
-		uri:    uri,
-		tlsCfg: tlsCfg,
-		conns:  conns,
+		uri:     uri,
+		tlsCfg:  tlsCfg,
+		conns:   conns,
+		factory: f,
 	}
 }
+
+func (relayListenerFactory) Enabled(cfg config.Configuration) bool {
+	return cfg.Options.RelaysEnabled
+}

+ 105 - 48
lib/connections/service.go

@@ -9,6 +9,7 @@ package connections
 import (
 	"crypto/tls"
 	"encoding/binary"
+	"errors"
 	"fmt"
 	"io"
 	"net"
@@ -112,6 +113,10 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
 	return service
 }
 
+var (
+	errDisabled = errors.New("disabled by configuration")
+)
+
 func (s *Service) handle() {
 next:
 	for c := range s.conns {
@@ -237,24 +242,35 @@ next:
 
 func (s *Service) connect() {
 	nextDial := make(map[string]time.Time)
-	delay := time.Second
-	sleep := time.Second
 
-	bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
-	for _, df := range dialers {
-		if prio := df.Priority(); prio < bestDialerPrio {
-			bestDialerPrio = prio
-		}
-	}
+	// Used as delay for the first few connection attempts, increases
+	// exponentially
+	initialRampup := time.Second
+
+	// Calculated from actual dialers reconnectInterval
+	var sleep time.Duration
 
 	for {
+		cfg := s.cfg.Raw()
+
+		bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
+		for _, df := range dialers {
+			if !df.Enabled(cfg) {
+				continue
+			}
+			if prio := df.Priority(); prio < bestDialerPrio {
+				bestDialerPrio = prio
+			}
+		}
+
 		l.Debugln("Reconnect loop")
 
 		now := time.Now()
 		var seen []string
 
 	nextDevice:
-		for deviceID, deviceCfg := range s.cfg.Devices() {
+		for _, deviceCfg := range cfg.Devices {
+			deviceID := deviceCfg.DeviceID
 			if deviceID == s.myID {
 				continue
 			}
@@ -292,35 +308,40 @@ func (s *Service) connect() {
 			seen = append(seen, addrs...)
 
 			for _, addr := range addrs {
+				nextDialAt, ok := nextDial[addr]
+				if ok && initialRampup >= sleep && nextDialAt.After(now) {
+					l.Debugf("Not dialing %v as sleep is %v, next dial is at %s and current time is %s", addr, sleep, nextDialAt, now)
+					continue
+				}
+				// If we fail at any step before actually getting the dialer
+				// retry in a minute
+				nextDial[addr] = now.Add(time.Minute)
+
 				uri, err := url.Parse(addr)
 				if err != nil {
-					l.Infoln("Failed to parse connection url:", addr, err)
+					l.Infof("Dialer for %s: %v", addr, err)
 					continue
 				}
 
-				dialerFactory, ok := dialers[uri.Scheme]
-				if !ok {
-					l.Debugln("Unknown address schema", uri)
+				dialerFactory, err := s.getDialerFactory(cfg, uri)
+				if err == errDisabled {
+					l.Debugln("Dialer for", uri, "is disabled")
 					continue
 				}
-
-				dialer := dialerFactory.New(s.cfg, s.tlsCfg)
-
-				nextDialAt, ok := nextDial[uri.String()]
-				// See below for comments on this delay >= sleep check
-				if delay >= sleep && ok && nextDialAt.After(now) {
-					l.Debugf("Not dialing as next dial is at %s and current time is %s", nextDialAt, now)
+				if err != nil {
+					l.Infof("Dialer for %v: %v", uri, err)
 					continue
 				}
 
-				nextDial[uri.String()] = now.Add(dialer.RedialFrequency())
-
-				if connected && dialer.Priority() >= ct.Priority {
-					l.Debugf("Not dialing using %s as priorty is less than current connection (%d >= %d)", dialer, dialer.Priority(), ct.Priority)
+				if connected && dialerFactory.Priority() >= ct.Priority {
+					l.Debugf("Not dialing using %s as priorty is less than current connection (%d >= %d)", dialerFactory, dialerFactory.Priority(), ct.Priority)
 					continue
 				}
 
+				dialer := dialerFactory.New(s.cfg, s.tlsCfg)
 				l.Debugln("dial", deviceCfg.DeviceID, uri)
+				nextDial[addr] = now.Add(dialer.RedialFrequency())
+
 				conn, err := dialer.Dial(deviceID, uri)
 				if err != nil {
 					l.Debugln("dial failed", deviceCfg.DeviceID, uri, err)
@@ -338,12 +359,12 @@ func (s *Service) connect() {
 
 		nextDial, sleep = filterAndFindSleepDuration(nextDial, seen, now)
 
-		// delay variable is used to trigger much more frequent dialing after
-		// initial startup, essentially causing redials every 1, 2, 4, 8... seconds
-		if delay < sleep {
-			time.Sleep(delay)
-			delay *= 2
+		if initialRampup < sleep {
+			l.Debugln("initial rampup; sleep", initialRampup, "and update to", initialRampup*2)
+			time.Sleep(initialRampup)
+			initialRampup *= 2
 		} else {
+			l.Debugln("sleep until next dial", sleep)
 			time.Sleep(sleep)
 		}
 	}
@@ -366,24 +387,16 @@ func (s *Service) shouldLimit(addr net.Addr) bool {
 	return !tcpaddr.IP.IsLoopback()
 }
 
-func (s *Service) createListener(addr string) {
+func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
 	// must be called with listenerMut held
-	uri, err := url.Parse(addr)
-	if err != nil {
-		l.Infoln("Failed to parse listen address:", addr, err)
-		return
-	}
 
-	listenerFactory, ok := listeners[uri.Scheme]
-	if !ok {
-		l.Infoln("Unknown listen address scheme:", uri.String())
-		return
-	}
+	l.Debugln("Starting listener", uri)
 
-	listener := listenerFactory(uri, s.tlsCfg, s.conns, s.natService)
+	listener := factory.New(uri, s.cfg, s.tlsCfg, s.conns, s.natService)
 	listener.OnAddressesChanged(s.logListenAddressesChangedEvent)
-	s.listeners[addr] = listener
-	s.listenerTokens[addr] = s.Add(listener)
+	s.listeners[uri.String()] = listener
+	s.listenerTokens[uri.String()] = s.Add(listener)
+	return true
 }
 
 func (s *Service) logListenAddressesChangedEvent(l genericListener) {
@@ -417,15 +430,33 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
 	s.listenersMut.Lock()
 	seen := make(map[string]struct{})
 	for _, addr := range config.Wrap("", to).ListenAddresses() {
-		if _, ok := s.listeners[addr]; !ok {
-			l.Debugln("Staring listener", addr)
-			s.createListener(addr)
+		if _, ok := s.listeners[addr]; ok {
+			seen[addr] = struct{}{}
+			continue
 		}
+
+		uri, err := url.Parse(addr)
+		if err != nil {
+			l.Infof("Listener for %s: %v", addr, err)
+			continue
+		}
+
+		factory, err := s.getListenerFactory(to, uri)
+		if err == errDisabled {
+			l.Debugln("Listener for", uri, "is disabled")
+			continue
+		}
+		if err != nil {
+			l.Infof("Listener for %v: %v", uri, err)
+			continue
+		}
+
+		s.createListener(factory, uri)
 		seen[addr] = struct{}{}
 	}
 
-	for addr := range s.listeners {
-		if _, ok := seen[addr]; !ok {
+	for addr, listener := range s.listeners {
+		if _, ok := seen[addr]; !ok || !listener.Factory().Enabled(to) {
 			l.Debugln("Stopping listener", addr)
 			s.Remove(s.listenerTokens[addr])
 			delete(s.listenerTokens, addr)
@@ -494,6 +525,32 @@ func (s *Service) Status() map[string]interface{} {
 	return result
 }
 
+func (s *Service) getDialerFactory(cfg config.Configuration, uri *url.URL) (dialerFactory, error) {
+	dialerFactory, ok := dialers[uri.Scheme]
+	if !ok {
+		return nil, fmt.Errorf("unknown address scheme %q", uri.Scheme)
+	}
+
+	if !dialerFactory.Enabled(cfg) {
+		return nil, errDisabled
+	}
+
+	return dialerFactory, nil
+}
+
+func (s *Service) getListenerFactory(cfg config.Configuration, uri *url.URL) (listenerFactory, error) {
+	listenerFactory, ok := listeners[uri.Scheme]
+	if !ok {
+		return nil, fmt.Errorf("unknown address scheme %q", uri.Scheme)
+	}
+
+	if !listenerFactory.Enabled(cfg) {
+		return nil, errDisabled
+	}
+
+	return listenerFactory, nil
+}
+
 func exchangeHello(c net.Conn, h protocol.HelloMessage) (protocol.HelloMessage, error) {
 	if err := c.SetDeadline(time.Now().Add(2 * time.Second)); err != nil {
 		return protocol.HelloMessage{}, err

+ 7 - 3
lib/connections/structs.go

@@ -31,16 +31,19 @@ type Connection struct {
 type dialerFactory interface {
 	New(*config.Wrapper, *tls.Config) genericDialer
 	Priority() int
+	Enabled(config.Configuration) bool
+	String() string
 }
 
 type genericDialer interface {
 	Dial(protocol.DeviceID, *url.URL) (IntermediateConnection, error)
-	Priority() int
 	RedialFrequency() time.Duration
-	String() string
 }
 
-type listenerFactory func(*url.URL, *tls.Config, chan IntermediateConnection, *nat.Service) genericListener
+type listenerFactory interface {
+	New(*url.URL, *config.Wrapper, *tls.Config, chan IntermediateConnection, *nat.Service) genericListener
+	Enabled(config.Configuration) bool
+}
 
 type genericListener interface {
 	Serve()
@@ -58,6 +61,7 @@ type genericListener interface {
 	Error() error
 	OnAddressesChanged(func(genericListener))
 	String() string
+	Factory() listenerFactory
 }
 
 type Model interface {

+ 10 - 9
lib/connections/tcp_dial.go

@@ -20,8 +20,9 @@ import (
 const tcpPriority = 10
 
 func init() {
+	factory := &tcpDialerFactory{}
 	for _, scheme := range []string{"tcp", "tcp4", "tcp6"} {
-		dialers[scheme] = tcpDialerFactory{}
+		dialers[scheme] = factory
 	}
 }
 
@@ -55,18 +56,10 @@ func (d *tcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (IntermediateConnec
 	return IntermediateConnection{tc, "TCP (Client)", tcpPriority}, nil
 }
 
-func (tcpDialer) Priority() int {
-	return tcpPriority
-}
-
 func (d *tcpDialer) RedialFrequency() time.Duration {
 	return time.Duration(d.cfg.Options().ReconnectIntervalS) * time.Second
 }
 
-func (d *tcpDialer) String() string {
-	return "TCP Dialer"
-}
-
 type tcpDialerFactory struct{}
 
 func (tcpDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
@@ -79,3 +72,11 @@ func (tcpDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDial
 func (tcpDialerFactory) Priority() int {
 	return tcpPriority
 }
+
+func (tcpDialerFactory) Enabled(cfg config.Configuration) bool {
+	return true
+}
+
+func (tcpDialerFactory) String() string {
+	return "TCP Dialer"
+}

+ 57 - 6
lib/connections/tcp_listen.go

@@ -14,23 +14,26 @@ import (
 	"sync"
 	"time"
 
+	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/dialer"
 	"github.com/syncthing/syncthing/lib/nat"
 )
 
 func init() {
+	factory := &tcpListenerFactory{}
 	for _, scheme := range []string{"tcp", "tcp4", "tcp6"} {
-		listeners[scheme] = newTCPListener
+		listeners[scheme] = factory
 	}
 }
 
 type tcpListener struct {
 	onAddressesChangedNotifier
 
-	uri    *url.URL
-	tlsCfg *tls.Config
-	stop   chan struct{}
-	conns  chan IntermediateConnection
+	uri     *url.URL
+	tlsCfg  *tls.Config
+	stop    chan struct{}
+	conns   chan IntermediateConnection
+	factory listenerFactory
 
 	natService *nat.Service
 	mapping    *nat.Mapping
@@ -63,6 +66,9 @@ func (t *tcpListener) Serve() {
 	}
 	defer listener.Close()
 
+	l.Infof("TCP listener (%v) starting", listener.Addr())
+	defer l.Infof("TCP listener (%v) shutting down", listener.Addr())
+
 	mapping := t.natService.NewMapping(nat.TCP, tcaddr.IP, tcaddr.Port)
 	mapping.OnChanged(func(_ *nat.Mapping, _, _ []nat.Address) {
 		t.notifyAddressesChanged(t)
@@ -152,14 +158,59 @@ func (t *tcpListener) String() string {
 	return t.uri.String()
 }
 
-func newTCPListener(uri *url.URL, tlsCfg *tls.Config, conns chan IntermediateConnection, natService *nat.Service) genericListener {
+func (t *tcpListener) Factory() listenerFactory {
+	return t.factory
+}
+
+type tcpListenerFactory struct{}
+
+func (f *tcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan IntermediateConnection, natService *nat.Service) genericListener {
 	return &tcpListener{
 		uri:        fixupPort(uri),
 		tlsCfg:     tlsCfg,
 		conns:      conns,
 		natService: natService,
 		stop:       make(chan struct{}),
+		factory:    f,
+	}
+}
+
+func (tcpListenerFactory) Enabled(cfg config.Configuration) bool {
+	return true
+}
+
+func isPublicIPv4(ip net.IP) bool {
+	ip = ip.To4()
+	if ip == nil {
+		// Not an IPv4 address (IPv6)
+		return false
 	}
+
+	// IsGlobalUnicast below only checks that it's not link local or
+	// multicast, and we want to exclude private (NAT:ed) addresses as well.
+	rfc1918 := []net.IPNet{
+		{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}},
+		{IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}},
+		{IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}},
+	}
+	for _, n := range rfc1918 {
+		if n.Contains(ip) {
+			return false
+		}
+	}
+
+	return ip.IsGlobalUnicast()
+}
+
+func isPublicIPv6(ip net.IP) bool {
+	if ip.To4() != nil {
+		// Not an IPv6 address (IPv4)
+		// (To16() returns a v6 mapped v4 address so can't be used to check
+		// that it's an actual v6 address)
+		return false
+	}
+
+	return ip.IsGlobalUnicast()
 }
 
 func fixupPort(uri *url.URL) *url.URL {

+ 1 - 0
lib/model/model.go

@@ -2018,6 +2018,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
 	from.Options.URAccepted = to.Options.URAccepted
 	from.Options.URUniqueID = to.Options.URUniqueID
 	from.Options.ListenAddresses = to.Options.ListenAddresses
+	from.Options.RelaysEnabled = to.Options.RelaysEnabled
 	// All of the other generic options require restart. Or at least they may;
 	// removing this check requires going through those options carefully and
 	// making sure there are individual services that handle them correctly.

+ 2 - 1
lib/relay/client/static.go

@@ -89,7 +89,8 @@ func (c *staticClient) Serve() {
 		return
 	}
 
-	l.Infoln("Joined relay", c.uri)
+	l.Infof("Joined relay %s://%s", c.uri.Scheme, c.uri.Host)
+	defer l.Infof("Disconnected from relay %s://%s", c.uri.Scheme, c.uri.Host)
 
 	c.mut.Lock()
 	c.connected = true

+ 22 - 13
test/h1/config.xml

@@ -1,5 +1,5 @@
-<configuration version="12">
-    <folder id="default" path="s1/" ro="false" rescanIntervalS="10" ignorePerms="false" autoNormalize="true">
+<configuration version="14">
+    <folder id="default" label="" path="s1/" type="readwrite" rescanIntervalS="10" ignorePerms="false" autoNormalize="true">
         <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
         <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC"></device>
         <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"></device>
@@ -15,8 +15,10 @@
         <pullerSleepS>0</pullerSleepS>
         <pullerPauseS>0</pullerPauseS>
         <maxConflicts>-1</maxConflicts>
+        <disableSparseFiles>false</disableSparseFiles>
+        <disableTempIndexes>false</disableTempIndexes>
     </folder>
-    <folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" path="s12-1/" ro="false" rescanIntervalS="10" ignorePerms="false" autoNormalize="true">
+    <folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" label="" path="s12-1/" type="readwrite" rescanIntervalS="10" ignorePerms="false" autoNormalize="true">
         <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
         <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC"></device>
         <minDiskFreePct>1</minDiskFreePct>
@@ -30,6 +32,8 @@
         <pullerSleepS>0</pullerSleepS>
         <pullerPauseS>0</pullerPauseS>
         <maxConflicts>-1</maxConflicts>
+        <disableSparseFiles>false</disableSparseFiles>
+        <disableTempIndexes>false</disableTempIndexes>
     </folder>
     <device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="metadata" introducer="false">
         <address>tcp://127.0.0.1:22004</address>
@@ -51,26 +55,25 @@
         <user>testuser</user>
         <password>$2a$10$7tKL5uvLDGn5s2VLPM2yWOK/II45az0mTel8hxAUJDRQN1Tk2QYwu</password>
         <apikey>abc123</apikey>
+        <theme>default</theme>
     </gui>
     <options>
         <listenAddress>tcp://127.0.0.1:22001</listenAddress>
+        <listenAddress>dynamic+https://relays.syncthing.net/endpoint</listenAddress>
         <globalAnnounceServer>default</globalAnnounceServer>
         <globalAnnounceEnabled>false</globalAnnounceEnabled>
         <localAnnounceEnabled>true</localAnnounceEnabled>
         <localAnnouncePort>21027</localAnnouncePort>
         <localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
-        <relayServer>dynamic+https://relays.syncthing.net/endpoint</relayServer>
         <maxSendKbps>0</maxSendKbps>
         <maxRecvKbps>0</maxRecvKbps>
         <reconnectionIntervalS>5</reconnectionIntervalS>
-        <relaysEnabled>true</relaysEnabled>
         <relayReconnectIntervalM>10</relayReconnectIntervalM>
-        <relayWithoutGlobalAnn>false</relayWithoutGlobalAnn>
         <startBrowser>false</startBrowser>
-        <upnpEnabled>true</upnpEnabled>
-        <upnpLeaseMinutes>0</upnpLeaseMinutes>
-        <upnpRenewalMinutes>30</upnpRenewalMinutes>
-        <upnpTimeoutSeconds>10</upnpTimeoutSeconds>
+        <natEnabled>true</natEnabled>
+        <natLeaseMinutes>0</natLeaseMinutes>
+        <natRenewalMinutes>30</natRenewalMinutes>
+        <natTimeoutSeconds>10</natTimeoutSeconds>
         <urAccepted>-1</urAccepted>
         <urUniqueID></urUniqueID>
         <urURL>https://data.syncthing.net/newdata</urURL>
@@ -79,12 +82,18 @@
         <restartOnWakeup>true</restartOnWakeup>
         <autoUpgradeIntervalH>12</autoUpgradeIntervalH>
         <keepTemporariesH>24</keepTemporariesH>
-        <cacheIgnoredFiles>true</cacheIgnoredFiles>
+        <cacheIgnoredFiles>false</cacheIgnoredFiles>
         <progressUpdateIntervalS>5</progressUpdateIntervalS>
         <symlinksEnabled>true</symlinksEnabled>
         <limitBandwidthInLan>false</limitBandwidthInLan>
-        <databaseBlockCacheMiB>0</databaseBlockCacheMiB>
         <minHomeDiskFreePct>1</minHomeDiskFreePct>
-        <releasesURL>https://api.github.com/repos/syncthing/syncthing/releases?per_page=30</releasesURL>
+        <releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
+        <overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
+        <tempIndexMinBlocks>10</tempIndexMinBlocks>
+        <upnpEnabled>true</upnpEnabled>
+        <upnpLeaseMinutes>0</upnpLeaseMinutes>
+        <upnpRenewalMinutes>30</upnpRenewalMinutes>
+        <upnpTimeoutSeconds>10</upnpTimeoutSeconds>
+        <relaysEnabled>false</relaysEnabled>
     </options>
 </configuration>