5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-11-26 10:41:40 +00:00

some minor refactoring to dht callbacks and searches, work in progress

This commit is contained in:
Arceliar 2019-06-25 19:31:29 -05:00
parent 2fd3ac6837
commit 29a0f8b572
4 changed files with 61 additions and 58 deletions

View File

@ -517,21 +517,14 @@ func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, err
rq := dhtReqKey{info.key, target} rq := dhtReqKey{info.key, target}
sendPing := func() { sendPing := func() {
c.dht.addCallback(&rq, func(res *dhtRes) { c.dht.addCallback(&rq, func(res *dhtRes) {
defer func() { recover() }() resCh <- res
select {
case resCh <- res:
default:
}
}) })
c.dht.ping(&info, &target) c.dht.ping(&info, &target)
} }
c.router.doAdmin(sendPing) c.router.doAdmin(sendPing)
go func() {
time.Sleep(6 * time.Second)
close(resCh)
}()
// TODO: do something better than the below... // TODO: do something better than the below...
for res := range resCh { res := <-resCh
if res != nil {
r := DHTRes{ r := DHTRes{
Coords: append([]byte{}, res.Coords...), Coords: append([]byte{}, res.Coords...),
} }

View File

@ -128,7 +128,7 @@ func (c *Conn) startSearch() {
c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
} }
// Continue the search // Continue the search
c.core.searches.continueSearch(sinfo) sinfo.continueSearch()
} }
// Take a copy of the session object, in case it changes later // Take a copy of the session object, in case it changes later
c.mutex.RLock() c.mutex.RLock()

View File

@ -70,7 +70,7 @@ type dht struct {
nodeID crypto.NodeID nodeID crypto.NodeID
peers chan *dhtInfo // other goroutines put incoming dht updates here peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks
// These next two could be replaced by a single linked list or similar... // These next two could be replaced by a single linked list or similar...
table map[crypto.NodeID]*dhtInfo table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo imp []*dhtInfo
@ -88,7 +88,7 @@ func (t *dht) init(c *Core) {
}() }()
t.nodeID = *t.core.NodeID() t.nodeID = *t.core.NodeID()
t.peers = make(chan *dhtInfo, 1024) t.peers = make(chan *dhtInfo, 1024)
t.callbacks = make(map[dhtReqKey]dht_callbackInfo) t.callbacks = make(map[dhtReqKey][]dht_callbackInfo)
t.reset() t.reset()
} }
@ -244,15 +244,17 @@ type dht_callbackInfo struct {
// Adds a callback and removes it after some timeout. // Adds a callback and removes it after some timeout.
func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) {
info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)} info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)}
t.callbacks[*rq] = info t.callbacks[*rq] = append(t.callbacks[*rq], info)
} }
// Reads a lookup response, checks that we had sent a matching request, and processes the response info. // Reads a lookup response, checks that we had sent a matching request, and processes the response info.
// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses
func (t *dht) handleRes(res *dhtRes) { func (t *dht) handleRes(res *dhtRes) {
rq := dhtReqKey{res.Key, res.Dest} rq := dhtReqKey{res.Key, res.Dest}
if callback, isIn := t.callbacks[rq]; isIn { if callbacks, isIn := t.callbacks[rq]; isIn {
for _, callback := range callbacks {
callback.f(res) callback.f(res)
}
delete(t.callbacks, rq) delete(t.callbacks, rq)
} }
_, isIn := t.reqs[rq] _, isIn := t.reqs[rq]
@ -326,10 +328,15 @@ func (t *dht) doMaintenance() {
} }
} }
t.reqs = newReqs t.reqs = newReqs
newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks)) newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks))
for key, callback := range t.callbacks { for key, cs := range t.callbacks {
if now.Before(callback.time) { for _, c := range cs {
newCallbacks[key] = callback if now.Before(c.time) {
newCallbacks[key] = append(newCallbacks[key], c)
} else {
// Signal failure
c.f(nil)
}
} }
} }
t.callbacks = newCallbacks t.callbacks = newCallbacks

View File

