service.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. // Copyright (C) 2015 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package upnp
  7. import (
  8. "fmt"
  9. "time"
  10. "github.com/syncthing/syncthing/lib/config"
  11. "github.com/syncthing/syncthing/lib/events"
  12. "github.com/syncthing/syncthing/lib/sync"
  13. "github.com/syncthing/syncthing/lib/util"
  14. )
  15. // Service runs a loop for discovery of IGDs (Internet Gateway Devices) and
  16. // setup/renewal of a port mapping.
  17. type Service struct {
  18. cfg *config.Wrapper
  19. localPort int
  20. extPort int
  21. extPortMut sync.Mutex
  22. stop chan struct{}
  23. }
  24. func NewUPnPService(cfg *config.Wrapper, localPort int) *Service {
  25. return &Service{
  26. cfg: cfg,
  27. localPort: localPort,
  28. extPortMut: sync.NewMutex(),
  29. }
  30. }
  31. func (s *Service) Serve() {
  32. foundIGD := true
  33. s.stop = make(chan struct{})
  34. for {
  35. igds := Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
  36. if len(igds) > 0 {
  37. foundIGD = true
  38. s.extPortMut.Lock()
  39. oldExtPort := s.extPort
  40. s.extPortMut.Unlock()
  41. newExtPort := s.tryIGDs(igds, oldExtPort)
  42. s.extPortMut.Lock()
  43. s.extPort = newExtPort
  44. s.extPortMut.Unlock()
  45. } else if foundIGD {
  46. // Only print a notice if we've previously found an IGD or this is
  47. // the first time around.
  48. foundIGD = false
  49. l.Infof("No UPnP device detected")
  50. }
  51. d := time.Duration(s.cfg.Options().UPnPRenewalM) * time.Minute
  52. if d == 0 {
  53. // We always want to do renewal so lets just pick a nice sane number.
  54. d = 30 * time.Minute
  55. }
  56. select {
  57. case <-s.stop:
  58. return
  59. case <-time.After(d):
  60. }
  61. }
  62. }
  63. func (s *Service) Stop() {
  64. close(s.stop)
  65. }
  66. func (s *Service) ExternalPort() int {
  67. s.extPortMut.Lock()
  68. port := s.extPort
  69. s.extPortMut.Unlock()
  70. return port
  71. }
  72. func (s *Service) tryIGDs(igds []IGD, prevExtPort int) int {
  73. // Lets try all the IGDs we found and use the first one that works.
  74. // TODO: Use all of them, and sort out the resulting mess to the
  75. // discovery announcement code...
  76. for _, igd := range igds {
  77. extPort, err := s.tryIGD(igd, prevExtPort)
  78. if err != nil {
  79. l.Warnf("Failed to set UPnP port mapping: external port %d on device %s.", extPort, igd.FriendlyIdentifier())
  80. continue
  81. }
  82. if extPort != prevExtPort {
  83. l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
  84. events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
  85. }
  86. l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
  87. return extPort
  88. }
  89. return 0
  90. }
  91. func (s *Service) tryIGD(igd IGD, suggestedPort int) (int, error) {
  92. var err error
  93. leaseTime := s.cfg.Options().UPnPLeaseM * 60
  94. if suggestedPort != 0 {
  95. // First try renewing our existing mapping.
  96. name := fmt.Sprintf("syncthing-%d", suggestedPort)
  97. err = igd.AddPortMapping(TCP, suggestedPort, s.localPort, name, leaseTime)
  98. if err == nil {
  99. return suggestedPort, nil
  100. }
  101. }
  102. for i := 0; i < 10; i++ {
  103. // Then try up to ten random ports.
  104. extPort := 1024 + util.PredictableRandom.Intn(65535-1024)
  105. name := fmt.Sprintf("syncthing-%d", extPort)
  106. err = igd.AddPortMapping(TCP, extPort, s.localPort, name, leaseTime)
  107. if err == nil {
  108. return extPort, nil
  109. }
  110. }
  111. return 0, err
  112. }