global.go 9.6 KB

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