global.go 8.7 KB

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