global_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. "crypto/tls"
  9. "io/ioutil"
  10. "net"
  11. "net/http"
  12. "strings"
  13. "testing"
  14. "time"
  15. "github.com/syncthing/syncthing/lib/protocol"
  16. "github.com/syncthing/syncthing/lib/tlsutil"
  17. )
  18. func TestParseOptions(t *testing.T) {
  19. testcases := []struct {
  20. in string
  21. out string
  22. opts serverOptions
  23. }{
  24. {"https://example.com/", "https://example.com/", serverOptions{}},
  25. {"https://example.com/?insecure", "https://example.com/", serverOptions{insecure: true}},
  26. {"https://example.com/?insecure=true", "https://example.com/", serverOptions{insecure: true}},
  27. {"https://example.com/?insecure=yes", "https://example.com/", serverOptions{insecure: true}},
  28. {"https://example.com/?insecure=false&noannounce", "https://example.com/", serverOptions{noAnnounce: true}},
  29. {"https://example.com/?id=abc", "https://example.com/", serverOptions{id: "abc", insecure: true}},
  30. }
  31. for _, tc := range testcases {
  32. res, opts, err := parseOptions(tc.in)
  33. if err != nil {
  34. t.Errorf("Unexpected err %v for %v", err, tc.in)
  35. continue
  36. }
  37. if res != tc.out {
  38. t.Errorf("Incorrect server, %v!= %v for %v", res, tc.out, tc.in)
  39. }
  40. if opts != tc.opts {
  41. t.Errorf("Incorrect options, %v!= %v for %v", opts, tc.opts, tc.in)
  42. }
  43. }
  44. }
  45. func TestGlobalOverHTTP(t *testing.T) {
  46. // HTTP works for queries, but is obviously insecure and we can't do
  47. // announces over it (as we don't present a certificate). As such, http://
  48. // is only allowed in combination with the "insecure" and "noannounce"
  49. // parameters.
  50. if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil, nil); err == nil {
  51. t.Fatal("http is not allowed without insecure and noannounce")
  52. }
  53. if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil, nil); err == nil {
  54. t.Fatal("http is not allowed without noannounce")
  55. }
  56. if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil, nil); err == nil {
  57. t.Fatal("http is not allowed without insecure")
  58. }
  59. // Now lets check that lookups work over HTTP, given the correct options.
  60. list, err := net.Listen("tcp4", "127.0.0.1:0")
  61. if err != nil {
  62. t.Fatal(err)
  63. }
  64. defer list.Close()
  65. s := new(fakeDiscoveryServer)
  66. mux := http.NewServeMux()
  67. mux.HandleFunc("/", s.handler)
  68. go http.Serve(list, mux)
  69. // This should succeed
  70. direct, relays, err := testLookup("http://" + list.Addr().String() + "?insecure&noannounce")
  71. if err != nil {
  72. t.Fatalf("unexpected error: %v", err)
  73. }
  74. if !testing.Short() {
  75. // This should time out
  76. _, _, err = testLookup("http://" + list.Addr().String() + "/block?insecure&noannounce")
  77. if err == nil {
  78. t.Fatalf("unexpected nil error, should have been a timeout")
  79. }
  80. }
  81. // This should work again
  82. _, _, err = testLookup("http://" + list.Addr().String() + "?insecure&noannounce")
  83. if err != nil {
  84. t.Fatalf("unexpected error: %v", err)
  85. }
  86. if len(direct) != 1 || direct[0] != "tcp://192.0.2.42::22000" {
  87. t.Errorf("incorrect direct list: %+v", direct)
  88. }
  89. if len(relays) != 1 || relays[0] != (Relay{URL: "relay://192.0.2.43:443", Latency: 42}) {
  90. t.Errorf("incorrect relays list: %+v", direct)
  91. }
  92. }
  93. func TestGlobalOverHTTPS(t *testing.T) {
  94. dir, err := ioutil.TempDir("", "syncthing")
  95. if err != nil {
  96. t.Fatal(err)
  97. }
  98. // Generate a server certificate, using fewer bits than usual to hurry the
  99. // process along a bit.
  100. cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 1024)
  101. if err != nil {
  102. t.Fatal(err)
  103. }
  104. list, err := tls.Listen("tcp4", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{cert}})
  105. if err != nil {
  106. t.Fatal(err)
  107. }
  108. defer list.Close()
  109. s := new(fakeDiscoveryServer)
  110. mux := http.NewServeMux()
  111. mux.HandleFunc("/", s.handler)
  112. go http.Serve(list, mux)
  113. // With default options the lookup code expects the server certificate to
  114. // check out according to the usual CA chains etc. That won't be the case
  115. // here so we expect the lookup to fail.
  116. url := "https://" + list.Addr().String()
  117. if _, _, err := testLookup(url); err == nil {
  118. t.Fatalf("unexpected nil error when we should have got a certificate error")
  119. }
  120. // With "insecure" set, whatever certificate is on the other side should
  121. // be accepted.
  122. url = "https://" + list.Addr().String() + "?insecure"
  123. if direct, relays, err := testLookup(url); err != nil {
  124. t.Fatalf("unexpected error: %v", err)
  125. } else {
  126. if len(direct) != 1 || direct[0] != "tcp://192.0.2.42::22000" {
  127. t.Errorf("incorrect direct list: %+v", direct)
  128. }
  129. if len(relays) != 1 || relays[0] != (Relay{URL: "relay://192.0.2.43:443", Latency: 42}) {
  130. t.Errorf("incorrect relays list: %+v", direct)
  131. }
  132. }
  133. // With "id" set to something incorrect, the checks should fail again.
  134. url = "https://" + list.Addr().String() + "?id=" + protocol.LocalDeviceID.String()
  135. if _, _, err := testLookup(url); err == nil {
  136. t.Fatalf("unexpected nil error for incorrect discovery server ID")
  137. }
  138. // With the correct device ID, the check should pass and we should get a
  139. // lookup response.
  140. id := protocol.NewDeviceID(cert.Certificate[0])
  141. url = "https://" + list.Addr().String() + "?id=" + id.String()
  142. if direct, relays, err := testLookup(url); err != nil {
  143. t.Fatalf("unexpected error: %v", err)
  144. } else {
  145. if len(direct) != 1 || direct[0] != "tcp://192.0.2.42::22000" {
  146. t.Errorf("incorrect direct list: %+v", direct)
  147. }
  148. if len(relays) != 1 || relays[0] != (Relay{URL: "relay://192.0.2.43:443", Latency: 42}) {
  149. t.Errorf("incorrect relays list: %+v", direct)
  150. }
  151. }
  152. }
  153. func TestGlobalAnnounce(t *testing.T) {
  154. dir, err := ioutil.TempDir("", "syncthing")
  155. if err != nil {
  156. t.Fatal(err)
  157. }
  158. // Generate a server certificate, using fewer bits than usual to hurry the
  159. // process along a bit.
  160. cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 1024)
  161. if err != nil {
  162. t.Fatal(err)
  163. }
  164. list, err := tls.Listen("tcp4", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{cert}})
  165. if err != nil {
  166. t.Fatal(err)
  167. }
  168. defer list.Close()
  169. s := new(fakeDiscoveryServer)
  170. mux := http.NewServeMux()
  171. mux.HandleFunc("/", s.handler)
  172. go http.Serve(list, mux)
  173. url := "https://" + list.Addr().String() + "?insecure"
  174. disco, err := NewGlobal(url, cert, new(fakeAddressLister), new(fakeRelayStatus))
  175. if err != nil {
  176. t.Fatal(err)
  177. }
  178. go disco.Serve()
  179. defer disco.Stop()
  180. // The discovery thing should attempt an announcement immediately. We wait
  181. // for it to succeed, a while.
  182. t0 := time.Now()
  183. for err := disco.Error(); err != nil; err = disco.Error() {
  184. if time.Since(t0) > 10*time.Second {
  185. t.Fatal("announce failed:", err)
  186. }
  187. time.Sleep(100 * time.Millisecond)
  188. }
  189. if !strings.Contains(string(s.announce), "tcp://0.0.0.0:22000") {
  190. t.Errorf("announce missing direct address: %s", s.announce)
  191. }
  192. if !strings.Contains(string(s.announce), "relay://192.0.2.42:443") {
  193. t.Errorf("announce missing relay address: %s", s.announce)
  194. }
  195. }
  196. func testLookup(url string) ([]string, []Relay, error) {
  197. disco, err := NewGlobal(url, tls.Certificate{}, nil, nil)
  198. if err != nil {
  199. return nil, nil, err
  200. }
  201. go disco.Serve()
  202. defer disco.Stop()
  203. return disco.Lookup(protocol.LocalDeviceID)
  204. }
  205. type fakeDiscoveryServer struct {
  206. announce []byte
  207. }
  208. func (s *fakeDiscoveryServer) handler(w http.ResponseWriter, r *http.Request) {
  209. if r.URL.Path == "/block" {
  210. // Never return for requests here
  211. select {}
  212. }
  213. if r.Method == "POST" {
  214. s.announce, _ = ioutil.ReadAll(r.Body)
  215. w.WriteHeader(204)
  216. } else {
  217. w.Header().Set("Content-Type", "application/json")
  218. w.Write([]byte(`{"direct":["tcp://192.0.2.42::22000"], "relays":[{"url": "relay://192.0.2.43:443", "latency": 42}]}`))
  219. }
  220. }
  221. type fakeAddressLister struct{}
  222. func (f *fakeAddressLister) ExternalAddresses() []string {
  223. return []string{"tcp://0.0.0.0:22000"}
  224. }
  225. func (f *fakeAddressLister) AllAddresses() []string {
  226. return []string{"tcp://0.0.0.0:22000", "tcp://192.168.0.1:22000"}
  227. }
  228. type fakeRelayStatus struct{}
  229. func (f *fakeRelayStatus) Relays() []string {
  230. return []string{"relay://192.0.2.42:443"}
  231. }
  232. func (f *fakeRelayStatus) RelayStatus(uri string) (time.Duration, bool) {
  233. return 42 * time.Millisecond, true
  234. }