/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "crypto/AddressCalc.h" #include "dht/dhtcore/ReplySerializer.h" #include "subnode/SupernodeHunter.h" #include "subnode/AddrSet.h" #include "util/Identity.h" #include "util/events/Timeout.h" #include "util/AddrTools.h" #include "util/events/Time.h" #include "switch/LabelSplicer.h" #include #define CYCLE_MS 3000 struct SupernodeHunter_pvt { struct SupernodeHunter pub; /** Nodes which are authorized to be our supernode. */ struct AddrSet* authorizedSnodes; /** Our peers, DO NOT TOUCH, changed from in SubnodePathfinder. */ struct AddrSet* myPeerAddrs; struct AddrSet* blacklist; // Number of the next peer to ping in the peers AddrSet int nextPeer; // Will be set to the best known supernode possibility struct Address snodeCandidate; bool snodePathUpdated; struct Allocator* alloc; struct Log* log; struct MsgCore* msgCore; EventBase_t* base; struct SwitchPinger* sp; struct Address* myAddress; String* selfAddrStr; struct ReachabilityCollector* rc; Identity }; struct Query { struct SupernodeHunter_pvt* snp; // If this is a findNode request, this is the search target, if it's a getPeers it's null. struct Address* searchTar; int64_t sendTime; bool isGetRoute; Identity }; int SupernodeHunter_addSnode(struct SupernodeHunter* snh, struct Address* snodeAddr) { struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) snh); int length0 = snp->authorizedSnodes->length; AddrSet_add(snp->authorizedSnodes, snodeAddr, AddrSet_Match_ADDRESS_ONLY); if (snp->authorizedSnodes->length == length0) { return SupernodeHunter_addSnode_EXISTS; } return 0; } int SupernodeHunter_listSnodes(struct SupernodeHunter* snh, struct Address*** outP, struct Allocator* alloc) { struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) snh); struct Address** out = Allocator_calloc(alloc, sizeof(char*), snp->authorizedSnodes->length); for (int i = 0; i < snp->authorizedSnodes->length; i++) { out[i] = AddrSet_get(snp->authorizedSnodes, i); } *outP = out; return snp->authorizedSnodes->length; } int SupernodeHunter_removeSnode(struct SupernodeHunter* snh, struct Address* toRemove) { struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) snh); int length0 = snp->authorizedSnodes->length; AddrSet_remove(snp->authorizedSnodes, toRemove, AddrSet_Match_ADDRESS_ONLY); if (snp->authorizedSnodes->length == length0) { return SupernodeHunter_removeSnode_NONEXISTANT; } return 0; } static struct Address* getPeerByNpn(struct SupernodeHunter_pvt* snp, int npn) { npn = npn % snp->myPeerAddrs->length; int i = npn; do { struct Address* peer = AddrSet_get(snp->myPeerAddrs, i); if (peer && peer->protocolVersion > 19) { return peer; } i = (i + 1) % snp->myPeerAddrs->length; } while (i != npn); return NULL; } static void adoptSupernode2(Dict* msg, struct Address* src, struct MsgCore_Promise* prom) { struct Query* q = Identity_check((struct Query*) prom->userData); struct SupernodeHunter_pvt* snp = Identity_check(q->snp); if (!src) { Log_debug(snp->log, "timeout sending to %s", Address_toString(prom->target, prom->alloc)->bytes); // If we're in this state and it doesn't work, we're going to drop the snode and // go back to the beginning because while there's a possibility of a lost packet, // it's a bigger possibility that we don't have a working path and we'd better // try another one. AddrSet_add(snp->blacklist, prom->target, AddrSet_Match_BOTH); Bits_memset(&snp->snodeCandidate, 0, Address_SIZE); snp->snodePathUpdated = false; return; } Log_debug(snp->log, "Reply from %s", Address_toString(src, prom->alloc)->bytes); int64_t* snodeRecvTime = Dict_getIntC(msg, "recvTime"); if (!snodeRecvTime) { Log_info(snp->log, "getRoute reply with no timeStamp, bad snode"); return; } Log_debug(snp->log, "\n\nSupernode location confirmed [%s]\n\n", Address_toString(src, prom->alloc)->bytes); if (snp->pub.snodeIsReachable) { // If while we were searching, the outside code declared that indeed the snode // is reachable, we will not try to change their snode. } else if (snp->pub.onSnodeChange) { Bits_memcpy(&snp->pub.snodeAddr, src, Address_SIZE); snp->pub.snodeIsReachable = ( AddrSet_indexOf(snp->authorizedSnodes, src, AddrSet_Match_ADDRESS_ONLY) != -1 ) ? 2 : 1; snp->pub.onSnodeChange(&snp->pub, q->sendTime, *snodeRecvTime); } else { Log_warn(snp->log, "onSnodeChange is not set"); } } static void adoptSupernode(struct SupernodeHunter_pvt* snp, struct Address* candidate) { struct MsgCore_Promise* qp = MsgCore_createQuery(snp->msgCore, 0, snp->alloc); struct Query* q = Allocator_calloc(qp->alloc, sizeof(struct Query), 1); Identity_set(q); q->snp = snp; q->sendTime = Time_currentTimeMilliseconds(); Dict* msg = qp->msg = Dict_new(qp->alloc); qp->cb = adoptSupernode2; qp->userData = q; qp->target = Address_clone(candidate, qp->alloc); // NOTE: we don't immediately request a path because the RS doesn't know about us // quite yet, so it will tell us it doesn't know a path, so we need to ping it // and take it on faith until we get some announcements announced. Log_debug(snp->log, "Pinging snode [%s]", Address_toString(qp->target, qp->alloc)->bytes); Dict_putStringCC(msg, "sq", "pn", qp->alloc); Assert_true(AddressCalc_validAddress(candidate->ip6.bytes)); return; } static void updateSnodePath2(Dict* msg, struct Address* src, struct MsgCore_Promise* prom) { struct Query* q = Identity_check((struct Query*) prom->userData); struct SupernodeHunter_pvt* snp = Identity_check(q->snp); if (!src) { String* addrStr = Address_toString(prom->target, prom->alloc); Log_debug(snp->log, "timeout sending to %s", addrStr->bytes); return; } int64_t* snodeRecvTime = Dict_getIntC(msg, "recvTime"); if (!snodeRecvTime) { Log_info(snp->log, "getRoute reply with no timeStamp, bad snode"); return; } struct Address_List* al = ReplySerializer_parse(src, msg, snp->log, false, prom->alloc); if (!al || al->length == 0) { Log_debug(snp->log, "Requesting route to snode [%s], it doesn't know one", Address_toString(prom->target, prom->alloc)->bytes); return; } Log_debug(snp->log, "Supernode path updated with [%s]", Address_toString(&al->elems[0], prom->alloc)->bytes); snp->snodePathUpdated = true; if (!Bits_memcmp(&snp->pub.snodeAddr, &al->elems[0], Address_SIZE)) { Log_debug(snp->log, "Requestes route to snode [%s], the one we have is fine", Address_toString(prom->target, prom->alloc)->bytes); return; } Bits_memcpy(&snp->pub.snodeAddr, &al->elems[0], Address_SIZE); Bits_memcpy(&snp->snodeCandidate, &al->elems[0], Address_SIZE); AddrSet_flush(snp->blacklist); if (snp->pub.onSnodeChange) { snp->pub.snodeIsReachable = ( AddrSet_indexOf(snp->authorizedSnodes, src, AddrSet_Match_ADDRESS_ONLY) != -1 ) ? 2 : 1; snp->pub.onSnodeChange(&snp->pub, q->sendTime, *snodeRecvTime); } } static void updateSnodePath(struct SupernodeHunter_pvt* snp) { struct MsgCore_Promise* qp = MsgCore_createQuery(snp->msgCore, 0, snp->alloc); struct Query* q = Allocator_calloc(qp->alloc, sizeof(struct Query), 1); Identity_set(q); q->snp = snp; q->sendTime = Time_currentTimeMilliseconds(); Dict* msg = qp->msg = Dict_new(qp->alloc); qp->cb = updateSnodePath2; qp->userData = q; qp->target = Address_clone(&snp->pub.snodeAddr, qp->alloc); Log_debug(snp->log, "Update snode [%s] path", Address_toString(qp->target, qp->alloc)->bytes); Dict_putStringCC(msg, "sq", "gr", qp->alloc); String* src = String_newBinary(snp->myAddress->ip6.bytes, 16, qp->alloc); Dict_putStringC(msg, "src", src, qp->alloc); String* target = String_newBinary(snp->pub.snodeAddr.ip6.bytes, 16, qp->alloc); Dict_putStringC(msg, "tar", target, qp->alloc); } static void queryForAuthorized(struct SupernodeHunter_pvt* snp, struct Address* snode) { /* struct MsgCore_Promise* qp = MsgCore_createQuery(snp->msgCore, 0, snp->alloc); struct Query* q = Allocator_calloc(qp->alloc, sizeof(struct Query), 1); Identity_set(q); q->snp = snp; q->sendTime = Time_currentTimeMilliseconds(); Dict* msg = qp->msg = Dict_new(qp->alloc); qp->cb = onReply; qp->userData = q; qp->target = candidate; Log_debug(snp->log, "Pinging snode [%s]", Address_toString(qp->target, qp->alloc)->bytes); Dict_putStringCC(msg, "sq", "gr", qp->alloc); */ } static void peerResponseOK(struct SwitchPinger_Response* resp, struct SupernodeHunter_pvt* snp) { ReachabilityCollector_lagSample(snp->rc, resp->label, resp->milliseconds); struct Address snode = {0}; Bits_memcpy(&snode, &resp->snode, sizeof(struct Address)); if (!snode.path) { uint8_t label[20]; AddrTools_printPath(label, resp->label); Log_debug(snp->log, "Peer [%s] reports no supernode", label); return; } uint64_t path = LabelSplicer_splice(snode.path, resp->label); if (path == UINT64_MAX) { Log_debug(snp->log, "Supernode path could not be spliced"); return; } snode.path = path; struct Address peerAddr = { .path = resp->label }; int i = AddrSet_indexOf(snp->myPeerAddrs, &peerAddr, AddrSet_Match_LABEL_ONLY); if (i == -1) { Log_info(snp->log, "We got a snode reply from a node which is not in peer list"); return; } struct Address* peer = AddrSet_get(snp->myPeerAddrs, i); struct Address* firstPeer = getPeerByNpn(snp, 0); if (!firstPeer) { Log_info(snp->log, "All peers have gone away while packet was outstanding"); return; } // 1. // If we have looped around and queried all of our peers returning to the first and we have // still not found an snode in our authorized snodes list, we should simply accept this one. if (!snp->pub.snodeIsReachable && snp->myPeerAddrs->length > 1 && snp->nextPeer >= snp->myPeerAddrs->length && Address_isSameIp(firstPeer, peer)) { if (!snp->snodeCandidate.path) { Log_info(snp->log, "No snode candidate found [%s]", Address_toStringKey(&snp->snodeCandidate, resp->ping->pingAlloc)->bytes); snp->nextPeer = 0; AddrSet_flush(snp->blacklist); return; } Log_debug(snp->log, "Peer [%s] has proposed we use supernode [%s] we will accept it", Address_toString(peer, resp->ping->pingAlloc)->bytes, Address_toString(&snp->snodeCandidate, resp->ping->pingAlloc)->bytes); adoptSupernode(snp, &snp->snodeCandidate); return; } // 2. // If this snode is one of our authorized snodes OR if we have none defined, accept this one. if (AddrSet_indexOf(snp->blacklist, &snode, AddrSet_Match_BOTH) > -1) { Log_debug(snp->log, "Peer [%s] [%" PRIx64 "] has proposed supernode [%s] " "but it is blacklisted, continue", Address_toString(peer, resp->ping->pingAlloc)->bytes, resp->label, Address_toString(&snode, resp->ping->pingAlloc)->bytes); } else if (!snp->authorizedSnodes->length || AddrSet_indexOf(snp->authorizedSnodes, &snode, AddrSet_Match_ADDRESS_ONLY) > -1) { Address_getPrefix(&snode); Log_debug(snp->log, "Peer [%s] has proposed supernode [%s] and %s so we will use it", Address_toString(peer, resp->ping->pingAlloc)->bytes, Address_toString(&snode, resp->ping->pingAlloc)->bytes, (snp->authorizedSnodes->length) ? "it is authorized" : "we have none authorized"); adoptSupernode(snp, &snode); return; } else if (!snp->snodeCandidate.path) { Log_debug(snp->log, "Peer [%s] has proposed supernode [%s], we're not using it yet " "but we will store it as a candidate.", Address_toString(peer, resp->ping->pingAlloc)->bytes, Address_toString(&snp->snodeCandidate, resp->ping->pingAlloc)->bytes); Bits_memcpy(&snp->snodeCandidate, &snode, sizeof(struct Address)); Address_getPrefix(&snp->snodeCandidate); } // 3. // If this snode is not one of our authorized snodes, query it for all of our authorized snodes. queryForAuthorized(snp, &snode); } static void peerResponse(struct SwitchPinger_Response* resp, void* userData) { struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) userData); char* err = ""; switch (resp->res) { case SwitchPinger_Result_OK: peerResponseOK(resp, snp); return; case SwitchPinger_Result_LABEL_MISMATCH: err = "LABEL_MISMATCH"; break; case SwitchPinger_Result_WRONG_DATA: err = "WRONG_DATA"; break; case SwitchPinger_Result_ERROR_RESPONSE: err = "ERROR_RESPONSE"; break; case SwitchPinger_Result_LOOP_ROUTE: err = "LOOP_ROUTE"; break; case SwitchPinger_Result_TIMEOUT: err = "TIMEOUT"; break; default: err = "unknown error"; break; } Log_debug(snp->log, "Error sending snp query to peer [%" PRIx64 "] [%s]", resp->label, err); } static void probePeerCycle(void* vsn) { struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) vsn); if (snp->pub.snodeIsReachable && !snp->snodePathUpdated) { updateSnodePath(snp); } if (snp->pub.snodeIsReachable > 1) { return; } if (snp->pub.snodeIsReachable && !snp->authorizedSnodes->length) { return; } if (!snp->myPeerAddrs->length) { return; } //Log_debug(snp->log, "probePeerCycle()"); if (AddrSet_indexOf(snp->authorizedSnodes, snp->myAddress, AddrSet_Match_ADDRESS_ONLY) != -1) { Log_info(snp->log, "Self is specified as supernode, pinging..."); adoptSupernode(snp, snp->myAddress); return; } struct Address* peer = getPeerByNpn(snp, snp->nextPeer); if (!peer) { Log_info(snp->log, "No peer found who is version >= 20"); return; } struct SwitchPinger_Ping* p = SwitchPinger_newPing(peer->path, String_CONST(""), 3000, peerResponse, snp->alloc, snp->sp); Assert_true(p); Log_debug(snp->log, "Querying peer [%s] [%d] total [%d], blacklist size [%d]", Address_toString(peer, p->pingAlloc)->bytes, snp->nextPeer, snp->myPeerAddrs->length, snp->blacklist->length); snp->nextPeer++; p->type = SwitchPinger_Type_GETSNODE; if (snp->pub.snodeIsReachable) { Bits_memcpy(&p->snode, &snp->pub.snodeAddr, sizeof(struct Address)); } p->onResponseContext = snp; } static void onSnodeUnreachable(struct SupernodeHunter* snh, int64_t sendTime, int64_t snodeRecvTime) { struct SupernodeHunter_pvt* snp = Identity_check((struct SupernodeHunter_pvt*) snh); Log_debug(snp->log, "Supernode unreachable."); snp->snodePathUpdated = false; // Snode unreachable, we need also reset peer snode candidate Bits_memset(&snp->snodeCandidate, 0, Address_SIZE); } struct SupernodeHunter* SupernodeHunter_new(struct Allocator* allocator, struct Log* log, EventBase_t* base, struct SwitchPinger* sp, struct AddrSet* peers, struct MsgCore* msgCore, struct Address* myAddress, struct ReachabilityCollector* rc) { struct Allocator* alloc = Allocator_child(allocator); struct SupernodeHunter_pvt* out = Allocator_calloc(alloc, sizeof(struct SupernodeHunter_pvt), 1); Identity_set(out); out->authorizedSnodes = AddrSet_new(alloc); out->blacklist = AddrSet_new(alloc); out->myPeerAddrs = peers; out->base = base; //out->rand = rand; //out->nodes = AddrSet_new(alloc); //out->timeSnodeCalled = Time_currentTimeMilliseconds(); //out->snodeCandidates = AddrSet_new(alloc); out->log = log; out->alloc = alloc; out->msgCore = msgCore; out->myAddress = myAddress; out->rc = rc; out->selfAddrStr = String_newBinary(myAddress->ip6.bytes, 16, alloc); out->sp = sp; out->snodePathUpdated = false; out->pub.onSnodeUnreachable = onSnodeUnreachable; Timeout_setInterval(probePeerCycle, out, CYCLE_MS, base, alloc); return &out->pub; }