/* * This file is part of mlDHT. * * mlDHT is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * mlDHT 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 mlDHT. If not, see <http://www.gnu.org/licenses/>. */ package lbms.plugins.mldht.kad.tasks; import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import lbms.plugins.mldht.kad.DHT.DHTtype; import lbms.plugins.mldht.kad.DHT.LogLevel; import lbms.plugins.mldht.kad.DHT; import lbms.plugins.mldht.kad.DHTConstants; import lbms.plugins.mldht.kad.KBucketEntry; import lbms.plugins.mldht.kad.KClosestNodesSearch; import lbms.plugins.mldht.kad.Key; import lbms.plugins.mldht.kad.Node; import lbms.plugins.mldht.kad.NodeList; import lbms.plugins.mldht.kad.RPCCall; import lbms.plugins.mldht.kad.RPCServer; import lbms.plugins.mldht.kad.messages.FindNodeRequest; import lbms.plugins.mldht.kad.messages.FindNodeResponse; import lbms.plugins.mldht.kad.messages.MessageBase; import lbms.plugins.mldht.kad.messages.MessageBase.Method; import lbms.plugins.mldht.kad.messages.MessageBase.Type; import lbms.plugins.mldht.kad.utils.AddressUtils; /** * @author Damokles * */ public class NodeLookup extends IteratingTask { private int validReponsesSinceLastClosestSetModification; private boolean forBootstrap = false; public NodeLookup (Key node_id, RPCServer rpc, Node node, boolean isBootstrap) { super(node_id, rpc, node); forBootstrap = isBootstrap; addListener(t -> updatedPopulationEstimates()); } @Override void update () { for(;;) { // while(!todo.isEmpty() && canDoRequest() && !isClosestSetStable() && !nextTodoUseless()) RequestPermit p = checkFreeSlot(); if(p == RequestPermit.NONE_ALLOWED) return; KBucketEntry e = todo.next().orElse(null); if(e == null) return; if(!new RequestCandidateEvaluator(this, closest, todo, e, inFlight).goodForRequest(p)) return; // send a findNode to the node FindNodeRequest fnr = new FindNodeRequest(targetKey); fnr.setWant4(rpc.getDHT().getType() == DHTtype.IPV4_DHT || rpc.getDHT().getSiblings().stream().anyMatch(sib -> sib.isRunning() && sib.getType() == DHTtype.IPV4_DHT && sib.getNode().getNumEntriesInRoutingTable() < DHTConstants.BOOTSTRAP_IF_LESS_THAN_X_PEERS)); fnr.setWant6(rpc.getDHT().getType() == DHTtype.IPV6_DHT || rpc.getDHT().getSiblings().stream().anyMatch(sib -> sib.isRunning() && sib.getType() == DHTtype.IPV6_DHT && sib.getNode().getNumEntriesInRoutingTable() < DHTConstants.BOOTSTRAP_IF_LESS_THAN_X_PEERS)); fnr.setDestination(e.getAddress()); if(!rpcCall(fnr,e.getID(), (call) -> { long rtt = e.getRTT(); rtt = rtt + rtt / 2; // *1.5 since this is the average and not the 90th percentile like the timeout filter if(rtt < DHTConstants.RPC_CALL_TIMEOUT_MAX && rtt < rpc.getTimeoutFilter().getStallTimeout()) call.setExpectedRTT(rtt); // only set a node-specific timeout if it's better than what the server would apply anyway call.builtFromEntry(e); todo.addCall(call, e); if(DHT.isLogLevelEnabled(LogLevel.Verbose)) { List<InetSocketAddress> sources = todo.getSources(e).stream().map(KBucketEntry::getAddress).collect(Collectors.toList()); DHT.log("Task "+getTaskID()+" sending call to "+ e + " sources:" + sources, LogLevel.Verbose); } })) { break; } } } @Override protected boolean isDone() { int waitingFor = getNumOutstandingRequests(); if(waitingFor != 0) return false; KBucketEntry next = todo.next().orElse(null); if(next == null) return true; return new RequestCandidateEvaluator(this, closest, todo, next, inFlight).terminationPrecondition(); } @Override void callFinished (RPCCall c, MessageBase rsp) { // check the response and see if it is a good one if (rsp.getMethod() != Method.FIND_NODE || rsp.getType() != Type.RSP_MSG) return; KBucketEntry match = todo.acceptResponse(c); if(match == null) return; FindNodeResponse fnr = (FindNodeResponse) rsp; closest.insert(match); for (DHTtype type : DHTtype.values()) { NodeList nodes = fnr.getNodes(type); if (nodes == null) continue; if (type == rpc.getDHT().getType()) { Set<KBucketEntry> entries = nodes.entries().filter(e -> !AddressUtils.isBogon(e.getAddress()) && !node.isLocalId(e.getID())).collect(Collectors.toSet()); todo.addCandidates(match, entries); } else { rpc.getDHT().getSiblings().stream().filter(sib -> sib.getType() == type).forEach(sib -> { nodes.entries().forEach(e -> { sib.addDHTNode(e.getAddress().getAddress().getHostAddress(), e.getAddress().getPort()); }); }); } } } @Override void callTimeout (RPCCall c) { } public void injectCandidates(Collection<KBucketEntry> cand) { todo.addCandidates(null, cand); } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.Task#start() */ @Override public void start () { // if we're bootstrapping start from the bucket that has the greatest possible distance from ourselves so we discover new things along the (longer) path Key knsTargetKey = forBootstrap ? targetKey.distance(Key.MAX_KEY) : targetKey; // delay the filling of the todo list until we actually start the task KClosestNodesSearch kns = new KClosestNodesSearch(knsTargetKey, 3 * DHTConstants.MAX_ENTRIES_PER_BUCKET, rpc.getDHT()); kns.filter = KBucketEntry::eligibleForLocalLookup; kns.fill(); todo.addCandidates(null, kns.getEntries()); addListener(unused -> { logClosest(); }); super.start(); } private void updatedPopulationEstimates () { synchronized (closest) { rpc.getDHT().getEstimator().update(closest.ids().collect(Collectors.toSet()),targetKey); } } }