stun.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/.
  6. package stun
  7. import (
  8. "context"
  9. "errors"
  10. "fmt"
  11. "log/slog"
  12. "net"
  13. "time"
  14. "github.com/ccding/go-stun/stun"
  15. "github.com/syncthing/syncthing/lib/config"
  16. "github.com/syncthing/syncthing/lib/svcutil"
  17. )
  18. const stunRetryInterval = 5 * time.Minute
  19. type (
  20. Host = stun.Host
  21. NATType = stun.NATType
  22. )
  23. // NAT types.
  24. const (
  25. NATError = stun.NATError
  26. NATUnknown = stun.NATUnknown
  27. NATNone = stun.NATNone
  28. NATBlocked = stun.NATBlocked
  29. NATFull = stun.NATFull
  30. NATSymmetric = stun.NATSymmetric
  31. NATRestricted = stun.NATRestricted
  32. NATPortRestricted = stun.NATPortRestricted
  33. NATSymmetricUDPFirewall = stun.NATSymmetricUDPFirewall
  34. )
  35. var errNotPunchable = errors.New("not punchable")
  36. type Subscriber interface {
  37. OnNATTypeChanged(natType NATType)
  38. OnExternalAddressChanged(address *Host, via string)
  39. }
  40. type Service struct {
  41. name string
  42. cfg config.Wrapper
  43. subscriber Subscriber
  44. client *stun.Client
  45. lastWriter LastWriter
  46. natType NATType
  47. addr *Host
  48. }
  49. type LastWriter interface {
  50. LastWrite() time.Time
  51. }
  52. func New(cfg config.Wrapper, subscriber Subscriber, conn net.PacketConn, lastWriter LastWriter) *Service {
  53. // Construct the client to use the stun conn
  54. client := stun.NewClientWithConnection(conn)
  55. client.SetSoftwareName("") // Explicitly unset this, seems to freak some servers out.
  56. // Return the service and the other conn to the client
  57. name := "Stun@"
  58. if local := conn.LocalAddr(); local != nil {
  59. name += local.Network() + "://" + local.String()
  60. } else {
  61. name += "unknown"
  62. }
  63. s := &Service{
  64. name: name,
  65. cfg: cfg,
  66. subscriber: subscriber,
  67. client: client,
  68. lastWriter: lastWriter,
  69. natType: NATUnknown,
  70. addr: nil,
  71. }
  72. return s
  73. }
  74. func (s *Service) Serve(ctx context.Context) error {
  75. defer func() {
  76. s.setNATType(NATUnknown)
  77. s.setExternalAddress(nil, "")
  78. }()
  79. timer := time.NewTimer(time.Millisecond)
  80. for {
  81. disabled:
  82. select {
  83. case <-ctx.Done():
  84. return ctx.Err()
  85. case <-timer.C:
  86. }
  87. if s.cfg.Options().IsStunDisabled() {
  88. timer.Reset(time.Second)
  89. continue
  90. }
  91. l.Debugf("Starting stun for %s", s)
  92. for _, addr := range s.cfg.Options().StunServers() {
  93. // This blocks until we hit an exit condition or there are
  94. // issues with the STUN server.
  95. if err := s.runStunForServer(ctx, addr); errors.Is(err, errNotPunchable) {
  96. break // we will sleep for a while
  97. }
  98. // Have we been asked to stop?
  99. select {
  100. case <-ctx.Done():
  101. return ctx.Err()
  102. default:
  103. }
  104. // Are we disabled?
  105. if s.cfg.Options().IsStunDisabled() {
  106. slog.InfoContext(ctx, "STUN disabled")
  107. s.setNATType(NATUnknown)
  108. s.setExternalAddress(nil, "")
  109. goto disabled
  110. }
  111. }
  112. // We failed to contact all provided stun servers or the nat is not punchable.
  113. // Chillout for a while.
  114. timer.Reset(stunRetryInterval)
  115. }
  116. }
  117. func (s *Service) runStunForServer(ctx context.Context, addr string) error {
  118. l.Debugf("Running stun for %s via %s", s, addr)
  119. // Resolve the address, so that in case the server advertises two
  120. // IPs, we always hit the same one, as otherwise, the mapping might
  121. // expire as we hit the other address, and cause us to flip flop
  122. // between servers/external addresses, as a result flooding discovery
  123. // servers.
  124. udpAddr, err := net.ResolveUDPAddr("udp", addr)
  125. if err != nil {
  126. l.Debugf("%s stun addr resolution on %s: %s", s, addr, err)
  127. return err
  128. }
  129. s.client.SetServerAddr(udpAddr.String())
  130. var natType stun.NATType
  131. var extAddr *stun.Host
  132. err = svcutil.CallWithContext(ctx, func() error {
  133. natType, extAddr, err = s.client.Discover()
  134. return err
  135. })
  136. if err != nil {
  137. l.Debugf("%s stun discovery on %s: %v", s, addr, err)
  138. return err
  139. } else if extAddr == nil {
  140. l.Debugf("%s stun discovery on %s resulted in no address", s, addr)
  141. return fmt.Errorf("%s: no address", addr)
  142. }
  143. // The stun server is most likely borked, try another one.
  144. if natType == NATError || natType == NATUnknown || natType == NATBlocked {
  145. l.Debugf("%s stun discovery on %s resolved to %s", s, addr, natType)
  146. return fmt.Errorf("%s: bad result: %v", addr, natType)
  147. }
  148. s.setNATType(natType)
  149. l.Debugf("%s detected NAT type: %s via %s", s, natType, addr)
  150. // We can't punch through this one, so no point doing keepalives
  151. // and such, just let the caller check the nat type and work it out themselves.
  152. if !s.isCurrentNATTypePunchable() {
  153. l.Debugf("%s cannot punch %s, skipping", s, natType)
  154. return errNotPunchable
  155. }
  156. s.setExternalAddress(extAddr, addr)
  157. return s.stunKeepAlive(ctx, addr, extAddr)
  158. }
  159. func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) error {
  160. var err error
  161. nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second
  162. l.Debugf("%s starting stun keepalive via %s, next sleep %s", s, addr, nextSleep)
  163. var ourLastWrite time.Time
  164. for {
  165. if areDifferent(s.addr, extAddr) {
  166. // If the port has changed (addresses are not equal but the hosts are equal),
  167. // we're probably spending too much time between keepalives, reduce the sleep.
  168. if s.addr != nil && extAddr != nil && s.addr.IP() == extAddr.IP() {
  169. nextSleep /= 2
  170. l.Debugf("%s stun port change (%s to %s), next sleep %s", s, s.addr.TransportAddr(), extAddr.TransportAddr(), nextSleep)
  171. }
  172. s.setExternalAddress(extAddr, addr)
  173. // The stun server is probably stuffed, we've gone beyond min timeout, yet the address keeps changing.
  174. minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
  175. if nextSleep < minSleep {
  176. l.Debugf("%s keepalive aborting, sleep below min: %s < %s", s, nextSleep, minSleep)
  177. return fmt.Errorf("unreasonably low keepalive: %v", minSleep)
  178. }
  179. }
  180. // Adjust the keepalives to fire only nextSleep after last write.
  181. lastWrite := ourLastWrite
  182. if quicLastWrite := s.lastWriter.LastWrite(); quicLastWrite.After(lastWrite) {
  183. lastWrite = quicLastWrite
  184. }
  185. minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
  186. if nextSleep < minSleep {
  187. nextSleep = minSleep
  188. }
  189. tryLater:
  190. sleepFor := nextSleep
  191. timeUntilNextKeepalive := time.Until(lastWrite.Add(sleepFor))
  192. if timeUntilNextKeepalive > 0 {
  193. sleepFor = timeUntilNextKeepalive
  194. }
  195. l.Debugf("%s stun sleeping for %s", s, sleepFor)
  196. select {
  197. case <-time.After(sleepFor):
  198. case <-ctx.Done():
  199. l.Debugf("%s stopping, aborting stun", s)
  200. return ctx.Err()
  201. }
  202. if s.cfg.Options().IsStunDisabled() {
  203. // Disabled, give up
  204. l.Debugf("%s disabled, aborting stun ", s)
  205. return errors.New("disabled")
  206. }
  207. // Check if any writes happened while we were sleeping, if they did, sleep again
  208. lastWrite = s.lastWriter.LastWrite()
  209. if gap := time.Since(lastWrite); gap < nextSleep {
  210. l.Debugf("%s stun last write gap less than next sleep: %s < %s. Will try later", s, gap, nextSleep)
  211. goto tryLater
  212. }
  213. l.Debugf("%s stun keepalive", s)
  214. extAddr, err = s.client.Keepalive()
  215. if err != nil {
  216. l.Debugf("%s stun keepalive on %s: %s (%v)", s, addr, err, extAddr)
  217. return err
  218. }
  219. ourLastWrite = time.Now()
  220. }
  221. }
  222. func (s *Service) setNATType(natType NATType) {
  223. if natType != s.natType {
  224. l.Debugf("Notifying %s of NAT type change: %s", s.subscriber, natType)
  225. s.subscriber.OnNATTypeChanged(natType)
  226. }
  227. s.natType = natType
  228. }
  229. func (s *Service) setExternalAddress(addr *Host, via string) {
  230. if areDifferent(s.addr, addr) {
  231. l.Debugf("Notifying %s of address change: %s via %s", s.subscriber, addr, via)
  232. s.subscriber.OnExternalAddressChanged(addr, via)
  233. }
  234. s.addr = addr
  235. }
  236. func (s *Service) String() string {
  237. return s.name
  238. }
  239. func (s *Service) isCurrentNATTypePunchable() bool {
  240. return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull || s.natType == NATSymmetricUDPFirewall
  241. }
  242. func areDifferent(first, second *Host) bool {
  243. if (first == nil) != (second == nil) {
  244. return true
  245. }
  246. if first != nil {
  247. return first.TransportAddr() != second.TransportAddr()
  248. }
  249. return false
  250. }