global.go 9.5 KB

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