global_test.go 7.4 KB

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