2018-04-28 02:16:12 +02:00
|
|
|
package dht
|
|
|
|
|
2018-05-13 22:02:46 +02:00
|
|
|
import (
|
|
|
|
"math/rand"
|
|
|
|
"net"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2018-06-14 17:48:02 +02:00
|
|
|
"github.com/lbryio/reflector.go/dht/bits"
|
2018-05-13 22:02:46 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
bootstrapDefaultRefreshDuration = 15 * time.Minute
|
|
|
|
)
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// BootstrapNode is a configured node setup for testing.
|
2018-04-28 02:16:12 +02:00
|
|
|
type BootstrapNode struct {
|
2018-05-13 22:02:46 +02:00
|
|
|
Node
|
|
|
|
|
|
|
|
initialPingInterval time.Duration
|
|
|
|
checkInterval time.Duration
|
|
|
|
|
|
|
|
nlock *sync.RWMutex
|
|
|
|
nodes []peer
|
2018-06-14 17:48:02 +02:00
|
|
|
nodeKeys map[bits.Bitmap]int
|
2018-05-13 22:02:46 +02:00
|
|
|
}
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// NewBootstrapNode returns a BootstrapNode pointer.
|
2018-06-14 17:48:02 +02:00
|
|
|
func NewBootstrapNode(id bits.Bitmap, initialPingInterval, rePingInterval time.Duration) *BootstrapNode {
|
2018-05-13 22:02:46 +02:00
|
|
|
b := &BootstrapNode{
|
|
|
|
Node: *NewNode(id),
|
|
|
|
|
|
|
|
initialPingInterval: initialPingInterval,
|
|
|
|
checkInterval: rePingInterval,
|
|
|
|
|
|
|
|
nlock: &sync.RWMutex{},
|
|
|
|
nodes: make([]peer, 0),
|
2018-06-14 17:48:02 +02:00
|
|
|
nodeKeys: make(map[bits.Bitmap]int),
|
2018-05-13 22:02:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
b.requestHandler = b.handleRequest
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add manually adds a contact
|
|
|
|
func (b *BootstrapNode) Add(c Contact) {
|
|
|
|
b.upsert(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connect connects to the given connection and starts any background threads necessary
|
|
|
|
func (b *BootstrapNode) Connect(conn UDPConn) error {
|
|
|
|
err := b.Node.Connect(conn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("[%s] bootstrap: node connected", b.id.HexShort())
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
t := time.NewTicker(b.checkInterval / 5)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-t.C:
|
|
|
|
b.check()
|
2018-05-24 19:05:05 +02:00
|
|
|
case <-b.stop.Ch():
|
2018-05-13 22:02:46 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// upsert adds the contact to the list, or updates the lastPinged time
|
2018-05-13 22:02:46 +02:00
|
|
|
func (b *BootstrapNode) upsert(c Contact) {
|
|
|
|
b.nlock.Lock()
|
|
|
|
defer b.nlock.Unlock()
|
|
|
|
|
2018-05-19 19:05:30 +02:00
|
|
|
if i, exists := b.nodeKeys[c.ID]; exists {
|
|
|
|
log.Debugf("[%s] bootstrap: touching contact %s", b.id.HexShort(), b.nodes[i].Contact.ID.HexShort())
|
2018-05-13 22:02:46 +02:00
|
|
|
b.nodes[i].Touch()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-19 19:05:30 +02:00
|
|
|
log.Debugf("[%s] bootstrap: adding new contact %s", b.id.HexShort(), c.ID.HexShort())
|
|
|
|
b.nodeKeys[c.ID] = len(b.nodes)
|
2018-05-13 22:02:46 +02:00
|
|
|
b.nodes = append(b.nodes, peer{c, time.Now(), 0})
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove removes the contact from the list
|
|
|
|
func (b *BootstrapNode) remove(c Contact) {
|
|
|
|
b.nlock.Lock()
|
|
|
|
defer b.nlock.Unlock()
|
|
|
|
|
2018-05-19 19:05:30 +02:00
|
|
|
i, exists := b.nodeKeys[c.ID]
|
2018-05-13 22:02:46 +02:00
|
|
|
if !exists {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-19 19:05:30 +02:00
|
|
|
log.Debugf("[%s] bootstrap: removing contact %s", b.id.HexShort(), c.ID.HexShort())
|
2018-05-13 22:02:46 +02:00
|
|
|
b.nodes = append(b.nodes[:i], b.nodes[i+1:]...)
|
2018-05-19 19:05:30 +02:00
|
|
|
delete(b.nodeKeys, c.ID)
|
2018-05-13 22:02:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// get returns up to `limit` random contacts from the list
|
|
|
|
func (b *BootstrapNode) get(limit int) []Contact {
|
|
|
|
b.nlock.RLock()
|
|
|
|
defer b.nlock.RUnlock()
|
|
|
|
|
|
|
|
if len(b.nodes) < limit {
|
|
|
|
limit = len(b.nodes)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := make([]Contact, limit)
|
|
|
|
for i, k := range randKeys(len(b.nodes))[:limit] {
|
2018-05-19 19:05:30 +02:00
|
|
|
ret[i] = b.nodes[k].Contact
|
2018-05-13 22:02:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// ping pings a node. if the node responds, it is added to the list. otherwise, it is removed
|
|
|
|
func (b *BootstrapNode) ping(c Contact) {
|
2018-05-24 23:49:43 +02:00
|
|
|
b.stop.Add(1)
|
|
|
|
defer b.stop.Done()
|
2018-05-13 22:02:46 +02:00
|
|
|
|
2018-05-19 19:05:30 +02:00
|
|
|
resCh, cancel := b.SendCancelable(c, Request{Method: pingMethod})
|
2018-05-13 22:02:46 +02:00
|
|
|
|
|
|
|
var res *Response
|
|
|
|
|
|
|
|
select {
|
|
|
|
case res = <-resCh:
|
2018-05-24 19:05:05 +02:00
|
|
|
case <-b.stop.Ch():
|
2018-05-13 22:02:46 +02:00
|
|
|
cancel()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res != nil && res.Data == pingSuccessResponse {
|
|
|
|
b.upsert(c)
|
|
|
|
} else {
|
|
|
|
b.remove(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BootstrapNode) check() {
|
|
|
|
b.nlock.RLock()
|
|
|
|
defer b.nlock.RUnlock()
|
|
|
|
|
|
|
|
for i := range b.nodes {
|
|
|
|
if !b.nodes[i].ActiveInLast(b.checkInterval) {
|
2018-05-19 19:05:30 +02:00
|
|
|
go b.ping(b.nodes[i].Contact)
|
2018-05-13 22:02:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleRequest handles the requests received from udp.
|
|
|
|
func (b *BootstrapNode) handleRequest(addr *net.UDPAddr, request Request) {
|
|
|
|
switch request.Method {
|
|
|
|
case pingMethod:
|
2018-05-30 03:38:55 +02:00
|
|
|
if err := b.sendMessage(addr, Response{ID: request.ID, NodeID: b.id, Data: pingSuccessResponse}); err != nil {
|
|
|
|
log.Error("error sending response message - ", err)
|
|
|
|
}
|
2018-05-13 22:02:46 +02:00
|
|
|
case findNodeMethod:
|
|
|
|
if request.Arg == nil {
|
|
|
|
log.Errorln("request is missing arg")
|
|
|
|
return
|
|
|
|
}
|
2018-05-30 03:38:55 +02:00
|
|
|
if err := b.sendMessage(addr, Response{
|
2018-05-13 22:02:46 +02:00
|
|
|
ID: request.ID,
|
|
|
|
NodeID: b.id,
|
|
|
|
Contacts: b.get(bucketSize),
|
2018-05-30 03:38:55 +02:00
|
|
|
}); err != nil {
|
|
|
|
log.Error("error sending 'findnodemethod' response message - ", err)
|
|
|
|
}
|
2018-05-13 22:02:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
log.Debugf("[%s] bootstrap: queuing %s to ping", b.id.HexShort(), request.NodeID.HexShort())
|
|
|
|
<-time.After(b.initialPingInterval)
|
2018-05-19 19:05:30 +02:00
|
|
|
b.ping(Contact{ID: request.NodeID, IP: addr.IP, Port: addr.Port})
|
2018-05-13 22:02:46 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func randKeys(max int) []int {
|
|
|
|
keys := make([]int, max)
|
|
|
|
for k := range keys {
|
|
|
|
keys[k] = k
|
|
|
|
}
|
|
|
|
rand.Shuffle(max, func(i, j int) {
|
|
|
|
keys[i], keys[j] = keys[j], keys[i]
|
|
|
|
})
|
|
|
|
return keys
|
2018-04-28 02:16:12 +02:00
|
|
|
}
|