Large refactoring/feature commit

1. Change listen addresses to URIs
2. Break out connectionSvc to support listeners and dialers based on schema
3. Add relay announcement and lookups part of discovery service
This commit is contained in:
Audrius Butkevicius
2015-06-23 13:55:30 +01:00
parent 50702eda94
commit 34c04babbe
18 changed files with 657 additions and 321 deletions

View File

@@ -21,11 +21,11 @@ func main() {
var server string var server string
flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22026", "Announce server") flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22027", "Announce server")
flag.Parse() flag.Parse()
if len(flag.Args()) != 1 || server == "" { if len(flag.Args()) != 1 || server == "" {
log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22026\"] <device>", os.Args[0]) log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22027\"] <device>", os.Args[0])
os.Exit(64) os.Exit(64)
} }
@@ -35,9 +35,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil) discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil, nil)
discoverer.StartGlobal([]string{server}, 1) discoverer.StartGlobal([]string{server}, 1)
for _, addr := range discoverer.Lookup(id) { addresses, relays := discoverer.Lookup(id)
log.Println(addr) for _, addr := range addresses {
log.Println("address:", addr)
}
for _, addr := range relays {
log.Println("relay:", addr)
} }
} }

View File

@@ -11,7 +11,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strings" "net/url"
"time" "time"
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
@@ -21,6 +21,14 @@ import (
"github.com/thejerf/suture" "github.com/thejerf/suture"
) )
type DialerFactory func(*url.URL, *tls.Config) (*tls.Conn, error)
type ListenerFactory func(*url.URL, *tls.Config, chan<- *tls.Conn)
var (
dialers = make(map[string]DialerFactory, 0)
listeners = make(map[string]ListenerFactory, 0)
)
// The connection service listens on TLS and dials configured unconnected // The connection service listens on TLS and dials configured unconnected
// devices. Successful connections are handed to the model. // devices. Successful connections are handed to the model.
type connectionSvc struct { type connectionSvc struct {
@@ -51,9 +59,9 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.
// //
// +-----------------+ // +-----------------+
// Incoming | +---------------+-+ +-----------------+ // Incoming | +---------------+-+ +-----------------+
// Connections | | | | | Outgoing // Connections | | | | |
// -------------->| | svc.listen | | | Connections // -------------->| | listener | | | Outgoing connections via dialers
// | | (1 per listen | | svc.connect |--------------> // | | (1 per listen | | svc.connect |----------------------------------->
// | | address) | | | // | | address) | | |
// +-+ | | | // +-+ | | |
// +-----------------+ +-----------------+ // +-----------------+ +-----------------+
@@ -79,11 +87,25 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.
svc.Add(serviceFunc(svc.connect)) svc.Add(serviceFunc(svc.connect))
for _, addr := range svc.cfg.Options().ListenAddress { for _, addr := range svc.cfg.Options().ListenAddress {
addr := addr uri, err := url.Parse(addr)
listener := serviceFunc(func() { if err != nil {
svc.listen(addr) l.Infoln("Failed to parse listen address:", addr, err)
}) continue
svc.Add(listener) }
listener, ok := listeners[uri.Scheme]
if !ok {
l.Infoln("Unknown listen address scheme:", uri.String())
continue
}
if debugNet {
l.Debugln("listening on", uri.String())
}
svc.Add(serviceFunc(func() {
listener(uri, svc.tlsCfg, svc.conns)
}))
} }
svc.Add(serviceFunc(svc.handle)) svc.Add(serviceFunc(svc.handle))
@@ -197,46 +219,6 @@ next:
} }
} }
func (s *connectionSvc) listen(addr string) {
if debugNet {
l.Debugln("listening on", addr)
}
tcaddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection:", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
tcpConn := conn.(*net.TCPConn)
s.setTCPOptions(tcpConn)
tc := tls.Server(conn, s.tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
s.conns <- tc
}
}
func (s *connectionSvc) connect() { func (s *connectionSvc) connect() {
delay := time.Second delay := time.Second
for { for {
@@ -254,7 +236,7 @@ func (s *connectionSvc) connect() {
for _, addr := range deviceCfg.Addresses { for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" { if addr == "dynamic" {
if discoverer != nil { if discoverer != nil {
t := discoverer.Lookup(deviceID) t, _ := discoverer.Lookup(deviceID)
if len(t) == 0 { if len(t) == 0 {
continue continue
} }
@@ -266,45 +248,30 @@ func (s *connectionSvc) connect() {
} }
for _, addr := range addrs { for _, addr := range addrs {
host, port, err := net.SplitHostPort(addr) uri, err := url.Parse(addr)
if err != nil && strings.HasPrefix(err.Error(), "missing port") { if err != nil {
// addr is on the form "1.2.3.4" l.Infoln("Failed to parse connection url:", addr, err)
addr = net.JoinHostPort(addr, "22000") continue
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
addr = net.JoinHostPort(host, "22000")
} }
dialer, ok := dialers[uri.Scheme]
if !ok {
l.Infoln("Unknown address schema", uri.String())
continue
}
if debugNet { if debugNet {
l.Debugln("dial", deviceCfg.DeviceID, addr) l.Debugln("dial", deviceCfg.DeviceID, uri.String())
} }
conn, err := dialer(uri, s.tlsCfg)
raddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil { if err != nil {
if debugNet { if debugNet {
l.Debugln(err) l.Debugln("dial failed", deviceCfg.DeviceID, uri.String(), err)
} }
continue continue
} }
conn, err := net.DialTCP("tcp", nil, raddr) s.conns <- conn
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
s.setTCPOptions(conn)
tc := tls.Client(conn, s.tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
s.conns <- tc
continue nextDevice continue nextDevice
} }
} }
@@ -317,22 +284,6 @@ func (s *connectionSvc) connect() {
} }
} }
func (*connectionSvc) setTCPOptions(conn *net.TCPConn) {
var err error
if err = conn.SetLinger(0); err != nil {
l.Infoln(err)
}
if err = conn.SetNoDelay(false); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlive(true); err != nil {
l.Infoln(err)
}
}
func (s *connectionSvc) shouldLimit(addr net.Addr) bool { func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
if s.cfg.Options().LimitBandwidthInLan { if s.cfg.Options().LimitBandwidthInLan {
return true return true
@@ -370,3 +321,19 @@ func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool
return true return true
} }
func setTCPOptions(conn *net.TCPConn) {
var err error
if err = conn.SetLinger(0); err != nil {
l.Infoln(err)
}
if err = conn.SetNoDelay(false); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlive(true); err != nil {
l.Infoln(err)
}
}

View File

@@ -0,0 +1,95 @@
// 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,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"crypto/tls"
"net"
"net/url"
"strings"
)
func init() {
dialers["tcp"] = tcpDialer
listeners["tcp"] = tcpListener
}
func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
host, port, err := net.SplitHostPort(uri.Host)
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
// addr is on the form "1.2.3.4"
uri.Host = net.JoinHostPort(uri.Host, "22000")
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
uri.Host = net.JoinHostPort(host, "22000")
}
raddr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
if debugNet {
l.Debugln(err)
}
return nil, err
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
if debugNet {
l.Debugln(err)
}
return nil, err
}
setTCPOptions(conn)
tc := tls.Client(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
tc.Close()
return nil, err
}
return tc, nil
}
func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- *tls.Conn) {
tcaddr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
l.Fatalln("listen (BEP/tcp):", err)
return
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP/tcp):", err)
return
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection (BEP/tcp):", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
tcpConn := conn.(*net.TCPConn)
setTCPOptions(tcpConn)
tc := tls.Server(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake (BEP/tcp):", err)
tc.Close()
continue
}
conns <- tc
}
}

