stun.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. "net"
  10. "sync/atomic"
  11. "time"
  12. "github.com/AudriusButkevicius/pfilter"
  13. "github.com/ccding/go-stun/stun"
  14. "github.com/syncthing/syncthing/lib/config"
  15. "github.com/syncthing/syncthing/lib/util"
  16. )
  17. const stunRetryInterval = 5 * time.Minute
  18. type Host = stun.Host
  19. type NATType = stun.NATType
  20. // NAT types.
  21. const (
  22. NATError = stun.NATError
  23. NATUnknown = stun.NATUnknown
  24. NATNone = stun.NATNone
  25. NATBlocked = stun.NATBlocked
  26. NATFull = stun.NATFull
  27. NATSymmetric = stun.NATSymmetric
  28. NATRestricted = stun.NATRestricted
  29. NATPortRestricted = stun.NATPortRestricted
  30. NATSymmetricUDPFirewall = stun.NATSymmetricUDPFirewall
  31. )
  32. type writeTrackingUdpConn struct {
  33. lastWrite int64 // atomic, must remain 64-bit aligned
  34. // Needs to be UDPConn not PacketConn, as pfilter checks for WriteMsgUDP/ReadMsgUDP
  35. // and even if we embed UDPConn here, in place of a PacketConn, seems the interface
  36. // check fails.
  37. *net.UDPConn
  38. }
  39. func (c *writeTrackingUdpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
  40. atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
  41. return c.UDPConn.WriteTo(p, addr)
  42. }
  43. func (c *writeTrackingUdpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
  44. atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
  45. return c.UDPConn.WriteMsgUDP(b, oob, addr)
  46. }
  47. func (c *writeTrackingUdpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
  48. atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
  49. return c.UDPConn.WriteToUDP(b, addr)
  50. }
  51. func (c *writeTrackingUdpConn) Write(b []byte) (int, error) {
  52. atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
  53. return c.UDPConn.Write(b)
  54. }
  55. func (c *writeTrackingUdpConn) getLastWrite() time.Time {
  56. unix := atomic.LoadInt64(&c.lastWrite)
  57. return time.Unix(unix, 0)
  58. }
  59. type Subscriber interface {
  60. OnNATTypeChanged(natType NATType)
  61. OnExternalAddressChanged(address *Host, via string)
  62. }
  63. type Service struct {
  64. name string
  65. cfg config.Wrapper
  66. subscriber Subscriber
  67. stunConn net.PacketConn
  68. client *stun.Client
  69. writeTrackingUdpConn *writeTrackingUdpConn
  70. natType NATType
  71. addr *Host
  72. }
  73. func New(cfg config.Wrapper, subscriber Subscriber, conn *net.UDPConn) (*Service, net.PacketConn) {
  74. // Wrap the original connection to track writes on it
  75. writeTrackingUdpConn := &writeTrackingUdpConn{lastWrite: 0, UDPConn: conn}
  76. // Wrap it in a filter and split it up, so that stun packets arrive on stun conn, others arrive on the data conn
  77. filterConn := pfilter.NewPacketFilter(writeTrackingUdpConn)
  78. otherDataConn := filterConn.NewConn(otherDataPriority, nil)
  79. stunConn := filterConn.NewConn(stunFilterPriority, &stunFilter{
  80. ids: make(map[string]time.Time),
  81. })
  82. filterConn.Start()
  83. // Construct the client to use the stun conn
  84. client := stun.NewClientWithConnection(stunConn)
  85. client.SetSoftwareName("") // Explicitly unset this, seems to freak some servers out.
  86. // Return the service and the other conn to the client
  87. name := "Stun@"
  88. if local := conn.LocalAddr(); local != nil {
  89. name += local.Network() + "://" + local.String()
  90. } else {
  91. name += "unknown"
  92. }
  93. s := &Service{
  94. name: name,
  95. cfg: cfg,
  96. subscriber: subscriber,
  97. stunConn: stunConn,
  98. client: client,
  99. writeTrackingUdpConn: writeTrackingUdpConn,
  100. natType: NATUnknown,
  101. addr: nil,
  102. }
  103. return s, otherDataConn
  104. }
  105. func (s *Service) Serve(ctx context.Context) error {
  106. defer func() {
  107. s.setNATType(NATUnknown)
  108. s.setExternalAddress(nil, "")
  109. }()
  110. // Closing s.stunConn unblocks operations that use the connection
  111. // (Discover, Keepalive) and might otherwise block us from returning.
  112. go func() {
  113. <-ctx.Done()
  114. _ = s.stunConn.Close()
  115. }()
  116. timer := time.NewTimer(time.Millisecond)
  117. for {
  118. disabled:
  119. select {
  120. case <-ctx.Done():
  121. return ctx.Err()
  122. case <-timer.C:
  123. }
  124. if s.cfg.Options().IsStunDisabled() {
  125. timer.Reset(time.Second)
  126. continue
  127. }
  128. l.Debugf("Starting stun for %s", s)
  129. for _, addr := range s.cfg.Options().StunServers() {
  130. // This blocks until we hit an exit condition or there are issues with the STUN server.
  131. // This returns a boolean signifying if a different STUN server should be tried (oppose to the whole thing
  132. // shutting down and this winding itself down.
  133. s.runStunForServer(ctx, addr)
  134. // Have we been asked to stop?
  135. select {
  136. case <-ctx.Done():
  137. return ctx.Err()
  138. default:
  139. }
  140. // Are we disabled?
  141. if s.cfg.Options().IsStunDisabled() {
  142. l.Infoln("STUN disabled")
  143. s.setNATType(NATUnknown)
  144. s.setExternalAddress(nil, "")
  145. goto disabled
  146. }
  147. // Unpunchable NAT? Chillout for some time.
  148. if !s.isCurrentNATTypePunchable() {
  149. break
  150. }
  151. }
  152. // We failed to contact all provided stun servers or the nat is not punchable.
  153. // Chillout for a while.
  154. timer.Reset(stunRetryInterval)
  155. }
  156. }
  157. func (s *Service) runStunForServer(ctx context.Context, addr string) {
  158. l.Debugf("Running stun for %s via %s", s, addr)
  159. // Resolve the address, so that in case the server advertises two
  160. // IPs, we always hit the same one, as otherwise, the mapping might
  161. // expire as we hit the other address, and cause us to flip flop
  162. // between servers/external addresses, as a result flooding discovery
  163. // servers.
  164. udpAddr, err := net.ResolveUDPAddr("udp", addr)
  165. if err != nil {
  166. l.Debugf("%s stun addr resolution on %s: %s", s, addr, err)
  167. return
  168. }
  169. s.client.SetServerAddr(udpAddr.String())
  170. var natType stun.NATType
  171. var extAddr *stun.Host
  172. err = util.CallWithContext(ctx, func() error {
  173. natType, extAddr, err = s.client.Discover()
  174. return err
  175. })
  176. if err != nil || extAddr == nil {
  177. l.Debugf("%s stun discovery on %s: %s", s, addr, err)
  178. return
  179. }
  180. // The stun server is most likely borked, try another one.
  181. if natType == NATError || natType == NATUnknown || natType == NATBlocked {
  182. l.Debugf("%s stun discovery on %s resolved to %s", s, addr, natType)
  183. return
  184. }
  185. s.setNATType(natType)
  186. l.Debugf("%s detected NAT type: %s via %s", s, natType, addr)
  187. // We can't punch through this one, so no point doing keepalives
  188. // and such, just let the caller check the nat type and work it out themselves.
  189. if !s.isCurrentNATTypePunchable() {
  190. l.Debugf("%s cannot punch %s, skipping", s, natType)
  191. return
  192. }
  193. s.setExternalAddress(extAddr, addr)
  194. s.stunKeepAlive(ctx, addr, extAddr)
  195. }
  196. func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) {
  197. var err error
  198. nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second
  199. l.Debugf("%s starting stun keepalive via %s, next sleep %s", s, addr, nextSleep)
  200. for {
  201. if areDifferent(s.addr, extAddr) {
  202. // If the port has changed (addresses are not equal but the hosts are equal),
  203. // we're probably spending too much time between keepalives, reduce the sleep.
  204. if s.addr != nil && extAddr != nil && s.addr.IP() == extAddr.IP() {
  205. nextSleep /= 2
  206. l.Debugf("%s stun port change (%s to %s), next sleep %s", s, s.addr.TransportAddr(), extAddr.TransportAddr(), nextSleep)
  207. }
  208. s.setExternalAddress(extAddr, addr)
  209. // The stun server is probably stuffed, we've gone beyond min timeout, yet the address keeps changing.
  210. minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
  211. if nextSleep < minSleep {
  212. l.Debugf("%s keepalive aborting, sleep below min: %s < %s", s, nextSleep, minSleep)
  213. return
  214. }
  215. }
  216. // Adjust the keepalives to fire only nextSleep after last write.
  217. lastWrite := s.writeTrackingUdpConn.getLastWrite()
  218. minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
  219. if nextSleep < minSleep {
  220. nextSleep = minSleep
  221. }
  222. tryLater:
  223. sleepFor := nextSleep
  224. timeUntilNextKeepalive := time.Until(lastWrite.Add(sleepFor))
  225. if timeUntilNextKeepalive > 0 {
  226. sleepFor = timeUntilNextKeepalive
  227. }
  228. l.Debugf("%s stun sleeping for %s", s, sleepFor)
  229. select {
  230. case <-time.After(sleepFor):
  231. case <-ctx.Done():
  232. l.Debugf("%s stopping, aborting stun", s)
  233. return
  234. }
  235. if s.cfg.Options().IsStunDisabled() {
  236. // Disabled, give up
  237. l.Debugf("%s disabled, aborting stun ", s)
  238. return
  239. }
  240. // Check if any writes happened while we were sleeping, if they did, sleep again
  241. lastWrite = s.writeTrackingUdpConn.getLastWrite()
  242. if gap := time.Since(lastWrite); gap < nextSleep {
  243. l.Debugf("%s stun last write gap less than next sleep: %s < %s. Will try later", s, gap, nextSleep)
  244. goto tryLater
  245. }
  246. l.Debugf("%s stun keepalive", s)
  247. extAddr, err = s.client.Keepalive()
  248. if err != nil {
  249. l.Debugf("%s stun keepalive on %s: %s (%v)", s, addr, err, extAddr)
  250. return
  251. }
  252. }
  253. }
  254. func (s *Service) setNATType(natType NATType) {
  255. if natType != s.natType {
  256. l.Debugf("Notifying %s of NAT type change: %s", s.subscriber, natType)
  257. s.subscriber.OnNATTypeChanged(natType)
  258. }
  259. s.natType = natType
  260. }
  261. func (s *Service) setExternalAddress(addr *Host, via string) {
  262. if areDifferent(s.addr, addr) {
  263. l.Debugf("Notifying %s of address change: %s via %s", s.subscriber, addr, via)
  264. s.subscriber.OnExternalAddressChanged(addr, via)
  265. }
  266. s.addr = addr
  267. }
  268. func (s *Service) String() string {
  269. return s.name
  270. }
  271. func (s *Service) isCurrentNATTypePunchable() bool {
  272. return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull || s.natType == NATSymmetricUDPFirewall
  273. }
  274. func areDifferent(first, second *Host) bool {
  275. if (first == nil) != (second == nil) {
  276. return true
  277. }
  278. if first != nil {
  279. return first.TransportAddr() != second.TransportAddr()
  280. }
  281. return false
  282. }