yggdrasil-go/src/yggdrasil/search.go

268 lines
6.9 KiB
Go
Raw Normal View History

2017-12-29 07:16:20 +03:00
package yggdrasil
// This thing manages search packets
// The basic idea is as follows:
// We may know a NodeID (with a mask) and want to connect
// We forward a searchReq packet through the dht
// The last person in the dht will respond with a searchRes
// If the responders nodeID is close enough to the requested key, it matches
// The "close enough" is handled by a bitmask, set when the request is sent
// For testing in the sim, it must match exactly
// For the real world, the mask would need to map it to the desired IPv6
// This is also where we store the temporary keys used to send a request
// Would go in sessions, but can't open one without knowing perm key
// This is largely to avoid using an iterative DHT lookup approach
// The iterative parallel lookups from kad can skip over some DHT blackholes
// This hides bugs, which I don't want to do right now
2018-06-02 07:34:21 +03:00
import "sort"
2017-12-29 07:16:20 +03:00
import "time"
2018-01-05 01:37:51 +03:00
2017-12-29 07:16:20 +03:00
//import "fmt"
type searchInfo struct {
2018-06-02 07:34:21 +03:00
dest *NodeID
mask *NodeID
time time.Time
packet []byte
toVisit []*dhtInfo
2017-12-29 07:16:20 +03:00
}
type searches struct {
2018-01-05 01:37:51 +03:00
core *Core
searches map[NodeID]*searchInfo
2017-12-29 07:16:20 +03:00
}
func (s *searches) init(core *Core) {
2018-01-05 01:37:51 +03:00
s.core = core
s.searches = make(map[NodeID]*searchInfo)
2017-12-29 07:16:20 +03:00
}
func (s *searches) createSearch(dest *NodeID, mask *NodeID) *searchInfo {
2018-01-05 01:37:51 +03:00
now := time.Now()
for dest, sinfo := range s.searches {
if now.Sub(sinfo.time) > time.Minute {
delete(s.searches, dest)
}
}
info := searchInfo{
dest: dest,
mask: mask,
time: now.Add(-time.Second),
}
s.searches[*dest] = &info
return &info
2017-12-29 07:16:20 +03:00
}
////////////////////////////////////////////////////////////////////////////////
2018-06-02 07:34:21 +03:00
func (s *searches) handleDHTRes(res *dhtRes) {
if s.checkDHTRes(res) {
return
}
s.addToSearch(res)
}
func (s *searches) addToSearch(res *dhtRes) {
// TODO
sinfo, isIn := s.searches[res.dest]
if !isIn {
return
}
from := dhtInfo{key: res.key, coords: res.coords}
for _, info := range res.infos {
if dht_firstCloserThanThird(info.getNodeID(), &res.dest, from.getNodeID()) {
sinfo.toVisit = append(sinfo.toVisit, info)
}
}
sort.SliceStable(sinfo.toVisit, func(i, j int) bool {
return dht_firstCloserThanThird(sinfo.toVisit[i].getNodeID(), &res.dest, sinfo.toVisit[j].getNodeID())
})
s.doSearchStep(sinfo)
}
func (s *searches) doSearchStep(sinfo *searchInfo) {
if len(sinfo.toVisit) == 0 || time.Since(sinfo.time) > 6*time.Second {
// Dead end or timeout, do cleanup
delete(s.searches, *sinfo.dest)
return
} else {
// Send to the next search target
var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
s.core.dht.ping(next, sinfo.dest)
}
}
func (s *searches) continueSearch(sinfo *searchInfo) {
if time.Since(sinfo.time) < time.Second {
return
}
sinfo.time = time.Now()
s.doSearchStep(sinfo)
}
func (s *searches) newIterSearch(dest *NodeID, mask *NodeID) *searchInfo {
sinfo := s.createSearch(dest, mask)
sinfo.toVisit = s.core.dht.lookup(dest, false)
return sinfo
}
func (s *searches) checkDHTRes(res *dhtRes) bool {
info, isIn := s.searches[res.dest]
if !isIn {
return false
}
them := getNodeID(&res.key)
var destMasked NodeID
var themMasked NodeID
for idx := 0; idx < NodeIDLen; idx++ {
destMasked[idx] = info.dest[idx] & info.mask[idx]
themMasked[idx] = them[idx] & info.mask[idx]
}
if themMasked != destMasked {
return false
}
// They match, so create a session and send a sessionRequest
sinfo, isIn := s.core.sessions.getByTheirPerm(&res.key)
if !isIn {
sinfo = s.core.sessions.createSession(&res.key)
_, isIn := s.core.sessions.getByTheirPerm(&res.key)
if !isIn {
panic("This should never happen")
}
}
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sinfo.coords = res.coords
sinfo.packet = info.packet
s.core.sessions.ping(sinfo)
// Cleanup
delete(s.searches, res.dest)
return true
}
////////////////////////////////////////////////////////////////////////////////
2017-12-29 07:16:20 +03:00
type searchReq struct {
2018-01-05 01:37:51 +03:00
key boxPubKey // Who I am
coords []byte // Where I am
dest NodeID // Who I'm trying to connect to
2017-12-29 07:16:20 +03:00
}
type searchRes struct {
2018-01-05 01:37:51 +03:00
key boxPubKey // Who I am
coords []byte // Where I am
dest NodeID // Who I was asked about
2017-12-29 07:16:20 +03:00
}
func (s *searches) sendSearch(info *searchInfo) {
2018-01-05 01:37:51 +03:00
now := time.Now()
if now.Sub(info.time) < time.Second {
return
}
loc := s.core.switchTable.getLocator()
coords := loc.getCoords()
req := searchReq{
key: s.core.boxPub,
coords: coords,
dest: *info.dest,
}
info.time = time.Now()
s.handleSearchReq(&req)
2017-12-29 07:16:20 +03:00
}
func (s *searches) handleSearchReq(req *searchReq) {
lookup := s.core.dht.lookup(&req.dest, false)
2018-01-05 01:37:51 +03:00
sent := false
//fmt.Println("DEBUG len:", len(lookup))
for _, info := range lookup {
//fmt.Println("DEBUG lup:", info.getNodeID())
if dht_firstCloserThanThird(info.getNodeID(),
&req.dest,
&s.core.dht.nodeID) {
s.forwardSearch(req, info)
sent = true
break
}
}
if !sent {
s.sendSearchRes(req)
}
2017-12-29 07:16:20 +03:00
}
func (s *searches) forwardSearch(req *searchReq, next *dhtInfo) {
2018-01-05 01:37:51 +03:00
//fmt.Println("DEBUG fwd:", req.dest, next.getNodeID())
bs := req.encode()
shared := s.core.sessions.getSharedKey(&s.core.boxPriv, &next.key)
payload, nonce := boxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
ttl: ^uint64(0),
coords: next.coords,
toKey: next.key,
fromKey: s.core.boxPub,
nonce: *nonce,
payload: payload,
}
packet := p.encode()
s.core.router.out(packet)
2017-12-29 07:16:20 +03:00
}
func (s *searches) sendSearchRes(req *searchReq) {
2018-01-05 01:37:51 +03:00
//fmt.Println("DEBUG res:", req.dest, s.core.dht.nodeID)
loc := s.core.switchTable.getLocator()
coords := loc.getCoords()
res := searchRes{
key: s.core.boxPub,
coords: coords,
dest: req.dest,
}
bs := res.encode()
shared := s.core.sessions.getSharedKey(&s.core.boxPriv, &req.key)
payload, nonce := boxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
ttl: ^uint64(0),
coords: req.coords,
toKey: req.key,
fromKey: s.core.boxPub,
nonce: *nonce,
payload: payload,
}
packet := p.encode()
s.core.router.out(packet)
2017-12-29 07:16:20 +03:00
}
func (s *searches) handleSearchRes(res *searchRes) {
2018-01-05 01:37:51 +03:00
info, isIn := s.searches[res.dest]
if !isIn {
return
}
them := getNodeID(&res.key)
var destMasked NodeID
var themMasked NodeID
for idx := 0; idx < NodeIDLen; idx++ {
destMasked[idx] = info.dest[idx] & info.mask[idx]
themMasked[idx] = them[idx] & info.mask[idx]
}
//fmt.Println("DEBUG search res1:", themMasked, destMasked)
//fmt.Println("DEBUG search res2:", *them, *info.dest, *info.mask)
if themMasked != destMasked {
return
}
// They match, so create a session and send a sessionRequest
sinfo, isIn := s.core.sessions.getByTheirPerm(&res.key)
if !isIn {
sinfo = s.core.sessions.createSession(&res.key)
_, isIn := s.core.sessions.getByTheirPerm(&res.key)
if !isIn {
panic("This should never happen")
}
}
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
2018-01-05 01:37:51 +03:00
sinfo.coords = res.coords
sinfo.packet = info.packet
s.core.sessions.ping(sinfo)
// Cleanup
delete(s.searches, res.dest)
2017-12-29 07:16:20 +03:00
}