diff --git a/cmd/syncthing/connections.go b/cmd/syncthing/connections.go
index 60098ecc..3c997a96 100644
--- a/cmd/syncthing/connections.go
+++ b/cmd/syncthing/connections.go
@@ -16,9 +16,12 @@ import (
"time"
"github.com/syncthing/protocol"
+
+ "github.com/syncthing/relaysrv/client"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
+
"github.com/thejerf/suture"
)
@@ -40,6 +43,8 @@ type connectionSvc struct {
tlsCfg *tls.Config
conns chan intermediateConnection
+ lastRelayCheck map[protocol.DeviceID]time.Time
+
mut sync.RWMutex
connType map[protocol.DeviceID]model.ConnectionType
}
@@ -58,7 +63,8 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
tlsCfg: tlsCfg,
conns: make(chan intermediateConnection),
- connType: make(map[protocol.DeviceID]model.ConnectionType),
+ connType: make(map[protocol.DeviceID]model.ConnectionType),
+ lastRelayCheck: make(map[protocol.DeviceID]time.Time),
}
cfg.Subscribe(svc)
@@ -135,13 +141,23 @@ next:
continue
}
- // We should not already be connected to the other party. TODO: This
- // could use some better handling. If the old connection is dead but
- // hasn't timed out yet we may want to drop *that* connection and keep
- // this one. But in case we are two devices connecting to each other
- // in parallel we don't want to do that or we end up with no
- // connections still established...
- if s.model.ConnectedTo(remoteID) {
+ // If we have a relay connection, and the new incoming connection is
+ // not a relay connection, we should drop that, and prefer the this one.
+ s.mut.RLock()
+ ct, ok := s.connType[remoteID]
+ s.mut.RUnlock()
+ if ok && !ct.IsDirect() && c.connType.IsDirect() {
+ if debugNet {
+ l.Debugln("Switching connections", remoteID)
+ }
+ s.model.Close(remoteID, fmt.Errorf("switching connections"))
+ } else if s.model.ConnectedTo(remoteID) {
+ // We should not already be connected to the other party. TODO: This
+ // could use some better handling. If the old connection is dead but
+ // hasn't timed out yet we may want to drop *that* connection and keep
+ // this one. But in case we are two devices connecting to each other
+ // in parallel we don't want to do that or we end up with no
+ // connections still established...
l.Infof("Connected to already connected device (%s)", remoteID)
c.conn.Close()
continue
@@ -224,15 +240,22 @@ func (s *connectionSvc) connect() {
continue
}
- if s.model.ConnectedTo(deviceID) {
+ connected := s.model.ConnectedTo(deviceID)
+
+ s.mut.RLock()
+ ct, ok := s.connType[deviceID]
+ s.mut.RUnlock()
+ if connected && ok && ct.IsDirect() {
continue
}
var addrs []string
+ var relays []string
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if discoverer != nil {
- t, _ := discoverer.Lookup(deviceID)
+ t, r := discoverer.Lookup(deviceID)
+ relays = append(relays, r...)
if len(t) == 0 {
continue
}
@@ -267,11 +290,83 @@ func (s *connectionSvc) connect() {
continue
}
+ if connected {
+ s.model.Close(deviceID, fmt.Errorf("switching connections"))
+ }
+
s.conns <- intermediateConnection{
conn, model.ConnectionTypeBasicDial,
}
continue nextDevice
}
+
+ // Only connect via relays if not already connected
+ // Also, do not set lastRelayCheck time if we have no relays,
+ // as otherwise when we do discover relays, we might have to
+ // wait up to RelayReconnectIntervalM to connect again.
+ if connected || len(relays) == 0 {
+ continue nextDevice
+ }
+
+ reconIntv := time.Duration(s.cfg.Options().RelayReconnectIntervalM) * time.Minute
+ if last, ok := s.lastRelayCheck[deviceID]; ok && time.Since(last) < reconIntv {
+ if debugNet {
+ l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
+ }
+ continue nextDevice
+ } else if debugNet {
+ l.Debugln("Trying relay connections to", deviceID, relays)
+ }
+
+ s.lastRelayCheck[deviceID] = time.Now()
+
+ for _, addr := range relays {
+ uri, err := url.Parse(addr)
+ if err != nil {
+ l.Infoln("Failed to parse relay connection url:", addr, err)
+ continue
+ }
+
+ inv, err := client.GetInvitationFromRelay(uri, deviceID, s.tlsCfg.Certificates)
+ if err != nil {
+ if debugNet {
+ l.Debugf("Failed to get invitation for %s from %s: %v", deviceID, uri, err)
+ }
+ continue
+ } else if debugNet {
+ l.Debugln("Succesfully retrieved relay invitation", inv, "from", uri)
+ }
+
+ conn, err := client.JoinSession(inv)
+ if err != nil {
+ if debugNet {
+ l.Debugf("Failed to join relay session %s: %v", inv, err)
+ }
+ continue
+ } else if debugNet {
+ l.Debugln("Sucessfully joined relay session", inv)
+ }
+
+ setTCPOptions(conn.(*net.TCPConn))
+
+ var tc *tls.Conn
+
+ if inv.ServerSocket {
+ tc = tls.Server(conn, s.tlsCfg)
+ } else {
+ tc = tls.Client(conn, s.tlsCfg)
+ }
+ err = tc.Handshake()
+ if err != nil {
+ l.Infof("TLS handshake (BEP/relay %s): %v", inv, err)
+ tc.Close()
+ continue
+ }
+ s.conns <- intermediateConnection{
+ tc, model.ConnectionTypeRelayDial,
+ }
+ continue nextDevice
+ }
}
time.Sleep(delay)
diff --git a/lib/config/config.go b/lib/config/config.go
index 737fca5a..394a2bfb 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -223,6 +223,7 @@ type OptionsConfiguration struct {
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
+ RelayReconnectIntervalM int `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
UPnPEnabled bool `xml:"upnpEnabled" json:"upnpEnabled" default:"true"`
UPnPLeaseM int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"60"`
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index aae70f43..a9c245bc 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -40,6 +40,7 @@ func TestDefaultValues(t *testing.T) {
MaxSendKbps: 0,
MaxRecvKbps: 0,
ReconnectIntervalS: 60,
+ RelayReconnectIntervalM: 10,
StartBrowser: true,
UPnPEnabled: true,
UPnPLeaseM: 60,
@@ -152,6 +153,7 @@ func TestOverriddenValues(t *testing.T) {
MaxSendKbps: 1234,
MaxRecvKbps: 2341,
ReconnectIntervalS: 6000,
+ RelayReconnectIntervalM: 20,
StartBrowser: false,
UPnPEnabled: false,
UPnPLeaseM: 90,
diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml
index 7ab01d06..c6d026fd 100755
--- a/lib/config/testdata/overridenvalues.xml
+++ b/lib/config/testdata/overridenvalues.xml
@@ -13,6 +13,7 @@
1234
2341
6000
+ 20
false
false
90