@Override @VarZ("/dht/messages/incoming") protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { int length = request.getContentLength(); byte[] data = new byte[length]; DataInputStream dataIs = new DataInputStream(request.getInputStream()); dataIs.readFully(data); dataIs.close(); String hostname = request.getHeader("X-Node-Host"); String port = request.getHeader("X-Node-Port"); InetSocketAddress src = InetSocketAddress.createUnresolved(hostname, Integer.valueOf(port)); try { DHTMessage destination = context.getMessageFactory().createMessage(src, ByteBuffer.wrap(data)); varZ.log("/dht/messages/incoming/" + destination.getOpCode().name().toLowerCase()); dispatcher.get().handleMessage(destination); } catch (Exception e) { logger.error(e); } }
public void handleLateResponse(ResponseMessage message) { Contact node = message.getContact(); if (!node.isFirewalled()) { context.getRouteTable().add(node); // update } }
/** * This method depends on addLiveContactInfo(...) and does two things. It either forwards or * removes a DHTValue it from the local Database. For details see Kademlia spec! */ private void forwardOrRemoveValues(Contact node, Contact existing, DHTMessage message) { List<DHTValueEntity> valuesToForward = new ArrayList<DHTValueEntity>(); Database database = context.getDatabase(); synchronized (database) { for (KUID primaryKey : database.keySet()) { Operation op = getOperation(node, existing, primaryKey); if (LOG.isDebugEnabled()) LOG.debug("node: " + node + "existing: " + existing + "operation: " + op); if (op.equals(Operation.FORWARD)) { Map<KUID, DHTValueEntity> bag = database.get(primaryKey); valuesToForward.addAll(bag.values()); databaseStats.STORE_FORWARD_COUNT.incrementStat(); } else if (op.equals(Operation.DELETE) && DatabaseSettings.DELETE_VALUE_IF_FURTHEST_NODE.getValue()) { Map<KUID, DHTValueEntity> bag = database.get(primaryKey); for (DHTValueEntity entity : bag.values()) { // System.out.println("REMOVING: " + entity + "\n"); database.remove(entity.getPrimaryKey(), entity.getSecondaryKey()); } databaseStats.STORE_FORWARD_REMOVALS.incrementStat(); } } } if (!valuesToForward.isEmpty()) { SecurityToken securityToken = null; if (message instanceof SecurityTokenProvider) { securityToken = ((SecurityTokenProvider) message).getSecurityToken(); if (securityToken == null && StoreSettings.STORE_REQUIRES_SECURITY_TOKEN.getValue()) { if (LOG.isInfoEnabled()) { LOG.info(node + " sent us a null SecurityToken"); } return; } } context.store(node, securityToken, valuesToForward); } }
@Test public void testSettingTheBootstrapNodeAsBootStrapped() throws Exception { Context dht = (Context) MojitoFactory.createDHT("bootstrap"); dht.bind(new InetSocketAddress("localhost", 8080)); dht.setBootstrapped(true); dht.start(); MojitoDHT node = MojitoFactory.createDHT("node"); node.bind(new InetSocketAddress("localhost", 8081)); node.start(); node.bootstrap(new InetSocketAddress("localhost", 8080)).get(); assertTrue(node.isBootstrapped()); DHTValueImpl value = new DHTValueImpl(DHTValueType.TEXT, Version.ZERO, "hello world".getBytes()); node.put(Keys.of("key"), value).get(); FindValueResult result = dht.get(EntityKey.createEntityKey(Keys.of("key"), DHTValueType.TEXT)).get(); assertTrue(result.isSuccess()); assertEquals(1, result.getEntities().size()); assertEquals( "hello world", new String(result.getEntities().iterator().next().getValue().getValue())); node.close(); dht.close(); }
public static ArcsVisualizer show(final Context context) { final ArcsVisualizer arcs = new ArcsVisualizer(context, context.getLocalNodeID()); SwingUtilities.invokeLater( new Runnable() { public void run() { JFrame frame = new JFrame(context.getName()); frame.getContentPane().add(arcs); frame.setBounds(20, 30, 640, 640); frame.addWindowListener( new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { arcs.stopArcs(); } }); frame.setVisible(true); } }); arcs.startArcs(); return arcs; }
public void handleTimeout(KUID nodeId, SocketAddress dst, RequestMessage message, long time) { context.getRouteTable().handleFailure(nodeId, dst); }
public DefaultMessageHandler(Context context) { this.context = context; databaseStats = context.getDatabaseStats(); }
/** * Determines whether to remove, forward or to do nothing with the value that is associated with * the given valueId. */ private Operation getOperation(Contact node, Contact existing, KUID valueId) { // To avoid redundant STORE forward, a node only transfers a value // if it is the closest to the key or if its ID is closer than any // other ID (except the new closest one of course) // TODO: maybe relax this a little bit: what if we're not the closest // and the closest is stale? int k = KademliaSettings.REPLICATION_PARAMETER.getValue(); RouteTable routeTable = context.getRouteTable(); List<Contact> nodes = org.limewire.collection.CollectionUtils.toList( routeTable.select(valueId, k, SelectMode.ALL)); Contact closest = nodes.get(0); Contact furthest = nodes.get(nodes.size() - 1); if (LOG.isDebugEnabled()) { LOG.debug( MessageFormat.format( "node: {0}, existing: {1}, close nodes: {2}", node, existing, nodes)); } // StringBuilder sb = new StringBuilder(); // sb.append("ME: "+context.getLocalNode()+"\n"); // sb.append("Them: "+node).append("\n"); // sb.append("RT nearest: " + closest).append("\n"); // sb.append("RT furthest: " + furthest).append("\n"); // sb.append(CollectionUtils.toString(nodes)).append("\n"); // We store forward if: // #1 We're the nearest Node of the k-closest Nodes to // the given valueId // // #2 We're the second nearest of the k-closest Nodes to // the given valueId AND the other Node is the nearest. // In other words it changed its instance ID 'cause it // was offline for a short period of time or whatsoever. // (see also pre-condition(s) from where we're calling // this method) // // The first condition applies if the Node is new // and we're the closest Node. The second condition // applies if the Node has changed it's instanceId. // That means we're the second closest and since // the other Node has changed its instanceId we must // re-send the values if (context.isLocalNode(closest) || (node.equals(closest) && nodes.size() > 1 && context.isLocalNode(nodes.get(1)))) { KUID nodeId = node.getNodeID(); KUID furthestId = furthest.getNodeID(); // #3 The other Node must be equal to the furthest Node // or better if (nodeId.equals(furthestId) || nodeId.isNearerTo(valueId, furthestId)) { // sb.append("CONDITION B (FORWARD)").append("\n"); // sb.append("Local (from): " + context.getLocalNode()).append("\n"); // sb.append("Remote (to): " + node).append("\n"); // sb.append(CollectionUtils.toString(nodes)).append("\n"); // System.out.println(sb.toString()); if (LOG.isTraceEnabled()) { LOG.trace( "Node " + node + " is now close enough to a value and we are responsible for xfer"); } return Operation.FORWARD; } // We remove a value if: // #1 The value is stored at k Nodes // (i.e. the total number of Nodes in the DHT // is equal or greater than k. If the DHT has // less than k Nodes then there's no reason to // remove a value) // // #2 This Node is the furthest of the k-closest Nodes // // #3 The new Node isn't in our RouteTable yet. That means // adding it will push this Node out of the club of the // k-closest Nodes and makes it the (k+1)-closest Node. // // #4 The new Node is nearer to the given valueId then // the furthest away Node (we). } else if (nodes.size() >= k && context.isLocalNode(furthest) && (existing == null || existing.isDead())) { KUID nodeId = node.getNodeID(); KUID furthestId = furthest.getNodeID(); if (nodeId.isNearerTo(valueId, furthestId)) { // sb.append("CONDITION C").append("\n"); // sb.append("ME:").append(context.getLocalNode()).append("\n"); // sb.append("VALUE:").append(valueId).append("\n"); // sb.append("NODE:").append(node).append("\n"); // sb.append(CollectionUtils.toString(nodes)).append("\n"); // System.out.println(sb.toString()); return Operation.DELETE; } } return Operation.NOTHING; }
/** * Adds the given <code>Contact</code> or updates it if it's already in our <code>RouteTable * </code> */ private synchronized void addLiveContactInfo(Contact node, DHTMessage message) { RouteTable routeTable = context.getRouteTable(); // If the Node is going to shutdown then don't bother // further than this. if (node.isShutdown()) { if (LOG.isInfoEnabled()) { LOG.info(node + " is going to shut down"); } synchronized (routeTable) { // Make sure there's an existing Contact in the RouteTable. // Otherwise don't bother! Contact existing = routeTable.get(node.getNodeID()); if (node.equals(existing)) { // Update the new Contact in the RouteTable and // mark it as shutdown // mark the existing contact as shutdown if its alive or // it will not be removed. if (existing.isAlive()) existing.shutdown(true); routeTable.add(node); node.shutdown(true); } } return; } // Ignore firewalled Nodes if (node.isFirewalled()) { if (LOG.isInfoEnabled()) { LOG.info(node + " is firewalled"); } return; } if (ContactUtils.isPrivateAddress(node)) { if (LOG.isInfoEnabled()) { LOG.info(node + " has a private address"); } return; } KUID nodeId = node.getNodeID(); if (context.isLocalNodeID(nodeId)) { // This is expected if there's a Node ID collision assert (message instanceof PingResponse) : "Expected a PingResponse but got a " + message.getClass() + " from " + message.getContact(); if (LOG.isInfoEnabled()) { LOG.info("Looks like our NodeID collides with " + node); } return; } if (StoreSettings.STORE_FORWARD_ENABLED.getValue()) { // Only do store forward if it is a new node in our routing table // (we are (re)connecting to the network) or a node that is reconnecting Contact existing = routeTable.get(nodeId); if (existing == null || existing.isDead() || existing.getInstanceID() != node.getInstanceID()) { // Store forward only if we're bootstrapped if (context.isBootstrapped()) { int k = KademliaSettings.REPLICATION_PARAMETER.getValue(); // we select the 2*k closest nodes in order to also check those values // where the local node is part of the k closest to the value but not part // of the k closest to the new joining node. Collection<Contact> nodes = routeTable.select(nodeId, 2 * k, SelectMode.ALL); // Are we one of the K nearest Nodes to the contact? if (containsNodeID(nodes, context.getLocalNodeID())) { if (LOG.isTraceEnabled()) { LOG.trace( "Node " + node + " is new or has changed his instanceID, will check for store forward!"); } forwardOrRemoveValues(node, existing, message); } } } } // Add the Node to our RouteTable or if it's // already there update its timeStamp and whatsoever routeTable.add(node); }
public void stopArcs() { timer.stop(); context.getMessageDispatcher().removeMessageDispatcherListener(this); }
public void startArcs() { timer.start(); context.getMessageDispatcher().addMessageDispatcherListener(this); }
@Test public void testCreatorAddressIsCorrect() throws Exception { Context root = (Context) MojitoFactory.createDHT("bootstrap"); root.bind(new InetSocketAddress("localhost", 8081)); root.start(); Context dht = (Context) MojitoFactory.createDHT("dht"); dht.bind(new InetSocketAddress("localhost", 8080)); dht.start(); dht.bootstrap(new InetSocketAddress("localhost", 8081)).get(); assertTrue(dht.isBootstrapped()); DHTValueImpl value = new DHTValueImpl(DHTValueType.TEXT, Version.ZERO, "hello world".getBytes()); StoreResult store = dht.put(Keys.of("key"), value).get(); assertEquals(2, store.getLocations().size()); FindValueResult result = dht.get(EntityKey.createEntityKey(Keys.of("key"), DHTValueType.TEXT)).get(); assertTrue(result.isSuccess()); assertEquals(1, result.getEntities().size()); DHTValueEntity entity = result.getEntities().iterator().next(); assertEquals( InetSocketAddress.createUnresolved("localhost", 8080), entity.getCreator().getContactAddress()); assertEquals( InetSocketAddress.createUnresolved("localhost", 8081), entity.getSender().getContactAddress()); root.close(); dht.close(); }