package com.limegroup.gnutella.filters;
import com.limegroup.gnutella.ByteOrder;
/**
* An IP address. More precisely, a set of IP addresses specified by a regular
* expression (e.g., "18.239.*.*") or a submask (e.g., "18.239.0.0/255.255.0.0".
* Immutable. Used to implement IPFilter; the generic usefulness of this
* class is questionable.
*
* This class is heavily optimized, as IP objects are constructed for every
* PingReply, QueryReply, PushRequest, and internally or externally generated
* connection.
*
* @author Gregorio Roper
*/
public class IP {
private static final String MSG = "Could not parse: ";
private final int addr;
private final int mask;
/**
* Creates an IP object out of a four byte array of the IP in
* BIG ENDIAN format (most significant byte first).
*/
public IP(byte[] ip_bytes) throws IllegalArgumentException {
if( ip_bytes.length != 4 )
throw new IllegalArgumentException(MSG);
this.addr = bytesToInt(ip_bytes, 0);
this.mask = -1; /* 255.255.255.255 == 0xFFFFFFFF */
}
/**
* Creates an IP object out of a String in the format
* "0.0.0.0", "0.0.0.0/0.0.0.0" or "0.0.0.0/0"
*
* @param ip_str a String of the format "0.0.0.0", "0.0.0.0/0.0.0.0",
* or "0.0.0.0/0" as an argument.
*/
public IP(final String ip_str) throws IllegalArgumentException {
int slash = ip_str.indexOf('/');
if (slash == -1) { //assume a simple IP "0.0.*.*"
this.mask = createNetmaskFromWildChars(ip_str);
this.addr = stringToInt(ip_str);
}
// ensure that it isn't a malformed address like
// 0.0.0.0/0/0
else if (ip_str.lastIndexOf('/') == slash) {
// maybe an IP of the form "0.0.0.0/0.0.0.0" or "0.0.0.0/0"
this.mask = parseNetmask(ip_str.substring(slash + 1));
this.addr = stringToInt(ip_str.substring(0, slash)) & this.mask;
} else
throw new IllegalArgumentException(MSG + ip_str);
}
/**
* Convert String containing an netmask to long variable containing
* a bitmask
* @param mask String containing a netmask of the format "0.0.0.0" or
* containing an unsigned integer < 32 if this netmask uses the
* simplified BSD syntax.
* @return int containing the subnetmask
*/
private static int parseNetmask(final String mask)
throws IllegalArgumentException {
if (mask.indexOf('.') != -1)
return stringToInt(mask);
// assume simple syntax, an integer in [0..32]
try {
// if the String contains the number k, we should return a
// mask of k ones followed by (32 - k) zeroes
int k = Integer.parseInt(mask);
if (k >= 0 && k <= 32)
// note: the >>> operator on 32-bit ints only considers the
// five lowest bits of the shift count, so 32 shifts would
// actually perform 0 shift!
return (k == 32) ? -1 : ~(-1 >>> k);
} catch (NumberFormatException e) {
}
throw new IllegalArgumentException(MSG + mask);
}
/**
* Converts a four byte array into a 32-bit int.
*/
private static int bytesToInt(byte[] ip_bytes, int offset) {
return ByteOrder.beb2int(ip_bytes, offset);
}
/**
* Convert String containing an ip_address or subnetmask to long
* containing a bitmask.
* @param String of the format "0.0.0..." presenting ip_address or
* subnetmask. A '*' will be converted to a '0'.
* @return long containing a bit representation of the ip address
*/
private static int stringToInt(final String ip_str)
throws IllegalArgumentException {
int ip = 0;
int numOctets = 0;
int length = ip_str.length();
// loop over each octet
for (int i = 0; i < length; i++, numOctets++) {
int octet = 0;
// loop over each character making the octet
for (int j = 0; i < length; i++, j++) {
char c = ip_str.charAt(i);
if (c == '.') { // finished octet?
// can't be first in octet, and not ending 4th octet.
if (j != 0 && numOctets < 3)
break; // loop to next octet
} else if (c == '*') { // convert wildcard.
// wildcard be the only character making an octet
if (j == 0) // functionality equivalent to setting c to be '0'
continue;
} else if (c >= '0' && c <= '9') {
// check we read no more than 3 digits
if (j <= 2) {
octet = octet * 10 + c - '0';
// check it's not a faulty addr.
if (octet <= 255)
continue;
}
}
throw new IllegalArgumentException(MSG + ip_str);
}
ip = (ip << 8) | octet;
}
// if the address had less than 4 octets, push the ip over suitably.
if (numOctets < 4)
ip <<= (4 - numOctets) * 8;
return ip;
}
/**
* Create new subnet mask from IP-address of the format "0.*.*.0".
* @param ip_str String of the format "W.X.Y.Z", W, X, Y and Z can be
* numbers or '*'.
* @return a 32-bit int with a subnet mask.
*/
private static int createNetmaskFromWildChars(final String ip_str)
throws IllegalArgumentException {
int mask = 0;
int numOctets = 0;
int length = ip_str.length();
// loop over each octet
for (int i = 0; i < length; i++, numOctets++) {
int submask = 255;
// loop over each character in the octet
// if we encounter a single non '*', mask it off.
for (int j = 0; i < length; i++, j++) {
char c = ip_str.charAt(i);
if (c == '.') {
// can't be first in octet, and not ending 4th octet.
if (j != 0 && numOctets < 3)
break; // loop to next octet
} else if (c == '*') {
// wildcard be the only character making an octet
if (j == 0) { // functionality equivalent to setting c to be '0'
submask = 0;
continue;
}
} else if (c >= '0' && c <= '9') {
// can't accept more than three characters.
if (j <= 2)
continue;
}
throw new IllegalArgumentException(MSG + ip_str);
}
mask = (mask << 8) | submask;
}
// if the address had less than 4 octets, push the mask over suitably.
if (numOctets < 4)
mask <<= (4 - numOctets) * 8;
return mask;
}
/** Returns the 32-bit netmask for this IPv4 address range. */
/* package */ int getMask() {
return mask;
}
/**
* Computes the minimum distance between any two IPv4 addresses within two
* IPv4 address ranges. Uses xor as the distance metric.
*
* @param ip a 32-bit IPv4 address range, represented as an IP object
* @return the distance between ipV4Addr and the nearest ip address
* represented by the range using the xor metric, a 32-bit unsigned
* integer value returned as a 32-bit signed int.
*/
public int getDistanceTo(IP ip) {
return (ip.addr ^ this.addr) & ip.mask & this.mask;
}
/**
* Returns if ip is contained in this.
* @param ip a singleton IP set, e.g., one representing a single address
*/
public boolean contains(IP ip) {
//Example: let this=1.1.1.*=1.1.1.0/255.255.255.0
// let ip =1.1.1.2=1.1.1.2/255.255.255.255
// => ip.addr&this.mask=1.1.1.0 (equals this.addr)
// => this.addr&this.mask=1.1.1.0 (equals this.mask)
return (ip.addr & this.mask) == (this.addr /* & this.mask*/) &&
(ip.mask & this.mask) == this.mask;
}
/**
* Returns true if other is an IP with the same address and mask. Note that
* "1.1.1.1/0.0.0.0" DOES equal "2.2.2.2/0.0.0.0", because they
* denote the same sets of addresses. But:<ul>
* <li>"1.1.1.1/255.255.255.255" DOES NOT equal "2.2.2.2/255.255.255.255"
* (disjoined sets of addresses, intersection and difference is empty).</li>
* <li>"1.1.1.1/255.255.255.240" DOES NOT equal "1.1.1.1/255.255.255.255"
* (intersection is not empty, but difference is not empty)</li>
* </ul>
* To be equal, the two compared sets must have the same netmask, and their
* start address (computed from the ip and netmask) must be equal.
*/
public boolean equals(Object other) {
if (other instanceof IP) {
IP ip = (IP)other;
return this.mask == ip.mask &&
(this.addr & this.mask) == (ip.addr & ip.mask);
} else {
return false;
}
}
public int hashCode() {
return addr^mask;
}
}