diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 0115b2ef..ca58573f 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -73,15 +73,16 @@ func init() { } var ( - cfg config.Configuration - myID protocol.NodeID - confDir string - logFlags int = log.Ltime - rateBucket *ratelimit.Bucket - stop = make(chan bool) - discoverer *discover.Discoverer - lockConn *net.TCPListener - lockPort int + cfg config.Configuration + myID protocol.NodeID + confDir string + logFlags int = log.Ltime + rateBucket *ratelimit.Bucket + stop = make(chan bool) + discoverer *discover.Discoverer + lockConn *net.TCPListener + lockPort int + externalPort int ) const ( @@ -470,11 +471,10 @@ nextRepo: // UPnP - var externalPort = 0 if cfg.Options.UPnPEnabled { // We seed the random number generator with the node ID to get a // repeatable sequence of random external ports. - externalPort = setupUPnP(rand.NewSource(certSeed(cert.Certificate[0]))) + setupUPnP(rand.NewSource(certSeed(cert.Certificate[0]))) } // Routine to connect out to configured nodes @@ -561,8 +561,7 @@ func waitForParentExit() { l.Infoln("Continuing") } -func setupUPnP(r rand.Source) int { - var externalPort = 0 +func setupUPnP(rnd rand.Source) { if len(cfg.Options.ListenAddress) == 1 { _, portStr, err := net.SplitHostPort(cfg.Options.ListenAddress[0]) if err != nil { @@ -573,11 +572,12 @@ func setupUPnP(r rand.Source) int { igd, err := upnp.Discover() if err == nil { for i := 0; i < 10; i++ { - r := 1024 + int(r.Int63()%(65535-1024)) - err := igd.AddPortMapping(upnp.TCP, r, port, "syncthing", 0) + r := 1024 + int(rnd.Int63()%(65535-1024)) + err := igd.AddPortMapping(upnp.TCP, r, port, "syncthing", cfg.Options.UPnPLease*60) if err == nil { externalPort = r l.Infoln("Created UPnP port mapping - external port", externalPort) + go renewUPnP(igd, rnd, port) break } } @@ -594,7 +594,31 @@ func setupUPnP(r rand.Source) int { } else { l.Warnln("Multiple listening addresses; not attempting UPnP port mapping") } - return externalPort +} + +func renewUPnP(igd *upnp.IGD, rnd rand.Source, port int) { + if cfg.Options.UPnPRenewal < 1 { + return + } + + for { + time.Sleep(time.Duration(cfg.Options.UPnPRenewal) * time.Minute) + + err := igd.AddPortMapping(upnp.TCP, externalPort, port, "syncthing", cfg.Options.UPnPLease*60) + if err == nil { + l.Infoln("Renewed UPnP port mapping - external port", externalPort) + continue + } + + r := 1024 + int(rnd.Int63()%(65535-1024)) + err = igd.AddPortMapping(upnp.TCP, r, port, "syncthing", cfg.Options.UPnPLease*60) + if err == nil { + l.Infoln("Updated UPnP port mapping - external port", externalPort) + externalPort = r + continue + } + l.Warnln("Failed to update UPnP port mapping - externalPort", externalPort) + } } func resetRepositories() { diff --git a/config/config.go b/config/config.go index 66f3b746..d98ae14c 100644 --- a/config/config.go +++ b/config/config.go @@ -113,6 +113,8 @@ type OptionsConfiguration struct { MaxChangeKbps int `xml:"maxChangeKbps" default:"10000"` StartBrowser bool `xml:"startBrowser" default:"true"` UPnPEnabled bool `xml:"upnpEnabled" default:"true"` + UPnPLease int `xml:"upnpLeaseMinutes" default:"0"` + UPnPRenewal int `xml:"upnpRenewalMinutes" default:"0"` URAccepted int `xml:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently) Deprecated_UREnabled bool `xml:"urEnabled,omitempty" json:"-"`