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:
parent
2fd3ac6837
commit
29a0f8b572
@ -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...),
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user