/* * Mojito Distributed Hash Table (Mojito DHT) * Copyright (C) 2006-2007 LimeWire LLC * * This program 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. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.limewire.mojito.util; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.collection.CollectionUtils; import org.limewire.io.NetworkInstanceUtils; import org.limewire.io.NetworkUtils; import org.limewire.io.SimpleNetworkInstanceUtils; import org.limewire.mojito.Context; import org.limewire.mojito.KUID; import org.limewire.mojito.routing.Contact; import org.limewire.mojito.routing.ContactFactory; import org.limewire.mojito.routing.Vendor; import org.limewire.mojito.routing.Version; import org.limewire.mojito.routing.impl.LocalContact; import com.google.inject.Inject; /** * Miscellaneous utilities for Contacts. */ public final class ContactUtils { private static final Log LOG = LogFactory.getLog(ContactUtils.class); @Inject private static volatile NetworkInstanceUtils networkInstanceUtils = new SimpleNetworkInstanceUtils(); public static void setNetworkInstanceUtils(NetworkInstanceUtils networkInstanceUtils) { ContactUtils.networkInstanceUtils = networkInstanceUtils; } /** * A helper method to compare longs. */ private static int compareLong(long a, long b) { if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } } /** * A Comparator that orders a Collection of Contacts from * most recently seen to least recently seen. */ public static final Comparator<Contact> CONTACT_MRS_COMPARATOR = new Comparator<Contact>() { public int compare(Contact a, Contact b) { // Note: There's a minus sign to change the order from // 'small to big' to 'big to small' values return -compareLong(a.getTimeStamp(), b.getTimeStamp()); } }; /** * A Comparator that orders a Collection of Contacts from alive * to failed. The sub-set of alive Contacts is ordered from most * recently seen to least recently seen and the sub-set of failed * Contacts is ordered by least recently failed to most recently * failed. */ public static final Comparator<Contact> CONTACT_ALIVE_TO_FAILED_COMPARATOR = new Comparator<Contact>() { public int compare(Contact a, Contact b) { // If neither a nor b has failed then use the standard // most recently seen (MRS) Comparator if (!a.hasFailed() && !b.hasFailed()) { return CONTACT_MRS_COMPARATOR.compare(a, b); // If a has failed and b hasn't then move a to the // end of the Collection } else if (a.hasFailed() && !b.hasFailed()) { return 1; // If a hasn't failed and b has then move b to // the end of the Collection } else if (!a.hasFailed() && b.hasFailed()) { return -1; // If both have failed then order by least recently // failed to most recently failed } else { return compareLong(a.getLastFailedTime(), b.getLastFailedTime()); } } }; private ContactUtils() {} /** * Returns the nodeId and address as a formatted String. */ public static String toString(KUID nodeId, SocketAddress address) { if (nodeId != null) { if (address != null) { return nodeId + " (" + address + ")"; } else { return nodeId.toString(); } } else if (address != null) { return address.toString(); } else { return "null"; } } /** * Returns true if the given Contact's address is any of * localhost's addresses. */ public static boolean isLocalAddress(Contact node) { return NetworkUtils.isLocalAddress(node.getContactAddress()); } /** * Returns true if the given Contacts have both a localhost address. */ public static boolean areLocalContacts(Contact existing, Contact node) { // Huh? The addresses are not equal but both belong // obviously to this local machine!? InetSocketAddress newAddress = (InetSocketAddress) node.getContactAddress(); InetSocketAddress oldAddress = (InetSocketAddress) existing.getContactAddress(); if (NetworkUtils.isLocalAddress(newAddress) && NetworkUtils.isLocalAddress(oldAddress) && newAddress.getPort() == oldAddress.getPort()) { return true; } return false; } /** * Returns true if the Contact has a valid SocketAddress. */ public static boolean isValidSocketAddress(Contact node) { return NetworkUtils.isValidSocketAddress(node.getContactAddress()); } /** * Returns true if the given InetAddress is a private address. * <p> * NOTE: ContactUtils.isPrivateAddress() is checking internally * if NetworkSettings.LOCAL_IS_PRIVATE is true! If you're planning * to run the DHT on a Local Area Network (LAN) you want to set * LOCAL_IS_PRIVATE to false! */ public static boolean isPrivateAddress(InetAddress addr) { return networkInstanceUtils.isPrivateAddress(addr); } /** * Returns true if the given SocketAddress is a private address. * <p> * NOTE: ContactUtils.isPrivateAddress() is checking internally * if NetworkSettings.LOCAL_IS_PRIVATE is true! If you're planning * to run the DHT on a Local Area Network (LAN) you want to set * LOCAL_IS_PRIVATE to false! */ public static boolean isPrivateAddress(SocketAddress address) { return networkInstanceUtils.isPrivateAddress(address); } /** * Returns true if the Contact has a private SocketAddress. * <p> * NOTE: ContactUtils.isPrivateAddress() is checking internally * if NetworkSettings.LOCAL_IS_PRIVATE is true! If you're planning * to run the DHT on a Local Area Network (LAN) you want to set * LOCAL_IS_PRIVATE to false! */ public static boolean isPrivateAddress(Contact node) { return isPrivateAddress(node.getContactAddress()); } /** * Returns true if the given Contact's contact address is * an IPv4 address. */ public static boolean isIPv4Address(Contact node) { InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); return (addr instanceof Inet4Address); } /** * Returns true if the given Contact's contact address is * an IPv4-compatible IPv6 address. */ public static boolean isIPv4CompatibleAddress(Contact node) { InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); if (addr instanceof Inet6Address && ((Inet6Address)addr).isIPv4CompatibleAddress()) { return true; } return false; } /** * Returns the masked Class C Network address of the given * Contact. * * @see NetworkUtils#getClassC(InetAddress) */ public static int getClassC(Contact node) { InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); return NetworkUtils.getClassC(addr); } /** * Returns true if the given Contact's contact address is * a private IPv4-compatible IPv6 address. */ public static boolean isPrivateIPv4CompatibleAddress(Contact node) { InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); return NetworkUtils.isPrivateIPv4CompatibleAddress(addr); } /** * Returns true if both Contacts have the same Node ID. */ public static boolean isSameNodeID(Contact node1, Contact node2) { return node1.getNodeID().equals(node2.getNodeID()); } /** * Returns true if the given Contact has the same Node ID as the * local Node but a different IP Address. */ public static boolean isCollision(Context context, Contact node) { if (context.isLocalNodeID(node.getNodeID()) && !context.isLocalContactAddress(node.getContactAddress())) { return true; } return false; } /** * Returns true if the given Contact has the same Node ID or the * same IP Address as the local Node. */ public static boolean isLocalContact(Context context, Contact node) { if (context.isLocalNodeID(node.getNodeID())) { return true; } // Imagine you have two Nodes that have each other in // their RouteTable. The first Node quits and restarts // with a new Node ID. The second Node pings the first // Node and we add it to the RouteTable. The first Node // starts a lookup and we get a Set of contacts from // the second Node which contains our old Contact (different // Node ID but same IPP). So what happens now is that // we're sending a lookup to that Node which is the same // as sending the lookup to ourself (loopback). if (context.isLocalContactAddress(node.getContactAddress())) { if (LOG.isWarnEnabled()) { LOG.warn(node + " has the same Contact addess as we do " + context.getLocalNode()); } return true; } return false; } /** * Returns true if both Contacts have an IPv4 or IPv6 address. */ public static boolean isSameAddressSpace(Contact a, Contact b) { return NetworkUtils.isSameAddressSpace( a.getContactAddress(), b.getContactAddress()); } /** * Takes the given Contact and returns a version of it that * can be used to test for Node ID collisions. */ public static Contact createCollisionPingSender(Contact localNode) { if (!(localNode instanceof LocalContact)) { throw new IllegalArgumentException("Contact must be an instance of LocalContact: " + localNode); } // The idea is to invert our local Node ID so that the // other Node doesn't get the impression we're trying // to spoof anything and we don't want that the other // guy adds this Contact to its RouteTable. To do so // we're creating a firewalled version of our local Node // (with the inverted Node ID of course). Vendor vendor = localNode.getVendor(); Version version = localNode.getVersion(); KUID nodeId = localNode.getNodeID().invert(); SocketAddress addr = localNode.getContactAddress(); Contact sender = ContactFactory.createLiveContact( addr, vendor, version, nodeId, addr, 0, Contact.FIREWALLED_FLAG); return sender; } /** * Returns true if the given Contact is a collision ping sender. */ public static boolean isCollisionPingSender(KUID localNodeId, Contact sender) { // The sender must be firewalled! if (!sender.isFirewalled()) { return false; } // See createCollisionPingSender(...) KUID expectedSenderId = localNodeId.invert(); return expectedSenderId.equals(sender.getNodeID()); } /** * Returns the most recently seen contact from the list. * Use ContactUtils.sort() prior to calling this Method! */ public static <T extends Contact> Contact getMostRecentlySeen(Collection<T> nodes) { List<T> list = CollectionUtils.toList(nodes); assert (list.get(0).getTimeStamp() >= list.get(nodes.size()-1).getTimeStamp()); return list.get(0); } /** * Returns the least recently seen contact from the list. * Use ContactUtils.sort() prior to calling this Method! */ public static <T extends Contact> Contact getLeastRecentlySeen(Collection<T> nodes) { List<T> list = CollectionUtils.toList(nodes); assert (list.get(nodes.size()-1).getTimeStamp() <= list.get(0).getTimeStamp()); return list.get(nodes.size()-1); } /** * Sorts the given List of Contacts from most recently seen to * least recently seen and returns a sub-list with at most * count number of elements. */ public static <T extends Contact> Collection<T> sort(Collection<T> nodes, int count) { return sort(nodes).subList(0, Math.min(count, nodes.size())); } /** * Sorts the Contacts from most recently seen to least recently seen. */ public static <T extends Contact> List<T> sort(Collection<T> nodes) { List<T> list = CollectionUtils.toList(nodes); Collections.sort(list, CONTACT_MRS_COMPARATOR); return list; } /** * Sorts the contacts from most recently seen to * least recently seen based on their timestamp and last failed time. * <p> * Used when loading the routing table if our nodeID has changed. */ public static <T extends Contact> List<T> sortAliveToFailed(Collection<T> nodes) { List<T> list = CollectionUtils.toList(nodes); Collections.sort(list, CONTACT_ALIVE_TO_FAILED_COMPARATOR); return list; } }