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
241 lines
5.4 KiB
Go
241 lines
5.4 KiB
Go
// Copyright (C) 2014 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 discover
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/syncthing/protocol"
|
|
"github.com/syncthing/syncthing/lib/sync"
|
|
)
|
|
|
|
func init() {
|
|
for _, proto := range []string{"udp", "udp4", "udp6"} {
|
|
Register(proto, func(uri *url.URL, pkt *Announce) (Client, error) {
|
|
c := &UDPClient{
|
|
wg: sync.NewWaitGroup(),
|
|
mut: sync.NewRWMutex(),
|
|
}
|
|
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()
|
|
|
|
conn, err := net.ListenUDP(d.url.Scheme, d.listenAddress)
|
|
for err != nil {
|
|
if debug {
|
|
l.Debugf("discover %s: broadcast listen: %v; trying again in %v", d.url, err, d.errorRetryInterval)
|
|
}
|
|
select {
|
|
case <-d.stop:
|
|
return
|
|
case <-time.After(d.errorRetryInterval):
|
|
}
|
|
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 {
|
|
if debug {
|
|
l.Debugf("discover %s: broadcast resolve: %v; trying again in %v", d.url, err, d.errorRetryInterval)
|
|
}
|
|
select {
|
|
case <-d.stop:
|
|
return
|
|
case <-time.After(d.errorRetryInterval):
|
|
}
|
|
remote, err = net.ResolveUDPAddr(d.url.Scheme, d.url.Host)
|
|
}
|
|
|
|
timer := time.NewTimer(0)
|
|
for {
|
|
select {
|
|
case <-d.stop:
|
|
return
|
|
|
|
case <-timer.C:
|
|
var ok bool
|
|
|
|
if debug {
|
|
l.Debugf("discover %s: broadcast: Sending self announcement to %v", d.url, remote)
|
|
}
|
|
|
|
_, err := conn.WriteTo(pkt, remote)
|
|
if err != nil {
|
|
if debug {
|
|
l.Debugf("discover %s: broadcast: Failed to send self announcement: %s", d.url, err)
|
|
}
|
|
ok = false
|
|
} else {
|
|
// Verify that the announce server responds positively for our device ID
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
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(pkt.This.Addresses) > 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) (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 Announce{}, err
|
|
}
|
|
|
|
conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP)
|
|
if err != nil {
|
|
if debug {
|
|
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
|
}
|
|
return Announce{}, err
|
|
}
|
|
defer conn.Close()
|
|
|
|
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
|
if err != nil {
|
|
if debug {
|
|
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
|
}
|
|
return Announce{}, err
|
|
}
|
|
|
|
buf := Query{QueryMagic, device[:]}.MustMarshalXDR()
|
|
_, err = conn.Write(buf)
|
|
if err != nil {
|
|
if debug {
|
|
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
|
}
|
|
return Announce{}, err
|
|
}
|
|
|
|
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 Announce{}, err
|
|
}
|
|
if debug {
|
|
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
|
}
|
|
return Announce{}, err
|
|
}
|
|
|
|
var pkt Announce
|
|
err = pkt.UnmarshalXDR(buf[:n])
|
|
if err != nil && err != io.EOF {
|
|
if debug {
|
|
l.Debugf("discover %s: Lookup(%s): %s\n%s", d.url, device, err, hex.Dump(buf[:n]))
|
|
}
|
|
return Announce{}, err
|
|
}
|
|
|
|
if debug {
|
|
l.Debugf("discover %s: Lookup(%s) result: %v relays: %v", d.url, device, pkt.This.Addresses, pkt.This.Relays)
|
|
}
|
|
return pkt, nil
|
|
}
|
|
|
|
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()
|
|
}
|