Move top level packages to internal.
This commit is contained in:
138
internal/discover/PROTOCOL.md
Normal file
138
internal/discover/PROTOCOL.md
Normal file
@@ -0,0 +1,138 @@
|
||||
Node Discovery Protocol v2
|
||||
==========================
|
||||
|
||||
Mode of Operation
|
||||
-----------------
|
||||
|
||||
There are two distinct modes: "local discovery", performed on a LAN
|
||||
segment (broadcast domain) and "global discovery" performed over the
|
||||
Internet in general with the support of a well known server.
|
||||
|
||||
Local discovery does not use Query packets. Instead Announcement packets
|
||||
are sent periodically and each participating node keeps a table of the
|
||||
announcements it has seen. On multihomed hosts the announcement packets
|
||||
should be sent on each interface that syncthing will accept connections.
|
||||
|
||||
It is recommended that local discovery Announcement packets are sent on
|
||||
a 30 to 60 second interval, possibly with forced transmissions when a
|
||||
previously unknown node is discovered.
|
||||
|
||||
Global discovery is made possible by periodically updating a global server
|
||||
using Announcement packets indentical to those transmitted for local
|
||||
discovery. The node performing discovery will transmit a Query packet to
|
||||
the global server and expect an Announcement packet in response. In case
|
||||
the global server has no knowledge of the queried node ID, there will be
|
||||
no response. A timeout is to be used to determine lookup failure.
|
||||
|
||||
There is no message to unregister from the global server; instead
|
||||
registrations are forgotten after 60 minutes. It is recommended to
|
||||
send Announcement packets to the global server on a 30 minute interval.
|
||||
|
||||
Packet Formats
|
||||
--------------
|
||||
|
||||
The Announcement packet has the following 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Magic (0x9D79BC39) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Node Structure \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Extra Nodes |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Zero or more Node Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Node 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 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ ID (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Addresses |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Zero or more Address Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
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) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Port | 0x0000 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
This is the XDR encoding of:
|
||||
|
||||
struct Announcement {
|
||||
unsigned int Magic;
|
||||
Node This;
|
||||
Node Extra<>;
|
||||
}
|
||||
|
||||
struct Node {
|
||||
string ID<>;
|
||||
Address Addresses<>;
|
||||
}
|
||||
|
||||
struct Address {
|
||||
opaque IP<>;
|
||||
unsigned short Port;
|
||||
}
|
||||
|
||||
The first Node structure contains information about the sending node.
|
||||
The following zero or more Extra nodes contain information about other
|
||||
nodes known to the sending node.
|
||||
|
||||
In the Address structure, the IP field can be of three differnt kinds;
|
||||
|
||||
- A zero length indicates that the IP address should be taken from the
|
||||
source address of the announcement packet, be it IPv4 or IPv6. The
|
||||
source address must be a valid unicast address. This is only valid
|
||||
in the first node structure, not in the list of extras.
|
||||
|
||||
- A four byte length indicates that the address is an IPv4 unicast
|
||||
address.
|
||||
|
||||
- A sixteen byte length indicates that the address is an IPv6 unicast
|
||||
address.
|
||||
|
||||
The Query packet has the following 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Magic Number (0x2CA856F5) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Node ID |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Node ID (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
This is the XDR encoding of:
|
||||
|
||||
struct Announcement {
|
||||
unsigned int MagicNumber;
|
||||
string NodeID<>;
|
||||
}
|
||||
17
internal/discover/debug.go
Normal file
17
internal/discover/debug.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "discover") || os.Getenv("STTRACE") == "all"
|
||||
l = logger.DefaultLogger
|
||||
)
|
||||
501
internal/discover/discover.go
Normal file
501
internal/discover/discover.go
Normal file
@@ -0,0 +1,501 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/beacon"
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
"github.com/syncthing/syncthing/internal/protocol"
|
||||
)
|
||||
|
||||
type Discoverer struct {
|
||||
myID protocol.NodeID
|
||||
listenAddrs []string
|
||||
localBcastIntv time.Duration
|
||||
globalBcastIntv time.Duration
|
||||
errorRetryIntv time.Duration
|
||||
cacheLifetime time.Duration
|
||||
broadcastBeacon beacon.Interface
|
||||
multicastBeacon beacon.Interface
|
||||
registry map[protocol.NodeID][]cacheEntry
|
||||
registryLock sync.RWMutex
|
||||
extServer string
|
||||
extPort uint16
|
||||
localBcastTick <-chan time.Time
|
||||
stopGlobal chan struct{}
|
||||
globalWG sync.WaitGroup
|
||||
forcedBcastTick chan time.Time
|
||||
extAnnounceOK bool
|
||||
extAnnounceOKmut sync.Mutex
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
addr string
|
||||
seen time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
ErrIncorrectMagic = errors.New("incorrect magic number")
|
||||
)
|
||||
|
||||
func NewDiscoverer(id protocol.NodeID, 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.NodeID][]cacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discoverer) StartLocal(localPort int, localMCAddr string) {
|
||||
if localPort > 0 {
|
||||
bb, err := beacon.NewBroadcast(localPort)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Infoln("Local discovery over IPv4 unavailable")
|
||||
} else {
|
||||
d.broadcastBeacon = bb
|
||||
go d.recvAnnouncements(bb)
|
||||
}
|
||||
}
|
||||
|
||||
if len(localMCAddr) > 0 {
|
||||
mb, err := beacon.NewMulticast(localMCAddr)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
}
|
||||
l.Infoln("Local discovery over IPv6 unavailable")
|
||||
} else {
|
||||
d.multicastBeacon = mb
|
||||
go d.recvAnnouncements(mb)
|
||||
}
|
||||
}
|
||||
|
||||
if d.broadcastBeacon == nil && d.multicastBeacon == nil {
|
||||
l.Warnln("Local discovery unavailable")
|
||||
} else {
|
||||
d.localBcastTick = time.Tick(d.localBcastIntv)
|
||||
d.forcedBcastTick = make(chan time.Time)
|
||||
go d.sendLocalAnnouncements()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discoverer) StartGlobal(server string, extPort uint16) {
|
||||
// Wait for any previous announcer to stop before starting a new one.
|
||||
d.globalWG.Wait()
|
||||
d.extServer = server
|
||||
d.extPort = extPort
|
||||
d.stopGlobal = make(chan struct{})
|
||||
d.globalWG.Add(1)
|
||||
go d.sendExternalAnnouncements()
|
||||
}
|
||||
|
||||
func (d *Discoverer) StopGlobal() {
|
||||
if d.stopGlobal != nil {
|
||||
close(d.stopGlobal)
|
||||
d.globalWG.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discoverer) ExtAnnounceOK() bool {
|
||||
d.extAnnounceOKmut.Lock()
|
||||
defer d.extAnnounceOKmut.Unlock()
|
||||
return d.extAnnounceOK
|
||||
}
|
||||
|
||||
func (d *Discoverer) Lookup(node protocol.NodeID) []string {
|
||||
d.registryLock.Lock()
|
||||
cached := d.filterCached(d.registry[node])
|
||||
d.registryLock.Unlock()
|
||||
|
||||
if len(cached) > 0 {
|
||||
addrs := make([]string, len(cached))
|
||||
for i := range cached {
|
||||
addrs[i] = cached[i].addr
|
||||
}
|
||||
return addrs
|
||||
} else if len(d.extServer) != 0 {
|
||||
addrs := d.externalLookup(node)
|
||||
cached = make([]cacheEntry, len(addrs))
|
||||
for i := range addrs {
|
||||
cached[i] = cacheEntry{
|
||||
addr: addrs[i],
|
||||
seen: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
d.registryLock.Lock()
|
||||
d.registry[node] = cached
|
||||
d.registryLock.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Discoverer) Hint(node string, addrs []string) {
|
||||
resAddrs := resolveAddrs(addrs)
|
||||
var id protocol.NodeID
|
||||
id.UnmarshalText([]byte(node))
|
||||
d.registerNode(nil, Node{
|
||||
Addresses: resAddrs,
|
||||
ID: id[:],
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Discoverer) All() map[protocol.NodeID][]cacheEntry {
|
||||
d.registryLock.RLock()
|
||||
nodes := make(map[protocol.NodeID][]cacheEntry, len(d.registry))
|
||||
for node, addrs := range d.registry {
|
||||
addrsCopy := make([]cacheEntry, len(addrs))
|
||||
copy(addrsCopy, addrs)
|
||||
nodes[node] = addrsCopy
|
||||
}
|
||||
d.registryLock.RUnlock()
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (d *Discoverer) announcementPkt() []byte {
|
||||
var addrs []Address
|
||||
for _, astr := range d.listenAddrs {
|
||||
addr, err := net.ResolveTCPAddr("tcp", astr)
|
||||
if err != nil {
|
||||
l.Warnln("%v: not announcing %s", err, astr)
|
||||
continue
|
||||
} else if debug {
|
||||
l.Debugf("discover: announcing %s: %#v", astr, addr)
|
||||
}
|
||||
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
|
||||
addrs = append(addrs, Address{Port: uint16(addr.Port)})
|
||||
} else if bs := addr.IP.To4(); bs != nil {
|
||||
addrs = append(addrs, Address{IP: bs, Port: uint16(addr.Port)})
|
||||
} else if bs := addr.IP.To16(); bs != nil {
|
||||
addrs = append(addrs, Address{IP: bs, Port: uint16(addr.Port)})
|
||||
}
|
||||
}
|
||||
var pkt = Announce{
|
||||
Magic: AnnouncementMagic,
|
||||
This: Node{d.myID[:], addrs},
|
||||
}
|
||||
return pkt.MarshalXDR()
|
||||
}
|
||||
|
||||
func (d *Discoverer) sendLocalAnnouncements() {
|
||||
var addrs = resolveAddrs(d.listenAddrs)
|
||||
|
||||
var pkt = Announce{
|
||||
Magic: AnnouncementMagic,
|
||||
This: Node{d.myID[:], addrs},
|
||||
}
|
||||
msg := pkt.MarshalXDR()
|
||||
|
||||
for {
|
||||
if d.multicastBeacon != nil {
|
||||
d.multicastBeacon.Send(msg)
|
||||
}
|
||||
if d.broadcastBeacon != nil {
|
||||
d.broadcastBeacon.Send(msg)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-d.localBcastTick:
|
||||
case <-d.forcedBcastTick:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discoverer) sendExternalAnnouncements() {
|
||||
defer d.globalWG.Done()
|
||||
|
||||
remote, err := net.ResolveUDPAddr("udp", d.extServer)
|
||||
for err != nil {
|
||||
l.Warnf("Global discovery: %v; trying again in %v", err, d.errorRetryIntv)
|
||||
time.Sleep(d.errorRetryIntv)
|
||||
remote, err = net.ResolveUDPAddr("udp", d.extServer)
|
||||
}
|
||||
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
for err != nil {
|
||||
l.Warnf("Global discovery: %v; trying again in %v", err, d.errorRetryIntv)
|
||||
time.Sleep(d.errorRetryIntv)
|
||||
conn, err = net.ListenUDP("udp", nil)
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if d.extPort != 0 {
|
||||
var pkt = Announce{
|
||||
Magic: AnnouncementMagic,
|
||||
This: Node{d.myID[:], []Address{{Port: d.extPort}}},
|
||||
}
|
||||
buf = pkt.MarshalXDR()
|
||||
} else {
|
||||
buf = d.announcementPkt()
|
||||
}
|
||||
|
||||
var bcastTick = time.Tick(d.globalBcastIntv)
|
||||
var errTick <-chan time.Time
|
||||
|
||||
sendOneAnnouncement := func() {
|
||||
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: warning:", err)
|
||||
}
|
||||
ok = false
|
||||
} else {
|
||||
// Verify that the announce server responds positively for our node ID
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
res := d.externalLookup(d.myID)
|
||||
if debug {
|
||||
l.Debugln("discover: external lookup check:", res)
|
||||
}
|
||||
ok = len(res) > 0
|
||||
}
|
||||
|
||||
d.extAnnounceOKmut.Lock()
|
||||
d.extAnnounceOK = ok
|
||||
d.extAnnounceOKmut.Unlock()
|
||||
|
||||
if ok {
|
||||
errTick = nil
|
||||
} else if errTick != nil {
|
||||
errTick = time.Tick(d.errorRetryIntv)
|
||||
}
|
||||
}
|
||||
|
||||
// Announce once, immediately
|
||||
sendOneAnnouncement()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-d.stopGlobal:
|
||||
break loop
|
||||
|
||||
case <-errTick:
|
||||
sendOneAnnouncement()
|
||||
|
||||
case <-bcastTick:
|
||||
sendOneAnnouncement()
|
||||
}
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugln("discover: stopping global")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discoverer) recvAnnouncements(b beacon.Interface) {
|
||||
for {
|
||||
buf, addr := b.Recv()
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: read announcement from %s:\n%s", addr, hex.Dump(buf))
|
||||
}
|
||||
|
||||
var pkt Announce
|
||||
err := pkt.UnmarshalXDR(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
continue
|
||||
}
|
||||
|
||||
var newNode bool
|
||||
if bytes.Compare(pkt.This.ID, d.myID[:]) != 0 {
|
||||
newNode = d.registerNode(addr, pkt.This)
|
||||
}
|
||||
|
||||
if newNode {
|
||||
select {
|
||||
case d.forcedBcastTick <- time.Now():
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discoverer) registerNode(addr net.Addr, node Node) bool {
|
||||
var id protocol.NodeID
|
||||
copy(id[:], node.ID)
|
||||
|
||||
d.registryLock.RLock()
|
||||
current := d.filterCached(d.registry[id])
|
||||
d.registryLock.RUnlock()
|
||||
|
||||
orig := current
|
||||
|
||||
for _, a := range node.Addresses {
|
||||
var nodeAddr string
|
||||
if len(a.IP) > 0 {
|
||||
nodeAddr = 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)
|
||||
nodeAddr = ua.String()
|
||||
}
|
||||
for i := range current {
|
||||
if current[i].addr == nodeAddr {
|
||||
current[i].seen = time.Now()
|
||||
goto done
|
||||
}
|
||||
}
|
||||
current = append(current, cacheEntry{
|
||||
addr: nodeAddr,
|
||||
seen: time.Now(),
|
||||
})
|
||||
done:
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: register: %v -> %v", id, current)
|
||||
}
|
||||
|
||||
d.registryLock.Lock()
|
||||
d.registry[id] = current
|
||||
d.registryLock.Unlock()
|
||||
|
||||
if len(current) > len(orig) {
|
||||
addrs := make([]string, len(current))
|
||||
for i := range current {
|
||||
addrs[i] = current[i].addr
|
||||
}
|
||||
events.Default.Log(events.NodeDiscovered, map[string]interface{}{
|
||||
"node": id.String(),
|
||||
"addrs": addrs,
|
||||
})
|
||||
}
|
||||
|
||||
return len(current) > len(orig)
|
||||
}
|
||||
|
||||
func (d *Discoverer) externalLookup(node protocol.NodeID) []string {
|
||||
extIP, err := net.ResolveUDPAddr("udp", d.extServer)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover: %v; no external lookup", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := net.DialUDP("udp", nil, extIP)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover: %v; no external lookup", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover: %v; no external lookup", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := Query{QueryMagic, node[:]}.MarshalXDR()
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover: %v; no external lookup", 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 node ID
|
||||
return nil
|
||||
}
|
||||
if debug {
|
||||
l.Debugf("discover: %v; no external lookup", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: read external:\n%s", hex.Dump(buf[:n]))
|
||||
}
|
||||
|
||||
var pkt Announce
|
||||
err = pkt.UnmarshalXDR(buf[:n])
|
||||
if err != nil && err != io.EOF {
|
||||
if debug {
|
||||
l.Debugln("discover:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var addrs []string
|
||||
for _, a := range pkt.This.Addresses {
|
||||
nodeAddr := net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port)))
|
||||
addrs = append(addrs, nodeAddr)
|
||||
}
|
||||
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 {
|
||||
if debug {
|
||||
l.Debugf("removing cached address %s: seen %v ago", c[i].addr, ago)
|
||||
}
|
||||
c[i] = c[len(c)-1]
|
||||
c = c[:len(c)-1]
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func addrToAddr(addr *net.TCPAddr) Address {
|
||||
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
|
||||
return Address{Port: uint16(addr.Port)}
|
||||
} else if bs := addr.IP.To4(); bs != nil {
|
||||
return Address{IP: bs, Port: uint16(addr.Port)}
|
||||
} else if bs := addr.IP.To16(); bs != nil {
|
||||
return Address{IP: bs, Port: uint16(addr.Port)}
|
||||
}
|
||||
return Address{}
|
||||
}
|
||||
|
||||
func resolveAddrs(addrs []string) []Address {
|
||||
var raddrs []Address
|
||||
for _, addrStr := range addrs {
|
||||
addrRes, err := net.ResolveTCPAddr("tcp", addrStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
addr := addrToAddr(addrRes)
|
||||
if len(addr.IP) > 0 {
|
||||
raddrs = append(raddrs, addr)
|
||||
} else {
|
||||
raddrs = append(raddrs, Address{Port: addr.Port})
|
||||
}
|
||||
}
|
||||
return raddrs
|
||||
}
|
||||
7
internal/discover/discover_test.go
Normal file
7
internal/discover/discover_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package discover_test
|
||||
|
||||
// Empty test file to generate 0% coverage rather than no coverage
|
||||
6
internal/discover/doc.go
Normal file
6
internal/discover/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package discover implements the node discovery protocol.
|
||||
package discover
|
||||
31
internal/discover/packets.go
Normal file
31
internal/discover/packets.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package discover
|
||||
|
||||
const (
|
||||
AnnouncementMagic = 0x9D79BC39
|
||||
QueryMagic = 0x2CA856F5
|
||||
)
|
||||
|
||||
type Query struct {
|
||||
Magic uint32
|
||||
NodeID []byte // max:32
|
||||
}
|
||||
|
||||
type Announce struct {
|
||||
Magic uint32
|
||||
This Node
|
||||
Extra []Node // max:16
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
ID []byte // max:32
|
||||
Addresses []Address // max:16
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
IP []byte // max:16
|
||||
Port uint16
|
||||
}
|
||||
317
internal/discover/packets_xdr.go
Normal file
317
internal/discover/packets_xdr.go
Normal file
@@ -0,0 +1,317 @@
|
||||
// ************************************************************
|
||||
// This file is automatically generated by genxdr. Do not edit.
|
||||
// ************************************************************
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/calmh/xdr"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Query 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Magic |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Node ID |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Node ID (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Query {
|
||||
unsigned int Magic;
|
||||
opaque NodeID<32>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Query) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o Query) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Query) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o Query) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteUint32(o.Magic)
|
||||
if len(o.NodeID) > 32 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteBytes(o.NodeID)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Query) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Query) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Query) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Magic = xr.ReadUint32()
|
||||
o.NodeID = xr.ReadBytesMax(32)
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Announce 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Magic |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Node |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Extra |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Zero or more Node Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Announce {
|
||||
unsigned int Magic;
|
||||
Node This;
|
||||
Node Extra<16>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Announce) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o Announce) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Announce) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o Announce) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteUint32(o.Magic)
|
||||
_, err := o.This.encodeXDR(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
if len(o.Extra) > 16 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteUint32(uint32(len(o.Extra)))
|
||||
for i := range o.Extra {
|
||||
_, err := o.Extra[i].encodeXDR(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
}
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Announce) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Announce) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Announce) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Magic = xr.ReadUint32()
|
||||
(&o.This).decodeXDR(xr)
|
||||
_ExtraSize := int(xr.ReadUint32())
|
||||
if _ExtraSize > 16 {
|
||||
return xdr.ErrElementSizeExceeded
|
||||
}
|
||||
o.Extra = make([]Node, _ExtraSize)
|
||||
for i := range o.Extra {
|
||||
(&o.Extra[i]).decodeXDR(xr)
|
||||
}
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Node 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 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ ID (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Addresses |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Zero or more Address Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct Node {
|
||||
opaque ID<32>;
|
||||
Address Addresses<16>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o Node) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o Node) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Node) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o Node) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
if len(o.ID) > 32 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteBytes(o.ID)
|
||||
if len(o.Addresses) > 16 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteUint32(uint32(len(o.Addresses)))
|
||||
for i := range o.Addresses {
|
||||
_, err := o.Addresses[i].encodeXDR(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
}
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *Node) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Node) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Node) decodeXDR(xr *xdr.Reader) error {
|
||||
o.ID = xr.ReadBytesMax(32)
|
||||
_AddressesSize := int(xr.ReadUint32())
|
||||
if _AddressesSize > 16 {
|
||||
return xdr.ErrElementSizeExceeded
|
||||
}
|
||||
o.Addresses = make([]Address, _AddressesSize)
|
||||
for i := range o.Addresses {
|
||||
(&o.Addresses[i]).decodeXDR(xr)
|
||||
}
|
||||
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.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o Address) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o Address) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o Address) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
if len(o.IP) > 16 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
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.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Address) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *Address) decodeXDR(xr *xdr.Reader) error {
|
||||
o.IP = xr.ReadBytesMax(16)
|
||||
o.Port = xr.ReadUint16()
|
||||
return xr.Error()
|
||||
}
|
||||
Reference in New Issue
Block a user