View File

@@ -658,7 +658,12 @@ func syncthingMain() {
// The default port we announce, possibly modified by setupUPnP next. // The default port we announce, possibly modified by setupUPnP next.
addr, err := net.ResolveTCPAddr("tcp", opts.ListenAddress[0]) uri, err := url.Parse(opts.ListenAddress[0])
if err != nil {
l.Fatalf("Failed to parse listen address %s: %v", opts.ListenAddress[0], err)
}
addr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil { if err != nil {
l.Fatalln("Bad listen address:", err) l.Fatalln("Bad listen address:", err)
} }
@@ -902,7 +907,7 @@ func shutdown() {
func discovery(extPort int) *discover.Discoverer { func discovery(extPort int) *discover.Discoverer {
opts := cfg.Options() opts := cfg.Options()
disc := discover.NewDiscoverer(myID, opts.ListenAddress) disc := discover.NewDiscoverer(myID, opts.ListenAddress, opts.RelayServers)
if opts.LocalAnnEnabled { if opts.LocalAnnEnabled {
l.Infoln("Starting local discovery announcements") l.Infoln("Starting local discovery announcements")

View File

@@ -32,7 +32,7 @@
<div class="form-group"> <div class="form-group">
<label translate for="addresses">Addresses</label> <label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input> <input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input>
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p> <p translate class="help-block">Enter comma separated ("tcp://ip:port", "tcp://host:port") addresses or "dynamic" to perform automatic discovery of the address.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label translate>Compression</label> <label translate>Compression</label>

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ package config
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"math/rand" "math/rand"
"os" "os"
@@ -26,7 +27,7 @@ import (
const ( const (
OldestHandledVersion = 5 OldestHandledVersion = 5
CurrentVersion = 11 CurrentVersion = 12
MaxRescanIntervalS = 365 * 24 * 60 * 60 MaxRescanIntervalS = 365 * 24 * 60 * 60
) )
@@ -212,12 +213,13 @@ type FolderDeviceConfiguration struct {
} }
type OptionsConfiguration struct { type OptionsConfiguration struct {
ListenAddress []string `xml:"listenAddress" json:"listenAddress" default:"0.0.0.0:22000"` ListenAddress []string `xml:"listenAddress" json:"listenAddress" default:"tcp://0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"` GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22027, udp6://announce-v6.syncthing.net:22027"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"` GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"` LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"` LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"` LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
RelayServers []string `xml:"relayServer" json:"relayServers" default:""`
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"` MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"` MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"` ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
@@ -346,6 +348,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
} }
} }
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
cfg.Options.GlobalAnnServers = uniqueStrings(cfg.Options.GlobalAnnServers)
if cfg.Version < OldestHandledVersion { if cfg.Version < OldestHandledVersion {
l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version) l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
} }
@@ -369,6 +374,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
if cfg.Version == 10 { if cfg.Version == 10 {
convertV10V11(cfg) convertV10V11(cfg)
} }
if cfg.Version == 11 {
convertV11V12(cfg)
}
// Hash old cleartext passwords // Hash old cleartext passwords
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' { if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
@@ -420,9 +428,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
cfg.Options.ReconnectIntervalS = 5 cfg.Options.ReconnectIntervalS = 5
} }
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
cfg.Options.GlobalAnnServers = uniqueStrings(cfg.Options.GlobalAnnServers)
if cfg.GUI.APIKey == "" { if cfg.GUI.APIKey == "" {
cfg.GUI.APIKey = randomString(32) cfg.GUI.APIKey = randomString(32)
} }
@@ -467,6 +472,38 @@ func convertV10V11(cfg *Configuration) {
cfg.Version = 11 cfg.Version = 11
} }
func convertV11V12(cfg *Configuration) {
// Change listen address schema
for i, addr := range cfg.Options.ListenAddress {
if len(addr) > 0 && !strings.HasPrefix(addr, "tcp://") {
cfg.Options.ListenAddress[i] = fmt.Sprintf("tcp://%s", addr)
}
}
for i, device := range cfg.Devices {
for j, addr := range device.Addresses {
if addr != "dynamic" && addr != "" {
cfg.Devices[i].Addresses[j] = fmt.Sprintf("tcp://%s", addr)
}
}
}
// Use new discovery server
for i, addr := range cfg.Options.GlobalAnnServers {
if addr == "udp4://announce.syncthing.net:22026" {
cfg.Options.GlobalAnnServers[i] = "udp4://announce.syncthing.net:22027"
} else if addr == "udp6://announce-v6.syncthing.net:22026" {
cfg.Options.GlobalAnnServers[i] = "udp6://announce-v6.syncthing.net:22027"
} else if addr == "udp4://194.126.249.5:22026" {
cfg.Options.GlobalAnnServers[i] = "udp4://194.126.249.5:22027"
} else if addr == "udp6://[2001:470:28:4d6::5]:22026" {
cfg.Options.GlobalAnnServers[i] = "udp6://[2001:470:28:4d6::5]:22027"
}
}
cfg.Version = 12
}
func convertV9V10(cfg *Configuration) { func convertV9V10(cfg *Configuration) {
// Enable auto normalization on existing folders. // Enable auto normalization on existing folders.
for i := range cfg.Folders { for i := range cfg.Folders {

View File

@@ -31,8 +31,8 @@ func init() {
func TestDefaultValues(t *testing.T) { func TestDefaultValues(t *testing.T) {
expected := OptionsConfiguration{ expected := OptionsConfiguration{
ListenAddress: []string{"0.0.0.0:22000"}, ListenAddress: []string{"tcp://0.0.0.0:22000"},
GlobalAnnServers: []string{"udp4://announce.syncthing.net:22026", "udp6://announce-v6.syncthing.net:22026"}, GlobalAnnServers: []string{"udp4://announce.syncthing.net:22027", "udp6://announce-v6.syncthing.net:22027"},
GlobalAnnEnabled: true, GlobalAnnEnabled: true,
LocalAnnEnabled: true, LocalAnnEnabled: true,
LocalAnnPort: 21025, LocalAnnPort: 21025,
@@ -100,13 +100,13 @@ func TestDeviceConfig(t *testing.T) {
{ {
DeviceID: device1, DeviceID: device1,
Name: "node one", Name: "node one",
Addresses: []string{"a"}, Addresses: []string{"tcp://a"},
Compression: protocol.CompressMetadata, Compression: protocol.CompressMetadata,
}, },
{ {
DeviceID: device4, DeviceID: device4,
Name: "node two", Name: "node two",
Addresses: []string{"b"}, Addresses: []string{"tcp://b"},
Compression: protocol.CompressMetadata, Compression: protocol.CompressMetadata,
}, },
} }
@@ -142,12 +142,13 @@ func TestNoListenAddress(t *testing.T) {
func TestOverriddenValues(t *testing.T) { func TestOverriddenValues(t *testing.T) {
expected := OptionsConfiguration{ expected := OptionsConfiguration{
ListenAddress: []string{":23000"}, ListenAddress: []string{"tcp://:23000"},
GlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"}, GlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"},
GlobalAnnEnabled: false, GlobalAnnEnabled: false,
LocalAnnEnabled: false, LocalAnnEnabled: false,
LocalAnnPort: 42123, LocalAnnPort: 42123,
LocalAnnMCAddr: "quux:3232", LocalAnnMCAddr: "quux:3232",
RelayServers: []string{"relay://123.123.123.123:1234", "relay://125.125.125.125:1255"},
MaxSendKbps: 1234, MaxSendKbps: 1234,
MaxRecvKbps: 2341, MaxRecvKbps: 2341,
ReconnectIntervalS: 6000, ReconnectIntervalS: 6000,
@@ -255,15 +256,15 @@ func TestDeviceAddressesStatic(t *testing.T) {
expected := map[protocol.DeviceID]DeviceConfiguration{ expected := map[protocol.DeviceID]DeviceConfiguration{
device1: { device1: {
DeviceID: device1, DeviceID: device1,
Addresses: []string{"192.0.2.1", "192.0.2.2"}, Addresses: []string{"tcp://192.0.2.1", "tcp://192.0.2.2"},
}, },
device2: { device2: {
DeviceID: device2, DeviceID: device2,
Addresses: []string{"192.0.2.3:6070", "[2001:db8::42]:4242"}, Addresses: []string{"tcp://192.0.2.3:6070", "tcp://[2001:db8::42]:4242"},
}, },
device3: { device3: {
DeviceID: device3, DeviceID: device3,
Addresses: []string{"[2001:db8::44]:4444", "192.0.2.4:6090"}, Addresses: []string{"tcp://[2001:db8::44]:4444", "tcp://192.0.2.4:6090"},
}, },
device4: { device4: {
DeviceID: device4, DeviceID: device4,
@@ -330,12 +331,12 @@ func TestIssue1750(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if cfg.Options().ListenAddress[0] != ":23000" { if cfg.Options().ListenAddress[0] != "tcp://:23000" {
t.Errorf("%q != %q", cfg.Options().ListenAddress[0], ":23000") t.Errorf("%q != %q", cfg.Options().ListenAddress[0], "tcp://:23000")
} }
if cfg.Options().ListenAddress[1] != ":23001" { if cfg.Options().ListenAddress[1] != "tcp://:23001" {
t.Errorf("%q != %q", cfg.Options().ListenAddress[1], ":23001") t.Errorf("%q != %q", cfg.Options().ListenAddress[1], "tcp://:23001")
} }
if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" { if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" {

View File

@@ -7,6 +7,8 @@
<localAnnounceEnabled>false</localAnnounceEnabled> <localAnnounceEnabled>false</localAnnounceEnabled>
<localAnnouncePort>42123</localAnnouncePort> <localAnnouncePort>42123</localAnnouncePort>
<localAnnounceMCAddr>quux:3232</localAnnounceMCAddr> <localAnnounceMCAddr>quux:3232</localAnnounceMCAddr>
<relayServer>relay://123.123.123.123:1234</relayServer>
<relayServer>relay://125.125.125.125:1255</relayServer>
<parallelRequests>32</parallelRequests> <parallelRequests>32</parallelRequests>
<maxSendKbps>1234</maxSendKbps> <maxSendKbps>1234</maxSendKbps>
<maxRecvKbps>2341</maxRecvKbps> <maxRecvKbps>2341</maxRecvKbps>

13
lib/config/testdata/v12.xml vendored Normal file
View File

@@ -0,0 +1,13 @@
<configuration version="12">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFreePct>1</minDiskFreePct>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>tcp://b</address>
</device>
</configuration>

View File

@@ -43,7 +43,7 @@ func New(addr string, pkt *Announce) (Client, error) {
} }
type Client interface { type Client interface {
Lookup(device protocol.DeviceID) []string Lookup(device protocol.DeviceID) (Announce, error)
StatusOK() bool StatusOK() bool
Address() string Address() string
Stop() Stop()

View File

@@ -37,10 +37,8 @@ func TestUDP4Success(t *testing.T) {
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{ This: Device{
device[:], device[:],
[]Address{{ []string{"tcp://123.123.123.123:1234"},
IP: net.IPv4(123, 123, 123, 123), nil,
Port: 1234,
}},
}, },
} }
@@ -101,7 +99,12 @@ func TestUDP4Success(t *testing.T) {
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
wg.Add(1) wg.Add(1)
go func() { go func() {
addrs = client.Lookup(device) pkt, err := client.Lookup(device)
if err == nil {
for _, addr := range pkt.This.Addresses {
addrs = append(addrs, addr)
}
}
wg.Done() wg.Done()
}() }()
@@ -117,7 +120,7 @@ func TestUDP4Success(t *testing.T) {
// Wait for the lookup to arrive, verify that the number of answers is correct // Wait for the lookup to arrive, verify that the number of answers is correct
wg.Wait() wg.Wait()
if len(addrs) != 1 || addrs[0] != "123.123.123.123:1234" { if len(addrs) != 1 || addrs[0] != "tcp://123.123.123.123:1234" {
t.Fatal("Wrong number of answers") t.Fatal("Wrong number of answers")
} }
@@ -138,10 +141,8 @@ func TestUDP4Failure(t *testing.T) {
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{ This: Device{
device[:], device[:],
[]Address{{ []string{"tcp://123.123.123.123:1234"},
IP: net.IPv4(123, 123, 123, 123), nil,
Port: 1234,
}},
}, },
} }
@@ -197,7 +198,12 @@ func TestUDP4Failure(t *testing.T) {
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
wg.Add(1) wg.Add(1)
go func() { go func() {
addrs = client.Lookup(device) pkt, err := client.Lookup(device)
if err == nil {
for _, addr := range pkt.This.Addresses {
addrs = append(addrs, addr)
}
}
wg.Done() wg.Done()
}() }()

View File

@@ -137,11 +137,13 @@ func (d *UDPClient) broadcast(pkt []byte) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
res := d.Lookup(d.id) pkt, err := d.Lookup(d.id)
if debug { if err != nil && debug {
l.Debugf("discover %s: broadcast: Self-lookup returned: %v", d.url, res) l.Debugf("discover %s: broadcast: Self-lookup failed: %v", d.url, err)
} else if debug {
l.Debugf("discover %s: broadcast: Self-lookup returned: %v", d.url, pkt.This.Addresses)
} }
ok = len(res) > 0 ok = len(pkt.This.Addresses) > 0
} }
d.mut.Lock() d.mut.Lock()
@@ -157,13 +159,13 @@ func (d *UDPClient) broadcast(pkt []byte) {
} }
} }
func (d *UDPClient) Lookup(device protocol.DeviceID) []string { func (d *UDPClient) Lookup(device protocol.DeviceID) (Announce, error) {
extIP, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host) extIP, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host)
if err != nil { if err != nil {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP) conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP)
@@ -171,7 +173,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
defer conn.Close() defer conn.Close()
@@ -180,7 +182,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
buf := Query{QueryMagic, device[:]}.MustMarshalXDR() buf := Query{QueryMagic, device[:]}.MustMarshalXDR()
@@ -189,7 +191,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
buf = make([]byte, 2048) buf = make([]byte, 2048)
@@ -197,12 +199,12 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if err != nil { if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() { if err, ok := err.(net.Error); ok && err.Timeout() {
// Expected if the server doesn't know about requested device ID // Expected if the server doesn't know about requested device ID
return nil return Announce{}, err
} }
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
var pkt Announce var pkt Announce
@@ -211,18 +213,13 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s\n%s", d.url, device, err, hex.Dump(buf[:n])) l.Debugf("discover %s: Lookup(%s): %s\n%s", d.url, device, err, hex.Dump(buf[:n]))
} }
return nil return Announce{}, err
} }
var addrs []string
for _, a := range pkt.This.Addresses {
deviceAddr := net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port)))
addrs = append(addrs, deviceAddr)
}
if debug { if debug {
l.Debugf("discover %s: Lookup(%s) result: %v", d.url, device, addrs) l.Debugf("discover %s: Lookup(%s) result: %v relays: %v", d.url, device, pkt.This.Addresses, pkt.This.Relays)
} }
return addrs return pkt, nil
} }
func (d *UDPClient) Stop() { func (d *UDPClient) Stop() {

View File

@@ -10,21 +10,25 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"net/url"
"runtime" "runtime"
"strconv" "sort"
"time" "time"
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/beacon" "github.com/syncthing/syncthing/lib/beacon"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
) )
type Discoverer struct { type Discoverer struct {
myID protocol.DeviceID myID protocol.DeviceID
listenAddrs []string listenAddrs []string
relays []Relay
localBcastIntv time.Duration localBcastIntv time.Duration
localBcastStart time.Time localBcastStart time.Time
cacheLifetime time.Duration cacheLifetime time.Duration
@@ -34,9 +38,10 @@ type Discoverer struct {
localBcastTick <-chan time.Time localBcastTick <-chan time.Time
forcedBcastTick chan time.Time forcedBcastTick chan time.Time
registryLock sync.RWMutex registryLock sync.RWMutex
registry map[protocol.DeviceID][]CacheEntry addressRegistry map[protocol.DeviceID][]CacheEntry
lastLookup map[protocol.DeviceID]time.Time relayRegistry map[protocol.DeviceID][]CacheEntry
lastLookup map[protocol.DeviceID]time.Time
clients []Client clients []Client
mut sync.RWMutex mut sync.RWMutex
@@ -51,17 +56,19 @@ var (
ErrIncorrectMagic = errors.New("incorrect magic number") ErrIncorrectMagic = errors.New("incorrect magic number")
) )
func NewDiscoverer(id protocol.DeviceID, addresses []string) *Discoverer { func NewDiscoverer(id protocol.DeviceID, addresses []string, relayAdresses []string) *Discoverer {
return &Discoverer{ return &Discoverer{
myID: id, myID: id,
listenAddrs: addresses, listenAddrs: addresses,
localBcastIntv: 30 * time.Second, relays: measureLatency(relayAdresses),
cacheLifetime: 5 * time.Minute, localBcastIntv: 30 * time.Second,
negCacheCutoff: 3 * time.Minute, cacheLifetime: 5 * time.Minute,
registry: make(map[protocol.DeviceID][]CacheEntry), negCacheCutoff: 3 * time.Minute,
lastLookup: make(map[protocol.DeviceID]time.Time), addressRegistry: make(map[protocol.DeviceID][]CacheEntry),
registryLock: sync.NewRWMutex(), relayRegistry: make(map[protocol.DeviceID][]CacheEntry),
mut: sync.NewRWMutex(), lastLookup: make(map[protocol.DeviceID]time.Time),
registryLock: sync.NewRWMutex(),
mut: sync.NewRWMutex(),
} }
} }
@@ -184,75 +191,108 @@ func (d *Discoverer) ExtAnnounceOK() map[string]bool {
return ret return ret
} }
func (d *Discoverer) Lookup(device protocol.DeviceID) []string { // Lookup returns a list of addresses the device is available at, as well as
// a list of relays the device is supposed to be available on sorted by the
// sum of latencies between this device, and the device in question.
func (d *Discoverer) Lookup(device protocol.DeviceID) ([]string, []string) {
d.registryLock.RLock() d.registryLock.RLock()
cached := d.filterCached(d.registry[device]) cachedAddresses := d.filterCached(d.addressRegistry[device])
cachedRelays := d.filterCached(d.relayRegistry[device])
lastLookup := d.lastLookup[device] lastLookup := d.lastLookup[device]
d.registryLock.RUnlock() d.registryLock.RUnlock()
d.mut.RLock() d.mut.RLock()
defer d.mut.RUnlock() defer d.mut.RUnlock()
if len(cached) > 0 { relays := make([]string, len(cachedRelays))
for i := range cachedRelays {
relays[i] = cachedRelays[i].Address
}
if len(cachedAddresses) > 0 {
// There are cached address entries. // There are cached address entries.
addrs := make([]string, len(cached)) addrs := make([]string, len(cachedAddresses))
for i := range cached { for i := range cachedAddresses {
addrs[i] = cached[i].Address addrs[i] = cachedAddresses[i].Address
} }
return addrs return addrs, relays
} }
if time.Since(lastLookup) < d.negCacheCutoff { if time.Since(lastLookup) < d.negCacheCutoff {
// We have recently tried to lookup this address and failed. Lets // We have recently tried to lookup this address and failed. Lets
// chill for a while. // chill for a while.
return nil return nil, relays
} }
if len(d.clients) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv { if len(d.clients) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv {
// Only perform external lookups if we have at least one external // Only perform external lookups if we have at least one external
// server client and one local announcement interval has passed. This is // server client and one local announcement interval has passed. This is
// to avoid finding local peers on their remote address at startup. // to avoid finding local peers on their remote address at startup.
results := make(chan []string, len(d.clients)) results := make(chan Announce, len(d.clients))
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
for _, client := range d.clients { for _, client := range d.clients {
wg.Add(1) wg.Add(1)
go func(c Client) { go func(c Client) {
defer wg.Done() defer wg.Done()
results <- c.Lookup(device) ann, err := c.Lookup(device)
if err == nil {
results <- ann
}
}(client) }(client)
} }
wg.Wait() wg.Wait()
close(results) close(results)
cached := []CacheEntry{} cachedAddresses := []CacheEntry{}
seen := make(map[string]struct{}) availableRelays := []Relay{}
seenAddresses := make(map[string]struct{})
seenRelays := make(map[string]struct{})
now := time.Now() now := time.Now()
var addrs []string var addrs []string
for result := range results { for result := range results {
for _, addr := range result { for _, addr := range result.This.Addresses {
_, ok := seen[addr] _, ok := seenAddresses[addr]
if !ok { if !ok {
cached = append(cached, CacheEntry{ cachedAddresses = append(cachedAddresses, CacheEntry{
Address: addr, Address: addr,
Seen: now, Seen: now,
}) })
seen[addr] = struct{}{} seenAddresses[addr] = struct{}{}
addrs = append(addrs, addr) addrs = append(addrs, addr)
} }
} }
for _, relay := range result.This.Relays {
_, ok := seenRelays[relay.Address]
if !ok {
availableRelays = append(availableRelays, relay)
seenRelays[relay.Address] = struct{}{}
}
}
}
relays = addressesSortedByLatency(availableRelays)
cachedRelays := make([]CacheEntry, len(relays))
for i := range relays {
cachedRelays[i] = CacheEntry{
Address: relays[i],
Seen: now,
}
} }
d.registryLock.Lock() d.registryLock.Lock()
d.registry[device] = cached d.addressRegistry[device] = cachedAddresses
d.relayRegistry[device] = cachedRelays
d.lastLookup[device] = time.Now() d.lastLookup[device] = time.Now()
d.registryLock.Unlock() d.registryLock.Unlock()
return addrs return addrs, relays
} }
return nil return nil, relays
} }
func (d *Discoverer) Hint(device string, addrs []string) { func (d *Discoverer) Hint(device string, addrs []string) {
@@ -267,8 +307,8 @@ func (d *Discoverer) Hint(device string, addrs []string) {
func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry { func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry {
d.registryLock.RLock() d.registryLock.RLock()
devices := make(map[protocol.DeviceID][]CacheEntry, len(d.registry)) devices := make(map[protocol.DeviceID][]CacheEntry, len(d.addressRegistry))
for device, addrs := range d.registry { for device, addrs := range d.addressRegistry {
addrsCopy := make([]CacheEntry, len(addrs)) addrsCopy := make([]CacheEntry, len(addrs))
copy(addrsCopy, addrs) copy(addrsCopy, addrs)
devices[device] = addrsCopy devices[device] = addrsCopy
@@ -278,30 +318,38 @@ func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry {
} }
func (d *Discoverer) announcementPkt() *Announce { func (d *Discoverer) announcementPkt() *Announce {
var addrs []Address var addrs []string
if d.extPort != 0 { if d.extPort != 0 {
addrs = []Address{{Port: d.extPort}} addrs = []string{fmt.Sprintf("tcp://:%d", d.extPort)}
} else { } else {
for _, astr := range d.listenAddrs { for _, aurl := range d.listenAddrs {
addr, err := net.ResolveTCPAddr("tcp", astr) uri, err := url.Parse(aurl)
if err != nil { if err != nil {
l.Warnln("discover: %v: not announcing %s", err, astr) if debug {
l.Debugf("discovery: failed to parse listen address %s: %s", aurl, err)
}
continue
}
addr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
l.Warnln("discover: %v: not announcing %s", err, aurl)
continue continue
} else if debug { } else if debug {
l.Debugf("discover: resolved %s as %#v", astr, addr) l.Debugf("discover: resolved %s as %#v", aurl, uri.Host)
} }
if len(addr.IP) == 0 || addr.IP.IsUnspecified() { if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
addrs = append(addrs, Address{Port: uint16(addr.Port)}) uri.Host = fmt.Sprintf(":%d", addr.Port)
} else if bs := addr.IP.To4(); bs != nil { } else if bs := addr.IP.To4(); bs != nil {
addrs = append(addrs, Address{IP: bs, Port: uint16(addr.Port)}) uri.Host = fmt.Sprintf("%s:%d", bs.String(), addr.Port)
} else if bs := addr.IP.To16(); bs != nil { } else if bs := addr.IP.To16(); bs != nil {
addrs = append(addrs, Address{IP: bs, Port: uint16(addr.Port)}) uri.Host = fmt.Sprintf("[%s]:%d", bs.String(), addr.Port)
} }
addrs = append(addrs, uri.String())
} }
} }
return &Announce{ return &Announce{
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{d.myID[:], addrs}, This: Device{d.myID[:], addrs, d.relays},
} }
} }
@@ -310,7 +358,7 @@ func (d *Discoverer) sendLocalAnnouncements() {
var pkt = Announce{ var pkt = Announce{
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{d.myID[:], addrs}, This: Device{d.myID[:], addrs, d.relays},
} }
msg := pkt.MustMarshalXDR() msg := pkt.MustMarshalXDR()
@@ -363,19 +411,32 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool {
d.registryLock.Lock() d.registryLock.Lock()
defer d.registryLock.Unlock() defer d.registryLock.Unlock()
current := d.filterCached(d.registry[id]) current := d.filterCached(d.addressRegistry[id])
orig := current orig := current
for _, a := range device.Addresses { for _, deviceAddr := range device.Addresses {
var deviceAddr string uri, err := url.Parse(deviceAddr)
if len(a.IP) > 0 { if err != nil {
deviceAddr = net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port))) if debug {
} else if addr != nil { l.Debugf("discover: Failed to parse address %s: %s", deviceAddr, err)
ua := addr.(*net.UDPAddr) }
ua.Port = int(a.Port) continue
deviceAddr = ua.String()
} }
host, port, err := net.SplitHostPort(uri.Host)
if err != nil {
if debug {
l.Debugf("discover: Failed to split address host %s: %s", deviceAddr, err)
}
continue
}
if host == "" {
uri.Host = net.JoinHostPort(addr.(*net.UDPAddr).IP.String(), port)
deviceAddr = uri.String()
}
for i := range current { for i := range current {
if current[i].Address == deviceAddr { if current[i].Address == deviceAddr {
current[i].Seen = time.Now() current[i].Seen = time.Now()
@@ -393,7 +454,7 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool {
l.Debugf("discover: Caching %s addresses: %v", id, current) l.Debugf("discover: Caching %s addresses: %v", id, current)
} }
d.registry[id] = current d.addressRegistry[id] = current
if len(current) > len(orig) { if len(current) > len(orig) {
addrs := make([]string, len(current)) addrs := make([]string, len(current))
@@ -413,7 +474,7 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
for i := 0; i < len(c); { for i := 0; i < len(c); {
if ago := time.Since(c[i].Seen); ago > d.cacheLifetime { if ago := time.Since(c[i].Seen); ago > d.cacheLifetime {
if debug { if debug {
l.Debugf("discover: Removing cached address %s - seen %v ago", c[i].Address, ago) l.Debugf("discover: Removing cached entry %s - seen %v ago", c[i].Address, ago)
} }
c[i] = c[len(c)-1] c[i] = c[len(c)-1]
c = c[:len(c)-1] c = c[:len(c)-1]
@@ -424,30 +485,99 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
return c return c
} }
func addrToAddr(addr *net.TCPAddr) Address { func addrToAddr(addr *net.TCPAddr) string {
if len(addr.IP) == 0 || addr.IP.IsUnspecified() { if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
return Address{Port: uint16(addr.Port)} return fmt.Sprintf(":%d", addr.Port)
} else if bs := addr.IP.To4(); bs != nil { } else if bs := addr.IP.To4(); bs != nil {
return Address{IP: bs, Port: uint16(addr.Port)} return fmt.Sprintf("%s:%d", bs.String(), addr.Port)
} else if bs := addr.IP.To16(); bs != nil { } else if bs := addr.IP.To16(); bs != nil {
return Address{IP: bs, Port: uint16(addr.Port)} return fmt.Sprintf("[%s]:%d", bs.String(), addr.Port)
} }
return Address{} return ""
} }
func resolveAddrs(addrs []string) []Address { func resolveAddrs(addrs []string) []string {
var raddrs []Address var raddrs []string
for _, addrStr := range addrs { for _, addrStr := range addrs {
addrRes, err := net.ResolveTCPAddr("tcp", addrStr) uri, err := url.Parse(addrStr)
if err != nil {
continue
}
addrRes, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil { if err != nil {
continue continue
} }
addr := addrToAddr(addrRes) addr := addrToAddr(addrRes)
if len(addr.IP) > 0 { if len(addr) > 0 {
raddrs = append(raddrs, addr) uri.Host = addr
} else { raddrs = append(raddrs, uri.String())
raddrs = append(raddrs, Address{Port: addr.Port})
} }
} }
return raddrs return raddrs
} }
func measureLatency(relayAdresses []string) []Relay {
relays := make([]Relay, 0, len(relayAdresses))
for i, addr := range relayAdresses {
relay := Relay{
Address: addr,
Latency: int32(time.Hour / time.Millisecond),
}
relays = append(relays, relay)
if latency, err := getLatencyForURL(addr); err == nil {
if debug {
l.Debugf("Relay %s latency %s", addr, latency)
}
relays[i].Latency = int32(latency / time.Millisecond)
} else {
l.Debugf("Failed to get relay %s latency %s", addr, err)
}
}
return relays
}
// addressesSortedByLatency adds local latency to the relay, and sorts them
// by sum latency, and returns the addresses.
func addressesSortedByLatency(input []Relay) []string {
relays := make([]Relay, len(input))
copy(relays, input)
for i, relay := range relays {
if latency, err := getLatencyForURL(relay.Address); err == nil {
relays[i].Latency += int32(latency / time.Millisecond)
} else {
relays[i].Latency += int32(time.Hour / time.Millisecond)
}
}
sort.Sort(relayList(relays))
addresses := make([]string, 0, len(relays))
for _, relay := range relays {
addresses = append(addresses, relay.Address)
}
return addresses
}
func getLatencyForURL(addr string) (time.Duration, error) {
uri, err := url.Parse(addr)
if err != nil {
return 0, err
}
return osutil.TCPPing(uri.Host)
}
type relayList []Relay
func (l relayList) Len() int {
return len(l)
}
func (l relayList) Less(a, b int) bool {
return l[a].Latency < l[b].Latency
}
func (l relayList) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}

View File

@@ -18,15 +18,15 @@ import (
type DummyClient struct { type DummyClient struct {
url *url.URL url *url.URL
lookups []protocol.DeviceID lookups []protocol.DeviceID
lookupRet []string lookupRet Announce
stops int stops int
statusRet bool statusRet bool
statusChecks int statusChecks int
} }
func (c *DummyClient) Lookup(device protocol.DeviceID) []string { func (c *DummyClient) Lookup(device protocol.DeviceID) (Announce, error) {
c.lookups = append(c.lookups, device) c.lookups = append(c.lookups, device)
return c.lookupRet return c.lookupRet, nil
} }
func (c *DummyClient) StatusOK() bool { func (c *DummyClient) StatusOK() bool {
@@ -45,17 +45,41 @@ func (c *DummyClient) Address() string {
func TestGlobalDiscovery(t *testing.T) { func TestGlobalDiscovery(t *testing.T) {
c1 := &DummyClient{ c1 := &DummyClient{
statusRet: false, statusRet: false,
lookupRet: []string{"test.com:1234"}, lookupRet: Announce{
Magic: AnnouncementMagic,
This: Device{
ID: protocol.LocalDeviceID[:],
Addresses: []string{"test.com:1234"},
Relays: nil,
},
Extra: nil,
},
} }
c2 := &DummyClient{ c2 := &DummyClient{
statusRet: true, statusRet: true,
lookupRet: []string{}, lookupRet: Announce{
Magic: AnnouncementMagic,
This: Device{
ID: protocol.LocalDeviceID[:],
Addresses: nil,
Relays: nil,
},
Extra: nil,
},
} }
c3 := &DummyClient{ c3 := &DummyClient{
statusRet: true, statusRet: true,
lookupRet: []string{"best.com:2345"}, lookupRet: Announce{
Magic: AnnouncementMagic,
This: Device{
ID: protocol.LocalDeviceID[:],
Addresses: []string{"best.com:2345"},
Relays: nil,
},
Extra: nil,
},
} }
clients := []*DummyClient{c1, c2} clients := []*DummyClient{c1, c2}
@@ -72,7 +96,7 @@ func TestGlobalDiscovery(t *testing.T) {
return c3, nil return c3, nil
}) })
d := NewDiscoverer(device, []string{}) d := NewDiscoverer(device, []string{}, nil)
d.localBcastStart = time.Time{} d.localBcastStart = time.Time{}
servers := []string{ servers := []string{
"test1://123.123.123.123:1234", "test1://123.123.123.123:1234",
@@ -93,7 +117,7 @@ func TestGlobalDiscovery(t *testing.T) {
} }
} }
addrs := d.Lookup(device) addrs, _ := d.Lookup(device)
if len(addrs) != 2 { if len(addrs) != 2 {
t.Fatal("Wrong number of addresses", addrs) t.Fatal("Wrong number of addresses", addrs)
} }
@@ -117,7 +141,7 @@ func TestGlobalDiscovery(t *testing.T) {
} }
} }
addrs = d.Lookup(device) addrs, _ = d.Lookup(device)
if len(addrs) != 2 { if len(addrs) != 2 {
t.Fatal("Wrong number of addresses", addrs) t.Fatal("Wrong number of addresses", addrs)
} }

View File

@@ -10,8 +10,8 @@
package discover package discover
const ( const (
AnnouncementMagic = 0x9D79BC39 AnnouncementMagic = 0x9D79BC40
QueryMagic = 0x2CA856F5 QueryMagic = 0x2CA856F6
) )
type Query struct { type Query struct {
@@ -25,12 +25,13 @@ type Announce struct {
Extra []Device // max:16 Extra []Device // max:16
} }
type Device struct { type Relay struct {
ID []byte // max:32 Address string // max:256
Addresses []Address // max:16 Latency int32
} }
type Address struct { type Device struct {
IP []byte // max:16 ID []byte // max:32
Port uint16 Addresses []string // max:16
Relays []Relay // max:16
} }

View File

@@ -187,6 +187,80 @@ func (o *Announce) DecodeXDRFrom(xr *xdr.Reader) error {
/* /*
Relay Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Address (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Latency |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Relay {
string Address<256>;
int Latency;
}
*/
func (o Relay) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Relay) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Relay) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Relay) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Relay) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.Address); l > 256 {
return xw.Tot(), xdr.ElementSizeExceeded("Address", l, 256)
}
xw.WriteString(o.Address)
xw.WriteUint32(uint32(o.Latency))
return xw.Tot(), xw.Error()
}
func (o *Relay) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
o.Address = xr.ReadStringMax(256)
o.Latency = int32(xr.ReadUint32())
return xr.Error()
}
/*
Device Structure: Device Structure:
0 1 2 3 0 1 2 3
@@ -200,15 +274,24 @@ Device Structure:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Addresses | | Number of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ / / /
\ Zero or more Address Structures \ \ Addresses (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Relays |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Relay Structures \
/ / / /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Device { struct Device {
opaque ID<32>; opaque ID<32>;
Address Addresses<16>; string Addresses<16>;
Relay Relays<16>;
} }
*/ */
@@ -247,7 +330,14 @@ func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
} }
xw.WriteUint32(uint32(len(o.Addresses))) xw.WriteUint32(uint32(len(o.Addresses)))
for i := range o.Addresses { for i := range o.Addresses {
_, err := o.Addresses[i].EncodeXDRInto(xw) xw.WriteString(o.Addresses[i])
}
if l := len(o.Relays); l > 16 {
return xw.Tot(), xdr.ElementSizeExceeded("Relays", l, 16)
}
xw.WriteUint32(uint32(len(o.Relays)))
for i := range o.Relays {
_, err := o.Relays[i].EncodeXDRInto(xw)
if err != nil { if err != nil {
return xw.Tot(), err return xw.Tot(), err
} }
@@ -275,83 +365,20 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
if _AddressesSize > 16 { if _AddressesSize > 16 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16) return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
} }
o.Addresses = make([]Address, _AddressesSize) o.Addresses = make([]string, _AddressesSize)
for i := range o.Addresses { for i := range o.Addresses {
(&o.Addresses[i]).DecodeXDRFrom(xr) o.Addresses[i] = xr.ReadString()
}
_RelaysSize := int(xr.ReadUint32())
if _RelaysSize < 0 {
return xdr.ElementSizeExceeded("Relays", _RelaysSize, 16)
}
if _RelaysSize > 16 {
return xdr.ElementSizeExceeded("Relays", _RelaysSize, 16)
}
o.Relays = make([]Relay, _RelaysSize)
for i := range o.Relays {
(&o.Relays[i]).DecodeXDRFrom(xr)
} }
return xr.Error() return xr.Error()
} }
/*
Address Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of IP |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ IP (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0000 | Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Address {
opaque IP<16>;
unsigned int Port;
}
*/
func (o Address) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Address) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Address) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Address) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Address) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.IP); l > 16 {
return xw.Tot(), xdr.ElementSizeExceeded("IP", l, 16)
}
xw.WriteBytes(o.IP)
xw.WriteUint16(o.Port)
return xw.Tot(), xw.Error()
}
func (o *Address) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Address) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Address) DecodeXDRFrom(xr *xdr.Reader) error {
o.IP = xr.ReadBytesMax(16)
o.Port = xr.ReadUint16()
return xr.Error()
}

27
lib/osutil/ping.go Normal file
View File

@@ -0,0 +1,27 @@
// 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,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package osutil
import (
"net"
"time"
)
// TCPPing returns the duration required to establish a TCP connection
// to the given host. ICMP packets require root priviledges, hence why we use
// tcp.
func TCPPing(address string) (time.Duration, error) {
dialer := net.Dialer{
Deadline: time.Now().Add(time.Second),
}
start := time.Now()
conn, err := dialer.Dial("tcp", address)
if conn != nil {
conn.Close()
}
return time.Since(start), err
}