Browse Source

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
Audrius Butkevicius 10 years ago
parent
commit
34c04babbe

+ 9 - 5
cmd/stfinddevice/main.go

@@ -21,11 +21,11 @@ func main() {
 
 	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()
 
 	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)
 	}
 
@@ -35,9 +35,13 @@ func main() {
 		os.Exit(1)
 	}
 
-	discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil)
+	discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil, nil)
 	discoverer.StartGlobal([]string{server}, 1)
-	for _, addr := range discoverer.Lookup(id) {
-		log.Println(addr)
+	addresses, relays := discoverer.Lookup(id)
+	for _, addr := range addresses {
+		log.Println("address:", addr)
+	}
+	for _, addr := range relays {
+		log.Println("relay:", addr)
 	}
 }

+ 61 - 94
cmd/syncthing/connections.go

@@ -11,7 +11,7 @@ import (
 	"fmt"
 	"io"
 	"net"
-	"strings"
+	"net/url"
 	"time"
 
 	"github.com/syncthing/protocol"
@@ -21,6 +21,14 @@ import (
 	"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
 // devices. Successful connections are handed to the model.
 type connectionSvc struct {
@@ -51,9 +59,9 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.
 	//
 	//                +-----------------+
 	//    Incoming    | +---------------+-+      +-----------------+
-	//   Connections  | |                 |      |                 |   Outgoing
-	// -------------->| |   svc.listen    |      |                 |  Connections
-	//                | |  (1 per listen  |      |   svc.connect   |-------------->
+	//   Connections  | |                 |      |                 |
+	// -------------->| |    listener     |      |                 |  Outgoing connections via dialers
+	//                | |  (1 per listen  |      |   svc.connect   |----------------------------------->
 	//                | |    address)     |      |                 |
 	//                +-+                 |      |                 |
 	//                  +-----------------+      +-----------------+
@@ -79,11 +87,25 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.
 
 	svc.Add(serviceFunc(svc.connect))
 	for _, addr := range svc.cfg.Options().ListenAddress {
-		addr := addr
-		listener := serviceFunc(func() {
-			svc.listen(addr)
-		})
-		svc.Add(listener)
+		uri, err := url.Parse(addr)
+		if err != nil {
+			l.Infoln("Failed to parse listen address:", addr, err)
+			continue
+		}
+
+		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))
 
@@ -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() {
 	delay := time.Second
 	for {
@@ -254,7 +236,7 @@ func (s *connectionSvc) connect() {
 			for _, addr := range deviceCfg.Addresses {
 				if addr == "dynamic" {
 					if discoverer != nil {
-						t := discoverer.Lookup(deviceID)
+						t, _ := discoverer.Lookup(deviceID)
 						if len(t) == 0 {
 							continue
 						}
@@ -266,45 +248,30 @@ func (s *connectionSvc) connect() {
 			}
 
 			for _, addr := range addrs {
-				host, port, err := net.SplitHostPort(addr)
-				if err != nil && strings.HasPrefix(err.Error(), "missing port") {
-					// addr is on the form "1.2.3.4"
-					addr = net.JoinHostPort(addr, "22000")
-				} else if err == nil && port == "" {
-					// addr is on the form "1.2.3.4:"
-					addr = net.JoinHostPort(host, "22000")
-				}
-				if debugNet {
-					l.Debugln("dial", deviceCfg.DeviceID, addr)
-				}
-
-				raddr, err := net.ResolveTCPAddr("tcp", addr)
+				uri, err := url.Parse(addr)
 				if err != nil {
-					if debugNet {
-						l.Debugln(err)
-					}
+					l.Infoln("Failed to parse connection url:", addr, err)
 					continue
 				}
 
-				conn, err := net.DialTCP("tcp", nil, raddr)
-				if err != nil {
-					if debugNet {
-						l.Debugln(err)
-					}
+				dialer, ok := dialers[uri.Scheme]
+				if !ok {
+					l.Infoln("Unknown address schema", uri.String())
 					continue
 				}
 
-				s.setTCPOptions(conn)
-
-				tc := tls.Client(conn, s.tlsCfg)
-				err = tc.Handshake()
+				if debugNet {
+					l.Debugln("dial", deviceCfg.DeviceID, uri.String())
+				}
+				conn, err := dialer(uri, s.tlsCfg)
 				if err != nil {
-					l.Infoln("TLS handshake:", err)
-					tc.Close()
+					if debugNet {
+						l.Debugln("dial failed", deviceCfg.DeviceID, uri.String(), err)
+					}
 					continue
 				}
 
-				s.conns <- tc
+				s.conns <- conn
 				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 {
 	if s.cfg.Options().LimitBandwidthInLan {
 		return true
@@ -370,3 +321,19 @@ func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool
 
 	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)
+	}
+}

+ 95 - 0
cmd/syncthing/connections_tcp.go

@@ -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
+	}
+}

+ 7 - 2
cmd/syncthing/main.go

@@ -658,7 +658,12 @@ func syncthingMain() {
 
 	// 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 {
 		l.Fatalln("Bad listen address:", err)
 	}
@@ -902,7 +907,7 @@ func shutdown() {
 
 func discovery(extPort int) *discover.Discoverer {
 	opts := cfg.Options()
-	disc := discover.NewDiscoverer(myID, opts.ListenAddress)
+	disc := discover.NewDiscoverer(myID, opts.ListenAddress, opts.RelayServers)
 
 	if opts.LocalAnnEnabled {
 		l.Infoln("Starting local discovery announcements")

+ 1 - 1
gui/syncthing/device/editDeviceModalView.html

@@ -32,7 +32,7 @@
           <div class="form-group">
             <label translate for="addresses">Addresses</label>
             <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 class="form-group">
             <label translate>Compression</label>

File diff suppressed because it is too large
+ 1 - 1
lib/auto/gui.files.go


+ 43 - 6
lib/config/config.go

@@ -9,6 +9,7 @@ package config
 
 import (
 	"encoding/xml"
+	"fmt"
 	"io"
 	"math/rand"
 	"os"
@@ -26,7 +27,7 @@ import (
 
 const (
 	OldestHandledVersion = 5
-	CurrentVersion       = 11
+	CurrentVersion       = 12
 	MaxRescanIntervalS   = 365 * 24 * 60 * 60
 )
 
@@ -212,12 +213,13 @@ type FolderDeviceConfiguration struct {
 }
 
 type OptionsConfiguration struct {
-	ListenAddress           []string `xml:"listenAddress" json:"listenAddress" default:"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"`
+	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:22027, udp6://announce-v6.syncthing.net:22027"`
 	GlobalAnnEnabled        bool     `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
 	LocalAnnEnabled         bool     `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
 	LocalAnnPort            int      `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"`
 	LocalAnnMCAddr          string   `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
+	RelayServers            []string `xml:"relayServer" json:"relayServers" default:""`
 	MaxSendKbps             int      `xml:"maxSendKbps" json:"maxSendKbps"`
 	MaxRecvKbps             int      `xml:"maxRecvKbps" json:"maxRecvKbps"`
 	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 {
 		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 {
 		convertV10V11(cfg)
 	}
+	if cfg.Version == 11 {
+		convertV11V12(cfg)
+	}
 
 	// Hash old cleartext passwords
 	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.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
-	cfg.Options.GlobalAnnServers = uniqueStrings(cfg.Options.GlobalAnnServers)
-
 	if cfg.GUI.APIKey == "" {
 		cfg.GUI.APIKey = randomString(32)
 	}
@@ -467,6 +472,38 @@ func convertV10V11(cfg *Configuration) {
 	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) {
 	// Enable auto normalization on existing folders.
 	for i := range cfg.Folders {

+ 13 - 12
lib/config/config_test.go

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

+ 2 - 0
lib/config/testdata/overridenvalues.xml

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

+ 13 - 0
lib/config/testdata/v12.xml

@@ -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>

+ 1 - 1
lib/discover/client.go

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

+ 17 - 11
lib/discover/client_test.go

@@ -37,10 +37,8 @@ func TestUDP4Success(t *testing.T) {
 		Magic: AnnouncementMagic,
 		This: Device{
 			device[:],
-			[]Address{{
-				IP:   net.IPv4(123, 123, 123, 123),
-				Port: 1234,
-			}},
+			[]string{"tcp://123.123.123.123:1234"},
+			nil,
 		},
 	}
 
@@ -101,7 +99,12 @@ func TestUDP4Success(t *testing.T) {
 	wg := sync.NewWaitGroup()
 	wg.Add(1)
 	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()
 	}()
 
@@ -117,7 +120,7 @@ func TestUDP4Success(t *testing.T) {
 	// Wait for the lookup to arrive, verify that the number of answers is correct
 	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")
 	}
 
@@ -138,10 +141,8 @@ func TestUDP4Failure(t *testing.T) {
 		Magic: AnnouncementMagic,
 		This: Device{
 			device[:],
-			[]Address{{
-				IP:   net.IPv4(123, 123, 123, 123),
-				Port: 1234,
-			}},
+			[]string{"tcp://123.123.123.123:1234"},
+			nil,
 		},
 	}
 
@@ -197,7 +198,12 @@ func TestUDP4Failure(t *testing.T) {
 	wg := sync.NewWaitGroup()
 	wg.Add(1)
 	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()
 	}()
 

+ 16 - 19
lib/discover/client_udp.go

@@ -137,11 +137,13 @@ func (d *UDPClient) broadcast(pkt []byte) {
 
 				time.Sleep(1 * time.Second)
 
-				res := d.Lookup(d.id)
-				if debug {
-					l.Debugf("discover %s: broadcast: Self-lookup returned: %v", d.url, res)
+				pkt, err := d.Lookup(d.id)
+				if err != nil && debug {
+					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()
@@ -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)
 	if err != nil {
 		if debug {
 			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)
@@ -171,7 +173,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
 		if debug {
 			l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
 		}
-		return nil
+		return Announce{}, err
 	}
 	defer conn.Close()
 
@@ -180,7 +182,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
 		if debug {
 			l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
 		}
-		return nil
+		return Announce{}, err
 	}
 
 	buf := Query{QueryMagic, device[:]}.MustMarshalXDR()
@@ -189,7 +191,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
 		if debug {
 			l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
 		}
-		return nil
+		return Announce{}, err
 	}
 
 	buf = make([]byte, 2048)
@@ -197,12 +199,12 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
 	if err != nil {
 		if err, ok := err.(net.Error); ok && err.Timeout() {
 			// Expected if the server doesn't know about requested device ID
-			return nil
+			return Announce{}, err
 		}
 		if debug {
 			l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
 		}
-		return nil
+		return Announce{}, err
 	}
 
 	var pkt Announce
@@ -211,18 +213,13 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
 		if debug {
 			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 {
-		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() {

+ 199 - 69
lib/discover/discover.go

@@ -10,21 +10,25 @@ import (
 	"bytes"
 	"encoding/hex"
 	"errors"
+	"fmt"
 	"io"
 	"net"
+	"net/url"
 	"runtime"
-	"strconv"
+	"sort"
 	"time"
 
 	"github.com/syncthing/protocol"
 	"github.com/syncthing/syncthing/lib/beacon"
 	"github.com/syncthing/syncthing/lib/events"
+	"github.com/syncthing/syncthing/lib/osutil"
 	"github.com/syncthing/syncthing/lib/sync"
 )
 
 type Discoverer struct {
 	myID            protocol.DeviceID
 	listenAddrs     []string
+	relays          []Relay
 	localBcastIntv  time.Duration
 	localBcastStart time.Time
 	cacheLifetime   time.Duration
@@ -34,9 +38,10 @@ type Discoverer struct {
 	localBcastTick  <-chan time.Time
 	forcedBcastTick chan time.Time
 
-	registryLock sync.RWMutex
-	registry     map[protocol.DeviceID][]CacheEntry
-	lastLookup   map[protocol.DeviceID]time.Time
+	registryLock    sync.RWMutex
+	addressRegistry map[protocol.DeviceID][]CacheEntry
+	relayRegistry   map[protocol.DeviceID][]CacheEntry
+	lastLookup      map[protocol.DeviceID]time.Time
 
 	clients []Client
 	mut     sync.RWMutex
@@ -51,17 +56,19 @@ var (
 	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{
-		myID:           id,
-		listenAddrs:    addresses,
-		localBcastIntv: 30 * time.Second,
-		cacheLifetime:  5 * time.Minute,
-		negCacheCutoff: 3 * time.Minute,
-		registry:       make(map[protocol.DeviceID][]CacheEntry),
-		lastLookup:     make(map[protocol.DeviceID]time.Time),
-		registryLock:   sync.NewRWMutex(),
-		mut:            sync.NewRWMutex(),
+		myID:            id,
+		listenAddrs:     addresses,
+		relays:          measureLatency(relayAdresses),
+		localBcastIntv:  30 * time.Second,
+		cacheLifetime:   5 * time.Minute,
+		negCacheCutoff:  3 * time.Minute,
+		addressRegistry: make(map[protocol.DeviceID][]CacheEntry),
+		relayRegistry:   make(map[protocol.DeviceID][]CacheEntry),
+		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
 }
 
-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()
-	cached := d.filterCached(d.registry[device])
+	cachedAddresses := d.filterCached(d.addressRegistry[device])
+	cachedRelays := d.filterCached(d.relayRegistry[device])
 	lastLookup := d.lastLookup[device]
 	d.registryLock.RUnlock()
 
 	d.mut.RLock()
 	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.
-		addrs := make([]string, len(cached))
-		for i := range cached {
-			addrs[i] = cached[i].Address
+		addrs := make([]string, len(cachedAddresses))
+		for i := range cachedAddresses {
+			addrs[i] = cachedAddresses[i].Address
 		}
-		return addrs
+		return addrs, relays
 	}
 
 	if time.Since(lastLookup) < d.negCacheCutoff {
 		// We have recently tried to lookup this address and failed. Lets
 		// chill for a while.
-		return nil
+		return nil, relays
 	}
 
 	if len(d.clients) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv {
 		// Only perform external lookups if we have at least one external
 		// server client and one local announcement interval has passed. This is
 		// 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()
 		for _, client := range d.clients {
 			wg.Add(1)
 			go func(c Client) {
 				defer wg.Done()
-				results <- c.Lookup(device)
+				ann, err := c.Lookup(device)
+				if err == nil {
+					results <- ann
+				}
+
 			}(client)
 		}
 
 		wg.Wait()
 		close(results)
 
-		cached := []CacheEntry{}
-		seen := make(map[string]struct{})
+		cachedAddresses := []CacheEntry{}
+		availableRelays := []Relay{}
+		seenAddresses := make(map[string]struct{})
+		seenRelays := make(map[string]struct{})
 		now := time.Now()
 
 		var addrs []string
 		for result := range results {
-			for _, addr := range result {
-				_, ok := seen[addr]
+			for _, addr := range result.This.Addresses {
+				_, ok := seenAddresses[addr]
 				if !ok {
-					cached = append(cached, CacheEntry{
+					cachedAddresses = append(cachedAddresses, CacheEntry{
 						Address: addr,
 						Seen:    now,
 					})
-					seen[addr] = struct{}{}
+					seenAddresses[addr] = struct{}{}
 					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.registry[device] = cached
+		d.addressRegistry[device] = cachedAddresses
+		d.relayRegistry[device] = cachedRelays
 		d.lastLookup[device] = time.Now()
 		d.registryLock.Unlock()
 
-		return addrs
+		return addrs, relays
 	}
 
-	return nil
+	return nil, relays
 }
 
 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 {
 	d.registryLock.RLock()
-	devices := make(map[protocol.DeviceID][]CacheEntry, len(d.registry))
-	for device, addrs := range d.registry {
+	devices := make(map[protocol.DeviceID][]CacheEntry, len(d.addressRegistry))
+	for device, addrs := range d.addressRegistry {
 		addrsCopy := make([]CacheEntry, len(addrs))
 		copy(addrsCopy, addrs)
 		devices[device] = addrsCopy
@@ -278,30 +318,38 @@ func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry {
 }
 
 func (d *Discoverer) announcementPkt() *Announce {
-	var addrs []Address
+	var addrs []string
 	if d.extPort != 0 {
-		addrs = []Address{{Port: d.extPort}}
+		addrs = []string{fmt.Sprintf("tcp://:%d", d.extPort)}
 	} else {
-		for _, astr := range d.listenAddrs {
-			addr, err := net.ResolveTCPAddr("tcp", astr)
+		for _, aurl := range d.listenAddrs {
+			uri, err := url.Parse(aurl)
+			if err != nil {
+				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, astr)
+				l.Warnln("discover: %v: not announcing %s", err, aurl)
 				continue
 			} 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() {
-				addrs = append(addrs, Address{Port: uint16(addr.Port)})
+				uri.Host = fmt.Sprintf(":%d", addr.Port)
 			} 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 {
-				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{
 		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{
 		Magic: AnnouncementMagic,
-		This:  Device{d.myID[:], addrs},
+		This:  Device{d.myID[:], addrs, d.relays},
 	}
 	msg := pkt.MustMarshalXDR()
 
@@ -363,19 +411,32 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool {
 	d.registryLock.Lock()
 	defer d.registryLock.Unlock()
 
-	current := d.filterCached(d.registry[id])
+	current := d.filterCached(d.addressRegistry[id])
 
 	orig := current
 
-	for _, a := range device.Addresses {
-		var deviceAddr string
-		if len(a.IP) > 0 {
-			deviceAddr = net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port)))
-		} else if addr != nil {
-			ua := addr.(*net.UDPAddr)
-			ua.Port = int(a.Port)
-			deviceAddr = ua.String()
+	for _, deviceAddr := range device.Addresses {
+		uri, err := url.Parse(deviceAddr)
+		if err != nil {
+			if debug {
+				l.Debugf("discover: Failed to parse address %s: %s", deviceAddr, err)
+			}
+			continue
 		}
+
+		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 {
 			if current[i].Address == deviceAddr {
 				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)
 	}
 
-	d.registry[id] = current
+	d.addressRegistry[id] = current
 
 	if len(current) > len(orig) {
 		addrs := make([]string, len(current))
@@ -413,7 +474,7 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
 	for i := 0; i < len(c); {
 		if ago := time.Since(c[i].Seen); ago > d.cacheLifetime {
 			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 = c[:len(c)-1]
@@ -424,30 +485,99 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
 	return c
 }
 
-func addrToAddr(addr *net.TCPAddr) Address {
+func addrToAddr(addr *net.TCPAddr) string {
 	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 {
-		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 {
-		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 {
-	var raddrs []Address
+func resolveAddrs(addrs []string) []string {
+	var raddrs []string
 	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 {
 			continue
 		}
 		addr := addrToAddr(addrRes)
-		if len(addr.IP) > 0 {
-			raddrs = append(raddrs, addr)
-		} else {
-			raddrs = append(raddrs, Address{Port: addr.Port})
+		if len(addr) > 0 {
+			uri.Host = addr
+			raddrs = append(raddrs, uri.String())
 		}
 	}
 	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]
+}

+ 33 - 9
lib/discover/discover_test.go

@@ -18,15 +18,15 @@ import (
 type DummyClient struct {
 	url          *url.URL
 	lookups      []protocol.DeviceID
-	lookupRet    []string
+	lookupRet    Announce
 	stops        int
 	statusRet    bool
 	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)
-	return c.lookupRet
+	return c.lookupRet, nil
 }
 
 func (c *DummyClient) StatusOK() bool {
@@ -45,17 +45,41 @@ func (c *DummyClient) Address() string {
 func TestGlobalDiscovery(t *testing.T) {
 	c1 := &DummyClient{
 		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{
 		statusRet: true,
-		lookupRet: []string{},
+		lookupRet: Announce{
+			Magic: AnnouncementMagic,
+			This: Device{
+				ID:        protocol.LocalDeviceID[:],
+				Addresses: nil,
+				Relays:    nil,
+			},
+			Extra: nil,
+		},
 	}
 
 	c3 := &DummyClient{
 		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}
@@ -72,7 +96,7 @@ func TestGlobalDiscovery(t *testing.T) {
 		return c3, nil
 	})
 
-	d := NewDiscoverer(device, []string{})
+	d := NewDiscoverer(device, []string{}, nil)
 	d.localBcastStart = time.Time{}
 	servers := []string{
 		"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 {
 		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 {
 		t.Fatal("Wrong number of addresses", addrs)
 	}

+ 9 - 8
lib/discover/packets.go

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

+ 92 - 65
lib/discover/packets_xdr.go

@@ -187,42 +187,38 @@ func (o *Announce) DecodeXDRFrom(xr *xdr.Reader) error {
 
 /*
 
-Device Structure:
+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 ID                          |
+|                       Length of Address                       |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 /                                                               /
-\                     ID (variable length)                      \
+\                   Address (variable length)                   \
 /                                                               /
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-|                      Number of Addresses                      |
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-/                                                               /
-\                Zero or more Address Structures                \
-/                                                               /
+|                            Latency                            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 
 
-struct Device {
-	opaque ID<32>;
-	Address Addresses<16>;
+struct Relay {
+	string Address<256>;
+	int Latency;
 }
 
 */
 
-func (o Device) EncodeXDR(w io.Writer) (int, error) {
+func (o Relay) EncodeXDR(w io.Writer) (int, error) {
 	var xw = xdr.NewWriter(w)
 	return o.EncodeXDRInto(xw)
 }
 
-func (o Device) MarshalXDR() ([]byte, error) {
+func (o Relay) MarshalXDR() ([]byte, error) {
 	return o.AppendXDR(make([]byte, 0, 128))
 }
 
-func (o Device) MustMarshalXDR() []byte {
+func (o Relay) MustMarshalXDR() []byte {
 	bs, err := o.MarshalXDR()
 	if err != nil {
 		panic(err)
@@ -230,92 +226,86 @@ func (o Device) MustMarshalXDR() []byte {
 	return bs
 }
 
-func (o Device) AppendXDR(bs []byte) ([]byte, error) {
+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 Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
-	if l := len(o.ID); l > 32 {
-		return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
-	}
-	xw.WriteBytes(o.ID)
-	if l := len(o.Addresses); l > 16 {
-		return xw.Tot(), xdr.ElementSizeExceeded("Addresses", l, 16)
-	}
-	xw.WriteUint32(uint32(len(o.Addresses)))
-	for i := range o.Addresses {
-		_, err := o.Addresses[i].EncodeXDRInto(xw)
-		if err != nil {
-			return xw.Tot(), 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 *Device) DecodeXDR(r io.Reader) error {
+func (o *Relay) DecodeXDR(r io.Reader) error {
 	xr := xdr.NewReader(r)
 	return o.DecodeXDRFrom(xr)
 }
 
-func (o *Device) UnmarshalXDR(bs []byte) error {
+func (o *Relay) UnmarshalXDR(bs []byte) error {
 	var br = bytes.NewReader(bs)
 	var xr = xdr.NewReader(br)
 	return o.DecodeXDRFrom(xr)
 }
 
-func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
-	o.ID = xr.ReadBytesMax(32)
-	_AddressesSize := int(xr.ReadUint32())
-	if _AddressesSize < 0 {
-		return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
-	}
-	if _AddressesSize > 16 {
-		return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
-	}
-	o.Addresses = make([]Address, _AddressesSize)
-	for i := range o.Addresses {
-		(&o.Addresses[i]).DecodeXDRFrom(xr)
-	}
+func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
+	o.Address = xr.ReadStringMax(256)
+	o.Latency = int32(xr.ReadUint32())
 	return xr.Error()
 }
 
 /*
 
-Address Structure:
+Device 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                          |
+|                         Length of ID                          |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/                                                               /
+\                     ID (variable length)                      \
+/                                                               /
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|                      Number of Addresses                      |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|                      Length of Addresses                      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 /                                                               /
-\                     IP (variable length)                      \
+\                  Addresses (variable length)                  \
 /                                                               /
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-|            0x0000             |             Port              |
+|                       Number of Relays                        |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+/                                                               /
+\                 Zero or more Relay Structures                 \
+/                                                               /
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 
 
-struct Address {
-	opaque IP<16>;
-	unsigned int Port;
+struct Device {
+	opaque ID<32>;
+	string Addresses<16>;
+	Relay Relays<16>;
 }
 
 */
 
-func (o Address) EncodeXDR(w io.Writer) (int, error) {
+func (o Device) EncodeXDR(w io.Writer) (int, error) {
 	var xw = xdr.NewWriter(w)
 	return o.EncodeXDRInto(xw)
 }
 
-func (o Address) MarshalXDR() ([]byte, error) {
+func (o Device) MarshalXDR() ([]byte, error) {
 	return o.AppendXDR(make([]byte, 0, 128))
 }
 
-func (o Address) MustMarshalXDR() []byte {
+func (o Device) MustMarshalXDR() []byte {
 	bs, err := o.MarshalXDR()
 	if err != nil {
 		panic(err)
@@ -323,35 +313,72 @@ func (o Address) MustMarshalXDR() []byte {
 	return bs
 }
 
-func (o Address) AppendXDR(bs []byte) ([]byte, error) {
+func (o Device) 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)
+func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
+	if l := len(o.ID); l > 32 {
+		return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
+	}
+	xw.WriteBytes(o.ID)
+	if l := len(o.Addresses); l > 16 {
+		return xw.Tot(), xdr.ElementSizeExceeded("Addresses", l, 16)
+	}
+	xw.WriteUint32(uint32(len(o.Addresses)))
+	for i := range o.Addresses {
+		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 {
+			return xw.Tot(), err
+		}
 	}
-	xw.WriteBytes(o.IP)
-	xw.WriteUint16(o.Port)
 	return xw.Tot(), xw.Error()
 }
 
-func (o *Address) DecodeXDR(r io.Reader) error {
+func (o *Device) DecodeXDR(r io.Reader) error {
 	xr := xdr.NewReader(r)
 	return o.DecodeXDRFrom(xr)
 }
 
-func (o *Address) UnmarshalXDR(bs []byte) error {
+func (o *Device) 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()
+func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
+	o.ID = xr.ReadBytesMax(32)
+	_AddressesSize := int(xr.ReadUint32())
+	if _AddressesSize < 0 {
+		return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
+	}
+	if _AddressesSize > 16 {
+		return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
+	}
+	o.Addresses = make([]string, _AddressesSize)
+	for i := range o.Addresses {
+		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()
 }

+ 27 - 0
lib/osutil/ping.go

@@ -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
+}

Some files were not shown because too many files changed in this diff