global.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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 https://mozilla.org/MPL/2.0/.
  6. package discover
  7. import (
  8. "bytes"
  9. "context"
  10. "crypto/tls"
  11. "encoding/json"
  12. "errors"
  13. "fmt"
  14. "io"
  15. "net/http"
  16. "net/url"
  17. "strconv"
  18. stdsync "sync"
  19. "time"
  20. "github.com/syncthing/syncthing/lib/dialer"
  21. "github.com/syncthing/syncthing/lib/events"
  22. "github.com/syncthing/syncthing/lib/protocol"
  23. )
  24. type globalClient struct {
  25. server string
  26. addrList AddressLister
  27. announceClient httpClient
  28. queryClient httpClient
  29. noAnnounce bool
  30. noLookup bool
  31. evLogger events.Logger
  32. errorHolder
  33. }
  34. type httpClient interface {
  35. Get(ctx context.Context, url string) (*http.Response, error)
  36. Post(ctx context.Context, url, ctype string, data io.Reader) (*http.Response, error)
  37. }
  38. const (
  39. defaultReannounceInterval = 30 * time.Minute
  40. announceErrorRetryInterval = 5 * time.Minute
  41. requestTimeout = 5 * time.Second
  42. maxAddressChangesBetweenAnnouncements = 10
  43. )
  44. type announcement struct {
  45. Addresses []string `json:"addresses"`
  46. }
  47. type serverOptions struct {
  48. insecure bool // don't check certificate
  49. noAnnounce bool // don't announce
  50. noLookup bool // don't use for lookups
  51. id string // expected server device ID
  52. }
  53. // A lookupError is any other error but with a cache validity time attached.
  54. type lookupError struct {
  55. msg string
  56. cacheFor time.Duration
  57. }
  58. func (e *lookupError) Error() string { return e.msg }
  59. func (e *lookupError) CacheFor() time.Duration {
  60. return e.cacheFor
  61. }
  62. func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLogger events.Logger) (FinderService, error) {
  63. server, opts, err := parseOptions(server)
  64. if err != nil {
  65. return nil, err
  66. }
  67. var devID protocol.DeviceID
  68. if opts.id != "" {
  69. devID, err = protocol.DeviceIDFromString(opts.id)
  70. if err != nil {
  71. return nil, err
  72. }
  73. }
  74. // The http.Client used for announcements. It needs to have our
  75. // certificate to prove our identity, and may or may not verify the server
  76. // certificate depending on the insecure setting.
  77. var announceClient httpClient = &contextClient{&http.Client{
  78. Timeout: requestTimeout,
  79. Transport: &http.Transport{
  80. DialContext: dialer.DialContextReusePort,
  81. Proxy: http.ProxyFromEnvironment,
  82. TLSClientConfig: &tls.Config{
  83. InsecureSkipVerify: opts.insecure,
  84. Certificates: []tls.Certificate{cert},
  85. },
  86. },
  87. }}
  88. if opts.id != "" {
  89. announceClient = newIDCheckingHTTPClient(announceClient, devID)
  90. }
  91. // The http.Client used for queries. We don't need to present our
  92. // certificate here, so lets not include it. May be insecure if requested.
  93. var queryClient httpClient = &contextClient{&http.Client{
  94. Timeout: requestTimeout,
  95. Transport: &http.Transport{
  96. DialContext: dialer.DialContext,
  97. Proxy: http.ProxyFromEnvironment,
  98. TLSClientConfig: &tls.Config{
  99. InsecureSkipVerify: opts.insecure,
  100. },
  101. },
  102. }}
  103. if opts.id != "" {
  104. queryClient = newIDCheckingHTTPClient(queryClient, devID)
  105. }
  106. cl := &globalClient{
  107. server: server,
  108. addrList: addrList,
  109. announceClient: announceClient,
  110. queryClient: queryClient,
  111. noAnnounce: opts.noAnnounce,
  112. noLookup: opts.noLookup,
  113. evLogger: evLogger,
  114. }
  115. if !opts.noAnnounce {
  116. // If we are supposed to annonce, it's an error until we've done so.
  117. cl.setError(errors.New("not announced"))
  118. }
  119. return cl, nil
  120. }
  121. // Lookup returns the list of addresses where the given device is available
  122. func (c *globalClient) Lookup(ctx context.Context, device protocol.DeviceID) (addresses []string, err error) {
  123. if c.noLookup {
  124. return nil, &lookupError{
  125. msg: "lookups not supported",
  126. cacheFor: time.Hour,
  127. }
  128. }
  129. qURL, err := url.Parse(c.server)
  130. if err != nil {
  131. return nil, err
  132. }
  133. q := qURL.Query()
  134. q.Set("device", device.String())
  135. qURL.RawQuery = q.Encode()
  136. resp, err := c.queryClient.Get(ctx, qURL.String())
  137. if err != nil {
  138. l.Debugln("globalClient.Lookup", qURL, err)
  139. return nil, err
  140. }
  141. if resp.StatusCode != 200 {
  142. resp.Body.Close()
  143. l.Debugln("globalClient.Lookup", qURL, resp.Status)
  144. err := errors.New(resp.Status)
  145. if secs, atoiErr := strconv.Atoi(resp.Header.Get("Retry-After")); atoiErr == nil && secs > 0 {
  146. err = &lookupError{
  147. msg: resp.Status,
  148. cacheFor: time.Duration(secs) * time.Second,
  149. }
  150. }
  151. return nil, err
  152. }
  153. bs, err := io.ReadAll(resp.Body)
  154. if err != nil {
  155. return nil, err
  156. }
  157. resp.Body.Close()
  158. var ann announcement
  159. err = json.Unmarshal(bs, &ann)
  160. return ann.Addresses, err
  161. }
  162. func (c *globalClient) String() string {
  163. return "global@" + c.server
  164. }
  165. func (c *globalClient) Serve(ctx context.Context) error {
  166. if c.noAnnounce {
  167. // We're configured to not do announcements, only lookups. To maintain
  168. // the same interface, we just pause here if Serve() is run.
  169. <-ctx.Done()
  170. return ctx.Err()
  171. }
  172. timer := time.NewTimer(5 * time.Second)
  173. defer timer.Stop()
  174. eventSub := c.evLogger.Subscribe(events.ListenAddressesChanged)
  175. defer eventSub.Unsubscribe()
  176. timerResetCount := 0
  177. for {
  178. select {
  179. case <-eventSub.C():
  180. if timerResetCount < maxAddressChangesBetweenAnnouncements {
  181. // Defer announcement by 2 seconds, essentially debouncing
  182. // if we have a stream of events incoming in quick succession.
  183. timer.Reset(2 * time.Second)
  184. } else if timerResetCount == maxAddressChangesBetweenAnnouncements {
  185. // Yet only do it if we haven't had to reset maxAddressChangesBetweenAnnouncements times in a row,
  186. // so if something is flip-flopping within 2 seconds, we don't end up in a permanent reset loop.
  187. l.Warnf("Detected a flip-flopping listener")
  188. c.setError(errors.New("flip flopping listener"))
  189. // Incrementing the count above 10 will prevent us from warning or setting the error again
  190. // It will also suppress event based resets until we've had a proper round after announceErrorRetryInterval
  191. timer.Reset(announceErrorRetryInterval)
  192. }
  193. timerResetCount++
  194. case <-timer.C:
  195. timerResetCount = 0
  196. c.sendAnnouncement(ctx, timer)
  197. case <-ctx.Done():
  198. return ctx.Err()
  199. }
  200. }
  201. }
  202. func (c *globalClient) sendAnnouncement(ctx context.Context, timer *time.Timer) {
  203. var ann announcement
  204. if c.addrList != nil {
  205. ann.Addresses = c.addrList.ExternalAddresses()
  206. }
  207. if len(ann.Addresses) == 0 {
  208. // There are legitimate cases for not having anything to announce,
  209. // yet still using global discovery for lookups. Do not error out
  210. // here.
  211. c.setError(nil)
  212. timer.Reset(announceErrorRetryInterval)
  213. return
  214. }
  215. // The marshal doesn't fail, I promise.
  216. postData, _ := json.Marshal(ann)
  217. l.Debugf("%s Announcement: %v", c, ann)
  218. resp, err := c.announceClient.Post(ctx, c.server, "application/json", bytes.NewReader(postData))
  219. if err != nil {
  220. l.Debugln(c, "announce POST:", err)
  221. c.setError(err)
  222. timer.Reset(announceErrorRetryInterval)
  223. return
  224. }
  225. l.Debugln(c, "announce POST:", resp.Status)
  226. resp.Body.Close()
  227. if resp.StatusCode < 200 || resp.StatusCode > 299 {
  228. l.Debugln(c, "announce POST:", resp.Status)
  229. c.setError(errors.New(resp.Status))
  230. if h := resp.Header.Get("Retry-After"); h != "" {
  231. // The server has a recommendation on when we should
  232. // retry. Follow it.
  233. if secs, err := strconv.Atoi(h); err == nil && secs > 0 {
  234. l.Debugln(c, "announce Retry-After:", secs, err)
  235. timer.Reset(time.Duration(secs) * time.Second)
  236. return
  237. }
  238. }
  239. timer.Reset(announceErrorRetryInterval)
  240. return
  241. }
  242. c.setError(nil)
  243. if h := resp.Header.Get("Reannounce-After"); h != "" {
  244. // The server has a recommendation on when we should
  245. // reannounce. Follow it.
  246. if secs, err := strconv.Atoi(h); err == nil && secs > 0 {
  247. l.Debugln(c, "announce Reannounce-After:", secs, err)
  248. timer.Reset(time.Duration(secs) * time.Second)
  249. return
  250. }
  251. }
  252. timer.Reset(defaultReannounceInterval)
  253. }
  254. func (c *globalClient) Cache() map[protocol.DeviceID]CacheEntry {
  255. // The globalClient doesn't do caching
  256. return nil
  257. }
  258. // parseOptions parses and strips away any ?query=val options, setting the
  259. // corresponding field in the serverOptions struct. Unknown query options are
  260. // ignored and removed.
  261. func parseOptions(dsn string) (server string, opts serverOptions, err error) {
  262. p, err := url.Parse(dsn)
  263. if err != nil {
  264. return "", serverOptions{}, err
  265. }
  266. // Grab known options from the query string
  267. q := p.Query()
  268. opts.id = q.Get("id")
  269. opts.insecure = opts.id != "" || queryBool(q, "insecure")
  270. opts.noAnnounce = queryBool(q, "noannounce")
  271. opts.noLookup = queryBool(q, "nolookup")
  272. // Check for disallowed combinations
  273. if p.Scheme == "http" {
  274. if !opts.insecure {
  275. return "", serverOptions{}, errors.New("http without insecure not supported")
  276. }
  277. if !opts.noAnnounce {
  278. return "", serverOptions{}, errors.New("http without noannounce not supported")
  279. }
  280. } else if p.Scheme != "https" {
  281. return "", serverOptions{}, errors.New("unsupported scheme " + p.Scheme)
  282. }
  283. // Remove the query string
  284. p.RawQuery = ""
  285. server = p.String()
  286. return
  287. }
  288. // queryBool returns the query parameter parsed as a boolean. An empty value
  289. // ("?foo") is considered true, as is any value string except false
  290. // ("?foo=false").
  291. func queryBool(q url.Values, key string) bool {
  292. if _, ok := q[key]; !ok {
  293. return false
  294. }
  295. return q.Get(key) != "false"
  296. }
  297. type idCheckingHTTPClient struct {
  298. httpClient
  299. id protocol.DeviceID
  300. }
  301. func newIDCheckingHTTPClient(client httpClient, id protocol.DeviceID) *idCheckingHTTPClient {
  302. return &idCheckingHTTPClient{
  303. httpClient: client,
  304. id: id,
  305. }
  306. }
  307. func (c *idCheckingHTTPClient) check(resp *http.Response) error {
  308. if resp.TLS == nil {
  309. return errors.New("security: not TLS")
  310. }
  311. if len(resp.TLS.PeerCertificates) == 0 {
  312. return errors.New("security: no certificates")
  313. }
  314. id := protocol.NewDeviceID(resp.TLS.PeerCertificates[0].Raw)
  315. if !id.Equals(c.id) {
  316. return errors.New("security: incorrect device id")
  317. }
  318. return nil
  319. }
  320. func (c *idCheckingHTTPClient) Get(ctx context.Context, url string) (*http.Response, error) {
  321. resp, err := c.httpClient.Get(ctx, url)
  322. if err != nil {
  323. return nil, err
  324. }
  325. if err := c.check(resp); err != nil {
  326. return nil, err
  327. }
  328. return resp, nil
  329. }
  330. func (c *idCheckingHTTPClient) Post(ctx context.Context, url, ctype string, data io.Reader) (*http.Response, error) {
  331. resp, err := c.httpClient.Post(ctx, url, ctype, data)
  332. if err != nil {
  333. return nil, err
  334. }
  335. if err := c.check(resp); err != nil {
  336. return nil, err
  337. }
  338. return resp, nil
  339. }
  340. type errorHolder struct {
  341. err error
  342. mut stdsync.Mutex // uses stdlib sync as I want this to be trivially embeddable, and there is no risk of blocking
  343. }
  344. func (e *errorHolder) setError(err error) {
  345. e.mut.Lock()
  346. e.err = err
  347. e.mut.Unlock()
  348. }
  349. func (e *errorHolder) Error() error {
  350. e.mut.Lock()
  351. err := e.err
  352. e.mut.Unlock()
  353. return err
  354. }
  355. type contextClient struct {
  356. *http.Client
  357. }
  358. func (c *contextClient) Get(ctx context.Context, url string) (*http.Response, error) {
  359. // For <go1.13 compatibility. Use the following commented line once that
  360. // isn't required anymore.
  361. // req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  362. req, err := http.NewRequest("GET", url, nil)
  363. if err != nil {
  364. return nil, err
  365. }
  366. req.Cancel = ctx.Done()
  367. return c.Client.Do(req)
  368. }
  369. func (c *contextClient) Post(ctx context.Context, url, ctype string, data io.Reader) (*http.Response, error) {
  370. // For <go1.13 compatibility. Use the following commented line once that
  371. // isn't required anymore.
  372. // req, err := http.NewRequestWithContext(ctx, "POST", url, data)
  373. req, err := http.NewRequest("POST", url, data)
  374. if err != nil {
  375. return nil, err
  376. }
  377. req.Cancel = ctx.Done()
  378. req.Header.Set("Content-Type", ctype)
  379. return c.Client.Do(req)
  380. }
  381. func globalDiscoveryIdentity(addr string) string {
  382. return "global discovery server " + addr
  383. }
  384. func ipv4Identity(port int) string {
  385. return fmt.Sprintf("IPv4 local broadcast discovery on port %d", port)
  386. }
  387. func ipv6Identity(addr string) string {
  388. return fmt.Sprintf("IPv6 local multicast discovery on address %s", addr)
  389. }