|
@@ -142,10 +142,13 @@ type service struct {
|
|
natService *nat.Service
|
|
natService *nat.Service
|
|
evLogger events.Logger
|
|
evLogger events.Logger
|
|
|
|
|
|
- deviceAddressesChanged chan struct{}
|
|
|
|
- listenersMut sync.RWMutex
|
|
|
|
- listeners map[string]genericListener
|
|
|
|
- listenerTokens map[string]suture.ServiceToken
|
|
|
|
|
|
+ dialNow chan struct{}
|
|
|
|
+ dialNowDevices map[protocol.DeviceID]struct{}
|
|
|
|
+ dialNowDevicesMut sync.Mutex
|
|
|
|
+
|
|
|
|
+ listenersMut sync.RWMutex
|
|
|
|
+ listeners map[string]genericListener
|
|
|
|
+ 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) Service {
|
|
@@ -166,10 +169,13 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
|
|
natService: nat.NewService(myID, cfg),
|
|
natService: nat.NewService(myID, cfg),
|
|
evLogger: evLogger,
|
|
evLogger: evLogger,
|
|
|
|
|
|
- deviceAddressesChanged: make(chan struct{}, 1),
|
|
|
|
- listenersMut: sync.NewRWMutex(),
|
|
|
|
- listeners: make(map[string]genericListener),
|
|
|
|
- listenerTokens: make(map[string]suture.ServiceToken),
|
|
|
|
|
|
+ dialNowDevicesMut: sync.NewMutex(),
|
|
|
|
+ dialNow: make(chan struct{}, 1),
|
|
|
|
+ dialNowDevices: make(map[protocol.DeviceID]struct{}),
|
|
|
|
+
|
|
|
|
+ listenersMut: sync.NewRWMutex(),
|
|
|
|
+ listeners: make(map[string]genericListener),
|
|
|
|
+ listenerTokens: make(map[string]suture.ServiceToken),
|
|
}
|
|
}
|
|
cfg.Subscribe(service)
|
|
cfg.Subscribe(service)
|
|
|
|
|
|
@@ -324,6 +330,13 @@ func (s *service) handle(ctx context.Context) error {
|
|
rd, wr := s.limiter.getLimiters(remoteID, c, isLAN)
|
|
rd, wr := s.limiter.getLimiters(remoteID, c, isLAN)
|
|
|
|
|
|
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID))
|
|
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID))
|
|
|
|
+ go func() {
|
|
|
|
+ <-protoConn.Closed()
|
|
|
|
+ s.dialNowDevicesMut.Lock()
|
|
|
|
+ s.dialNowDevices[remoteID] = struct{}{}
|
|
|
|
+ s.scheduleDialNow()
|
|
|
|
+ s.dialNowDevicesMut.Unlock()
|
|
|
|
+ }()
|
|
|
|
|
|
l.Infof("Established secure connection to %s at %s", remoteID, c)
|
|
l.Infof("Established secure connection to %s at %s", remoteID, c)
|
|
|
|
|
|
@@ -334,7 +347,7 @@ func (s *service) handle(ctx context.Context) error {
|
|
|
|
|
|
func (s *service) connect(ctx context.Context) error {
|
|
func (s *service) connect(ctx context.Context) error {
|
|
// Map of when to earliest dial each given device + address again
|
|
// Map of when to earliest dial each given device + address again
|
|
- nextDialAt := make(map[string]time.Time)
|
|
|
|
|
|
+ nextDialAt := make(nextDialRegistry)
|
|
|
|
|
|
// Used as delay for the first few connection attempts (adjusted up to
|
|
// Used as delay for the first few connection attempts (adjusted up to
|
|
// minConnectionLoopSleep), increased exponentially until it reaches
|
|
// minConnectionLoopSleep), increased exponentially until it reaches
|
|
@@ -369,7 +382,7 @@ func (s *service) connect(ctx context.Context) error {
|
|
// The sleep time is until the next dial scheduled in nextDialAt,
|
|
// The sleep time is until the next dial scheduled in nextDialAt,
|
|
// clamped by stdConnectionLoopSleep as we don't want to sleep too
|
|
// clamped by stdConnectionLoopSleep as we don't want to sleep too
|
|
// long (config changes might happen).
|
|
// long (config changes might happen).
|
|
- sleep = filterAndFindSleepDuration(nextDialAt, now)
|
|
|
|
|
|
+ sleep = nextDialAt.sleepDurationAndCleanup(now)
|
|
}
|
|
}
|
|
|
|
|
|
// ... while making sure not to loop too quickly either.
|
|
// ... while making sure not to loop too quickly either.
|
|
@@ -379,9 +392,20 @@ func (s *service) connect(ctx context.Context) error {
|
|
|
|
|
|
l.Debugln("Next connection loop in", sleep)
|
|
l.Debugln("Next connection loop in", sleep)
|
|
|
|
|
|
|
|
+ timeout := time.NewTimer(sleep)
|
|
select {
|
|
select {
|
|
- case <-s.deviceAddressesChanged:
|
|
|
|
- case <-time.After(sleep):
|
|
|
|
|
|
+ case <-s.dialNow:
|
|
|
|
+ // Remove affected devices from nextDialAt to dial immediately,
|
|
|
|
+ // regardless of when we last dialed it (there's cool down in the
|
|
|
|
+ // registry for too many repeat dials).
|
|
|
|
+ s.dialNowDevicesMut.Lock()
|
|
|
|
+ for device := range s.dialNowDevices {
|
|
|
|
+ nextDialAt.redialDevice(device, now)
|
|
|
|
+ }
|
|
|
|
+ s.dialNowDevices = make(map[protocol.DeviceID]struct{})
|
|
|
|
+ s.dialNowDevicesMut.Unlock()
|
|
|
|
+ timeout.Stop()
|
|
|
|
+ case <-timeout.C:
|
|
case <-ctx.Done():
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
@@ -401,7 +425,7 @@ func (s *service) bestDialerPriority(cfg config.Configuration) int {
|
|
return bestDialerPriority
|
|
return bestDialerPriority
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Configuration, bestDialerPriority int, nextDialAt map[string]time.Time, initial bool) {
|
|
|
|
|
|
+func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Configuration, bestDialerPriority int, nextDialAt nextDialRegistry, initial bool) {
|
|
// Figure out current connection limits up front to see if there's any
|
|
// Figure out current connection limits up front to see if there's any
|
|
// point in resolving devices and such at all.
|
|
// point in resolving devices and such at all.
|
|
allowAdditional := 0 // no limit
|
|
allowAdditional := 0 // no limit
|
|
@@ -477,7 +501,7 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg config.Configuration, deviceCfg config.DeviceConfiguration, nextDialAt map[string]time.Time, initial bool, priorityCutoff int) []dialTarget {
|
|
|
|
|
|
+func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg config.Configuration, deviceCfg config.DeviceConfiguration, nextDialAt nextDialRegistry, initial bool, priorityCutoff int) []dialTarget {
|
|
deviceID := deviceCfg.DeviceID
|
|
deviceID := deviceCfg.DeviceID
|
|
|
|
|
|
addrs := s.resolveDeviceAddrs(ctx, deviceCfg)
|
|
addrs := s.resolveDeviceAddrs(ctx, deviceCfg)
|
|
@@ -485,18 +509,16 @@ func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg con
|
|
|
|
|
|
dialTargets := make([]dialTarget, 0, len(addrs))
|
|
dialTargets := make([]dialTarget, 0, len(addrs))
|
|
for _, addr := range addrs {
|
|
for _, addr := range addrs {
|
|
- // Use a special key that is more than just the address, as you
|
|
|
|
- // might have two devices connected to the same relay
|
|
|
|
- nextDialKey := deviceID.String() + "/" + addr
|
|
|
|
- when, ok := nextDialAt[nextDialKey]
|
|
|
|
- if ok && !initial && when.After(now) {
|
|
|
|
|
|
+ // Use both device and address, as you might have two devices connected
|
|
|
|
+ // to the same relay
|
|
|
|
+ if !initial && nextDialAt.get(deviceID, addr).After(now) {
|
|
l.Debugf("Not dialing %s via %v as it's not time yet", deviceID, addr)
|
|
l.Debugf("Not dialing %s via %v as it's not time yet", deviceID, addr)
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
|
|
|
|
// If we fail at any step before actually getting the dialer
|
|
// If we fail at any step before actually getting the dialer
|
|
// retry in a minute
|
|
// retry in a minute
|
|
- nextDialAt[nextDialKey] = now.Add(time.Minute)
|
|
|
|
|
|
+ nextDialAt.set(deviceID, addr, now.Add(time.Minute))
|
|
|
|
|
|
uri, err := url.Parse(addr)
|
|
uri, err := url.Parse(addr)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -532,7 +554,7 @@ func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg con
|
|
}
|
|
}
|
|
|
|
|
|
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg)
|
|
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg)
|
|
- nextDialAt[nextDialKey] = now.Add(dialer.RedialFrequency())
|
|
|
|
|
|
+ nextDialAt.set(deviceID, addr, now.Add(dialer.RedialFrequency()))
|
|
|
|
|
|
// For LAN addresses, increase the priority so that we
|
|
// For LAN addresses, increase the priority so that we
|
|
// try these first.
|
|
// try these first.
|
|
@@ -735,24 +757,24 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
|
}
|
|
}
|
|
|
|
|
|
func (s *service) checkAndSignalConnectLoopOnUpdatedDevices(from, to config.Configuration) {
|
|
func (s *service) checkAndSignalConnectLoopOnUpdatedDevices(from, to config.Configuration) {
|
|
- oldDevices := make(map[protocol.DeviceID]config.DeviceConfiguration, len(from.Devices))
|
|
|
|
- for _, dev := range from.Devices {
|
|
|
|
- oldDevices[dev.DeviceID] = dev
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
+ oldDevices := from.DeviceMap()
|
|
for _, dev := range to.Devices {
|
|
for _, dev := range to.Devices {
|
|
oldDev, ok := oldDevices[dev.DeviceID]
|
|
oldDev, ok := oldDevices[dev.DeviceID]
|
|
if !ok || !util.EqualStrings(oldDev.Addresses, dev.Addresses) {
|
|
if !ok || !util.EqualStrings(oldDev.Addresses, dev.Addresses) {
|
|
- select {
|
|
|
|
- case s.deviceAddressesChanged <- struct{}{}:
|
|
|
|
- default:
|
|
|
|
- // channel is blocked - a config update is already pending for the connection loop.
|
|
|
|
- }
|
|
|
|
|
|
+ s.scheduleDialNow()
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (s *service) scheduleDialNow() {
|
|
|
|
+ select {
|
|
|
|
+ case s.dialNow <- struct{}{}:
|
|
|
|
+ default:
|
|
|
|
+ // channel is blocked - a config update is already pending for the connection loop.
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
func (s *service) AllAddresses() []string {
|
|
func (s *service) AllAddresses() []string {
|
|
s.listenersMut.RLock()
|
|
s.listenersMut.RLock()
|
|
var addrs []string
|
|
var addrs []string
|
|
@@ -877,21 +899,6 @@ func getListenerFactory(cfg config.Configuration, uri *url.URL) (listenerFactory
|
|
return listenerFactory, nil
|
|
return listenerFactory, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func filterAndFindSleepDuration(nextDialAt map[string]time.Time, now time.Time) time.Duration {
|
|
|
|
- sleep := stdConnectionLoopSleep
|
|
|
|
- for key, next := range nextDialAt {
|
|
|
|
- if next.Before(now) {
|
|
|
|
- // Expired entry, address was not seen in last pass(es)
|
|
|
|
- delete(nextDialAt, key)
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
- if cur := next.Sub(now); cur < sleep {
|
|
|
|
- sleep = cur
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return sleep
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
func urlsToStrings(urls []*url.URL) []string {
|
|
func urlsToStrings(urls []*url.URL) []string {
|
|
strings := make([]string, len(urls))
|
|
strings := make([]string, len(urls))
|
|
for i, url := range urls {
|
|
for i, url := range urls {
|
|
@@ -1050,3 +1057,89 @@ func (s *service) validateIdentity(c internalConn, expectedID protocol.DeviceID)
|
|
|
|
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+type nextDialRegistry map[protocol.DeviceID]nextDialDevice
|
|
|
|
+
|
|
|
|
+type nextDialDevice struct {
|
|
|
|
+ nextDial map[string]time.Time
|
|
|
|
+ coolDownIntervalStart time.Time
|
|
|
|
+ attempts int
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (r nextDialRegistry) get(device protocol.DeviceID, addr string) time.Time {
|
|
|
|
+ return r[device].nextDial[addr]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const (
|
|
|
|
+ dialCoolDownInterval = 2 * time.Minute
|
|
|
|
+ dialCoolDownDelay = 5 * time.Minute
|
|
|
|
+ dialCoolDownMaxAttemps = 3
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// redialDevice marks the device for immediate redial, unless the remote keeps
|
|
|
|
+// dropping established connections. Thus we keep track of when the first forced
|
|
|
|
+// re-dial happened, and how many attempts happen in the dialCoolDownInterval
|
|
|
|
+// after that. If it's more than dialCoolDownMaxAttempts, don't force-redial
|
|
|
|
+// that device for dialCoolDownDelay (regular dials still happen).
|
|
|
|
+func (r nextDialRegistry) redialDevice(device protocol.DeviceID, now time.Time) {
|
|
|
|
+ dev, ok := r[device]
|
|
|
|
+ if !ok {
|
|
|
|
+ r[device] = nextDialDevice{
|
|
|
|
+ coolDownIntervalStart: now,
|
|
|
|
+ attempts: 1,
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if dev.attempts == 0 || now.Before(dev.coolDownIntervalStart.Add(dialCoolDownInterval)) {
|
|
|
|
+ if dev.attempts >= dialCoolDownMaxAttemps {
|
|
|
|
+ // Device has been force redialed too often - let it cool down.
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if dev.attempts == 0 {
|
|
|
|
+ dev.coolDownIntervalStart = now
|
|
|
|
+ }
|
|
|
|
+ dev.attempts++
|
|
|
|
+ dev.nextDial = make(map[string]time.Time)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if dev.attempts >= dialCoolDownMaxAttemps && now.Before(dev.coolDownIntervalStart.Add(dialCoolDownDelay)) {
|
|
|
|
+ return // Still cooling down
|
|
|
|
+ }
|
|
|
|
+ delete(r, device)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (r nextDialRegistry) set(device protocol.DeviceID, addr string, next time.Time) {
|
|
|
|
+ if _, ok := r[device]; !ok {
|
|
|
|
+ r[device] = nextDialDevice{nextDial: make(map[string]time.Time)}
|
|
|
|
+ }
|
|
|
|
+ r[device].nextDial[addr] = next
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (r nextDialRegistry) sleepDurationAndCleanup(now time.Time) time.Duration {
|
|
|
|
+ sleep := stdConnectionLoopSleep
|
|
|
|
+ for id, dev := range r {
|
|
|
|
+ for address, next := range dev.nextDial {
|
|
|
|
+ if next.Before(now) {
|
|
|
|
+ // Expired entry, address was not seen in last pass(es)
|
|
|
|
+ delete(dev.nextDial, address)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ if cur := next.Sub(now); cur < sleep {
|
|
|
|
+ sleep = cur
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if dev.attempts > 0 {
|
|
|
|
+ interval := dialCoolDownInterval
|
|
|
|
+ if dev.attempts >= dialCoolDownMaxAttemps {
|
|
|
|
+ interval = dialCoolDownDelay
|
|
|
|
+ }
|
|
|
|
+ if now.After(dev.coolDownIntervalStart.Add(interval)) {
|
|
|
|
+ dev.attempts = 0
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if len(dev.nextDial) == 0 && dev.attempts == 0 {
|
|
|
|
+ delete(r, id)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return sleep
|
|
|
|
+}
|