global_test.go 7.3 KB

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