|
@@ -25,6 +25,7 @@ package discover
|
|
|
import (
|
|
import (
|
|
|
"crypto/rand"
|
|
"crypto/rand"
|
|
|
"encoding/binary"
|
|
"encoding/binary"
|
|
|
|
|
+ "fmt"
|
|
|
"net"
|
|
"net"
|
|
|
"sort"
|
|
"sort"
|
|
|
"sync"
|
|
"sync"
|
|
@@ -56,7 +57,7 @@ type Table struct {
|
|
|
nursery []*Node // bootstrap nodes
|
|
nursery []*Node // bootstrap nodes
|
|
|
db *nodeDB // database of known nodes
|
|
db *nodeDB // database of known nodes
|
|
|
|
|
|
|
|
- refreshReq chan struct{}
|
|
|
|
|
|
|
+ refreshReq chan chan struct{}
|
|
|
closeReq chan struct{}
|
|
closeReq chan struct{}
|
|
|
closed chan struct{}
|
|
closed chan struct{}
|
|
|
|
|
|
|
@@ -102,7 +103,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
|
|
|
self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)),
|
|
self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)),
|
|
|
bonding: make(map[NodeID]*bondproc),
|
|
bonding: make(map[NodeID]*bondproc),
|
|
|
bondslots: make(chan struct{}, maxBondingPingPongs),
|
|
bondslots: make(chan struct{}, maxBondingPingPongs),
|
|
|
- refreshReq: make(chan struct{}),
|
|
|
|
|
|
|
+ refreshReq: make(chan chan struct{}),
|
|
|
closeReq: make(chan struct{}),
|
|
closeReq: make(chan struct{}),
|
|
|
closed: make(chan struct{}),
|
|
closed: make(chan struct{}),
|
|
|
}
|
|
}
|
|
@@ -179,21 +180,27 @@ func (tab *Table) Close() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Bootstrap sets the bootstrap nodes. These nodes are used to connect
|
|
|
|
|
-// to the network if the table is empty. Bootstrap will also attempt to
|
|
|
|
|
-// fill the table by performing random lookup operations on the
|
|
|
|
|
-// network.
|
|
|
|
|
-func (tab *Table) Bootstrap(nodes []*Node) {
|
|
|
|
|
|
|
+// SetFallbackNodes sets the initial points of contact. These nodes
|
|
|
|
|
+// are used to connect to the network if the table is empty and there
|
|
|
|
|
+// are no known nodes in the database.
|
|
|
|
|
+func (tab *Table) SetFallbackNodes(nodes []*Node) error {
|
|
|
|
|
+ for _, n := range nodes {
|
|
|
|
|
+ if err := n.validateComplete(); err != nil {
|
|
|
|
|
+ return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
tab.mutex.Lock()
|
|
tab.mutex.Lock()
|
|
|
- // TODO: maybe filter nodes with bad fields (nil, etc.) to avoid strange crashes
|
|
|
|
|
tab.nursery = make([]*Node, 0, len(nodes))
|
|
tab.nursery = make([]*Node, 0, len(nodes))
|
|
|
for _, n := range nodes {
|
|
for _, n := range nodes {
|
|
|
cpy := *n
|
|
cpy := *n
|
|
|
|
|
+ // Recompute cpy.sha because the node might not have been
|
|
|
|
|
+ // created by NewNode or ParseNode.
|
|
|
cpy.sha = crypto.Sha3Hash(n.ID[:])
|
|
cpy.sha = crypto.Sha3Hash(n.ID[:])
|
|
|
tab.nursery = append(tab.nursery, &cpy)
|
|
tab.nursery = append(tab.nursery, &cpy)
|
|
|
}
|
|
}
|
|
|
tab.mutex.Unlock()
|
|
tab.mutex.Unlock()
|
|
|
- tab.requestRefresh()
|
|
|
|
|
|
|
+ tab.refresh()
|
|
|
|
|
+ return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Resolve searches for a specific node with the given ID.
|
|
// Resolve searches for a specific node with the given ID.
|
|
@@ -224,26 +231,36 @@ func (tab *Table) Resolve(targetID NodeID) *Node {
|
|
|
// The given target does not need to be an actual node
|
|
// The given target does not need to be an actual node
|
|
|
// identifier.
|
|
// identifier.
|
|
|
func (tab *Table) Lookup(targetID NodeID) []*Node {
|
|
func (tab *Table) Lookup(targetID NodeID) []*Node {
|
|
|
|
|
+ return tab.lookup(targetID, true)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
|
|
|
var (
|
|
var (
|
|
|
target = crypto.Sha3Hash(targetID[:])
|
|
target = crypto.Sha3Hash(targetID[:])
|
|
|
asked = make(map[NodeID]bool)
|
|
asked = make(map[NodeID]bool)
|
|
|
seen = make(map[NodeID]bool)
|
|
seen = make(map[NodeID]bool)
|
|
|
reply = make(chan []*Node, alpha)
|
|
reply = make(chan []*Node, alpha)
|
|
|
pendingQueries = 0
|
|
pendingQueries = 0
|
|
|
|
|
+ result *nodesByDistance
|
|
|
)
|
|
)
|
|
|
// don't query further if we hit ourself.
|
|
// don't query further if we hit ourself.
|
|
|
// unlikely to happen often in practice.
|
|
// unlikely to happen often in practice.
|
|
|
asked[tab.self.ID] = true
|
|
asked[tab.self.ID] = true
|
|
|
|
|
|
|
|
- tab.mutex.Lock()
|
|
|
|
|
- // generate initial result set
|
|
|
|
|
- result := tab.closest(target, bucketSize)
|
|
|
|
|
- tab.mutex.Unlock()
|
|
|
|
|
-
|
|
|
|
|
- // If the result set is empty, all nodes were dropped, refresh.
|
|
|
|
|
- if len(result.entries) == 0 {
|
|
|
|
|
- tab.requestRefresh()
|
|
|
|
|
- return nil
|
|
|
|
|
|
|
+ for {
|
|
|
|
|
+ tab.mutex.Lock()
|
|
|
|
|
+ // generate initial result set
|
|
|
|
|
+ result = tab.closest(target, bucketSize)
|
|
|
|
|
+ tab.mutex.Unlock()
|
|
|
|
|
+ if len(result.entries) > 0 || !refreshIfEmpty {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ // The result set is empty, all nodes were dropped, refresh.
|
|
|
|
|
+ // We actually wait for the refresh to complete here. The very
|
|
|
|
|
+ // first query will hit this case and run the bootstrapping
|
|
|
|
|
+ // logic.
|
|
|
|
|
+ <-tab.refresh()
|
|
|
|
|
+ refreshIfEmpty = false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
for {
|
|
@@ -287,24 +304,24 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
|
|
|
return result.entries
|
|
return result.entries
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (tab *Table) requestRefresh() {
|
|
|
|
|
|
|
+func (tab *Table) refresh() <-chan struct{} {
|
|
|
|
|
+ done := make(chan struct{})
|
|
|
select {
|
|
select {
|
|
|
- case tab.refreshReq <- struct{}{}:
|
|
|
|
|
|
|
+ case tab.refreshReq <- done:
|
|
|
case <-tab.closed:
|
|
case <-tab.closed:
|
|
|
|
|
+ close(done)
|
|
|
}
|
|
}
|
|
|
|
|
+ return done
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// refreshLoop schedules doRefresh runs and coordinates shutdown.
|
|
|
func (tab *Table) refreshLoop() {
|
|
func (tab *Table) refreshLoop() {
|
|
|
- defer func() {
|
|
|
|
|
- tab.db.close()
|
|
|
|
|
- if tab.net != nil {
|
|
|
|
|
- tab.net.close()
|
|
|
|
|
- }
|
|
|
|
|
- close(tab.closed)
|
|
|
|
|
- }()
|
|
|
|
|
-
|
|
|
|
|
- timer := time.NewTicker(autoRefreshInterval)
|
|
|
|
|
- var done chan struct{}
|
|
|
|
|
|
|
+ var (
|
|
|
|
|
+ timer = time.NewTicker(autoRefreshInterval)
|
|
|
|
|
+ waiting []chan struct{} // accumulates waiting callers while doRefresh runs
|
|
|
|
|
+ done chan struct{} // where doRefresh reports completion
|
|
|
|
|
+ )
|
|
|
|
|
+loop:
|
|
|
for {
|
|
for {
|
|
|
select {
|
|
select {
|
|
|
case <-timer.C:
|
|
case <-timer.C:
|
|
@@ -312,20 +329,34 @@ func (tab *Table) refreshLoop() {
|
|
|
done = make(chan struct{})
|
|
done = make(chan struct{})
|
|
|
go tab.doRefresh(done)
|
|
go tab.doRefresh(done)
|
|
|
}
|
|
}
|
|
|
- case <-tab.refreshReq:
|
|
|
|
|
|
|
+ case req := <-tab.refreshReq:
|
|
|
|
|
+ waiting = append(waiting, req)
|
|
|
if done == nil {
|
|
if done == nil {
|
|
|
done = make(chan struct{})
|
|
done = make(chan struct{})
|
|
|
go tab.doRefresh(done)
|
|
go tab.doRefresh(done)
|
|
|
}
|
|
}
|
|
|
case <-done:
|
|
case <-done:
|
|
|
|
|
+ for _, ch := range waiting {
|
|
|
|
|
+ close(ch)
|
|
|
|
|
+ }
|
|
|
|
|
+ waiting = nil
|
|
|
done = nil
|
|
done = nil
|
|
|
case <-tab.closeReq:
|
|
case <-tab.closeReq:
|
|
|
- if done != nil {
|
|
|
|
|
- <-done
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ break loop
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if tab.net != nil {
|
|
|
|
|
+ tab.net.close()
|
|
|
|
|
+ }
|
|
|
|
|
+ if done != nil {
|
|
|
|
|
+ <-done
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, ch := range waiting {
|
|
|
|
|
+ close(ch)
|
|
|
|
|
+ }
|
|
|
|
|
+ tab.db.close()
|
|
|
|
|
+ close(tab.closed)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// doRefresh performs a lookup for a random target to keep buckets
|
|
// doRefresh performs a lookup for a random target to keep buckets
|
|
@@ -342,7 +373,7 @@ func (tab *Table) doRefresh(done chan struct{}) {
|
|
|
// We perform a lookup with a random target instead.
|
|
// We perform a lookup with a random target instead.
|
|
|
var target NodeID
|
|
var target NodeID
|
|
|
rand.Read(target[:])
|
|
rand.Read(target[:])
|
|
|
- result := tab.Lookup(target)
|
|
|
|
|
|
|
+ result := tab.lookup(target, false)
|
|
|
if len(result) > 0 {
|
|
if len(result) > 0 {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -366,7 +397,7 @@ func (tab *Table) doRefresh(done chan struct{}) {
|
|
|
tab.mutex.Unlock()
|
|
tab.mutex.Unlock()
|
|
|
|
|
|
|
|
// Finally, do a self lookup to fill up the buckets.
|
|
// Finally, do a self lookup to fill up the buckets.
|
|
|
- tab.Lookup(tab.self.ID)
|
|
|
|
|
|
|
+ tab.lookup(tab.self.ID, false)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// closest returns the n nodes in the table that are closest to the
|
|
// closest returns the n nodes in the table that are closest to the
|