From e69589c5c84e2b940eee2a5f26ae1d78c690483d Mon Sep 17 00:00:00 2001 From: Kleissner Date: Fri, 9 Apr 2021 16:36:35 +0200 Subject: [PATCH] Kademlia: Use information requests from updated DHT library. More testing & fixes upcoming. --- Bootstrap.go | 4 +-- Commands.go | 72 +++++++++++++++++++++++++++++++++++++-------- Kademlia.go | 57 +++++++++++++++++++++++++++++------ Message Encoding.go | 27 +++++++++++------ Network Init.go | 4 +-- Packet Encoding.go | 3 ++ Peer ID.go | 25 ++++++++++++++++ README.md | 20 ++++++------- go.mod | 10 +++---- go.sum | 27 +++++++++-------- 10 files changed, 185 insertions(+), 64 deletions(-) diff --git a/Bootstrap.go b/Bootstrap.go index bd96f93..be6f0e4 100644 --- a/Bootstrap.go +++ b/Bootstrap.go @@ -6,7 +6,7 @@ Author: Peter Kleissner Strategy for sending our IPv6 Multicast and IPv4 Broadcast messages: * During bootstrap: Immediately at the beginning, then every 10 seconds until there is at least 1 peer. * Every 10 minutes during regular operation. -* Each time a network adapter / IP change is detected. [FUTURE] +* Each time a network adapter / IP change is detected. */ @@ -180,7 +180,7 @@ func autoMulticastBroadcast() { // Send out multicast/broadcast immediately. sendMulticastBroadcast() - // Phase 1: Resend every 10 seconds until at least 1 peer in the peer list + // Phase 1: Resend every 10 seconds until at least 1 peer in the peer list. for { time.Sleep(time.Second * 10) diff --git a/Commands.go b/Commands.go index 8d18f34..9224f44 100644 --- a/Commands.go +++ b/Commands.go @@ -7,7 +7,6 @@ Author: Peter Kleissner package core import ( - "bytes" "fmt" "time" @@ -47,22 +46,36 @@ func (peer *PeerInfo) cmdAnouncement(msg *MessageAnnouncement) { // FIND_SELF: Requesting peers close to the sender? if msg.Actions&(1< 0 { + selfD := Hash2Peer{ID: KeyHash{peer.NodeID}} + // do not respond the caller's own peer (add to ignore list) for _, node := range nodesDHT.GetClosestContacts(respondClosesContactsCount, peer.NodeID, filterFunc(msg.connection.IsLocal(), allowIPv4, allowIPv6), peer.NodeID) { - if info := node.Info.(*PeerInfo).peer2Info(msg.connection.IsLocal(), allowIPv4, allowIPv6); info != nil { - hash2Peers = append(hash2Peers, Hash2Peer{ID: KeyHash{node.ID}, Closest: []InfoPeer{*info}}) + if info := node.Info.(*PeerInfo).peer2Record(msg.connection.IsLocal(), allowIPv4, allowIPv6); info != nil { + selfD.Closest = append(selfD.Closest, *info) } } + + hash2Peers = append(hash2Peers, selfD) } - // FIND_PEER: Find a different peer? Note that in this case no IPv4/IPv6 connectivity check is performed. - if msg.Actions&(1< 0 { - // TODO + // FIND_PEER: Find a different peer? + if msg.Actions&(1< 0 && len(msg.FindPeerKeys) > 0 { + for _, findPeer := range msg.FindPeerKeys { + details := Hash2Peer{ID: findPeer} + + for _, node := range nodesDHT.GetClosestContacts(respondClosesContactsCount, findPeer.Hash, filterFunc(msg.connection.IsLocal(), allowIPv4, allowIPv6)) { + if info := node.Info.(*PeerInfo).peer2Record(msg.connection.IsLocal(), allowIPv4, allowIPv6); info != nil { + details.Closest = append(details.Closest, *info) + } + } + + hash2Peers = append(hash2Peers, details) + } } // Find a value? if msg.Actions&(1< 0 { - // TODO + // TODO query store } // Information about files stored by the sender? @@ -74,9 +87,9 @@ func (peer *PeerInfo) cmdAnouncement(msg *MessageAnnouncement) { peer.sendResponse(added, hash2Peers, nil, nil) } -func (peer *PeerInfo) peer2Info(allowLocal, allowIPv4, allowIPv6 bool) (result *InfoPeer) { +func (peer *PeerInfo) peer2Record(allowLocal, allowIPv4, allowIPv6 bool) (result *PeerRecord) { if connection := peer.GetConnection2Share(allowLocal, allowIPv4, allowIPv6); connection != nil { - return &InfoPeer{ + return &PeerRecord{ PublicKey: peer.PublicKey, NodeID: peer.NodeID, IP: connection.Address.IP, @@ -89,22 +102,55 @@ func (peer *PeerInfo) peer2Info(allowLocal, allowIPv4, allowIPv6 bool) (result * // cmdResponse handles the response to the announcement func (peer *PeerInfo) cmdResponse(msg *MessageResponse) { - // Future: We should only accept responses from peers that we contacted first for security reasons. This can be easily identified by Peer ID. - if peer == nil { peer, _ = PeerlistAdd(msg.SenderPublicKey, msg.connection) fmt.Printf("Incoming initial response from %s\n", msg.connection.Address.String()) } + // Each result is checked against the list of information requests. Only 1 response by peer is permitted per query currently. + // Response packets could be duplicated, which could happen due to auto broadcast when the remote peer responds and has multiple connections to self but none was active. + + for _, hash := range msg.HashesNotFound { + info := nodesDHT.IRLookup(peer.NodeID, hash) + if info == nil { + continue + } + info.ActiveNodesSub(1) + } + + for _, hash2Peer := range msg.Hash2Peers { + info := nodesDHT.IRLookup(peer.NodeID, hash2Peer.ID.Hash) + if info == nil { + continue + } + + info.ResultChan <- &dht.NodeMessage{SenderID: peer.NodeID, Closest: records2Nodes(hash2Peer.Closest, msg.connection.Network), Storing: records2Nodes(hash2Peer.Storing, msg.connection.Network)} + + // Future: a terminate field inform wether the remote peer is done sending. For now terminate after 1 packet. + info.ActiveNodesSub(1) + } + + for _, file := range msg.FilesEmbed { + info := nodesDHT.IRLookup(peer.NodeID, file.ID.Hash) + if info == nil { + continue + } + + info.ResultChan <- &dht.NodeMessage{SenderID: peer.NodeID, Data: file.Data} + + info.ActiveNodesSub(1) + info.Terminate() // file was found, terminate the request. + } + // check if incoming response to FIND_SELF - for _, hash2peer := range msg.Hash2Peers { + /*for _, hash2peer := range msg.Hash2Peers { if !bytes.Equal(hash2peer.ID.Hash, nodeID) { for _, closePeer := range hash2peer.Closest { // Initiate contact. Once a response comes back, the peer is actually added to the list. contactArbitraryPeer(closePeer.PublicKey, closePeer.IP, closePeer.Port) } } - } + }*/ //fmt.Printf("Incoming response from %s on %s\n", msg.connection.Address.String(), msg.connection.Address.String()) } diff --git a/Kademlia.go b/Kademlia.go index d27e688..bcd97f0 100644 --- a/Kademlia.go +++ b/Kademlia.go @@ -6,26 +6,65 @@ Author: Peter Kleissner package core -import "github.com/PeernetOfficial/core/dht" +import ( + "bytes" + + "github.com/PeernetOfficial/core/dht" +) var nodesDHT *dht.DHT func initKademlia() { - nodesDHT = dht.NewDHT(&dht.Node{ID: nodeID}, 256, 20) + nodesDHT = dht.NewDHT(&dht.Node{ID: nodeID}, 256, 20, 5) // ShouldEvict determines whether the given node shall be evicted - // TODO For now always return true. nodesDHT.ShouldEvict = func(node *dht.Node) bool { + // TODO: logic return true } - // SendStore sends a store message to the remote node. I.e. asking it to store the given key-value - nodesDHT.SendStore = func(node *dht.Node, key []byte, value []byte) { - // TODO + // SendRequestStore sends a store message to the remote node. I.e. asking it to store the given key-value + nodesDHT.SendRequestStore = func(node *dht.Node, key []byte, value []byte) { + node.Info.(*PeerInfo).sendAnnouncementStore(key, value) } - // SendRequest sends an information request to the remote node. I.e. requesting information. - nodesDHT.SendRequest = func(request *dht.InformationRequest, nodes []*dht.Node) { - // TODO + // SendRequestFindNode sends an information request to find a particular node. nodes are the nodes to send the request to. + nodesDHT.SendRequestFindNode = func(request *dht.InformationRequest) { + for _, node := range request.Nodes { + node.Info.(*PeerInfo).sendAnnouncementFindNode(request) + } + } + + // SendRequestFindValue sends an information request to find data. nodes are the nodes to send the request to. + nodesDHT.SendRequestFindValue = func(request *dht.InformationRequest) { + for _, node := range request.Nodes { + node.Info.(*PeerInfo).sendAnnouncementFindValue(request) + } } } + +// Future sendAnnouncementX: If it detects that announcements are sent out to the same peer within 50ms it should activate a wait-and-group scheme. + +func (peer *PeerInfo) sendAnnouncementFindNode(request *dht.InformationRequest) { + // If the key is self, send it as FIND_SELF + if bytes.Equal(request.Key, nodeID) { + peer.sendAnnouncement(false, true, nil, nil, nil) + } else { + peer.sendAnnouncement(false, false, []KeyHash{{Hash: request.Key}}, nil, nil) + } +} + +func (peer *PeerInfo) sendAnnouncementFindValue(request *dht.InformationRequest) { + + findSelf := false + var findPeer []KeyHash + var findValue []KeyHash + + findValue = append(findValue, KeyHash{Hash: request.Key}) + + peer.sendAnnouncement(false, findSelf, findPeer, findValue, nil) +} + +func (peer *PeerInfo) sendAnnouncementStore(key []byte, value []byte) { + peer.sendAnnouncement(false, false, nil, nil, []InfoStore{{ID: KeyHash{Hash: key}, Size: uint64(len(value)), Type: 0}}) +} diff --git a/Message Encoding.go b/Message Encoding.go index 711d161..7c19239 100644 --- a/Message Encoding.go +++ b/Message Encoding.go @@ -92,8 +92,8 @@ type InfoStore struct { Type uint8 // Type of the file: 0 = File, 1 = Header file containing list of parts } -// InfoPeer informs about a peer -type InfoPeer struct { +// PeerRecord informs about a peer +type PeerRecord struct { PublicKey *btcec.PublicKey // Public Key NodeID []byte // Kademlia Node ID IP net.IP // IP @@ -103,9 +103,9 @@ type InfoPeer struct { // Hash2Peer links a hash to peers who are known to store the data and to peers who are considered close to the hash type Hash2Peer struct { - ID KeyHash // Hash that was queried - Closest []InfoPeer // Closest peers - Storing []InfoPeer // Peers known to store the data identified by the hash + ID KeyHash // Hash that was queried + Closest []PeerRecord // Closest peers + Storing []PeerRecord // Peers known to store the data identified by the hash } // EmbeddedFileData contains embedded data sent within a response @@ -289,11 +289,15 @@ func msgDecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) { countHashesNotFound := binary.LittleEndian.Uint16(msg.Payload[read+4 : read+4+2]) read += 6 + if countPeerResponses == 0 && countEmbeddedFiles == 0 && countHashesNotFound == 0 { + return nil, errors.New("response: empty") + } + data := msg.Payload[read:] // Peer response data if countPeerResponses > 0 { - hash2Peers, read, valid := decodeInfoPeer(data, int(countPeerResponses)) + hash2Peers, read, valid := decodePeerRecord(data, int(countPeerResponses)) if !valid { return nil, errors.New("response: peer info invalid data") } @@ -330,8 +334,8 @@ func msgDecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) { return } -// decodeInfoPeer decodes the response data for FIND_SELF, FIND_PEER and FIND_VALUE messages -func decodeInfoPeer(data []byte, count int) (hash2Peers []Hash2Peer, read int, valid bool) { +// decodePeerRecord decodes the response data for FIND_SELF, FIND_PEER and FIND_VALUE messages +func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, valid bool) { index := 0 for n := 0; n < count; n++ { @@ -352,7 +356,7 @@ func decodeInfoPeer(data []byte, count int) (hash2Peers []Hash2Peer, read int, v return nil, 0, false } - peer := InfoPeer{} + peer := PeerRecord{} peerIDcompressed := make([]byte, 33) copy(peerIDcompressed[:], data[index:index+33]) @@ -773,3 +777,8 @@ func (peer *PeerInfo) sendResponse(sendUA bool, hash2Peers []Hash2Peer, filesEmb return err } + +// lastContact2Time translates a last contact time in seconds to a timestamp +func lastContact2Time(LastContact uint32) time.Time { + return time.Now().Add(-time.Second * time.Duration(LastContact)) +} diff --git a/Network Init.go b/Network Init.go index d3dd00e..1025615 100644 --- a/Network Init.go +++ b/Network Init.go @@ -140,7 +140,7 @@ func networkStart(iface net.Interface, addresses []net.Addr) { func networkPrepareListen(ipA string, port int) (network *Network, err error) { ip := net.ParseIP(ipA) if ip == nil { - return nil, errors.New("Invalid input IP") + return nil, errors.New("invalid input IP") } network = new(Network) @@ -150,7 +150,7 @@ func networkPrepareListen(ipA string, port int) (network *Network, err error) { if ipA != "0.0.0.0" && ipA != "::" { network.iface, network.ipnet = FindInterfaceByIP(ip) if network.iface == nil { - return nil, errors.New("Error finding the network interface belonging to IP") + return nil, errors.New("error finding the network interface belonging to IP") } } diff --git a/Packet Encoding.go b/Packet Encoding.go index bcf8ac4..c0afded 100644 --- a/Packet Encoding.go +++ b/Packet Encoding.go @@ -147,3 +147,6 @@ func hashData(data []byte) (hash []byte) { hash32 := blake3.Sum256(data) return hash32[:] } + +// HashSize is blake3 hash digest size = 256 bits +const HashSize = 32 diff --git a/Peer ID.go b/Peer ID.go index a8da7b4..26848f5 100644 --- a/Peer ID.go +++ b/Peer ID.go @@ -9,6 +9,7 @@ package core import ( "encoding/hex" "log" + "net" "os" "sync" @@ -17,6 +18,7 @@ import ( ) // peerID is the current peers ID. It is a ECDSA (secp256k1) 257-bit public key. +// The node ID is the blake3 hash of the public key compressed form. var peerPrivateKey *btcec.PrivateKey var peerPublicKey *btcec.PublicKey var nodeID []byte @@ -158,3 +160,26 @@ func publicKey2Compressed(publicKey *btcec.PublicKey) [btcec.PubKeyBytesLenCompr func publicKey2NodeID(publicKey *btcec.PublicKey) (nodeID []byte) { return hashData(publicKey.SerializeCompressed()) } + +// record2Peer translate a peer record (from a message) into an actual usable PeerInfo structure +// It requires the network parameter which must be the same as caller/supplier. This ensures that peer details do not "jump" between physical network adapters. +func record2Peer(record PeerRecord, network *Network) (peerN *PeerInfo) { + if peerN = PeerlistLookup(record.PublicKey); peerN != nil { + return peerN + } + + // Create temporary peer which is not added to the global list and not added to Kademlia. + connection := &Connection{Network: network, Address: &net.UDPAddr{IP: record.IP, Port: int(record.Port)}, Status: ConnectionActive} + return &PeerInfo{PublicKey: record.PublicKey, connectionActive: []*Connection{connection}, connectionLatest: connection, NodeID: publicKey2NodeID(record.PublicKey)} +} + +// records2Nodes translates infoPeer structures to nodes +// LastContact is passed on in the Node.LastSeen field. +func records2Nodes(records []PeerRecord, network *Network) (nodes []*dht.Node) { + for _, record := range records { + peer := record2Peer(record, network) + nodes = append(nodes, &dht.Node{ID: peer.NodeID, LastSeen: lastContact2Time(record.LastContact), Info: peer}) + } + + return +} diff --git a/README.md b/README.md index 40ee729..570310f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Peernet Core -The core library which is needed for any Peernet application. It provides connectivity to the network and all basic functions. +The core library which is needed for any Peernet application. It provides connectivity to the network and all basic functions. For details about Peernet see https://peernet.org/. -Current version: 0.1 (pre-alpha) +Current version: 0.2 (early alpha) + +Current development status: Initial connectivity works. DHT functionality is in development. ## Use @@ -42,11 +44,11 @@ func main() { ## Dependencies -Go 1.16 or higher is required. Before compiling, make sure to download and update all 3rd party packages: +Go 1.16 or higher is required. These are the major dependencies: ``` -go get -u github.com/btcsuite/btcd/btcec -go get -u lukechampine.com/blake3 +github.com/btcsuite/btcd/btcec +lukechampine.com/blake3 ``` ## Configuration @@ -95,14 +97,10 @@ Above limits are constants and can be adjusted in the code via `pingTime`, `conn ### Kademlia -TBD - -### Ongoing Peerlist Exchange - -TBD +The routing table has a bucket size of 20 and the size of keys 256 bits (blake3 hash). Nodes within buckets are sorted by least recently seen. The number of nodes to contact concurrently in DHT lookups (also known as alpha number) is set to 5. ## Contributing Please note that by contributing code, documentation, ideas, snippets, or any other intellectual property you agree that you have all the necessary rights and you agree that we, the Peernet organization, may use it for any purpose. -© 2021 Peernet +© 2021 Peernet s.r.o. diff --git a/go.mod b/go.mod index 2808d80..d9f3d7e 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/PeernetOfficial/core go 1.16 require ( - github.com/PeernetOfficial/core/dht v0.0.0-20210405124533-d83123bf46c9 - github.com/PeernetOfficial/core/reuseport v0.0.0-20210316211915-8b329f7258d1 - github.com/btcsuite/btcd v0.21.0-beta - golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b - golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 + github.com/PeernetOfficial/core/dht v0.0.0-20210409140720-c44e57a0a02b + github.com/PeernetOfficial/core/reuseport v0.0.0-20210409140720-c44e57a0a02b + github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b lukechampine.com/blake3 v1.1.5 ) diff --git a/go.sum b/go.sum index be36f40..1d6d72d 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ -github.com/PeernetOfficial/core/dht v0.0.0-20210405124533-d83123bf46c9 h1:J3SCdaAy8XnuwgwdqYimNzyt4HFQv+EKCPYbItoky8c= -github.com/PeernetOfficial/core/dht v0.0.0-20210405124533-d83123bf46c9/go.mod h1:sb2H21VIVTohCXVrDFo/Skl2tN8Yzba+MXSS6NgCYXw= -github.com/PeernetOfficial/core/reuseport v0.0.0-20210316211915-8b329f7258d1 h1:m1dEg5E8ZwT3l/F6xiDrU2Q/ZJ9BeVFAZt/OUBxtr4M= -github.com/PeernetOfficial/core/reuseport v0.0.0-20210316211915-8b329f7258d1/go.mod h1:j5lvi5jDpdDyuQrsUkCv4kkibVhPWj87sMol41sXIxc= +github.com/PeernetOfficial/core/dht v0.0.0-20210409140720-c44e57a0a02b h1:4V7y4Kul7R57EAWxr85fVHG3/Gf6U6RBL3ZQFqjjSpU= +github.com/PeernetOfficial/core/dht v0.0.0-20210409140720-c44e57a0a02b/go.mod h1:sb2H21VIVTohCXVrDFo/Skl2tN8Yzba+MXSS6NgCYXw= +github.com/PeernetOfficial/core/reuseport v0.0.0-20210409140720-c44e57a0a02b h1:9pYGclxvKWSDhe1qlVg8bzeIS78kYehdbNSYautAT+4= +github.com/PeernetOfficial/core/reuseport v0.0.0-20210409140720-c44e57a0a02b/go.mod h1:j5lvi5jDpdDyuQrsUkCv4kkibVhPWj87sMol41sXIxc= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025 h1:aoVqvZk4mLyF3WZbqEVPq+vXnwL2wekZg4P4mjYJNLs= +github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= @@ -17,6 +17,7 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= @@ -39,22 +40,22 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=