| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- // 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
- }
|