local.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // Copyright (C) 2014 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. //go:generate go run ../../script/protofmt.go local.proto
  7. //go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. local.proto
  8. package discover
  9. import (
  10. "bytes"
  11. "encoding/binary"
  12. "encoding/hex"
  13. "io"
  14. "net"
  15. "net/url"
  16. "strconv"
  17. "time"
  18. "github.com/syncthing/syncthing/lib/beacon"
  19. "github.com/syncthing/syncthing/lib/events"
  20. "github.com/syncthing/syncthing/lib/protocol"
  21. "github.com/syncthing/syncthing/lib/rand"
  22. "github.com/thejerf/suture"
  23. )
  24. type localClient struct {
  25. *suture.Supervisor
  26. myID protocol.DeviceID
  27. addrList AddressLister
  28. name string
  29. beacon beacon.Interface
  30. localBcastStart time.Time
  31. localBcastTick <-chan time.Time
  32. forcedBcastTick chan time.Time
  33. *cache
  34. }
  35. const (
  36. BroadcastInterval = 30 * time.Second
  37. CacheLifeTime = 3 * BroadcastInterval
  38. Magic = uint32(0x2EA7D90B) // same as in BEP
  39. v13Magic = uint32(0x7D79BC40) // previous version
  40. )
  41. func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister) (FinderService, error) {
  42. c := &localClient{
  43. Supervisor: suture.NewSimple("local"),
  44. myID: id,
  45. addrList: addrList,
  46. localBcastTick: time.Tick(BroadcastInterval),
  47. forcedBcastTick: make(chan time.Time),
  48. localBcastStart: time.Now(),
  49. cache: newCache(),
  50. }
  51. host, port, err := net.SplitHostPort(addr)
  52. if err != nil {
  53. return nil, err
  54. }
  55. if len(host) == 0 {
  56. // A broadcast client
  57. c.name = "IPv4 local"
  58. bcPort, err := strconv.Atoi(port)
  59. if err != nil {
  60. return nil, err
  61. }
  62. c.startLocalIPv4Broadcasts(bcPort)
  63. } else {
  64. // A multicast client
  65. c.name = "IPv6 local"
  66. c.startLocalIPv6Multicasts(addr)
  67. }
  68. go c.sendLocalAnnouncements()
  69. return c, nil
  70. }
  71. func (c *localClient) startLocalIPv4Broadcasts(localPort int) {
  72. c.beacon = beacon.NewBroadcast(localPort)
  73. c.Add(c.beacon)
  74. go c.recvAnnouncements(c.beacon)
  75. }
  76. func (c *localClient) startLocalIPv6Multicasts(localMCAddr string) {
  77. c.beacon = beacon.NewMulticast(localMCAddr)
  78. c.Add(c.beacon)
  79. go c.recvAnnouncements(c.beacon)
  80. }
  81. // Lookup returns a list of addresses the device is available at.
  82. func (c *localClient) Lookup(device protocol.DeviceID) (addresses []string, err error) {
  83. if cache, ok := c.Get(device); ok {
  84. if time.Since(cache.when) < CacheLifeTime {
  85. addresses = cache.Addresses
  86. }
  87. }
  88. return
  89. }
  90. func (c *localClient) String() string {
  91. return c.name
  92. }
  93. func (c *localClient) Error() error {
  94. return c.beacon.Error()
  95. }
  96. func (c *localClient) announcementPkt() Announce {
  97. return Announce{
  98. ID: c.myID[:],
  99. Addresses: c.addrList.AllAddresses(),
  100. InstanceID: rand.Int63(),
  101. }
  102. }
  103. func (c *localClient) sendLocalAnnouncements() {
  104. msg := make([]byte, 4)
  105. binary.BigEndian.PutUint32(msg, Magic)
  106. var pkt = c.announcementPkt()
  107. bs, _ := pkt.Marshal()
  108. msg = append(msg, bs...)
  109. for {
  110. c.beacon.Send(msg)
  111. select {
  112. case <-c.localBcastTick:
  113. case <-c.forcedBcastTick:
  114. }
  115. }
  116. }
  117. func (c *localClient) recvAnnouncements(b beacon.Interface) {
  118. warnedAbout := make(map[string]bool)
  119. for {
  120. buf, addr := b.Recv()
  121. if len(buf) < 4 {
  122. l.Debugf("discover: short packet from %s")
  123. continue
  124. }
  125. magic := binary.BigEndian.Uint32(buf)
  126. switch magic {
  127. case Magic:
  128. // All good
  129. case v13Magic:
  130. // Old version
  131. if !warnedAbout[addr.String()] {
  132. l.Warnf("Incompatible (v0.13) local discovery packet from %v - upgrade that device to connect", addr)
  133. warnedAbout[addr.String()] = true
  134. }
  135. continue
  136. default:
  137. l.Debugf("discover: Incorrect magic %x from %s", magic, addr)
  138. continue
  139. }
  140. var pkt Announce
  141. err := pkt.Unmarshal(buf[4:])
  142. if err != nil && err != io.EOF {
  143. l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
  144. continue
  145. }
  146. l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.ID))
  147. var newDevice bool
  148. if !bytes.Equal(pkt.ID, c.myID[:]) {
  149. newDevice = c.registerDevice(addr, pkt)
  150. }
  151. if newDevice {
  152. // Force a transmit to announce ourselves, if we are ready to do
  153. // so right away.
  154. select {
  155. case c.forcedBcastTick <- time.Now():
  156. default:
  157. }
  158. }
  159. }
  160. }
  161. func (c *localClient) registerDevice(src net.Addr, device Announce) bool {
  162. var id protocol.DeviceID
  163. copy(id[:], device.ID)
  164. // Remember whether we already had a valid cache entry for this device.
  165. // If the instance ID has changed the remote device has restarted since
  166. // we last heard from it, so we should treat it as a new device.
  167. ce, existsAlready := c.Get(id)
  168. isNewDevice := !existsAlready || time.Since(ce.when) > CacheLifeTime || ce.instanceID != device.InstanceID
  169. // Any empty or unspecified addresses should be set to the source address
  170. // of the announcement. We also skip any addresses we can't parse.
  171. l.Debugln("discover: Registering addresses for", id)
  172. var validAddresses []string
  173. for _, addr := range device.Addresses {
  174. u, err := url.Parse(addr)
  175. if err != nil {
  176. continue
  177. }
  178. tcpAddr, err := net.ResolveTCPAddr("tcp", u.Host)
  179. if err != nil {
  180. continue
  181. }
  182. if len(tcpAddr.IP) == 0 || tcpAddr.IP.IsUnspecified() {
  183. srcAddr, err := net.ResolveTCPAddr("tcp", src.String())
  184. if err != nil {
  185. continue
  186. }
  187. // Do not use IPv6 source address if requested scheme is tcp4
  188. if u.Scheme == "tcp4" && srcAddr.IP.To4() == nil {
  189. continue
  190. }
  191. // Do not use IPv4 source address if requested scheme is tcp6
  192. if u.Scheme == "tcp6" && srcAddr.IP.To4() != nil {
  193. continue
  194. }
  195. host, _, err := net.SplitHostPort(src.String())
  196. if err != nil {
  197. continue
  198. }
  199. u.Host = net.JoinHostPort(host, strconv.Itoa(tcpAddr.Port))
  200. l.Debugf("discover: Reconstructed URL is %#v", u)
  201. validAddresses = append(validAddresses, u.String())
  202. l.Debugf("discover: Replaced address %v in %s to get %s", tcpAddr.IP, addr, u.String())
  203. } else {
  204. validAddresses = append(validAddresses, addr)
  205. l.Debugf("discover: Accepted address %s verbatim", addr)
  206. }
  207. }
  208. c.Set(id, CacheEntry{
  209. Addresses: validAddresses,
  210. when: time.Now(),
  211. found: true,
  212. instanceID: device.InstanceID,
  213. })
  214. if isNewDevice {
  215. events.Default.Log(events.DeviceDiscovered, map[string]interface{}{
  216. "device": id.String(),
  217. "addrs": validAddresses,
  218. })
  219. }
  220. return isNewDevice
  221. }