@ -33,6 +33,7 @@ const search_RETRY_TIME = time.Second
// Information about an ongoing search. // Information about an ongoing search.
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct { type searchInfo struct {
core *Core
dest crypto.NodeID dest crypto.NodeID
mask crypto.NodeID mask crypto.NodeID
time time.Time time time.Time
@ -40,6 +41,7 @@ type searchInfo struct {
toVisit []*dhtInfo toVisit []*dhtInfo
visited map[crypto.NodeID]bool visited map[crypto.NodeID]bool
callback func(*sessionInfo, error) callback func(*sessionInfo, error)
// TODO context.Context for timeout and cancellation
} }
// This stores a map of active searches. // This stores a map of active searches.
@ -49,7 +51,7 @@ type searches struct {
searches map[crypto.NodeID]*searchInfo searches map[crypto.NodeID]*searchInfo
} }
// Intializes the searches struct. // Initializes the searches struct.
func (s *searches) init(core *Core) { func (s *searches) init(core *Core) {
s.core = core s.core = core
s.reconfigure = make(chan chan error, 1) s.reconfigure = make(chan chan error, 1)
@ -65,12 +67,13 @@ func (s *searches) init(core *Core) {
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info. // Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
now := time.Now() now := time.Now()
for dest, sinfo := range s.searches { //for dest, sinfo := range s.searches {
if now.Sub(sinfo.time) > time.Minute { // if now.Sub(sinfo.time) > time.Minute {
delete(s.searches, dest) // delete(s.searches, dest)
} // }
} //}
info := searchInfo{ info := searchInfo{
core: s.core,
dest: *dest, dest: *dest,
mask: *mask, mask: *mask,
time: now.Add(-time.Second), time: now.Add(-time.Second),
@ -82,30 +85,29 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Checks if there's an ongoing search relaed to a dhtRes. // Checks if there's an ongoing search related to a dhtRes.
// If there is, it adds the response info to the search and triggers a new search step. // If there is, it adds the response info to the search and triggers a new search step.
// If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more.
func (s *searches) handleDHTRes(res *dhtRes) { func (sinfo *searchInfo) handleDHTRes(res *dhtRes) {
sinfo, isIn := s.searches[res.Dest] if res == nil || sinfo.checkDHTRes(res) {
if !isIn || s.checkDHTRes(sinfo, res) {
// Either we don't recognize this search, or we just finished it // Either we don't recognize this search, or we just finished it
return return
} }
// Add to the search and continue // Add to the search and continue
s.addToSearch(sinfo, res) sinfo.addToSearch(res)
s.doSearchStep(sinfo) sinfo.doSearchStep()
} }
// Adds the information from a dhtRes to an ongoing search. // Adds the information from a dhtRes to an ongoing search.
// Info about a node that has already been visited is not re-added to the search. // Info about a node that has already been visited is not re-added to the search.
// Duplicate information about nodes toVisit is deduplicated (the newest information is kept). // Duplicate information about nodes toVisit is deduplicated (the newest information is kept).
// The toVisit list is sorted in ascending order of keyspace distance from the destination. // The toVisit list is sorted in ascending order of keyspace distance from the destination.
func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { func (sinfo *searchInfo) addToSearch(res *dhtRes) {
// Add responses to toVisit if closer to dest than the res node // Add responses to toVisit if closer to dest than the res node
from := dhtInfo{key: res.Key, coords: res.Coords} from := dhtInfo{key: res.Key, coords: res.Coords}
sinfo.visited[*from.getNodeID()] = true sinfo.visited[*from.getNodeID()] = true
for _, info := range res.Infos { for _, info := range res.Infos {
if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
continue continue
} }
if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) {
@ -135,10 +137,10 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
// If there are no nodes left toVisit, then this cleans up the search. // If there are no nodes left toVisit, then this cleans up the search.
// Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. // Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping.
func (s *searches) doSearchStep(sinfo *searchInfo) { func (sinfo *searchInfo) doSearchStep() {
if len(sinfo.toVisit) == 0 { if len(sinfo.toVisit) == 0 {
// Dead end, do cleanup // Dead end, do cleanup
delete(s.searches, sinfo.dest) delete(sinfo.core.searches.searches, sinfo.dest)
go sinfo.callback(nil, errors.New("search reached dead end")) go sinfo.callback(nil, errors.New("search reached dead end"))
return return
} }
@ -146,31 +148,32 @@ func (s *searches) doSearchStep(sinfo *searchInfo) {
var next *dhtInfo var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
rq := dhtReqKey{next.key, sinfo.dest} rq := dhtReqKey{next.key, sinfo.dest}
s.core.dht.addCallback(&rq, s.handleDHTRes) sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes)
s.core.dht.ping(next, &sinfo.dest) sinfo.core.dht.ping(next, &sinfo.dest)
} }
// If we've recenty sent a ping for this search, do nothing. // If we've recenty sent a ping for this search, do nothing.
// Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. // Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME.
func (s *searches) continueSearch(sinfo *searchInfo) { func (sinfo *searchInfo) continueSearch() {
if time.Since(sinfo.time) < search_RETRY_TIME { if time.Since(sinfo.time) < search_RETRY_TIME {
return return
} }
sinfo.time = time.Now() sinfo.time = time.Now()
s.doSearchStep(sinfo) sinfo.doSearchStep()
// In case the search dies, try to spawn another thread later // In case the search dies, try to spawn another thread later
// Note that this will spawn multiple parallel searches as time passes // Note that this will spawn multiple parallel searches as time passes
// Any that die aren't restarted, but a new one will start later // Any that die aren't restarted, but a new one will start later
retryLater := func() { retryLater := func() {
newSearchInfo := s.searches[sinfo.dest] // FIXME this keeps the search alive forever if not for the searches map, fix that
newSearchInfo := sinfo.core.searches.searches[sinfo.dest]
if newSearchInfo != sinfo { if newSearchInfo != sinfo {
return return
} }
s.continueSearch(sinfo) sinfo.continueSearch()
} }
go func() { go func() {
time.Sleep(search_RETRY_TIME) time.Sleep(search_RETRY_TIME)
s.core.router.admin <- retryLater sinfo.core.router.admin <- retryLater
}() }()
} }
@ -185,37 +188,37 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb
// Checks if a dhtRes is good (called by handleDHTRes). // Checks if a dhtRes is good (called by handleDHTRes).
// If the response is from the target, get/create a session, trigger a session ping, and return true. // If the response is from the target, get/create a session, trigger a session ping, and return true.
// Otherwise return false. // Otherwise return false.
func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
them := crypto.GetNodeID(&res.Key) them := crypto.GetNodeID(&res.Key)
var destMasked crypto.NodeID var destMasked crypto.NodeID
var themMasked crypto.NodeID var themMasked crypto.NodeID
for idx := 0; idx < crypto.NodeIDLen; idx++ { for idx := 0; idx < crypto.NodeIDLen; idx++ {
destMasked[idx] = info.dest[idx] & info.mask[idx] destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx]
themMasked[idx] = them[idx] & info.mask[idx] themMasked[idx] = them[idx] & sinfo.mask[idx]
} }
if themMasked != destMasked { if themMasked != destMasked {
return false return false
} }
// They match, so create a session and send a sessionRequest // They match, so create a session and send a sessionRequest
sinfo, isIn := s.core.sessions.getByTheirPerm(&res.Key) sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn { if !isIn {
sinfo = s.core.sessions.createSession(&res.Key) sess = sinfo.core.sessions.createSession(&res.Key)
if sinfo == nil { if sess == nil {
// nil if the DHT search finished but the session wasn't allowed // nil if the DHT search finished but the session wasn't allowed
go info.callback(nil, errors.New("session not allowed")) go sinfo.callback(nil, errors.New("session not allowed"))
return true return true
} }
_, isIn := s.core.sessions.getByTheirPerm(&res.Key) _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn { if !isIn {
panic("This should never happen") panic("This should never happen")
} }
} }
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sinfo.coords = res.Coords sess.coords = res.Coords
sinfo.packet = info.packet sess.packet = sinfo.packet
s.core.sessions.ping(sinfo) sinfo.core.sessions.ping(sess)
go info.callback(sinfo, nil) go sinfo.callback(sess, nil)
// Cleanup // Cleanup
delete(s.searches, res.Dest) delete(sinfo.core.searches.searches, res.Dest)
return true return true
} }