mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-13 00:36:28 +00:00
133 lines
3.3 KiB
Go
133 lines
3.3 KiB
Go
// Copyright (C) 2015 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package upnp
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/events"
|
|
"github.com/syncthing/syncthing/lib/sync"
|
|
"github.com/syncthing/syncthing/lib/util"
|
|
)
|
|
|
|
// Service runs a loop for discovery of IGDs (Internet Gateway Devices) and
|
|
// setup/renewal of a port mapping.
|
|
type Service struct {
|
|
cfg *config.Wrapper
|
|
localPort int
|
|
extPort int
|
|
extPortMut sync.Mutex
|
|
stop chan struct{}
|
|
}
|
|
|
|
func NewUPnPService(cfg *config.Wrapper, localPort int) *Service {
|
|
return &Service{
|
|
cfg: cfg,
|
|
localPort: localPort,
|
|
extPortMut: sync.NewMutex(),
|
|
}
|
|
}
|
|
|
|
func (s *Service) Serve() {
|
|
foundIGD := true
|
|
s.stop = make(chan struct{})
|
|
|
|
for {
|
|
igds := Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
|
|
if len(igds) > 0 {
|
|
foundIGD = true
|
|
s.extPortMut.Lock()
|
|
oldExtPort := s.extPort
|
|
s.extPortMut.Unlock()
|
|
|
|
newExtPort := s.tryIGDs(igds, oldExtPort)
|
|
|
|
s.extPortMut.Lock()
|
|
s.extPort = newExtPort
|
|
s.extPortMut.Unlock()
|
|
} else if foundIGD {
|
|
// Only print a notice if we've previously found an IGD or this is
|
|
// the first time around.
|
|
foundIGD = false
|
|
l.Infof("No UPnP device detected")
|
|
}
|
|
|
|
d := time.Duration(s.cfg.Options().UPnPRenewalM) * time.Minute
|
|
if d == 0 {
|
|
// We always want to do renewal so lets just pick a nice sane number.
|
|
d = 30 * time.Minute
|
|
}
|
|
|
|
select {
|
|
case <-s.stop:
|
|
return
|
|
case <-time.After(d):
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Service) Stop() {
|
|
close(s.stop)
|
|
}
|
|
|
|
func (s *Service) ExternalPort() int {
|
|
s.extPortMut.Lock()
|
|
port := s.extPort
|
|
s.extPortMut.Unlock()
|
|
return port
|
|
}
|
|
|
|
func (s *Service) tryIGDs(igds []IGD, prevExtPort int) int {
|
|
// Lets try all the IGDs we found and use the first one that works.
|
|
// TODO: Use all of them, and sort out the resulting mess to the
|
|
// discovery announcement code...
|
|
for _, igd := range igds {
|
|
extPort, err := s.tryIGD(igd, prevExtPort)
|
|
if err != nil {
|
|
l.Warnf("Failed to set UPnP port mapping: external port %d on device %s.", extPort, igd.FriendlyIdentifier())
|
|
continue
|
|
}
|
|
|
|
if extPort != prevExtPort {
|
|
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
|
|
events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
|
|
}
|
|
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
|
|
return extPort
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (s *Service) tryIGD(igd IGD, suggestedPort int) (int, error) {
|
|
var err error
|
|
leaseTime := s.cfg.Options().UPnPLeaseM * 60
|
|
|
|
if suggestedPort != 0 {
|
|
// First try renewing our existing mapping.
|
|
name := fmt.Sprintf("syncthing-%d", suggestedPort)
|
|
err = igd.AddPortMapping(TCP, suggestedPort, s.localPort, name, leaseTime)
|
|
if err == nil {
|
|
return suggestedPort, nil
|
|
}
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
// Then try up to ten random ports.
|
|
extPort := 1024 + util.PredictableRandom.Intn(65535-1024)
|
|
name := fmt.Sprintf("syncthing-%d", extPort)
|
|
err = igd.AddPortMapping(TCP, extPort, s.localPort, name, leaseTime)
|
|
if err == nil {
|
|
return extPort, nil
|
|
}
|
|
}
|
|
|
|
return 0, err
|
|
}
|