package com.limegroup.gnutella.filters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.limewire.collection.PatriciaTrie;
import org.limewire.collection.Trie;
import org.limewire.collection.PatriciaTrie.KeyAnalyzer;
import org.limewire.collection.Trie.Cursor;
import org.limewire.io.IP;
import org.limewire.io.NetworkInstanceUtils;
import org.limewire.io.NetworkUtils;
import org.limewire.util.ByteUtils;
/**
* A mutable list of IP addresses. More specifically, a list of sets of
* addresses, like "18.239.0.*". Provides fast operations to find if an address
* is in the list. Used to implement IPFilter. Not synchronized.
* <p>
* This class is optimized by the use of a PATRICIA Trie to store the ranges.
* Many of the optimizations work because of two key properties that we use
* when inserting items into the Trie.
* <pre>
* 1) If the item to be inserted is within a range already in the Trie,
* the item is not inserted.
* 2) If the item to be inserted contains any items that are within the
* Trie, those items are removed.
* </pre>
* Maintaining these properties allows certain necessary optimizations, such as
* looking at only the closest node when performing a lookup. If these
* optimizations were not done, then certain items would appear closer within
* the Trie, despite there being a range further away that encompassed a given IP.
* <p>
* Using a PATRICIA allows an intelligent traversal to be done, so that at most
* 32 comparisons (the number of bits in an address) are performed regardless
* of the number of items inserted into the Trie. It also allows very efficient
* means of calculating the minimum distance (using an xor metric), because the
* Trie orders the IPs by distance.
*/
public class IPList {
/** A null IP, to use as a comparison when adding. */
private static final IP NULL_IP = new IP("*.*.*.*");
/** The list of IPs. */
private Trie<IP, IP> ips = new PatriciaTrie<IP, IP>(new IPKeyAnalyzer());
public IPList () {}
/**
* Determines if any hosts exist in this list.
*/
public synchronized boolean isEmpty() {
return ips.isEmpty();
}
/** Gets the number of addresses loaded. */
public synchronized int size() {
return ips.size();
}
/**
* Parses a string and adds it to the IPList if it represents a valid IP.
* @return true if the string is valid, otherwise false.
*/
public boolean add(String ipStr) {
IP ip;
try {
ip = new IP(ipStr);
} catch (IllegalArgumentException e) {
return false;
}
add(ip);
return true;
}
/**
* Adds a certain IP to the IPList.
*/
public synchronized void add(IP ip) {
// SPECIAL-CASE:
// If the IP we're adding is the 'null' key (0.0.0.0/0.0.0.0)
// then we must clear the trie. The AddFilter trick will not
// work in this case.
if(ip.equals(NULL_IP)) {
ips.clear();
ips.put(ip, ip);
return;
}
if(!NetworkUtils.isValidAddress(ip)) {
return;
}
// If we already had it (or an address that contained it),
// then don't include. Also remove any IPs we encountered
// that are contained by this new IP.
// These two properties are necessary to allow the optimization
// in Lookup to exit when the distance is greater than 1.
AddFilter filter = new AddFilter(ip);
Map.Entry<IP, IP> entry = ips.select(ip, filter);
if(entry != null) {
if(!entry.getKey().contains(ip)) {
for(IP obsolete : filter.getContained()) {
ips.remove(obsolete);
}
ips.put(ip, ip);
}
} else {
ips.put(ip, ip);
}
}
/**
* @returns true if ip_address is contained somewhere in the list of IPs
*/
public synchronized boolean contains(IP lookup) {
IP ip = ips.select(lookup);
return ip != null && ip.contains(lookup);
}
/**
* Determines if this filter is valid. If private IPs are not allowed,
* NetworkInstanceUtils must be non-null in order to check if an address
* is considered private. If allowPrivateIPs is true, networkInstanceUtils
* can be null.
*/
public synchronized boolean isValidFilter(boolean allowPrivateIPs, NetworkInstanceUtils networkInstanceUtils) {
ValidFilter filter = new ValidFilter(allowPrivateIPs, networkInstanceUtils);
ips.traverse(filter);
return filter.isValid();
}
/**
* Calculates the first set bit in the distance between an IPv4 address and
* the ranges represented by this list.
* <p>
* This is equivalent to floor(log2(distance)) + 1.
*
* @param ip an IPv4 address, represented as an IP object with a /32 netmask.
* @return an int on the interval [0,31].
*/
public int logMinDistanceTo(IP ip) {
int distance = minDistanceTo(ip);
int logDistance = 0;
int testMask = -1; // All bits set
// Guaranteed to terminate since testMask will reach zero
while ((distance & testMask) != 0) {
testMask <<= 1;
++logDistance;
}
return logDistance;
}
/**
* Calculates the minimum distance between an IPv4 address and this list of IPv4 address ranges.
*
* @param lookup an IPv4 address, represented as an IP object with a /32 netmask.
* @return 32-bit unsigned distance (using xor metric), represented as Java int
*/
public synchronized int minDistanceTo(IP lookup) {
if (lookup.mask != -1) {
throw new IllegalArgumentException("Expected single IP, not an IP range.");
}
// The nature of the PATRICIA Trie & the distance (using an xor metric)
// work well in that the closest item within the trie is also the shortest
// distance.
IP ip = ips.select(lookup);
if(ip == null)
return Integer.MAX_VALUE;
else
return ip.getDistanceTo(lookup);
}
/**
* A trie cursor that determines if the IP list contained in the
* trie is valid or not.
* A list is considered invalid if :
* <pre>
* 1) It contains a private IP
* 2) It contains an invalid IP
* 3) It spans a range of hosts larger than the MAX_LIST_SPACE constant
* </pre>
*/
private static class ValidFilter implements Trie.Cursor<IP, IP> {
/** The space covered by private addresses */
private static final int INVALID_SPACE = 60882944;
/** The total IP space available */
private static final long TOTAL_SPACE = (long)Math.pow(2,32) - INVALID_SPACE;
/** The maximum IP space (in percent) for this IPList to be valid */
private static final float MAX_LIST_SPACE = 0.025f;
private boolean isInvalid;
private long counter;
private final boolean allowPrivateIPs;
private final NetworkInstanceUtils networkInstanceUtils;
public boolean isValid() {
return !isInvalid && ((counter/(float)TOTAL_SPACE) < MAX_LIST_SPACE) ;
}
public ValidFilter(boolean allowPrivateIPs, NetworkInstanceUtils networkInstanceUtils) {
this.allowPrivateIPs = allowPrivateIPs;
this.networkInstanceUtils = networkInstanceUtils;
}
public SelectStatus select(Entry<? extends IP, ? extends IP> entry) {
IP key = entry.getKey();
byte[] buf = new byte[4];
ByteUtils.int2beb(key.addr,buf,0);
if(!allowPrivateIPs && networkInstanceUtils.isPrivateAddress(buf)) {
isInvalid = true;
return SelectStatus.EXIT;
}
counter += Math.pow(2,countBits(~key.mask));
return SelectStatus.CONTINUE;
}
/**
* Counts number of 1 bits in a 32 bit unsigned number.
*
* @param x unsigned 32 bit number whose bits you wish to count.
*
* @return number of 1 bits in x.
* @author Roedy Green
*/
private int countBits( int x ) {
// collapsing partial parallel sums method
// collapse 32x1 bit counts to 16x2 bit counts, mask 01010101
x = (x >>> 1 & 0x55555555) + (x & 0x55555555);
// collapse 16x2 bit counts to 8x4 bit counts, mask 00110011
x = (x >>> 2 & 0x33333333) + (x & 0x33333333);
// collapse 8x4 bit counts to 4x8 bit counts, mask 00001111
x = (x >>> 4 & 0x0f0f0f0f) + (x & 0x0f0f0f0f);
// collapse 4x8 bit counts to 2x16 bit counts
x = (x >>> 8 & 0x00ff00ff) + (x & 0x00ff00ff);
// collapse 2x16 bit counts to 1x32 bit count
return(x >>> 16) + (x & 0x0000ffff);
}
}
/**
* A filter for adding IPs -- stores IPs we encountered that
* are contained by the to-be-added IP, so they can later
* be removed.
*/
private static class AddFilter implements Trie.Cursor<IP, IP> {
private final IP lookup;
private List<IP> contained;
AddFilter(IP lookup) {
this.lookup = lookup;
}
/**
* Returns all the IPs we encountered while selecting
* that were contained by the IP being added.
*/
public List<IP> getContained() {
if(contained == null)
return Collections.emptyList();
else
return contained;
}
public Cursor.SelectStatus select(Map.Entry<? extends IP, ? extends IP> entry) {
IP compare = entry.getKey();
if (compare.contains(lookup)) {
return Cursor.SelectStatus.EXIT; // Terminate
}
if(lookup.contains(compare)) {
if(contained == null)
contained = new ArrayList<IP>();
contained.add(compare);
return SelectStatus.CONTINUE;
} else {
// Because select traverses in XOR closeness,
// the first time we encounter an item that's
// not contained, we know we've exhausted all
// possible containing values.
return SelectStatus.EXIT;
}
}
};
private static class IPKeyAnalyzer implements KeyAnalyzer<IP> {
private static final int[] createIntBitMask(final int bitCount) {
int[] bits = new int[bitCount];
for(int i = 0; i < bitCount; i++) {
bits[i] = 1 << (bitCount - i - 1);
}
return bits;
}
private static final int[] BITS = createIntBitMask(32);
public int length(IP key) {
return 32;
}
public boolean isBitSet(IP key, int keyLength, int bitIndex) {
int maddr = key.addr & key.mask;
return (maddr & BITS[bitIndex]) != 0;
}
public int bitIndex(IP key, int keyOff, int keyLength,
IP found, int foundOff, int foundKeyLength) {
int maddr1 = key.addr & key.mask;
int maddr2 = (found != null) ? found.addr & found.mask : 0;
if(keyOff != 0 || foundOff != 0)
throw new IllegalArgumentException("offsets must be 0 for fixed-size keys");
int length = Math.max(keyLength, foundKeyLength);
boolean allNull = true;
for (int i = 0; i < length; i++) {
int a = maddr1 & BITS[i];
int b = maddr2 & BITS[i];
if (allNull && a != 0) {
allNull = false;
}
if (a != b) {
return i;
}
}
if (allNull) {
return KeyAnalyzer.NULL_BIT_KEY;
}
return KeyAnalyzer.EQUAL_BIT_KEY;
}
public int compare(IP o1, IP o2) {
int addr1 = o1.addr & o1.mask;
int addr2 = o2.addr & o2.mask;
if(addr1 > addr2)
return 1;
else if(addr1 < addr2)
return -1;
else
return 0;
}
// This method is generally intended for variable length keys.
// Fixed-length keys, such as an IP address (32 bits) tend to
// look at each element as a bit, thus 1 element == 1 bit.
public int bitsPerElement() {
return 1;
}
public boolean isPrefix(IP prefix, int offset, int length, IP key) {
int addr1 = prefix.addr & prefix.mask;
int addr2 = key.addr & key.mask;
addr1 = addr1 << offset;
int mask = 0;
for(int i = 0; i < length; i++) {
mask |= (0x1 << i);
}
addr1 &= mask;
addr2 &= mask;
return addr1 == addr2;
}
}
}