2015-09-21 08:43:36 +00:00
|
|
|
// Copyright (C) 2015 The Syncthing Authors.
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
2017-02-09 06:52:18 +00:00
|
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
2015-09-21 08:43:36 +00:00
|
|
|
|
2015-09-20 13:30:25 +00:00
|
|
|
package discover
|
|
|
|
|
|
|
|
import (
|
2020-02-13 13:43:00 +00:00
|
|
|
"context"
|
2015-09-20 13:30:25 +00:00
|
|
|
"crypto/tls"
|
2021-11-22 07:59:47 +00:00
|
|
|
"io"
|
2015-09-20 13:30:25 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2022-04-09 14:04:56 +00:00
|
|
|
"github.com/syncthing/syncthing/lib/connections/registry"
|
2019-08-15 14:29:37 +00:00
|
|
|
"github.com/syncthing/syncthing/lib/events"
|
2015-09-22 17:38:46 +00:00
|
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
2015-09-20 13:30:25 +00:00
|
|
|
"github.com/syncthing/syncthing/lib/tlsutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestParseOptions(t *testing.T) {
|
|
|
|
testcases := []struct {
|
|
|
|
in string
|
|
|
|
out string
|
|
|
|
opts serverOptions
|
|
|
|
}{
|
|
|
|
{"https://example.com/", "https://example.com/", serverOptions{}},
|
|
|
|
{"https://example.com/?insecure", "https://example.com/", serverOptions{insecure: true}},
|
|
|
|
{"https://example.com/?insecure=true", "https://example.com/", serverOptions{insecure: true}},
|
|
|
|
{"https://example.com/?insecure=yes", "https://example.com/", serverOptions{insecure: true}},
|
|
|
|
{"https://example.com/?insecure=false&noannounce", "https://example.com/", serverOptions{noAnnounce: true}},
|
|
|
|
{"https://example.com/?id=abc", "https://example.com/", serverOptions{id: "abc", insecure: true}},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testcases {
|
|
|
|
res, opts, err := parseOptions(tc.in)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected err %v for %v", err, tc.in)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if res != tc.out {
|
|
|
|
t.Errorf("Incorrect server, %v!= %v for %v", res, tc.out, tc.in)
|
|
|
|
}
|
|
|
|
if opts != tc.opts {
|
|
|
|
t.Errorf("Incorrect options, %v!= %v for %v", opts, tc.opts, tc.in)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGlobalOverHTTP(t *testing.T) {
|
|
|
|
// HTTP works for queries, but is obviously insecure and we can't do
|
|
|
|
// announces over it (as we don't present a certificate). As such, http://
|
|
|
|
// is only allowed in combination with the "insecure" and "noannounce"
|
|
|
|
// parameters.
|
|
|
|
|
2022-04-09 14:04:56 +00:00
|
|
|
registry := registry.New()
|
|
|
|
|
|
|
|
if _, err := NewGlobal("http://192.0.2.42/", tls.Certificate{}, nil, events.NoopLogger, registry); err == nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatal("http is not allowed without insecure and noannounce")
|
|
|
|
}
|
|
|
|
|
2022-04-09 14:04:56 +00:00
|
|
|
if _, err := NewGlobal("http://192.0.2.42/?insecure", tls.Certificate{}, nil, events.NoopLogger, registry); err == nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatal("http is not allowed without noannounce")
|
|
|
|
}
|
|
|
|
|
2022-04-09 14:04:56 +00:00
|
|
|
if _, err := NewGlobal("http://192.0.2.42/?noannounce", tls.Certificate{}, nil, events.NoopLogger, registry); err == nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatal("http is not allowed without insecure")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now lets check that lookups work over HTTP, given the correct options.
|
|
|
|
|
|
|
|
list, err := net.Listen("tcp4", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer list.Close()
|
|
|
|
|
|
|
|
s := new(fakeDiscoveryServer)
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/", s.handler)
|
2019-02-02 10:02:28 +00:00
|
|
|
go func() { _ = http.Serve(list, mux) }()
|
2015-09-20 13:30:25 +00:00
|
|
|
|
2015-10-21 12:24:33 +00:00
|
|
|
// This should succeed
|
2016-05-04 19:38:12 +00:00
|
|
|
addresses, err := testLookup("http://" + list.Addr().String() + "?insecure&noannounce")
|
2015-09-20 13:30:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-10-21 12:24:33 +00:00
|
|
|
if !testing.Short() {
|
|
|
|
// This should time out
|
2016-05-04 19:38:12 +00:00
|
|
|
_, err = testLookup("http://" + list.Addr().String() + "/block?insecure&noannounce")
|
2015-10-21 12:24:33 +00:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("unexpected nil error, should have been a timeout")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should work again
|
2016-05-04 19:38:12 +00:00
|
|
|
_, err = testLookup("http://" + list.Addr().String() + "?insecure&noannounce")
|
2015-10-21 12:24:33 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-05-04 19:38:12 +00:00
|
|
|
if len(addresses) != 1 || addresses[0] != "tcp://192.0.2.42::22000" {
|
|
|
|
t.Errorf("incorrect addresses list: %+v", addresses)
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGlobalOverHTTPS(t *testing.T) {
|
2018-10-21 05:17:50 +00:00
|
|
|
// Generate a server certificate.
|
2021-11-07 22:59:48 +00:00
|
|
|
cert, err := tlsutil.NewCertificateInMemory("syncthing", 30)
|
2015-09-20 13:30:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
list, err := tls.Listen("tcp4", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{cert}})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer list.Close()
|
|
|
|
|
|
|
|
s := new(fakeDiscoveryServer)
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/", s.handler)
|
2019-02-02 10:02:28 +00:00
|
|
|
go func() { _ = http.Serve(list, mux) }()
|
2015-09-20 13:30:25 +00:00
|
|
|
|
|
|
|
// With default options the lookup code expects the server certificate to
|
|
|
|
// check out according to the usual CA chains etc. That won't be the case
|
|
|
|
// here so we expect the lookup to fail.
|
|
|
|
|
|
|
|
url := "https://" + list.Addr().String()
|
2016-05-04 19:38:12 +00:00
|
|
|
if _, err := testLookup(url); err == nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatalf("unexpected nil error when we should have got a certificate error")
|
|
|
|
}
|
|
|
|
|
|
|
|
// With "insecure" set, whatever certificate is on the other side should
|
|
|
|
// be accepted.
|
|
|
|
|
|
|
|
url = "https://" + list.Addr().String() + "?insecure"
|
2016-05-04 19:38:12 +00:00
|
|
|
if addresses, err := testLookup(url); err != nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatalf("unexpected error: %v", err)
|
2022-07-28 17:14:22 +00:00
|
|
|
} else if len(addresses) != 1 || addresses[0] != "tcp://192.0.2.42::22000" {
|
|
|
|
t.Errorf("incorrect addresses list: %+v", addresses)
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// With "id" set to something incorrect, the checks should fail again.
|
|
|
|
|
|
|
|
url = "https://" + list.Addr().String() + "?id=" + protocol.LocalDeviceID.String()
|
2016-05-04 19:38:12 +00:00
|
|
|
if _, err := testLookup(url); err == nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatalf("unexpected nil error for incorrect discovery server ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the correct device ID, the check should pass and we should get a
|
|
|
|
// lookup response.
|
|
|
|
|
|
|
|
id := protocol.NewDeviceID(cert.Certificate[0])
|
|
|
|
url = "https://" + list.Addr().String() + "?id=" + id.String()
|
2016-05-04 19:38:12 +00:00
|
|
|
if addresses, err := testLookup(url); err != nil {
|
2015-09-20 13:30:25 +00:00
|
|
|
t.Fatalf("unexpected error: %v", err)
|
2022-07-28 17:14:22 +00:00
|
|
|
} else if len(addresses) != 1 || addresses[0] != "tcp://192.0.2.42::22000" {
|
|
|
|
t.Errorf("incorrect addresses list: %+v", addresses)
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGlobalAnnounce(t *testing.T) {
|
2018-10-21 05:17:50 +00:00
|
|
|
// Generate a server certificate.
|
2021-11-07 22:59:48 +00:00
|
|
|
cert, err := tlsutil.NewCertificateInMemory("syncthing", 30)
|
2015-09-20 13:30:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
list, err := tls.Listen("tcp4", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{cert}})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer list.Close()
|
|
|
|
|
|
|
|
s := new(fakeDiscoveryServer)
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/", s.handler)
|
2019-02-02 10:02:28 +00:00
|
|
|
go func() { _ = http.Serve(list, mux) }()
|
2015-09-20 13:30:25 +00:00
|
|
|
|
|
|
|
url := "https://" + list.Addr().String() + "?insecure"
|
2022-04-09 14:04:56 +00:00
|
|
|
disco, err := NewGlobal(url, cert, new(fakeAddressLister), events.NoopLogger, registry.New())
|
2015-09-20 13:30:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-11-17 12:19:04 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go disco.Serve(ctx)
|
|
|
|
defer cancel()
|
2015-09-20 13:30:25 +00:00
|
|
|
|
|
|
|
// The discovery thing should attempt an announcement immediately. We wait
|
|
|
|
// for it to succeed, a while.
|
|
|
|
t0 := time.Now()
|
|
|
|
for err := disco.Error(); err != nil; err = disco.Error() {
|
|
|
|
if time.Since(t0) > 10*time.Second {
|
|
|
|
t.Fatal("announce failed:", err)
|
|
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(string(s.announce), "tcp://0.0.0.0:22000") {
|
2017-11-17 09:12:35 +00:00
|
|
|
t.Errorf("announce missing address: %q", s.announce)
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-04 19:38:12 +00:00
|
|
|
func testLookup(url string) ([]string, error) {
|
2022-04-09 14:04:56 +00:00
|
|
|
disco, err := NewGlobal(url, tls.Certificate{}, nil, events.NoopLogger, registry.New())
|
2015-09-20 13:30:25 +00:00
|
|
|
if err != nil {
|
2016-05-04 19:38:12 +00:00
|
|
|
return nil, err
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
2020-11-17 12:19:04 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go disco.Serve(ctx)
|
|
|
|
defer cancel()
|
2015-09-20 13:30:25 +00:00
|
|
|
|
2020-02-13 13:43:00 +00:00
|
|
|
return disco.Lookup(context.Background(), protocol.LocalDeviceID)
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type fakeDiscoveryServer struct {
|
|
|
|
announce []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fakeDiscoveryServer) handler(w http.ResponseWriter, r *http.Request) {
|
2015-10-21 12:24:33 +00:00
|
|
|
if r.URL.Path == "/block" {
|
|
|
|
// Never return for requests here
|
|
|
|
select {}
|
|
|
|
}
|
|
|
|
|
2015-09-20 13:30:25 +00:00
|
|
|
if r.Method == "POST" {
|
2021-11-22 07:59:47 +00:00
|
|
|
s.announce, _ = io.ReadAll(r.Body)
|
2015-09-20 13:30:25 +00:00
|
|
|
w.WriteHeader(204)
|
|
|
|
} else {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2019-02-02 11:16:27 +00:00
|
|
|
w.Write([]byte(`{"addresses":["tcp://192.0.2.42::22000"], "relays":[{"url": "relay://192.0.2.43:443", "latency": 42}]}`))
|
2015-09-20 13:30:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeAddressLister struct{}
|
|
|
|
|
2023-07-18 14:34:50 +00:00
|
|
|
func (*fakeAddressLister) ExternalAddresses() []string {
|
2015-09-20 13:30:25 +00:00
|
|
|
return []string{"tcp://0.0.0.0:22000"}
|
|
|
|
}
|
2023-07-18 14:34:50 +00:00
|
|
|
func (*fakeAddressLister) AllAddresses() []string {
|
2015-09-20 13:30:25 +00:00
|
|
|
return []string{"tcp://0.0.0.0:22000", "tcp://192.168.0.1:22000"}
|
|
|
|
}
|