重要的参数
alpha: 同一时间最多只能向alpha个节点查询。默认值为10
beta: 迭代查询结束条件,所有非unreachable的节点按照距离排序后,前beta个节点全部都查询过即可结束。默认值为3
K(bucketSize): 默认值为20
- 迭代查询开始时,从K桶中选取最多K个节点。
- 迭代查询结束后,所有非unreachable的节点按照距离排序,向前(最多)K个节点中那些未查询或者正在查询的节点发起查询。
- FindProviders和GetValue需要找到K个记录才能结束查找。
- handleGetValue,handleGetProviders和handleFindPeer会从K桶中选取最多K个节点信息,回复给对方
请求
var Message_MessageType_value = map[string]int32{
"PUT_VALUE": 0,
"GET_VALUE": 1,
"ADD_PROVIDER": 2,
"GET_PROVIDERS": 3,
"FIND_NODE": 4,
"PING": 5,
}
所有DHT节点收到请求后,调用各请求的处理函数,然后将发送者的节点ID保存到路由表中。
FindPeer
首先在本地查找,如果当前已经和该peer连接,或者可连接(之前连接过,优雅关闭了),则查询结束。否则向外迭代查询,发送"FIND_NODE"请求。
其它节点收到请求后,如果目标节点是自己,就将自己的节点信息发回去,否则将K桶中距离最近的K个节点信息回复给对方。
PutValue
首先在本地查找记录,如果本地有记录,且value比本地记录更旧,则报错返回;否则向外迭代查询,发送"FIND_NODE"请求,得到一批距离key最近的节点(最多K个),向它们发送"PUT_VALUE"请求,将记录发给它们。
其它节收到"PUT_VALUE"请求后,首先和本地记录进行比较,比本地记录新就替换。
GetValue
首先在本地查找,同时向外迭代查询,发送"GET_VALUE"请求,得到一些记录和距离资源最近的节点信息(最多K个),通过对这些记录进行比较,得到最新的记录,然后向这些节点中那些没有返回记录或者返回旧的记录的节点发送"PUT_VALUE"请求,将记录发给它们。
其它节点收到"GET_VALUE"请求后,将本地记录和K桶中距离最近的K个节点信息回复给对方。
value
value目前默认有两个validator: pk和ipns。pk用于验证ID和公钥是否匹配,无所谓新旧。
ipns记录可能是最新的,也可能是旧的。先比较序列号(Sequence),相等则比较有效期(validity),如果还是相等最后通过比较字节大小来判断。
type IpnsEntry struct {
Value []byte `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
Signature []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
ValidityType *IpnsEntry_ValidityType `protobuf:"varint,3,opt,name=validityType,enum=ipns.pb.IpnsEntry_ValidityType" json:"validityType,omitempty"`
Validity []byte `protobuf:"bytes,4,opt,name=validity" json:"validity,omitempty"`
Sequence *uint64 `protobuf:"varint,5,opt,name=sequence" json:"sequence,omitempty"`
Ttl *uint64 `protobuf:"varint,6,opt,name=ttl" json:"ttl,omitempty"`
// in order for nodes to properly validate a record upon receipt, they need the public
// key associated with it. For old RSA keys, its easiest if we just send this as part of
// the record itself. For newer ed25519 keys, the public key can be embedded in the
// peerID, making this field unnecessary.
PubKey []byte `protobuf:"bytes,7,opt,name=pubKey"
}
value的pb结构是Record,Record带有时间戳,但是该时间戳没有被使用,datastore的value时间戳是保存时的时间戳。
// Record represents a dht record that contains a value
// for a key value pair
type Record struct {
// The key that references this record
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// The actual value this record is storing
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
// Time the record was received, set by receiver
TimeReceived string `protobuf:"bytes,5,opt,name=timeReceived,proto3" json:"timeReceived,omitempty"`
}
值得注意的是,handleGetValue从datastore获取到value后,会检查value的时间,如果距离上次保存已经超过(默认)36h,则视为无效(本着对其它节点负责的态度?)。
PutValue和handlePutValue由于是写操作,所以不需检查时间戳,而GetValue也没有检查这个时间戳,可能是因为记录虽旧,但有好过无。
Provide
首先将provider记录保存在本地,接着向外迭代查询,发送"FIND_NODE"请求,得到一批距离key最近的节点(最多K个),向它们发送"ADD_PROVIDER"请求,将记录发给它们。
其它节点收到"ADD_PROVIDER"请求后,先将provider中的节点信息添加到peerstore,再将记录保存起来。
FindProviders
首先在本地查找,如果本地有不少于K个(该资源的)provider记录,则查询结束,否则向外迭代查询,发送"GET_PROVIDERS"请求。
其它节点收到请求后,将其本地(该资源的)所有provider记录以及K桶中距离最近的K个节点信息回复给对方。
provider
provider记录可能是有效的,也可能无效,所以FindProviders得到的是一组(最多K个)记录,这些记录只要有一个是有效的即可。
所有provider记录由ProviderManager管理,provider结构本身并没有时间戳,而是在添加provider时附带时间戳,以便ProviderManager定期对那些过期的provider记录进行清理。
type Message_Peer struct {
// ID of a given peer.
Id byteString `protobuf:"bytes,1,opt,name=id,proto3,customtype=byteString" json:"id"`
// multiaddrs for a given peer
Addrs [][]byte `protobuf:"bytes,2,rep,name=addrs,proto3" json:"addrs,omitempty"`
// used to signal the sender's connection capabilities to the peer
Connection Message_ConnectionType `protobuf:"varint,3,opt,name=connection,proto3,enum=dht.pb.Message_ConnectionType" json:"connection,omitempty"`
}
思考:
provider记录没有签名,是否可以构造大量的虚假provider记录,对网络进行DDOS攻击?