From 69f8ac6b56058c0fa93a6d48c187fe935dc2e32b Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Thu, 20 Nov 2014 21:24:11 +0000 Subject: [PATCH 1/3] Change to URL based announce server addresses (fixes #943) --- internal/config/config.go | 18 +- internal/config/config_test.go | 4 +- internal/config/testdata/v7.xml | 12 ++ internal/discover/client.go | 59 ++++++ internal/discover/client_udp.go | 250 +++++++++++++++++++++++ internal/discover/discover.go | 345 ++++++++++---------------------- 6 files changed, 445 insertions(+), 243 deletions(-) create mode 100644 internal/config/testdata/v7.xml create mode 100644 internal/discover/client.go create mode 100644 internal/discover/client_udp.go diff --git a/internal/config/config.go b/internal/config/config.go index 21fe2da9d..07679f091 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -34,7 +34,7 @@ import ( var l = logger.DefaultLogger -const CurrentVersion = 6 +const CurrentVersion = 7 type Configuration struct { Version int `xml:"version,attr"` @@ -160,7 +160,7 @@ type FolderDeviceConfiguration struct { type OptionsConfiguration struct { ListenAddress []string `xml:"listenAddress" default:"0.0.0.0:22000"` - GlobalAnnServers []string `xml:"globalAnnounceServer" default:"announce.syncthing.net:22026"` + GlobalAnnServers []string `xml:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026"` GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" default:"true"` LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true"` LocalAnnPort int `xml:"localAnnouncePort" default:"21025"` @@ -308,6 +308,11 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) { convertV5V6(cfg) } + // Upgrade to v7 configuration if appropriate + if cfg.Version == 6 { + convertV6V7(cfg) + } + // Hash old cleartext passwords if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' { hash, err := bcrypt.GenerateFromPassword([]byte(cfg.GUI.Password), 0) @@ -397,6 +402,15 @@ func ChangeRequiresRestart(from, to Configuration) bool { return false } +func convertV6V7(cfg *Configuration) { + // Migrate announce server addresses to the new URL based format + for i := range cfg.Options.GlobalAnnServers { + cfg.Options.GlobalAnnServers[i] = "udp4://" + cfg.Options.GlobalAnnServers[i] + } + + cfg.Version = 7 +} + func convertV5V6(cfg *Configuration) { // Added ".stfolder" file at folder roots to identify mount issues // Doesn't affect the config itself, but uses config migrations to identify diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 5de361399..a7d3b6b9e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -36,7 +36,7 @@ func init() { func TestDefaultValues(t *testing.T) { expected := OptionsConfiguration{ ListenAddress: []string{"0.0.0.0:22000"}, - GlobalAnnServers: []string{"announce.syncthing.net:22026"}, + GlobalAnnServers: []string{"udp4://announce.syncthing.net:22026"}, GlobalAnnEnabled: true, LocalAnnEnabled: true, LocalAnnPort: 21025, @@ -139,7 +139,7 @@ func TestNoListenAddress(t *testing.T) { func TestOverriddenValues(t *testing.T) { expected := OptionsConfiguration{ ListenAddress: []string{":23000"}, - GlobalAnnServers: []string{"syncthing.nym.se:22026"}, + GlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"}, GlobalAnnEnabled: false, LocalAnnEnabled: false, LocalAnnPort: 42123, diff --git a/internal/config/testdata/v7.xml b/internal/config/testdata/v7.xml new file mode 100644 index 000000000..ec5bc6c1d --- /dev/null +++ b/internal/config/testdata/v7.xml @@ -0,0 +1,12 @@ + + + + + + +
a
+
+ +
b
+
+
diff --git a/internal/discover/client.go b/internal/discover/client.go new file mode 100644 index 000000000..a506395f1 --- /dev/null +++ b/internal/discover/client.go @@ -0,0 +1,59 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . + +package discover + +import ( + "fmt" + "net/url" + "time" + + "github.com/syncthing/syncthing/internal/protocol" +) + +type Factory func(*url.URL, *Announce) (Client, error) + +var ( + factories = make(map[string]Factory) + DefaultErrorRetryInternval = 60 * time.Second + DefaultGlobalBroadcastInterval = 1800 * time.Second +) + +func Register(proto string, factory Factory) { + factories[proto] = factory +} + +func New(addr string, pkt *Announce) (Client, error) { + uri, err := url.Parse(addr) + if err != nil { + return nil, err + } + factory, ok := factories[uri.Scheme] + if !ok { + return nil, fmt.Errorf("Unsupported scheme: %s", uri.Scheme) + } + client, err := factory(uri, pkt) + if err != nil { + return nil, err + } + return client, nil +} + +type Client interface { + Lookup(device protocol.DeviceID) []string + StatusOK() bool + Address() string + Stop() +} diff --git a/internal/discover/client_udp.go b/internal/discover/client_udp.go new file mode 100644 index 000000000..79e5f6981 --- /dev/null +++ b/internal/discover/client_udp.go @@ -0,0 +1,250 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . + +package discover + +import ( + "encoding/hex" + "io" + "net" + "net/url" + "strconv" + "sync" + "time" + + "github.com/syncthing/syncthing/internal/protocol" +) + +func init() { + for _, proto := range []string{"udp", "udp4", "udp6"} { + Register(proto, func(uri *url.URL, pkt *Announce) (Client, error) { + c := &UDPClient{} + err := c.Start(uri, pkt) + if err != nil { + return nil, err + } + return c, nil + }) + } +} + +type UDPClient struct { + url *url.URL + + id protocol.DeviceID + + stop chan struct{} + wg sync.WaitGroup + listenAddress *net.UDPAddr + + globalBroadcastInterval time.Duration + errorRetryInterval time.Duration + + status bool + mut sync.RWMutex +} + +func (d *UDPClient) Start(uri *url.URL, pkt *Announce) error { + d.url = uri + d.id = protocol.DeviceIDFromBytes(pkt.This.ID) + d.stop = make(chan struct{}) + + params := uri.Query() + // The address must not have a port, as otherwise both announce and lookup + // sockets would try to bind to the same port. + addr, err := net.ResolveUDPAddr(d.url.Scheme, params.Get("listenaddress")+":0") + if err != nil { + return err + } + d.listenAddress = addr + + broadcastSeconds, err := strconv.ParseUint(params.Get("broadcast"), 0, 0) + if err != nil { + d.globalBroadcastInterval = DefaultGlobalBroadcastInterval + } else { + d.globalBroadcastInterval = time.Duration(broadcastSeconds) * time.Second + } + + retrySeconds, err := strconv.ParseUint(params.Get("retry"), 0, 0) + if err != nil { + d.errorRetryInterval = DefaultErrorRetryInternval + } else { + d.errorRetryInterval = time.Duration(retrySeconds) * time.Second + } + + d.wg.Add(1) + go d.broadcast(pkt.MustMarshalXDR()) + return nil +} + +func (d *UDPClient) broadcast(pkt []byte) { + defer d.wg.Done() + timer := time.NewTimer(0) + + conn, err := net.ListenUDP(d.url.Scheme, d.listenAddress) + for err != nil { + timer.Reset(d.errorRetryInterval) + l.Warnf("Global UDP discovery (%s): %v; trying again in %v", d.url, err, d.errorRetryInterval) + select { + case <-d.stop: + return + case <-timer.C: + } + conn, err = net.ListenUDP(d.url.Scheme, d.listenAddress) + } + defer conn.Close() + + remote, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host) + for err != nil { + timer.Reset(d.errorRetryInterval) + l.Warnf("Global UDP discovery (%s): %v; trying again in %v", d.url, err, d.errorRetryInterval) + select { + case <-d.stop: + return + case <-timer.C: + } + remote, err = net.ResolveUDPAddr(d.url.Scheme, d.url.Host) + } + + timer.Reset(0) + + for { + select { + case <-d.stop: + return + + case <-timer.C: + var ok bool + + if debug { + l.Debugf("Global UDP discovery (%s): send announcement -> %v\n%s", d.url, remote, hex.Dump(pkt)) + } + + _, err := conn.WriteTo(pkt, remote) + if err != nil { + if debug { + l.Debugf("discover %s: warning: %s", d.url, err) + } + ok = false + } else { + // Verify that the announce server responds positively for our device ID + + time.Sleep(1 * time.Second) + + res := d.Lookup(d.id) + if debug { + l.Debugf("discover %s: external lookup check: %v", d.url, res) + } + ok = len(res) > 0 + } + + d.mut.Lock() + d.status = ok + d.mut.Unlock() + + if ok { + timer.Reset(d.globalBroadcastInterval) + } else { + timer.Reset(d.errorRetryInterval) + } + } + } +} + +func (d *UDPClient) Lookup(device protocol.DeviceID) []string { + extIP, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host) + if err != nil { + if debug { + l.Debugf("discover %s: %v; no external lookup", d.url, err) + } + return nil + } + + conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP) + if err != nil { + if debug { + l.Debugf("discover %s: %v; no external lookup", d.url, err) + } + return nil + } + defer conn.Close() + + err = conn.SetDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + if debug { + l.Debugf("discover %s: %v; no external lookup", d.url, err) + } + return nil + } + + buf := Query{QueryMagic, device[:]}.MustMarshalXDR() + _, err = conn.Write(buf) + if err != nil { + if debug { + l.Debugf("discover %s: %v; no external lookup", d.url, err) + } + return nil + } + + buf = make([]byte, 2048) + n, err := conn.Read(buf) + 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 + } + if debug { + l.Debugf("discover %s: %v; no external lookup", d.url, err) + } + return nil + } + + if debug { + l.Debugf("discover %s: read external:\n%s", d.url, hex.Dump(buf[:n])) + } + + var pkt Announce + err = pkt.UnmarshalXDR(buf[:n]) + if err != nil && err != io.EOF { + if debug { + l.Debugln("discover %s:", d.url, err) + } + return nil + } + + 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) + } + return addrs +} + +func (d *UDPClient) Stop() { + if d.stop != nil { + close(d.stop) + d.wg.Wait() + } +} + +func (d *UDPClient) StatusOK() bool { + d.mut.RLock() + defer d.mut.RUnlock() + return d.status +} + +func (d *UDPClient) Address() string { + return d.url.String() +} diff --git a/internal/discover/discover.go b/internal/discover/discover.go index 92fd73763..840ae9608 100644 --- a/internal/discover/discover.go +++ b/internal/discover/discover.go @@ -31,25 +31,21 @@ import ( ) type Discoverer struct { - myID protocol.DeviceID - listenAddrs []string - localBcastIntv time.Duration - localBcastStart time.Time - globalBcastIntv time.Duration - errorRetryIntv time.Duration - cacheLifetime time.Duration - broadcastBeacon beacon.Interface - multicastBeacon beacon.Interface - registry map[protocol.DeviceID][]CacheEntry - registryLock sync.RWMutex - extServers []string - extPort uint16 - localBcastTick <-chan time.Time - stopGlobal chan struct{} - globalWG sync.WaitGroup - forcedBcastTick chan time.Time - extAnnounceOK map[string]bool - extAnnounceOKmut sync.Mutex + myID protocol.DeviceID + listenAddrs []string + localBcastIntv time.Duration + localBcastStart time.Time + cacheLifetime time.Duration + broadcastBeacon beacon.Interface + multicastBeacon beacon.Interface + registry map[protocol.DeviceID][]CacheEntry + registryLock sync.RWMutex + extPort uint16 + localBcastTick <-chan time.Time + forcedBcastTick chan time.Time + + clients []Client + mut sync.RWMutex } type CacheEntry struct { @@ -63,14 +59,11 @@ var ( func NewDiscoverer(id protocol.DeviceID, addresses []string) *Discoverer { return &Discoverer{ - myID: id, - listenAddrs: addresses, - localBcastIntv: 30 * time.Second, - globalBcastIntv: 1800 * time.Second, - errorRetryIntv: 60 * time.Second, - cacheLifetime: 5 * time.Minute, - registry: make(map[protocol.DeviceID][]CacheEntry), - extAnnounceOK: make(map[string]bool), + myID: id, + listenAddrs: addresses, + localBcastIntv: 30 * time.Second, + cacheLifetime: 5 * time.Minute, + registry: make(map[protocol.DeviceID][]CacheEntry), } } @@ -112,38 +105,60 @@ func (d *Discoverer) StartLocal(localPort int, localMCAddr string) { } func (d *Discoverer) StartGlobal(servers []string, extPort uint16) { - // Wait for any previous announcer to stop before starting a new one. - d.globalWG.Wait() - d.extServers = servers + d.mut.Lock() + defer d.mut.Unlock() + + if len(d.clients) > 0 { + d.stopGlobal() + } + d.extPort = extPort - d.stopGlobal = make(chan struct{}) - d.globalWG.Add(1) - go func() { - defer d.globalWG.Done() + pkt := d.announcementPkt() + wg := sync.WaitGroup{} + clients := make(chan Client, len(servers)) + for _, address := range servers { + wg.Add(1) + go func(addr string) { + defer wg.Done() + client, err := New(addr, pkt) + if err != nil { + l.Infoln("Error creating discovery client", addr, err) + return + } + clients <- client + }(address) + } - buf := d.announcementPkt() + wg.Wait() + close(clients) - for _, extServer := range d.extServers { - d.globalWG.Add(1) - go func(server string) { - d.sendExternalAnnouncements(server, buf) - d.globalWG.Done() - }(extServer) - } - }() -} - -func (d *Discoverer) StopGlobal() { - if d.stopGlobal != nil { - close(d.stopGlobal) - d.globalWG.Wait() + for client := range clients { + d.clients = append(d.clients, client) } } +func (d *Discoverer) StopGlobal() { + d.mut.Lock() + defer d.mut.Unlock() + d.stopGlobal() +} + +func (d *Discoverer) stopGlobal() { + for _, client := range d.clients { + client.Stop() + } + d.clients = []Client{} +} + func (d *Discoverer) ExtAnnounceOK() map[string]bool { - d.extAnnounceOKmut.Lock() - defer d.extAnnounceOKmut.Unlock() - return d.extAnnounceOK + d.mut.RLock() + defer d.mut.RUnlock() + + ret := make(map[string]bool) + for _, client := range d.clients { + ret[client.Address()] = client.StatusOK() + } + return ret } func (d *Discoverer) Lookup(device protocol.DeviceID) []string { @@ -151,22 +166,47 @@ func (d *Discoverer) Lookup(device protocol.DeviceID) []string { cached := d.filterCached(d.registry[device]) d.registryLock.RUnlock() + d.mut.RLock() + defer d.mut.RUnlock() + + var addrs []string if len(cached) > 0 { - addrs := make([]string, len(cached)) + addrs = make([]string, len(cached)) for i := range cached { addrs[i] = cached[i].Address } - return addrs - } else if len(d.extServers) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv { + } else if len(d.clients) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv { // Only perform external lookups if we have at least one external - // server and one local announcement interval has passed. This is to - // avoid finding local peers on their remote address at startup. - addrs := d.externalLookup(device) - cached = make([]CacheEntry, len(addrs)) - for i := range addrs { - cached[i] = CacheEntry{ - Address: addrs[i], - Seen: time.Now(), + // 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)) + wg := sync.WaitGroup{} + for _, client := range d.clients { + wg.Add(1) + go func(c Client) { + defer wg.Done() + results <- c.Lookup(device) + }(client) + } + + wg.Wait() + close(results) + + cached := []CacheEntry{} + seen := make(map[string]struct{}) + now := time.Now() + + for result := range results { + for _, addr := range result { + _, ok := seen[addr] + if !ok { + cached = append(cached, CacheEntry{ + Address: addr, + Seen: now, + }) + seen[addr] = struct{}{} + addrs = append(addrs, addr) + } } } @@ -174,7 +214,7 @@ func (d *Discoverer) Lookup(device protocol.DeviceID) []string { d.registry[device] = cached d.registryLock.Unlock() } - return nil + return addrs } func (d *Discoverer) Hint(device string, addrs []string) { @@ -199,7 +239,7 @@ func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry { return devices } -func (d *Discoverer) announcementPkt() []byte { +func (d *Discoverer) announcementPkt() *Announce { var addrs []Address if d.extPort != 0 { addrs = []Address{{Port: d.extPort}} @@ -221,11 +261,10 @@ func (d *Discoverer) announcementPkt() []byte { } } } - var pkt = Announce{ + return &Announce{ Magic: AnnouncementMagic, This: Device{d.myID[:], addrs}, } - return pkt.MustMarshalXDR() } func (d *Discoverer) sendLocalAnnouncements() { @@ -252,80 +291,6 @@ func (d *Discoverer) sendLocalAnnouncements() { } } -func (d *Discoverer) sendExternalAnnouncements(extServer string, buf []byte) { - timer := time.NewTimer(0) - - conn, err := net.ListenUDP("udp", nil) - for err != nil { - timer.Reset(d.errorRetryIntv) - l.Warnf("Global discovery: %v; trying again in %v", err, d.errorRetryIntv) - select { - case <-d.stopGlobal: - return - case <-timer.C: - } - conn, err = net.ListenUDP("udp", nil) - } - - remote, err := net.ResolveUDPAddr("udp", extServer) - for err != nil { - timer.Reset(d.errorRetryIntv) - l.Warnf("Global discovery: %s: %v; trying again in %v", extServer, err, d.errorRetryIntv) - select { - case <-d.stopGlobal: - return - case <-timer.C: - } - remote, err = net.ResolveUDPAddr("udp", extServer) - } - - // Delay the first announcement until after a full local announcement - // cycle, to increase the chance of other peers finding us locally first. - timer.Reset(d.localBcastIntv) - - for { - select { - case <-d.stopGlobal: - return - - case <-timer.C: - var ok bool - - if debug { - l.Debugf("discover: send announcement -> %v\n%s", remote, hex.Dump(buf)) - } - - _, err := conn.WriteTo(buf, remote) - if err != nil { - if debug { - l.Debugln("discover: %s: warning:", extServer, err) - } - ok = false - } else { - // Verify that the announce server responds positively for our device ID - - time.Sleep(1 * time.Second) - res := d.externalLookupOnServer(extServer, d.myID) - - if debug { - l.Debugln("discover:", extServer, "external lookup check:", res) - } - ok = len(res) > 0 - } - - d.extAnnounceOKmut.Lock() - d.extAnnounceOK[extServer] = ok - d.extAnnounceOKmut.Unlock() - - if ok { - timer.Reset(d.globalBcastIntv) - } else { - timer.Reset(d.errorRetryIntv) - } - } - } -} - func (d *Discoverer) recvAnnouncements(b beacon.Interface) { for { buf, addr := b.Recv() @@ -406,104 +371,6 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool { return len(current) > len(orig) } -func (d *Discoverer) externalLookup(device protocol.DeviceID) []string { - // Buffer up to as many answers as we have servers to query. - results := make(chan []string, len(d.extServers)) - - // Query all servers. - wg := sync.WaitGroup{} - for _, extServer := range d.extServers { - wg.Add(1) - go func(server string) { - result := d.externalLookupOnServer(server, device) - if debug { - l.Debugln("discover:", result, "from", server, "for", device) - } - results <- result - wg.Done() - }(extServer) - } - - wg.Wait() - close(results) - - addrs := []string{} - for result := range results { - addrs = append(addrs, result...) - } - - return addrs -} - -func (d *Discoverer) externalLookupOnServer(extServer string, device protocol.DeviceID) []string { - extIP, err := net.ResolveUDPAddr("udp", extServer) - if err != nil { - if debug { - l.Debugf("discover: %s: %v; no external lookup", extServer, err) - } - return nil - } - - conn, err := net.DialUDP("udp", nil, extIP) - if err != nil { - if debug { - l.Debugf("discover: %s: %v; no external lookup", extServer, err) - } - return nil - } - defer conn.Close() - - err = conn.SetDeadline(time.Now().Add(5 * time.Second)) - if err != nil { - if debug { - l.Debugf("discover: %s: %v; no external lookup", extServer, err) - } - return nil - } - - buf := Query{QueryMagic, device[:]}.MustMarshalXDR() - _, err = conn.Write(buf) - if err != nil { - if debug { - l.Debugf("discover: %s: %v; no external lookup", extServer, err) - } - return nil - } - - buf = make([]byte, 2048) - n, err := conn.Read(buf) - 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 - } - if debug { - l.Debugf("discover: %s: %v; no external lookup", extServer, err) - } - return nil - } - - if debug { - l.Debugf("discover: %s: read external:\n%s", extServer, hex.Dump(buf[:n])) - } - - var pkt Announce - err = pkt.UnmarshalXDR(buf[:n]) - if err != nil && err != io.EOF { - if debug { - l.Debugln("discover:", extServer, err) - } - return nil - } - - 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) - } - return addrs -} - func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry { for i := 0; i < len(c); { if ago := time.Since(c[i].Seen); ago > d.cacheLifetime { From 2912defb978b6c6eedf24a3934ef2767e4f18650 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Thu, 27 Nov 2014 23:14:41 +0000 Subject: [PATCH 2/3] Add tests for new discovery --- internal/discover/client_test.go | 227 +++++++++++++++++++++++++++++ internal/discover/discover_test.go | 134 ++++++++++++++++- 2 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 internal/discover/client_test.go diff --git a/internal/discover/client_test.go b/internal/discover/client_test.go new file mode 100644 index 000000000..f57085c31 --- /dev/null +++ b/internal/discover/client_test.go @@ -0,0 +1,227 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . + +package discover + +import ( + "fmt" + "net" + "sync" + "time" + + "testing" + + "github.com/syncthing/syncthing/internal/protocol" +) + +var device protocol.DeviceID + +func init() { + device, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2") +} + +func TestUDP4Success(t *testing.T) { + conn, err := net.ListenUDP("udp4", nil) + if err != nil { + t.Fatal(err) + } + + port := conn.LocalAddr().(*net.UDPAddr).Port + + address := fmt.Sprintf("udp4://127.0.0.1:%d", port) + pkt := &Announce{ + Magic: AnnouncementMagic, + This: Device{ + device[:], + []Address{{ + IP: net.IPv4(123, 123, 123, 123), + Port: 1234, + }}, + }, + } + + client, err := New(address, pkt) + if err != nil { + t.Fatal(err) + } + + udpclient := client.(*UDPClient) + if udpclient.errorRetryInterval != DefaultErrorRetryInternval { + t.Fatal("Incorrect retry interval") + } + + if udpclient.listenAddress.IP != nil || udpclient.listenAddress.Port != 0 { + t.Fatal("Wrong listen IP or port", udpclient.listenAddress) + } + + if client.Address() != address { + t.Fatal("Incorrect address") + } + + buf := make([]byte, 2048) + + // First announcement + conn.SetDeadline(time.Now().Add(time.Millisecond * 100)) + _, err = conn.Read(buf) + if err != nil { + t.Fatal(err) + } + + // Announcement verification + conn.SetDeadline(time.Now().Add(time.Millisecond * 1100)) + _, addr, err := conn.ReadFromUDP(buf) + if err != nil { + t.Fatal(err) + } + + // Reply to it. + _, err = conn.WriteToUDP(pkt.MustMarshalXDR(), addr) + if err != nil { + t.Fatal(err) + } + + // We should get nothing else + conn.SetDeadline(time.Now().Add(time.Millisecond * 100)) + _, err = conn.Read(buf) + if err == nil { + t.Fatal("Expected error") + } + + // Status should be ok + if !client.StatusOK() { + t.Fatal("Wrong status") + } + + // Do a lookup in a separate routine + addrs := []string{} + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + addrs = client.Lookup(device) + wg.Done() + }() + + // Receive the lookup and reply + conn.SetDeadline(time.Now().Add(time.Millisecond * 100)) + _, addr, err = conn.ReadFromUDP(buf) + if err != nil { + t.Fatal(err) + } + + conn.WriteToUDP(pkt.MustMarshalXDR(), addr) + + // 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" { + t.Fatal("Wrong number of answers") + } + + client.Stop() +} + +func TestUDP4Failure(t *testing.T) { + conn, err := net.ListenUDP("udp4", nil) + if err != nil { + t.Fatal(err) + } + + port := conn.LocalAddr().(*net.UDPAddr).Port + + address := fmt.Sprintf("udp4://127.0.0.1:%d/?listenaddress=127.0.0.1&retry=5", port) + + pkt := &Announce{ + Magic: AnnouncementMagic, + This: Device{ + device[:], + []Address{{ + IP: net.IPv4(123, 123, 123, 123), + Port: 1234, + }}, + }, + } + + client, err := New(address, pkt) + if err != nil { + t.Fatal(err) + } + + udpclient := client.(*UDPClient) + if udpclient.errorRetryInterval != time.Second*5 { + t.Fatal("Incorrect retry interval") + } + + if !udpclient.listenAddress.IP.Equal(net.IPv4(127, 0, 0, 1)) || udpclient.listenAddress.Port != 0 { + t.Fatal("Wrong listen IP or port", udpclient.listenAddress) + } + + if client.Address() != address { + t.Fatal("Incorrect address") + } + + buf := make([]byte, 2048) + + // First announcement + conn.SetDeadline(time.Now().Add(time.Millisecond * 100)) + _, err = conn.Read(buf) + if err != nil { + t.Fatal(err) + } + + // Announcement verification + conn.SetDeadline(time.Now().Add(time.Millisecond * 1100)) + _, _, err = conn.ReadFromUDP(buf) + if err != nil { + t.Fatal(err) + } + + // Don't reply + // We should get nothing else + conn.SetDeadline(time.Now().Add(time.Millisecond * 100)) + _, err = conn.Read(buf) + if err == nil { + t.Fatal("Expected error") + } + + // Status should be failure + if client.StatusOK() { + t.Fatal("Wrong status") + } + + // Do a lookup in a separate routine + addrs := []string{} + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + addrs = client.Lookup(device) + wg.Done() + }() + + // Receive the lookup and don't reply + conn.SetDeadline(time.Now().Add(time.Millisecond * 100)) + _, _, err = conn.ReadFromUDP(buf) + if err != nil { + t.Fatal(err) + } + + // Wait for the lookup to timeout, verify that the number of answers is none + wg.Wait() + + if len(addrs) != 0 { + t.Fatal("Wrong number of answers") + } + + client.Stop() +} diff --git a/internal/discover/discover_test.go b/internal/discover/discover_test.go index f2b69f580..fce201fba 100644 --- a/internal/discover/discover_test.go +++ b/internal/discover/discover_test.go @@ -13,6 +13,136 @@ // You should have received a copy of the GNU General Public License along // with this program. If not, see . -package discover_test +package discover -// Empty test file to generate 0% coverage rather than no coverage +import ( + "net/url" + "time" + + "testing" + + "github.com/syncthing/syncthing/internal/protocol" +) + +type DummyClient struct { + url *url.URL + lookups []protocol.DeviceID + lookupRet []string + stops int + statusRet bool + statusChecks int +} + +func (c *DummyClient) Lookup(device protocol.DeviceID) []string { + c.lookups = append(c.lookups, device) + return c.lookupRet +} + +func (c *DummyClient) StatusOK() bool { + c.statusChecks++ + return c.statusRet +} + +func (c *DummyClient) Stop() { + c.stops++ +} + +func (c *DummyClient) Address() string { + return c.url.String() +} + +func TestGlobalDiscovery(t *testing.T) { + c1 := &DummyClient{ + statusRet: false, + lookupRet: []string{"test.com:1234"}, + } + + c2 := &DummyClient{ + statusRet: true, + lookupRet: []string{}, + } + + c3 := &DummyClient{ + statusRet: true, + lookupRet: []string{"best.com:2345"}, + } + + clients := []*DummyClient{c1, c2} + + Register("test1", func(uri *url.URL, pkt *Announce) (Client, error) { + c := clients[0] + clients = clients[1:] + c.url = uri + return c, nil + }) + + Register("test2", func(uri *url.URL, pkt *Announce) (Client, error) { + c3.url = uri + return c3, nil + }) + + d := NewDiscoverer(device, []string{}) + d.localBcastStart = time.Time{} + servers := []string{ + "test1://123.123.123.123:1234", + "test1://23.23.23.23:234", + "test2://234.234.234.234.2345", + } + d.StartGlobal(servers, 1234) + + if len(d.clients) != 3 { + t.Fatal("Wrong number of clients") + } + + status := d.ExtAnnounceOK() + + for _, c := range []*DummyClient{c1, c2, c3} { + if status[c.url.String()] != c.statusRet || c.statusChecks != 1 { + t.Fatal("Wrong status") + } + } + + addrs := d.Lookup(device) + if len(addrs) != 2 { + t.Fatal("Wrong numer of addresses", addrs) + } + + for _, addr := range []string{"test.com:1234", "best.com:2345"} { + found := false + for _, laddr := range addrs { + if laddr == addr { + found = true + break + } + } + if !found { + t.Fatal("Couldn't find", addr) + } + } + + for _, c := range []*DummyClient{c1, c2, c3} { + if len(c.lookups) != 1 || c.lookups[0] != device { + t.Fatal("Wrong lookups") + } + } + + addrs = d.Lookup(device) + if len(addrs) != 2 { + t.Fatal("Wrong numer of addresses", addrs) + } + + // Answer should be cached, so number of lookups should have not incresed + for _, c := range []*DummyClient{c1, c2, c3} { + if len(c.lookups) != 1 || c.lookups[0] != device { + t.Fatal("Wrong lookups") + } + } + + d.StopGlobal() + + for _, c := range []*DummyClient{c1, c2, c3} { + if c.stops != 1 { + t.Fatal("Wrong number of stops") + } + } +} From d30d0b29a98f3ed9c6cee684733f4784459e1314 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 30 Nov 2014 21:47:21 +0000 Subject: [PATCH 3/3] Fix CSS --- gui/assets/css/overrides.css | 4 ++++ internal/auto/gui.files.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gui/assets/css/overrides.css b/gui/assets/css/overrides.css index 4b8a17f6d..5220cc2f8 100644 --- a/gui/assets/css/overrides.css +++ b/gui/assets/css/overrides.css @@ -41,6 +41,10 @@ identicon { margin-top: 0px; } +.popover { + max-width: none; +} + .identicon { width: 30px; height: 30px; diff --git a/internal/auto/gui.files.go b/internal/auto/gui.files.go index 4ecd750c6..df2a5a662 100644 --- a/internal/auto/gui.files.go +++ b/internal/auto/gui.files.go @@ -12,7 +12,7 @@ func Assets() map[string][]byte { var bs []byte var gr *gzip.Reader - bs, _ = base64.StdEncoding.DecodeString("H4sIAAAJbogA/5xWaW/bOBD97l8x28UCSdZXnAtrL4r1Bk0boE0Dx0GRj5REWUQoUiApHy363/dRVxxHToMN4NiiyDdvZt7McHDUGQzoUmcbIxaJo4PLQxoNj09pnnC626jQJUItaJq7RBvbx2a/f54IS5nRC8NSws/YcE5Wx27FDB/TRucUMkWGR8I6I4LccRKOmIoG2lCqIxFvsOChchVxQw7WHDepJR0XDx9v7ukjV9wwSbd5IEVIn0XIleXEYNqv2IRHFGyK7Vcg4NHuKg50pQHMnNCqS1xgi6ElNxbPdFLbqAC7BE4HzHnahnTmDx16MKY2JJl7OrrP/ScvIxKqwE50Bo8SoMLvlZCSAk655XEuu4Sd9O16/unr/dzDTW8e6Nt0NpvezB8m2IxQYwNf8hJKpJkUQIZfhim38fS/fJhdfsL+6b/Xn6/nD/DAA11dz28+3N3R1dcZTel2OptfX95/ns7o9n52+/XuQ5/uOP9VeOMSK9WIYsQdE7JJ+wPyakFORpSwJUd+Qy6WoMYohILekjup1cJDeS+x+SmOfbqOSWnXJQuOfyfOZePBYLVa9Rcq72uzGMgSww7e9ztHg04n0NGGfnQIfxmLIsi0F2jndDqmi2G2nhRvYq1cL2apkJsxvfvE5ZI7ETK64Tl/16VmoUtTIxhyY5myPcuNiCedn51OctylZITPCT6n+JxVJp8Dz5jkK7YB4v+wkcs/G9iUmYVQPaezMR33z3ha7BARV0CAeMtdEFwmGQwLJYXivUDq8LF0ONNWeAWPkR2IF+kp11cicgkwPaJ/Triv962FAujFqob0Y6lXY1oKKwLJCz79MOHhY6DXLayL0Ps9u6QrBidNbmpjJ82RjCkuQYL5bNILhMbtVn9ZYLVEDZbrBZfeaW1L8hiWen+1W/rxauieYpCICJR23UMZuFoVqPQx/X5+fl5ucnzteqlW2mYs5G3S+cKV1F36ohUL8X2pFbxgFkK6RDsSaD43fAUVNSAVMEMuerAe+ZqI3jvvyntn8KNLL9/6Wtn/Ntba7X/bIEevIre+bZCjyvdAG/T7UilKq8obqRf6mZQgo8l2ZTfPZVrP6rS2pawAREfuKd0Lcil5nZxi0boNKLpNxksC1XaQlnn6hvpKofNKyRd1kVTyNzu1E7DwcWH8IPLo2oDkIjgYnZx3aXQ69P+ODyfbUTFQY25REI17tffH2ZoaLdchGmFxVAp6ywOIRPUx+tjz2iwLYFi2lB1Hjof1YqFXJsXCR9S7M3ktHj506Lqh1OxxN3I+tvQbZpc2DlNrS7Vo+3VHSITjvULV/sDKsGyra6+q9nA6HD47He00/dKzEZrIPntR36ZMyt5WVFpN40hxYlfH1ZlyFd5hPI9R6mse+TPtRxqaL7tHE+inV1xKkVlhJ6+R+yfFfYrRQcrWVeYuzi+y9eE2vf08/N/gyI939JJAIC5ZghxZlBTGMlgUEyAwnD1af4vxkxpVhfGMMxLyHNgEN6uoGN0N4hGVsrN9wkyuV3ccMIh+6dnPUjNsGTBT1kvFDswKHlBeeTcoGnSNWae6rrCz5228uAnsSVol//pmUEkpQu/XRnyHzpj0TwqXK4Qgcs+H1WkzmvaeiNrq7Lw+Nzg66iBIt/6Sw61FTzC2dBCXGVwtfYYghY73FO7U29q5/6rrNceLHhCbgmutjq3KLk2/Pj/bZm3TLoZ/VPWV4N5dNZ6aNOo2eBSuWsVXrvyMLxEwC7+3v3mxWOCv9JvQR3vRR23oo+ratcNfii75xS2jUrQ2m602WsRVeM35+8WwvrL9BwAA//8BAAD//zISXddYDQAA") + bs, _ = base64.StdEncoding.DecodeString("H4sIAAAJbogA/5xW7W7buBL976eY24sLJLmW7ThfWGdRrDdo2gBtGjgOivykJMoiQpECScV2i777HurLjiOnxQZwbFHDwzMzZ2Y4POoNh3Sl87URi9TRwdUhjUfHpzRPOd2vVeRSoRY0LVyqjR3A2NvPU2EpN3phWEb4mRjOyerELZnhE1rrgiKmyPBYWGdEWDhOwhFT8VAbynQskjUWPFShYm7I4TTHTWZJJ+XDx9sH+sgVN0zSXRFKEdFnEXFlOTEc7VdsymMK16X5NQh4tPuaA11rADMntOoTFzAx9MyNxTOdNGfUgH0CpwPmPG1DOvebDj0YU2uSzG227nN/42VMQpXYqc7hUQpU+L0UUlLIqbA8KWSfYEnfbuafvj7MPdz09pG+TWez6e388RLGCDUM+DOvoESWSwFk+GWYcmtP/8uH2dUn2E//vvl8M3+EBx7o+mZ+++H+nq6/zmhKd9PZ/Obq4fN0RncPs7uv9x8GdM/5r8KbVFiZRhRj7piQbdofkVcLcjKmlD1z5Dfi4hnUGEVQ0O/kTmq18FDeSxhv4jigm4SUdn2y4Phn6lw+GQ6Xy+VgoYqBNouhrDDs8P2gdzTs9UIdr+lHj/CXsziGTINQO6ezCV2M8tVl+SbRygUJy4RcT+jdJy6fuRMRo1te8Hd9ahf6NDWCITeWKRtYbkRy2fvZ66XHfUrH+Jzgc4rPWX3kS+AZk3zJ1kD8F2cU8v8tbMbMQqjA6XxCx4MznpUWIuYKCBBvZQXB5ZLhYKGkUDwIpY6eKodzbYVX8ATZgXiRnmp9KWKXAtMj+ueU+3rfWiiBXq1qSD+RejmhZ2FFKHnJZxClPHoK9aqDdRl6b5Pr3O9uTVZBzUFpVcPs+lUbnLTpa/icbFCZ4hI8mU84vUJoI9MZEhZaLVGm1XpJNzhtzpI8wUnBH90n/XgzupswpSIGpV33UCmuEQ6awYT+e35+Xhk5vnJBppW2OYt4l7q+cCV1n75oxSJ8X2kFL5iF1q7QsQRCfMuXEFoLUgMzpCvA6bEvm/i98668dwY/+vT6rS+n/W8Trd3+ty1y/CZy59sWOa59D7XBSKjEtJGK1Av9Qm1Q2uV28bfPVVrPmrR2pawERNMOlA7CQkreJKdctG4Nim6d84pAbQ7Sssh+owQzlEKt5IumjuoKMTvlFbLoaWH8rPLo2oDkIjwYn5z3aXw68v+ODy+3o2KgxsKiIFr3Gu+P8xW1Wm5CNMbiuBL0lgcQiRpgOrKX5VsVwKjqOjuOHI+axVKvTIqFj6h35/KtePjQoTFHUrOn3cj52NJ/MN60cRhsW6rFZGg6QiocD0pV+w1Lw/Ktxr6s28PpaPRid7wzFyrPxmgi+86LBzZjUgZbUek8GlvKHbs6rvdUq/AOE3yCUl/x2O/p3tLSfN092kBvXnEpRW6FvXyL3F8ZrlyMDjbd9uL8Il8dbtPbz8P/DY/8DQC9JBSIS54iRxYlhckNFuWQCA1nT9ZfdPwwR1VhgmOPhDyHNsXlKy6ne4t4RJXs7IAwtpvVHQcMol959rPSDHsOmanqpWYHZiUPKK+6PpQNusFsUt1U2NnLNl5eFvYkrZZ/c3mopRSj92sjvkNnTPonhfsXQhC7l8PqtB1Ne3fEXXV23uwbHh31EKQ7fw/i1qInGFs5iPsObp8+Q5BCz3sKdxqzbu6/6nrt9rIHJKbk2qhjq7Kro9+en12ztm0Xo//V9ZXial43noY06jZ8Eq5exVeh/IyvEDALv3e/ebVY4i/1b6GP96KPu9DH9c1sh78UffKLW4dK0dlsttpoGVfhNefvF6PmVvcPAAAA//8BAAD//+ki3WZ7DQAA") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) assets["assets/css/overrides.css"] = bs