/*
* 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.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.mojito.Context;
import org.limewire.mojito.KUID;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.settings.NetworkSettings;
/**
* The ContactsScrubber is a pre-processing tool to ensure that
* all Contacts that are returned with FIND_NODE responses are
* correct and valid.
*/
public class ContactsScrubber {
private static final Log LOG = LogFactory.getLog(ContactsScrubber.class);
private final Collection<? extends Contact> nodes;
private final Map<KUID, Contact> scrubbed;
private final Collection<Contact> collisions;
private final boolean isValidResponse;
/*public static ContactsScrubber scrub(Context context, Contact sender,
Collection<? extends Contact> nodes) {
return scrub(context, sender, nodes, 0f);
}*/
/**
* Creates and returns a ContactsScrubber for the given arguments.
*/
public static ContactsScrubber scrub(Context context, Contact sender,
Collection<? extends Contact> nodes, float requiredRatio) {
if (nodes.isEmpty()) {
throw new IllegalArgumentException();
}
return new ContactsScrubber(context, sender, nodes, requiredRatio);
}
private ContactsScrubber(Context context, Contact sender,
Collection<? extends Contact> nodes, float requiredRatio) {
assert (!nodes.isEmpty());
assert (requiredRatio >= 0f && requiredRatio <= 1f);
this.nodes = nodes;
this.scrubbed = new LinkedHashMap<KUID, Contact>(nodes.size());
this.collisions = new LinkedHashSet<Contact>(1);
Contact localNode = context.getLocalNode();
SameClassFilter filter = new SameClassFilter(sender);
boolean containsLocal = false;
for (Contact node : nodes) {
// Make sure the SocketAddress is OK
if (!ContactUtils.isValidSocketAddress(node)) {
if (LOG.isInfoEnabled()) {
LOG.info(sender + " sent us a Contact with an invalid IP:Port " + node);
}
continue;
}
// Make sure the Contact has not a private IP:Port
// if it's not permitted
if (ContactUtils.isPrivateAddress(node)) {
if (LOG.isInfoEnabled()) {
if (ContactUtils.isSameNodeID(sender, node)) {
LOG.info(sender + " does not know its external address");
} else {
LOG.info(sender + " sent a Contact with a private IP:Port: " + node);
}
}
continue;
}
// Make sure we're not mixing IPv4 and IPv6 addresses.
// See RouteTableImpl.add() for more Info!
if (!ContactUtils.isSameAddressSpace(localNode, node)) {
if (LOG.isInfoEnabled()) {
LOG.info(node + " is from a different IP address space than local Node");
}
continue;
}
// IPv4-compatible addresses are 'tricky'. Two IPv6 aware systems
// may communicate with each other by using IPv4 infrastructure.
// This works only if both are dual-stack systems. In an IPv6 DHT
// we may have the situation that some systems don't understand
// IPv4 and they can't do anything with these Contacts.
if (NetworkSettings.DROP_PUBLIC_IPV4_COMPATIBLE_ADDRESSES.getValue()
&& ContactUtils.isIPv4CompatibleAddress(node)) {
if (LOG.isInfoEnabled()) {
LOG.info(node + " has an IPv4-compatible address");
}
continue;
}
// Same as above but somewhat undefined. It's unclear whether or not
// an address such as ::0000:192.168.0.1 is a site-local addresses
// or not. On one side it's an IPv6 address and therefore not a
// site-local address but if you read it as an IPv4 address then
// it is.
if (NetworkSettings.DROP_PRIVATE_IPV4_COMPATIBLE_ADDRESSES.getValue()
&& ContactUtils.isPrivateIPv4CompatibleAddress(node)) {
if (LOG.isInfoEnabled()) {
LOG.info(node + " has a private IPv4-compatible address");
}
continue;
}
// Make sure the IPs are from different Networks. Don't apply
// this filter if the sender is in the response Set though!
if (NetworkSettings.FILTER_CLASS_C.getValue()
&& ContactUtils.isIPv4Address(node)
&& !ContactUtils.isSameNodeID(sender, node)
&& filter.isSameNetwork(node)) {
if (LOG.isInfoEnabled()) {
LOG.info(sender + " sent one or more Contacts from the same Network-Class: " + node);
}
continue;
}
// Check if the Node collides with the local Node
if (ContactUtils.isCollision(context, node)) {
if (LOG.isInfoEnabled()) {
LOG.info(node + " seems to collide with " + context.getLocalNode());
}
collisions.add(node);
continue;
}
// Check if it's the local Node
if (ContactUtils.isLocalContact(context, node)) {
if (LOG.isInfoEnabled()) {
LOG.info("Skipping local Node");
}
containsLocal = true;
continue;
}
// All tests passed! Add the Contact to our Set
// of filtered Contacts!
scrubbed.put(node.getNodeID(), node);
}
if (requiredRatio > 0f) {
int total = scrubbed.size() + collisions.size();
if (containsLocal) {
total++;
}
float ratio = (float)total / nodes.size();
this.isValidResponse = (ratio >= requiredRatio);
} else {
this.isValidResponse = true;
}
}
/**
* Returns all Contacts.
*/
public Collection<? extends Contact> getContacts() {
return nodes;
}
/**
* Returns a Collection of scrubbed Contacts. Scrubbed Contacts
* are Contacts that passed our filters and are OK to be added
* to the RouteTable.
*/
public Collection<Contact> getScrubbed() {
return scrubbed.values();
}
/**
* Returns a Collection of Contacts that seem to collide with
* the local Node.
*/
public Collection<Contact> getCollisions() {
return collisions;
}
/**
* Returns true if the response contains any or rather the
* response itself can be considered as valid.
*/
public boolean isValidResponse() {
return isValidResponse;
}
}