From a388fb0bb762f07a017836f96a3bcbe9535f8f02 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 28 Jun 2015 21:09:03 +0100 Subject: [PATCH] Check relays for available devices --- cmd/syncthing/connections.go | 115 +++++++++++++++++++++--- lib/config/config.go | 1 + lib/config/config_test.go | 2 + lib/config/testdata/overridenvalues.xml | 1 + 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/cmd/syncthing/connections.go b/cmd/syncthing/connections.go index 60098eccb..3c997a96c 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 737fca5a1..394a2bfb3 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 aae70f439..a9c245bce 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 7ab01d06a..c6d026fd1 100755 --- a/lib/config/testdata/overridenvalues.xml +++ b/lib/config/testdata/overridenvalues.xml @@ -13,6 +13,7 @@ 1234 2341 6000 + 20 false false 90