Merge branch '3' of https://github.com/blucas/netty into 3
This commit is contained in:
commit
ce391d36f4
234
src/main/java/org/jboss/netty/handler/ipfilter/CIDR.java
Normal file
234
src/main/java/org/jboss/netty/handler/ipfilter/CIDR.java
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
*/
|
||||
public abstract class CIDR implements Comparable<CIDR> {
|
||||
/** The base address of the CIDR notation */
|
||||
protected InetAddress baseAddress;
|
||||
|
||||
/** The mask used in the CIDR notation */
|
||||
protected int cidrMask;
|
||||
|
||||
/**
|
||||
* Create CIDR using the CIDR Notation
|
||||
*
|
||||
* @return the generated CIDR
|
||||
*/
|
||||
public static CIDR newCIDR(InetAddress baseAddress, int cidrMask) throws UnknownHostException {
|
||||
if (cidrMask < 0) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + cidrMask);
|
||||
}
|
||||
if (baseAddress instanceof Inet4Address) {
|
||||
if (cidrMask > 32) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + cidrMask);
|
||||
}
|
||||
return new CIDR4((Inet4Address) baseAddress, cidrMask);
|
||||
}
|
||||
// IPv6.
|
||||
if (cidrMask > 128) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + cidrMask);
|
||||
}
|
||||
return new CIDR6((Inet6Address) baseAddress, cidrMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create CIDR using the normal Notation
|
||||
*
|
||||
* @return the generated CIDR
|
||||
*/
|
||||
public static CIDR newCIDR(InetAddress baseAddress, String scidrMask) throws UnknownHostException {
|
||||
int cidrMask = getNetMask(scidrMask);
|
||||
if (cidrMask < 0) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + cidrMask);
|
||||
}
|
||||
if (baseAddress instanceof Inet4Address) {
|
||||
if (cidrMask > 32) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + cidrMask);
|
||||
}
|
||||
return new CIDR4((Inet4Address) baseAddress, cidrMask);
|
||||
}
|
||||
cidrMask += 96;
|
||||
// IPv6.
|
||||
if (cidrMask > 128) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + cidrMask);
|
||||
}
|
||||
return new CIDR6((Inet6Address) baseAddress, cidrMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create CIDR using the CIDR or normal Notation<BR>
|
||||
* i.e.:
|
||||
* CIDR subnet = newCIDR ("10.10.10.0/24"); or
|
||||
* CIDR subnet = newCIDR ("1fff:0:0a88:85a3:0:0:ac1f:8001/24"); or
|
||||
* CIDR subnet = newCIDR ("10.10.10.0/255.255.255.0");
|
||||
*
|
||||
* @return the generated CIDR
|
||||
*/
|
||||
public static CIDR newCIDR(String cidr) throws UnknownHostException {
|
||||
int p = cidr.indexOf("/");
|
||||
if (p < 0) {
|
||||
throw new UnknownHostException("Invalid CIDR notation used: " + cidr);
|
||||
}
|
||||
String addrString = cidr.substring(0, p);
|
||||
String maskString = cidr.substring(p + 1);
|
||||
InetAddress addr = addressStringToInet(addrString);
|
||||
int mask = 0;
|
||||
if (maskString.indexOf(".") < 0) {
|
||||
mask = parseInt(maskString, -1);
|
||||
} else {
|
||||
mask = getNetMask(maskString);
|
||||
if (addr instanceof Inet6Address) {
|
||||
mask += 96;
|
||||
}
|
||||
}
|
||||
if (mask < 0) {
|
||||
throw new UnknownHostException("Invalid mask length used: " + maskString);
|
||||
}
|
||||
return newCIDR(addr, mask);
|
||||
}
|
||||
|
||||
/** @return the baseAddress of the CIDR block. */
|
||||
public InetAddress getBaseAddress() {
|
||||
return baseAddress;
|
||||
}
|
||||
|
||||
/** @return the Mask length. */
|
||||
public int getMask() {
|
||||
return cidrMask;
|
||||
}
|
||||
|
||||
/** @return the textual CIDR notation. */
|
||||
@Override
|
||||
public String toString() {
|
||||
return baseAddress.getHostAddress() + "/" + cidrMask;
|
||||
}
|
||||
|
||||
/** @return the end address of this block. */
|
||||
public abstract InetAddress getEndAddress();
|
||||
|
||||
/**
|
||||
* Compares the given InetAddress against the CIDR and returns true if
|
||||
* the ip is in the subnet-ip-range and false if not.
|
||||
*
|
||||
* @return returns true if the given IP address is inside the currently
|
||||
* set network.
|
||||
*/
|
||||
public abstract boolean contains(InetAddress inetAddress);
|
||||
|
||||
@Override
|
||||
public boolean equals(Object arg0) {
|
||||
if (!(arg0 instanceof CIDR)) {
|
||||
return false;
|
||||
}
|
||||
return this.compareTo((CIDR) arg0) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return baseAddress.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an IPv4 or IPv6 textual representation into an
|
||||
* InetAddress.
|
||||
*
|
||||
* @return the created InetAddress
|
||||
*/
|
||||
private static InetAddress addressStringToInet(String addr) throws UnknownHostException {
|
||||
return InetAddress.getByName(addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Subnet's Netmask in Decimal format.<BR>
|
||||
* i.e.: getNetMask("255.255.255.0") returns the integer CIDR mask
|
||||
*
|
||||
* @param netMask a network mask
|
||||
* @return the integer CIDR mask
|
||||
*/
|
||||
private static int getNetMask(String netMask) {
|
||||
StringTokenizer nm = new StringTokenizer(netMask, ".");
|
||||
int i = 0;
|
||||
int[] netmask = new int[4];
|
||||
while (nm.hasMoreTokens()) {
|
||||
netmask[i] = Integer.parseInt(nm.nextToken());
|
||||
i++;
|
||||
}
|
||||
int mask1 = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
mask1 += Integer.bitCount(netmask[i]);
|
||||
}
|
||||
return mask1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intstr a string containing an integer.
|
||||
* @param def the default if the string does not contain a valid
|
||||
* integer.
|
||||
* @return the inetAddress from the integer
|
||||
*/
|
||||
private static int parseInt(String intstr, int def) {
|
||||
Integer res;
|
||||
if (intstr == null) {
|
||||
return def;
|
||||
}
|
||||
try {
|
||||
res = Integer.decode(intstr);
|
||||
} catch (Exception e) {
|
||||
res = new Integer(def);
|
||||
}
|
||||
return res.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a byte representation of IpV4 from a IpV6
|
||||
*
|
||||
* @return the byte representation
|
||||
* @throws IllegalArgumentException if the IpV6 cannot be mapped to IpV4
|
||||
*/
|
||||
public static byte[] getIpV4FromIpV6(Inet6Address address) {
|
||||
byte[] baddr = address.getAddress();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (baddr[i] != 0) {
|
||||
throw new IllegalArgumentException("This IPv6 address cannot be used in IPv4 context");
|
||||
}
|
||||
}
|
||||
if (baddr[10] != 0 && baddr[10] != 0xFF || baddr[11] != 0 && baddr[11] != 0xFF) {
|
||||
throw new IllegalArgumentException("This IPv6 address cannot be used in IPv4 context");
|
||||
}
|
||||
return new byte[]
|
||||
{baddr[12], baddr[13], baddr[14], baddr[15]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a byte representation of IpV6 from a IpV4
|
||||
*
|
||||
* @return the byte representation
|
||||
* @throws IllegalArgumentException if the IpV6 cannot be mapped to IpV4
|
||||
*/
|
||||
public static byte[] getIpV6FromIpV4(Inet4Address address) {
|
||||
byte[] baddr = address.getAddress();
|
||||
return new byte[]
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, baddr[0], baddr[1], baddr[2], baddr[3]};
|
||||
}
|
||||
}
|
159
src/main/java/org/jboss/netty/handler/ipfilter/CIDR4.java
Normal file
159
src/main/java/org/jboss/netty/handler/ipfilter/CIDR4.java
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class CIDR4 extends CIDR {
|
||||
/** The integer for the base address */
|
||||
private int addressInt;
|
||||
|
||||
/** The integer for the end address */
|
||||
private final int addressEndInt;
|
||||
|
||||
/**
|
||||
* @param newaddr
|
||||
* @param mask
|
||||
*/
|
||||
protected CIDR4(Inet4Address newaddr, int mask) {
|
||||
cidrMask = mask;
|
||||
addressInt = ipv4AddressToInt(newaddr);
|
||||
int newmask = ipv4PrefixLengthToMask(mask);
|
||||
addressInt &= newmask;
|
||||
try {
|
||||
baseAddress = intToIPv4Address(addressInt);
|
||||
} catch (UnknownHostException e) {
|
||||
// this should never happen
|
||||
}
|
||||
addressEndInt = addressInt + ipv4PrefixLengthToLength(cidrMask) - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getEndAddress() {
|
||||
try {
|
||||
return intToIPv4Address(addressEndInt);
|
||||
} catch (UnknownHostException e) {
|
||||
// this should never happen
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CIDR arg) {
|
||||
if (arg instanceof CIDR6) {
|
||||
byte[] address = getIpV4FromIpV6((Inet6Address) arg.baseAddress);
|
||||
int net = ipv4AddressToInt(address);
|
||||
if (net == addressInt && arg.cidrMask == cidrMask) {
|
||||
return 0;
|
||||
}
|
||||
if (net < addressInt) {
|
||||
return 1;
|
||||
} else if (net > addressInt) {
|
||||
return -1;
|
||||
} else if (arg.cidrMask < cidrMask) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
CIDR4 o = (CIDR4) arg;
|
||||
if (o.addressInt == addressInt && o.cidrMask == cidrMask) {
|
||||
return 0;
|
||||
}
|
||||
if (o.addressInt < addressInt) {
|
||||
return 1;
|
||||
} else if (o.addressInt > addressInt) {
|
||||
return -1;
|
||||
} else if (o.cidrMask < cidrMask) {
|
||||
// greater Mask means less IpAddresses so -1
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(InetAddress inetAddress) {
|
||||
int search = ipv4AddressToInt(inetAddress);
|
||||
return search >= addressInt && search <= addressEndInt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IPv4 baseAddress length, return the block length. I.e., a
|
||||
* baseAddress length of 24 will return 256.
|
||||
*/
|
||||
private static int ipv4PrefixLengthToLength(int prefix_length) {
|
||||
return 1 << 32 - prefix_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a baseAddress length, return a netmask. I.e, a baseAddress length
|
||||
* of 24 will return 0xFFFFFF00.
|
||||
*/
|
||||
private static int ipv4PrefixLengthToMask(int prefix_length) {
|
||||
return ~((1 << 32 - prefix_length) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an integer into an (IPv4) InetAddress.
|
||||
*
|
||||
* @return the created InetAddress
|
||||
*/
|
||||
private static InetAddress intToIPv4Address(int addr) throws UnknownHostException {
|
||||
byte[] a = new byte[4];
|
||||
a[0] = (byte) (addr >> 24 & 0xFF);
|
||||
a[1] = (byte) (addr >> 16 & 0xFF);
|
||||
a[2] = (byte) (addr >> 8 & 0xFF);
|
||||
a[3] = (byte) (addr & 0xFF);
|
||||
return InetAddress.getByAddress(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IPv4 address, convert it into an integer.
|
||||
*
|
||||
* @return the integer representation of the InetAddress
|
||||
* @throws IllegalArgumentException if the address is really an
|
||||
* IPv6 address.
|
||||
*/
|
||||
private static int ipv4AddressToInt(InetAddress addr) {
|
||||
byte[] address = null;
|
||||
if (addr instanceof Inet6Address) {
|
||||
address = getIpV4FromIpV6((Inet6Address) addr);
|
||||
} else {
|
||||
address = addr.getAddress();
|
||||
}
|
||||
return ipv4AddressToInt(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IPv4 address as array of bytes, convert it into an integer.
|
||||
*
|
||||
* @return the integer representation of the InetAddress
|
||||
* @throws IllegalArgumentException if the address is really an
|
||||
* IPv6 address.
|
||||
*/
|
||||
private static int ipv4AddressToInt(byte[] address) {
|
||||
int net = 0;
|
||||
for (byte addres : address) {
|
||||
net <<= 8;
|
||||
net |= addres & 0xFF;
|
||||
}
|
||||
return net;
|
||||
}
|
||||
}
|
164
src/main/java/org/jboss/netty/handler/ipfilter/CIDR6.java
Normal file
164
src/main/java/org/jboss/netty/handler/ipfilter/CIDR6.java
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class CIDR6 extends CIDR {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(CIDR6.class);
|
||||
|
||||
/** The big integer for the base address */
|
||||
private BigInteger addressBigInt;
|
||||
|
||||
/** The big integer for the end address */
|
||||
private final BigInteger addressEndBigInt;
|
||||
|
||||
/**
|
||||
* @param newaddress
|
||||
* @param newmask
|
||||
*/
|
||||
protected CIDR6(Inet6Address newaddress, int newmask) {
|
||||
cidrMask = newmask;
|
||||
addressBigInt = ipv6AddressToBigInteger(newaddress);
|
||||
BigInteger mask = ipv6CidrMaskToMask(newmask);
|
||||
try {
|
||||
addressBigInt = addressBigInt.and(mask);
|
||||
baseAddress = bigIntToIPv6Address(addressBigInt);
|
||||
} catch (UnknownHostException e) {
|
||||
// this should never happen.
|
||||
}
|
||||
addressEndBigInt = addressBigInt.add(ipv6CidrMaskToBaseAddress(cidrMask)).subtract(BigInteger.ONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getEndAddress() {
|
||||
try {
|
||||
return bigIntToIPv6Address(addressEndBigInt);
|
||||
} catch (UnknownHostException e) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("invalid ip address calculated as an end address");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CIDR arg) {
|
||||
if (arg instanceof CIDR4) {
|
||||
BigInteger net = ipv6AddressToBigInteger(arg.baseAddress);
|
||||
int res = net.compareTo(addressBigInt);
|
||||
if (res == 0) {
|
||||
if (arg.cidrMask == cidrMask) {
|
||||
return 0;
|
||||
} else if (arg.cidrMask < cidrMask) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
CIDR6 o = (CIDR6) arg;
|
||||
if (o.addressBigInt.equals(addressBigInt) && o.cidrMask == cidrMask) {
|
||||
return 0;
|
||||
}
|
||||
int res = o.addressBigInt.compareTo(addressBigInt);
|
||||
if (res == 0) {
|
||||
if (o.cidrMask < cidrMask) {
|
||||
// greater Mask means less IpAddresses so -1
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(InetAddress inetAddress) {
|
||||
BigInteger search = ipv6AddressToBigInteger(inetAddress);
|
||||
return search.compareTo(addressBigInt) >= 0 && search.compareTo(addressEndBigInt) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IPv6 baseAddress length, return the block length. I.e., a
|
||||
* baseAddress length of 96 will return 2**32.
|
||||
*/
|
||||
private static BigInteger ipv6CidrMaskToBaseAddress(int cidrMask) {
|
||||
return BigInteger.ONE.shiftLeft(128 - cidrMask);
|
||||
}
|
||||
|
||||
private static BigInteger ipv6CidrMaskToMask(int cidrMask) {
|
||||
return BigInteger.ONE.shiftLeft(128 - cidrMask).subtract(BigInteger.ONE).not();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IPv6 address, convert it into a BigInteger.
|
||||
*
|
||||
* @return the integer representation of the InetAddress
|
||||
* @throws IllegalArgumentException if the address is not an IPv6
|
||||
* address.
|
||||
*/
|
||||
private static BigInteger ipv6AddressToBigInteger(InetAddress addr) {
|
||||
byte[] ipv6;
|
||||
if (addr instanceof Inet4Address) {
|
||||
ipv6 = getIpV6FromIpV4((Inet4Address) addr);
|
||||
} else {
|
||||
ipv6 = addr.getAddress();
|
||||
}
|
||||
if (ipv6[0] == -1) {
|
||||
return new BigInteger(1, ipv6);
|
||||
}
|
||||
return new BigInteger(ipv6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a big integer into an IPv6 address.
|
||||
*
|
||||
* @return the inetAddress from the integer
|
||||
* @throws UnknownHostException if the big integer is too large,
|
||||
* and thus an invalid IPv6 address.
|
||||
*/
|
||||
private static InetAddress bigIntToIPv6Address(BigInteger addr) throws UnknownHostException {
|
||||
byte[] a = new byte[16];
|
||||
byte[] b = addr.toByteArray();
|
||||
if (b.length > 16 && !(b.length == 17 && b[0] == 0)) {
|
||||
throw new UnknownHostException("invalid IPv6 address (too big)");
|
||||
}
|
||||
if (b.length == 16) {
|
||||
return InetAddress.getByAddress(b);
|
||||
}
|
||||
// handle the case where the IPv6 address starts with "FF".
|
||||
if (b.length == 17) {
|
||||
System.arraycopy(b, 1, a, 0, 16);
|
||||
} else {
|
||||
// copy the address into a 16 byte array, zero-filled.
|
||||
int p = 16 - b.length;
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
a[p + i] = b[i];
|
||||
}
|
||||
}
|
||||
return InetAddress.getByAddress(a);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
* The listener interface for receiving ipFilter events.
|
||||
*
|
||||
* @see IpFilteringHandler
|
||||
*/
|
||||
public interface IpFilterListener {
|
||||
|
||||
/**
|
||||
* Called when the channel has the CONNECTED status and the channel was allowed by a previous call to accept().
|
||||
* This method enables your implementation to send a message back to the client before closing
|
||||
* or whatever you need. This method returns a ChannelFuture on which the implementation
|
||||
* can wait uninterruptibly before continuing.<br>
|
||||
* For instance, If a message is sent back, the corresponding ChannelFuture has to be returned.
|
||||
*
|
||||
* @param inetSocketAddress the remote {@link InetSocketAddress} from client
|
||||
* @return the associated ChannelFuture to be waited for before closing the channel. Null is allowed.
|
||||
*/
|
||||
ChannelFuture allowed(ChannelHandlerContext ctx, ChannelEvent e, InetSocketAddress inetSocketAddress);
|
||||
|
||||
/**
|
||||
* Called when the channel has the CONNECTED status and the channel was refused by a previous call to accept().
|
||||
* This method enables your implementation to send a message back to the client before closing
|
||||
* or whatever you need. This method returns a ChannelFuture on which the implementation
|
||||
* will wait uninterruptibly before closing the channel.<br>
|
||||
* For instance, If a message is sent back, the corresponding ChannelFuture has to be returned.
|
||||
*
|
||||
* @param inetSocketAddress the remote {@link InetSocketAddress} from client
|
||||
* @return the associated ChannelFuture to be waited for before closing the channel. Null is allowed.
|
||||
*/
|
||||
ChannelFuture refused(ChannelHandlerContext ctx, ChannelEvent e, InetSocketAddress inetSocketAddress);
|
||||
|
||||
/**
|
||||
* Called in handleUpstream, if this channel was previously blocked,
|
||||
* to check if whatever the event, it should be passed to the next entry in the pipeline.<br>
|
||||
* If one wants to not block events, just overridden this method by returning always true.<br><br>
|
||||
* <b>Note that OPENED and BOUND events are still passed to the next entry in the pipeline since
|
||||
* those events come out before the CONNECTED event and so the possibility to filter the connection.</b>
|
||||
*
|
||||
* @return True if the event should continue, False if the event should not continue
|
||||
* since this channel was blocked by this filter
|
||||
*/
|
||||
boolean continues(ChannelHandlerContext ctx, ChannelEvent e);
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
/** This Interface defines an Ip Filter Rule. */
|
||||
public interface IpFilterRule extends IpSet {
|
||||
/** @return True if this Rule is an ALLOW rule */
|
||||
boolean isAllowRule();
|
||||
|
||||
/** @return True if this Rule is a DENY rule */
|
||||
boolean isDenyRule();
|
||||
}
|
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelHandler.Sharable;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
* Implementation of Filter of IP based on ALLOW and DENY rules.<br>
|
||||
* <br><br>
|
||||
* This implementation could be changed by implementing a new {@link IpFilterRule} than default
|
||||
* {@link IpV4SubnetFilterRule} (IPV4 support only), {@link IpSubnetFilterRule} (IPV4 and IPV6 support) or {@link IpFilterRule} (IP and host name string pattern support) .<br>
|
||||
* <br>
|
||||
* The check is done by going from step to step in the underlying array of IpFilterRule.<br>
|
||||
* Each {@link IpFilterRule} answers to the method accept if the {@link InetAddress} is accepted or not,
|
||||
* according to its implementation. If an InetAddress arrives at the end of the list, as in Firewall
|
||||
* usual rules, the InetAddress is therefore accepted by default.<br>
|
||||
* <ul>
|
||||
* <li>If it was constructed with True as first argument,
|
||||
* the IpFilterRule is an ALLOW rule (every InetAddress that fits in the rule will be accepted).</li>
|
||||
* <li>If it was constructed with False as first argument,
|
||||
* the IpFilterRule is a DENY rule (every InetAddress that fits in the rule will be refused).</li>
|
||||
* </ul><br>
|
||||
* <br>
|
||||
* An empty list means allow all (no limitation).<br><br>
|
||||
* <b>For efficiency reason, you should not add/remove too frequently IpFilterRules to/from this handler.
|
||||
* You should prefer to replace an entry (<tt>set</tt> method) with an ALLOW/DENY ALL IpFilterRule
|
||||
* if possible.</b><br><br><br>
|
||||
* <b>This handler should be created only once and reused on every pipeline since it handles
|
||||
* a global status of what is allowed or blocked.</b><br><br>
|
||||
* <p/>
|
||||
* Note that {@link IpSubnetFilterRule} which supports IPV4 and IPV6 should be used with as much as
|
||||
* possible no mixed IP protocol. Both IPV4 and IPV6 are supported but a mix (IpFilter in IPV6 notation
|
||||
* and the address from the channel in IPV4, or the reverse) can lead to wrong result.
|
||||
*/
|
||||
@Sharable
|
||||
public class IpFilterRuleHandler extends IpFilteringHandlerImpl {
|
||||
/** List of {@link IpFilterRule} */
|
||||
private final CopyOnWriteArrayList<IpFilterRule> ipFilterRuleList = new CopyOnWriteArrayList<IpFilterRule>();
|
||||
|
||||
/** Constructor from a new list of IpFilterRule */
|
||||
public IpFilterRuleHandler(List<IpFilterRule> newList) {
|
||||
if (newList != null) {
|
||||
ipFilterRuleList.addAll(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty constructor (no IpFilterRule in the List at construction). In such a situation,
|
||||
* empty list implies allow all.
|
||||
*/
|
||||
public IpFilterRuleHandler() {
|
||||
}
|
||||
|
||||
// Below are methods directly inspired from CopyOnWriteArrayList methods
|
||||
|
||||
/** Add an ipFilterRule in the list at the end */
|
||||
public void add(IpFilterRule ipFilterRule) {
|
||||
if (ipFilterRule == null) {
|
||||
throw new NullPointerException("IpFilterRule can not be null");
|
||||
}
|
||||
ipFilterRuleList.add(ipFilterRule);
|
||||
}
|
||||
|
||||
/** Add an ipFilterRule in the list at the specified position (shifting to the right other elements) */
|
||||
public void add(int index, IpFilterRule ipFilterRule) {
|
||||
if (ipFilterRule == null) {
|
||||
throw new NullPointerException("IpFilterRule can not be null");
|
||||
}
|
||||
ipFilterRuleList.add(index, ipFilterRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends all of the elements in the specified collection to the end of this list,
|
||||
* in the order that they are returned by the specified collection's iterator.
|
||||
*/
|
||||
public void addAll(Collection<IpFilterRule> c) {
|
||||
if (c == null) {
|
||||
throw new NullPointerException("Collection can not be null");
|
||||
}
|
||||
ipFilterRuleList.addAll(c);
|
||||
}
|
||||
|
||||
/** Inserts all of the elements in the specified collection into this list, starting at the specified position. */
|
||||
public void addAll(int index, Collection<IpFilterRule> c) {
|
||||
if (c == null) {
|
||||
throw new NullPointerException("Collection can not be null");
|
||||
}
|
||||
ipFilterRuleList.addAll(index, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the element if not present.
|
||||
*
|
||||
* @return the number of elements added
|
||||
*/
|
||||
public int addAllAbsent(Collection<IpFilterRule> c) {
|
||||
if (c == null) {
|
||||
throw new NullPointerException("Collection can not be null");
|
||||
}
|
||||
return ipFilterRuleList.addAllAbsent(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the element if not present.
|
||||
*
|
||||
* @return true if the element was added
|
||||
*/
|
||||
public boolean addIfAbsent(IpFilterRule ipFilterRule) {
|
||||
if (ipFilterRule == null) {
|
||||
throw new NullPointerException("IpFilterRule can not be null");
|
||||
}
|
||||
return ipFilterRuleList.addIfAbsent(ipFilterRule);
|
||||
}
|
||||
|
||||
/** Clear the list */
|
||||
public void clear() {
|
||||
ipFilterRuleList.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this list contains the specified element
|
||||
*
|
||||
* @return true if this list contains the specified element
|
||||
*/
|
||||
public boolean contains(IpFilterRule ipFilterRule) {
|
||||
if (ipFilterRule == null) {
|
||||
throw new NullPointerException("IpFilterRule can not be null");
|
||||
}
|
||||
return ipFilterRuleList.contains(ipFilterRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this list contains all of the elements of the specified collection
|
||||
*
|
||||
* @return true if this list contains all of the elements of the specified collection
|
||||
*/
|
||||
public boolean containsAll(Collection<IpFilterRule> c) {
|
||||
if (c == null) {
|
||||
throw new NullPointerException("Collection can not be null");
|
||||
}
|
||||
return ipFilterRuleList.containsAll(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element at the specified position in this list
|
||||
*
|
||||
* @return the element at the specified position in this list
|
||||
*/
|
||||
public IpFilterRule get(int index) {
|
||||
return ipFilterRuleList.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this list contains no elements
|
||||
*
|
||||
* @return true if this list contains no elements
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return ipFilterRuleList.isEmpty();
|
||||
}
|
||||
|
||||
/** Remove the ipFilterRule from the list */
|
||||
public void remove(IpFilterRule ipFilterRule) {
|
||||
if (ipFilterRule == null) {
|
||||
throw new NullPointerException("IpFilterRule can not be null");
|
||||
}
|
||||
ipFilterRuleList.remove(ipFilterRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element at the specified position in this list
|
||||
*
|
||||
* @return the element previously at the specified position
|
||||
*/
|
||||
public IpFilterRule remove(int index) {
|
||||
return ipFilterRuleList.remove(index);
|
||||
}
|
||||
|
||||
/** Removes from this list all of its elements that are contained in the specified collection */
|
||||
public void removeAll(Collection<IpFilterRule> c) {
|
||||
if (c == null) {
|
||||
throw new NullPointerException("Collection can not be null");
|
||||
}
|
||||
ipFilterRuleList.removeAll(c);
|
||||
}
|
||||
|
||||
/** Retains only the elements in this list that are contained in the specified collection */
|
||||
public void retainAll(Collection<IpFilterRule> c) {
|
||||
if (c == null) {
|
||||
throw new NullPointerException("Collection can not be null");
|
||||
}
|
||||
ipFilterRuleList.retainAll(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the element at the specified position in this list with the specified element
|
||||
*
|
||||
* @return the element previously at the specified position
|
||||
*/
|
||||
public IpFilterRule set(int index, IpFilterRule ipFilterRule) {
|
||||
if (ipFilterRule == null) {
|
||||
throw new NullPointerException("IpFilterRule can not be null");
|
||||
}
|
||||
return ipFilterRuleList.set(index, ipFilterRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in this list.
|
||||
*
|
||||
* @return the number of elements in this list.
|
||||
*/
|
||||
public int size() {
|
||||
return ipFilterRuleList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean accept(ChannelHandlerContext ctx, ChannelEvent e, InetSocketAddress inetSocketAddress)
|
||||
throws Exception {
|
||||
if (ipFilterRuleList.isEmpty()) {
|
||||
// No limitation neither in deny or allow, so accept
|
||||
return true;
|
||||
}
|
||||
InetAddress inetAddress = inetSocketAddress.getAddress();
|
||||
Iterator<IpFilterRule> iterator = ipFilterRuleList.iterator();
|
||||
IpFilterRule ipFilterRule = null;
|
||||
while (iterator.hasNext()) {
|
||||
ipFilterRule = iterator.next();
|
||||
if (ipFilterRule.contains(inetAddress)) {
|
||||
// Match founds, is it a ALLOW or DENY rule
|
||||
return ipFilterRule.isAllowRule();
|
||||
}
|
||||
}
|
||||
// No limitation founds and no allow either, but as it is like Firewall rules, it is therefore accepted
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* The Class IpFilterRuleList is a helper class to generate a List of Rules from a string.
|
||||
* In case of parse errors no exceptions are thrown. The error is logged.
|
||||
* <br>
|
||||
* Rule List Syntax:
|
||||
* <br>
|
||||
* <pre>
|
||||
* RuleList ::= Rule[,Rule]*
|
||||
* Rule ::= AllowRule | BlockRule
|
||||
* AllowRule ::= +Filter
|
||||
* BlockRule ::= -Filter
|
||||
* Filter ::= PatternFilter | CIDRFilter
|
||||
* PatternFilter ::= @see PatternRule
|
||||
* CIDRFilter ::= c:CIDRFilter
|
||||
* CIDRFilter ::= @see CIDR.newCIDR(String)
|
||||
* </pre>
|
||||
* <br>
|
||||
* Example: allow only localhost:
|
||||
* <br>
|
||||
* new IPFilterRuleHandler().addAll(new IpFilterRuleList("+n:localhost, -n:*"));
|
||||
* <br>
|
||||
*/
|
||||
public class IpFilterRuleList extends ArrayList<IpFilterRule> {
|
||||
private static final long serialVersionUID = -6164162941749588780L;
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(IpFilterRuleList.class);
|
||||
|
||||
/**
|
||||
* Instantiates a new ip filter rule list.
|
||||
*
|
||||
* @param rules the rules
|
||||
*/
|
||||
public IpFilterRuleList(String rules) {
|
||||
parseRules(rules);
|
||||
}
|
||||
|
||||
private void parseRules(String rules) {
|
||||
String[] ruless = rules.split(",");
|
||||
for (String rule : ruless) {
|
||||
parseRule(rule.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseRule(String rule) {
|
||||
if (rule == null || rule.length() == 0) {
|
||||
return;
|
||||
}
|
||||
if (!(rule.startsWith("+") || rule.startsWith("-"))) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("syntax error in ip filter rule:" + rule);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allow = rule.startsWith("+");
|
||||
if (rule.charAt(1) == 'n' || rule.charAt(1) == 'i') {
|
||||
this.add(new PatternRule(allow, rule.substring(1)));
|
||||
} else if (rule.charAt(1) == 'c') {
|
||||
try {
|
||||
this.add(new IpSubnetFilterRule(allow, rule.substring(3)));
|
||||
} catch (UnknownHostException e) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("error parsing ip filter " + rule, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("syntax error in ip filter rule:" + rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
/**
|
||||
* The Interface IpFilteringHandler.
|
||||
* A Filter of IP.
|
||||
* <br>
|
||||
* Users can add an {@link IpFilterListener} to add specific actions in case a connection is allowed or refused.
|
||||
*/
|
||||
public interface IpFilteringHandler {
|
||||
|
||||
/**
|
||||
* Sets the filter listener.
|
||||
*
|
||||
* @param listener the new ip filter listener
|
||||
*/
|
||||
void setIpFilterListener(IpFilterListener listener);
|
||||
|
||||
/** Remove the filter listener. */
|
||||
void removeIpFilterListener();
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.ChannelUpstreamHandler;
|
||||
import org.jboss.netty.channel.Channels;
|
||||
|
||||
// TODO: Auto-generated Javadoc
|
||||
|
||||
/** General class that handle Ip Filtering. */
|
||||
public abstract class IpFilteringHandlerImpl implements ChannelUpstreamHandler, IpFilteringHandler {
|
||||
|
||||
private IpFilterListener listener;
|
||||
|
||||
/**
|
||||
* Called when the channel is connected. It returns True if the corresponding connection
|
||||
* is to be allowed. Else it returns False.
|
||||
*
|
||||
* @param inetSocketAddress the remote {@link InetSocketAddress} from client
|
||||
* @return True if the corresponding connection is allowed, else False.
|
||||
*/
|
||||
protected abstract boolean accept(ChannelHandlerContext ctx, ChannelEvent e, InetSocketAddress inetSocketAddress)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Called when the channel has the CONNECTED status and the channel was refused by a previous call to accept().
|
||||
* This method enables your implementation to send a message back to the client before closing
|
||||
* or whatever you need. This method returns a ChannelFuture on which the implementation
|
||||
* will wait uninterruptibly before closing the channel.<br>
|
||||
* For instance, If a message is sent back, the corresponding ChannelFuture has to be returned.
|
||||
*
|
||||
* @param inetSocketAddress the remote {@link InetSocketAddress} from client
|
||||
* @return the associated ChannelFuture to be waited for before closing the channel. Null is allowed.
|
||||
*/
|
||||
protected ChannelFuture handleRefusedChannel(ChannelHandlerContext ctx, ChannelEvent e,
|
||||
InetSocketAddress inetSocketAddress) throws Exception {
|
||||
if (listener == null) {
|
||||
return null;
|
||||
}
|
||||
return listener.refused(ctx, e, inetSocketAddress);
|
||||
}
|
||||
|
||||
protected ChannelFuture handleAllowedChannel(ChannelHandlerContext ctx, ChannelEvent e,
|
||||
InetSocketAddress inetSocketAddress) throws Exception {
|
||||
if (listener == null) {
|
||||
return null;
|
||||
}
|
||||
return listener.allowed(ctx, e, inetSocketAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to test if the current channel is blocked. Should not be overridden.
|
||||
*
|
||||
* @return True if the current channel is blocked, else False
|
||||
*/
|
||||
protected boolean isBlocked(ChannelHandlerContext ctx) {
|
||||
return ctx.getAttachment() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in handleUpstream, if this channel was previously blocked,
|
||||
* to check if whatever the event, it should be passed to the next entry in the pipeline.<br>
|
||||
* If one wants to not block events, just overridden this method by returning always true.<br><br>
|
||||
* <b>Note that OPENED and BOUND events are still passed to the next entry in the pipeline since
|
||||
* those events come out before the CONNECTED event and so the possibility to filter the connection.</b>
|
||||
*
|
||||
* @return True if the event should continue, False if the event should not continue
|
||||
* since this channel was blocked by this filter
|
||||
*/
|
||||
protected boolean continues(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
|
||||
if (listener != null) {
|
||||
return listener.continues(ctx, e);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
|
||||
if (e instanceof ChannelStateEvent) {
|
||||
ChannelStateEvent evt = (ChannelStateEvent) e;
|
||||
switch (evt.getState()) {
|
||||
case OPEN:
|
||||
case BOUND:
|
||||
// Special case: OPEND and BOUND events are before CONNECTED,
|
||||
// but CLOSED and UNBOUND events are after DISCONNECTED: should those events be blocked too?
|
||||
if (isBlocked(ctx) && !continues(ctx, evt)) {
|
||||
// don't pass to next level since channel was blocked early
|
||||
return;
|
||||
} else {
|
||||
ctx.sendUpstream(e);
|
||||
return;
|
||||
}
|
||||
case CONNECTED:
|
||||
if (evt.getValue() != null) {
|
||||
// CONNECTED
|
||||
InetSocketAddress inetSocketAddress = (InetSocketAddress) e.getChannel().getRemoteAddress();
|
||||
if (!accept(ctx, e, inetSocketAddress)) {
|
||||
ctx.setAttachment(Boolean.TRUE);
|
||||
ChannelFuture future = handleRefusedChannel(ctx, e, inetSocketAddress);
|
||||
if (future != null) {
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
Channels.close(e.getChannel());
|
||||
}
|
||||
if (isBlocked(ctx) && !continues(ctx, evt)) {
|
||||
// don't pass to next level since channel was blocked early
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
handleAllowedChannel(ctx, e, inetSocketAddress);
|
||||
}
|
||||
// This channel is not blocked
|
||||
ctx.setAttachment(null);
|
||||
} else {
|
||||
// DISCONNECTED
|
||||
if (isBlocked(ctx) && !continues(ctx, evt)) {
|
||||
// don't pass to next level since channel was blocked early
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isBlocked(ctx) && !continues(ctx, e)) {
|
||||
// don't pass to next level since channel was blocked early
|
||||
return;
|
||||
}
|
||||
// Whatever it is, if not blocked, goes to the next level
|
||||
ctx.sendUpstream(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIpFilterListener(IpFilterListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIpFilterListener() {
|
||||
this.listener = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
30
src/main/java/org/jboss/netty/handler/ipfilter/IpSet.java
Normal file
30
src/main/java/org/jboss/netty/handler/ipfilter/IpSet.java
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
/** This Interface defines an IpSet object. */
|
||||
public interface IpSet {
|
||||
/**
|
||||
* Compares the given InetAddress against the IpSet and returns true if
|
||||
* the InetAddress is contained in this Rule and false if not.
|
||||
*
|
||||
* @return returns true if the given IP address is contained in the current
|
||||
* IpSet.
|
||||
*/
|
||||
boolean contains(InetAddress inetAddress1);
|
||||
}
|
168
src/main/java/org/jboss/netty/handler/ipfilter/IpSubnet.java
Normal file
168
src/main/java/org/jboss/netty/handler/ipfilter/IpSubnet.java
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* This class allows to check if an IP V4 or V6 Address is contained in a subnet.<BR>
|
||||
* <p/>
|
||||
* Supported IP V4 Formats for the Subnets are: 1.1.1.1/255.255.255.255 or 1.1.1.1/32 (CIDR-Notation)
|
||||
* and (InetAddress,Mask) where Mask is a integer for CIDR-notation or a String for Standard Mask notation.<BR>
|
||||
* <BR><BR>Example1:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet("192.168.1.0/24");</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains("192.168.1.123"));</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains(inetAddress2));</tt><BR>
|
||||
* <BR>Example1 bis:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet(inetAddress, 24);</tt><BR>
|
||||
* where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123<BR>
|
||||
* <BR><BR>Example2:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet("192.168.1.0/255.255.255.0");</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains("192.168.1.123"));</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains(inetAddress2));</tt><BR>
|
||||
* <BR>Example2 bis:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet(inetAddress, "255.255.255.0");</tt><BR>
|
||||
* where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123<BR>
|
||||
* <BR>
|
||||
* Supported IP V6 Formats for the Subnets are: a:b:c:d:e:f:g:h/NN (CIDR-Notation)
|
||||
* or any IPV6 notations (like a:b:c:d::/NN, a:b:c:d:e:f:w.x.y.z/NN)
|
||||
* and (InetAddress,Mask) where Mask is a integer for CIDR-notation
|
||||
* and (InetAddress,subnet).<BR>
|
||||
* <BR><BR>Example1:<BR>
|
||||
* <tt>IpSubnet ips = new IpSubnet("1fff:0:0a88:85a3:0:0:0:0/24");</tt><BR>
|
||||
* <tt>IpSubnet ips = new IpSubnet("1fff:0:0a88:85a3::/24");</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains("1fff:0:0a88:85a3:0:0:ac1f:8001"));</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains(inetAddress2));</tt><BR>
|
||||
* <BR>Example1 bis:<BR>
|
||||
* <tt>IpSubnet ips = new IpSubnet(inetAddress, 24);</tt><BR>
|
||||
* where inetAddress2 is 1fff:0:0a88:85a3:0:0:ac1f:8001<BR>
|
||||
*/
|
||||
public class IpSubnet implements IpSet, Comparable<IpSubnet> {
|
||||
|
||||
private static final InternalLogger logger =
|
||||
InternalLoggerFactory.getInstance(IpSubnet.class);
|
||||
|
||||
/** Internal representation */
|
||||
private CIDR cidr;
|
||||
|
||||
/** Create IpSubnet for ALL (used for ALLOW or DENY ALL) */
|
||||
public IpSubnet() {
|
||||
// ALLOW or DENY ALL
|
||||
cidr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create IpSubnet using the CIDR or normal Notation<BR>
|
||||
* i.e.:<br>
|
||||
* IpSubnet subnet = new IpSubnet("10.10.10.0/24"); or<br>
|
||||
* IpSubnet subnet = new IpSubnet("10.10.10.0/255.255.255.0"); or<br>
|
||||
* IpSubnet subnet = new IpSubnet("1fff:0:0a88:85a3:0:0:0:0/24");
|
||||
*
|
||||
* @param netAddress a network address as string.
|
||||
*/
|
||||
public IpSubnet(String netAddress) throws UnknownHostException {
|
||||
cidr = CIDR.newCIDR(netAddress);
|
||||
}
|
||||
|
||||
/** Create IpSubnet using the CIDR Notation */
|
||||
public IpSubnet(InetAddress inetAddress, int cidrNetMask) throws UnknownHostException {
|
||||
cidr = CIDR.newCIDR(inetAddress, cidrNetMask);
|
||||
}
|
||||
|
||||
/** Create IpSubnet using the normal Notation */
|
||||
public IpSubnet(InetAddress inetAddress, String netMask) throws UnknownHostException {
|
||||
cidr = CIDR.newCIDR(inetAddress, netMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the given IP-Address against the Subnet and returns true if
|
||||
* the ip is in the subnet-ip-range and false if not.
|
||||
*
|
||||
* @param ipAddr an ipaddress
|
||||
* @return returns true if the given IP address is inside the currently
|
||||
* set network.
|
||||
*/
|
||||
public boolean contains(String ipAddr) throws UnknownHostException {
|
||||
InetAddress inetAddress1 = InetAddress.getByName(ipAddr);
|
||||
return this.contains(inetAddress1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the given InetAddress against the Subnet and returns true if
|
||||
* the ip is in the subnet-ip-range and false if not.
|
||||
*
|
||||
* @return returns true if the given IP address is inside the currently
|
||||
* set network.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(InetAddress inetAddress) {
|
||||
if (cidr == null) {
|
||||
// ANY
|
||||
return true;
|
||||
}
|
||||
return cidr.contains(inetAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return cidr.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof IpSubnet)) {
|
||||
return false;
|
||||
}
|
||||
IpSubnet ipSubnet = (IpSubnet) o;
|
||||
return ipSubnet.cidr.equals(cidr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cidr.hashCode();
|
||||
}
|
||||
|
||||
/** Compare two IpSubnet */
|
||||
@Override
|
||||
public int compareTo(IpSubnet o) {
|
||||
return cidr.toString().compareTo(o.cidr.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test functions
|
||||
*
|
||||
* @param args where args[0] is the netmask (standard or CIDR notation) and optional args[1] is
|
||||
* the inetAddress to test with this IpSubnet
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length != 0) {
|
||||
IpSubnet ipSubnet = null;
|
||||
try {
|
||||
ipSubnet = new IpSubnet(args[0]);
|
||||
} catch (UnknownHostException e) {
|
||||
return;
|
||||
}
|
||||
logger.debug("IpSubnet: " + ipSubnet.toString() + " from " + ipSubnet.cidr.getBaseAddress() + " to "
|
||||
+ ipSubnet.cidr.getEndAddress() + " mask " + ipSubnet.cidr.getMask());
|
||||
if (args.length > 1) {
|
||||
logger.debug("Is IN: " + args[1] + " " + ipSubnet.contains(args[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Ip V4 and Ip V6 filter rule.<br>
|
||||
* <br>
|
||||
* Note that mix of IPV4 and IPV6 is allowed but it is not recommended. So it is preferable to not
|
||||
* mix IPV4 addresses with IPV6 rules, even if it should work.
|
||||
*/
|
||||
public class IpSubnetFilterRule extends IpSubnet implements IpFilterRule {
|
||||
/** Is this IpV4Subnet an ALLOW or DENY rule */
|
||||
private boolean isAllowRule = true;
|
||||
|
||||
/**
|
||||
* Constructor for a ALLOW or DENY ALL
|
||||
*
|
||||
* @param allow True for ALLOW, False for DENY
|
||||
*/
|
||||
public IpSubnetFilterRule(boolean allow) {
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
/** @param allow True for ALLOW, False for DENY */
|
||||
public IpSubnetFilterRule(boolean allow, InetAddress inetAddress, int cidrNetMask) throws UnknownHostException {
|
||||
super(inetAddress, cidrNetMask);
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
/** @param allow True for ALLOW, False for DENY */
|
||||
public IpSubnetFilterRule(boolean allow, InetAddress inetAddress, String netMask) throws UnknownHostException {
|
||||
super(inetAddress, netMask);
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
/** @param allow True for ALLOW, False for DENY */
|
||||
public IpSubnetFilterRule(boolean allow, String netAddress) throws UnknownHostException {
|
||||
super(netAddress);
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowRule() {
|
||||
return isAllowRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDenyRule() {
|
||||
return !isAllowRule;
|
||||
}
|
||||
|
||||
}
|
276
src/main/java/org/jboss/netty/handler/ipfilter/IpV4Subnet.java
Normal file
276
src/main/java/org/jboss/netty/handler/ipfilter/IpV4Subnet.java
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* This class allows to check if an IP-V4-Address is contained in a subnet.<BR>
|
||||
* Supported Formats for the Subnets are: 1.1.1.1/255.255.255.255 or 1.1.1.1/32 (CIDR-Notation)
|
||||
* and (InetAddress,Mask) where Mask is a integer for CIDR-notation or a String for Standard Mask notation.<BR>
|
||||
* <BR><BR>Example1:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet("192.168.1.0/24");</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains("192.168.1.123"));</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains(inetAddress2));</tt><BR>
|
||||
* <BR>Example1 bis:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet(inetAddress, 24);</tt><BR>
|
||||
* where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123<BR>
|
||||
* <BR><BR>Example2:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet("192.168.1.0/255.255.255.0");</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains("192.168.1.123"));</tt><BR>
|
||||
* <tt>System.out.println("Result: "+ ips.contains(inetAddress2));</tt><BR>
|
||||
* <BR>Example2 bis:<BR>
|
||||
* <tt>IpV4Subnet ips = new IpV4Subnet(inetAddress, "255.255.255.0");</tt><BR>
|
||||
* where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123<BR>
|
||||
*/
|
||||
public class IpV4Subnet implements IpSet, Comparable<IpV4Subnet> {
|
||||
|
||||
private static final InternalLogger logger =
|
||||
InternalLoggerFactory.getInstance(IpV4Subnet.class);
|
||||
|
||||
private static final int SUBNET_MASK = 0x80000000;
|
||||
|
||||
private static final int BYTE_ADDRESS_MASK = 0xFF;
|
||||
|
||||
private InetAddress inetAddress;
|
||||
|
||||
private int subnet;
|
||||
|
||||
private int mask;
|
||||
|
||||
private int cidrMask;
|
||||
|
||||
/** Create IpV4Subnet for ALL (used for ALLOW or DENY ALL) */
|
||||
public IpV4Subnet() {
|
||||
// ALLOW or DENY ALL
|
||||
mask = -1;
|
||||
// other will be ignored
|
||||
inetAddress = null;
|
||||
subnet = 0;
|
||||
cidrMask = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create IpV4Subnet using the CIDR or normal Notation<BR>
|
||||
* i.e.:
|
||||
* IpV4Subnet subnet = new IpV4Subnet("10.10.10.0/24"); or
|
||||
* IpV4Subnet subnet = new IpV4Subnet("10.10.10.0/255.255.255.0");
|
||||
*
|
||||
* @param netAddress a network address as string.
|
||||
*/
|
||||
public IpV4Subnet(String netAddress) throws UnknownHostException {
|
||||
setNetAddress(netAddress);
|
||||
}
|
||||
|
||||
/** Create IpV4Subnet using the CIDR Notation */
|
||||
public IpV4Subnet(InetAddress inetAddress, int cidrNetMask) {
|
||||
setNetAddress(inetAddress, cidrNetMask);
|
||||
}
|
||||
|
||||
/** Create IpV4Subnet using the normal Notation */
|
||||
public IpV4Subnet(InetAddress inetAddress, String netMask) {
|
||||
setNetAddress(inetAddress, netMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Network Address in either CIDR or Decimal Notation.<BR>
|
||||
* i.e.: setNetAddress("1.1.1.1/24"); or<BR>
|
||||
* setNetAddress("1.1.1.1/255.255.255.0");<BR>
|
||||
*
|
||||
* @param netAddress a network address as string.
|
||||
*/
|
||||
private void setNetAddress(String netAddress) throws UnknownHostException {
|
||||
Vector<Object> vec = new Vector<Object>();
|
||||
StringTokenizer st = new StringTokenizer(netAddress, "/");
|
||||
while (st.hasMoreTokens()) {
|
||||
vec.add(st.nextElement());
|
||||
}
|
||||
|
||||
if (vec.get(1).toString().length() < 3) {
|
||||
setNetId(vec.get(0).toString());
|
||||
setCidrNetMask(Integer.parseInt(vec.get(1).toString()));
|
||||
} else {
|
||||
setNetId(vec.get(0).toString());
|
||||
setNetMask(vec.get(1).toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the Network Address in CIDR Notation. */
|
||||
private void setNetAddress(InetAddress inetAddress, int cidrNetMask) {
|
||||
setNetId(inetAddress);
|
||||
setCidrNetMask(cidrNetMask);
|
||||
}
|
||||
|
||||
/** Sets the Network Address in Decimal Notation. */
|
||||
private void setNetAddress(InetAddress inetAddress, String netMask) {
|
||||
setNetId(inetAddress);
|
||||
setNetMask(netMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the BaseAdress of the Subnet.<BR>
|
||||
* i.e.: setNetId("192.168.1.0");
|
||||
*
|
||||
* @param netId a network ID
|
||||
*/
|
||||
private void setNetId(String netId) throws UnknownHostException {
|
||||
InetAddress inetAddress1 = InetAddress.getByName(netId);
|
||||
this.setNetId(inetAddress1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute integer representation of InetAddress
|
||||
*
|
||||
* @return the integer representation
|
||||
*/
|
||||
private int toInt(InetAddress inetAddress1) {
|
||||
byte[] address = inetAddress1.getAddress();
|
||||
int net = 0;
|
||||
for (byte addres : address) {
|
||||
net <<= 8;
|
||||
net |= addres & BYTE_ADDRESS_MASK;
|
||||
}
|
||||
return net;
|
||||
}
|
||||
|
||||
/** Sets the BaseAdress of the Subnet. */
|
||||
private void setNetId(InetAddress inetAddress) {
|
||||
this.inetAddress = inetAddress;
|
||||
subnet = toInt(inetAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Subnet's Netmask in Decimal format.<BR>
|
||||
* i.e.: setNetMask("255.255.255.0");
|
||||
*
|
||||
* @param netMask a network mask
|
||||
*/
|
||||
private void setNetMask(String netMask) {
|
||||
StringTokenizer nm = new StringTokenizer(netMask, ".");
|
||||
int i = 0;
|
||||
int[] netmask = new int[4];
|
||||
while (nm.hasMoreTokens()) {
|
||||
netmask[i] = Integer.parseInt(nm.nextToken());
|
||||
i++;
|
||||
}
|
||||
int mask1 = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
mask1 += Integer.bitCount(netmask[i]);
|
||||
}
|
||||
setCidrNetMask(mask1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CIDR Netmask<BR>
|
||||
* i.e.: setCidrNetMask(24);
|
||||
*
|
||||
* @param cidrNetMask a netmask in CIDR notation
|
||||
*/
|
||||
private void setCidrNetMask(int cidrNetMask) {
|
||||
cidrMask = cidrNetMask;
|
||||
mask = SUBNET_MASK >> cidrMask - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the given IP-Address against the Subnet and returns true if
|
||||
* the ip is in the subnet-ip-range and false if not.
|
||||
*
|
||||
* @param ipAddr an ipaddress
|
||||
* @return returns true if the given IP address is inside the currently
|
||||
* set network.
|
||||
*/
|
||||
public boolean contains(String ipAddr) throws UnknownHostException {
|
||||
InetAddress inetAddress1 = InetAddress.getByName(ipAddr);
|
||||
return this.contains(inetAddress1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the given InetAddress against the Subnet and returns true if
|
||||
* the ip is in the subnet-ip-range and false if not.
|
||||
*
|
||||
* @return returns true if the given IP address is inside the currently
|
||||
* set network.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(InetAddress inetAddress1) {
|
||||
if (mask == -1) {
|
||||
// ANY
|
||||
return true;
|
||||
}
|
||||
return (toInt(inetAddress1) & mask) == subnet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return inetAddress.getHostAddress() + "/" + cidrMask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof IpV4Subnet)) {
|
||||
return false;
|
||||
}
|
||||
IpV4Subnet ipV4Subnet = (IpV4Subnet) o;
|
||||
return ipV4Subnet.subnet == subnet && ipV4Subnet.cidrMask == cidrMask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return subnet;
|
||||
}
|
||||
|
||||
/** Compare two IpV4Subnet */
|
||||
@Override
|
||||
public int compareTo(IpV4Subnet o) {
|
||||
if (o.subnet == subnet && o.cidrMask == cidrMask) {
|
||||
return 0;
|
||||
}
|
||||
if (o.subnet < subnet) {
|
||||
return 1;
|
||||
} else if (o.subnet > subnet) {
|
||||
return -1;
|
||||
} else if (o.cidrMask < cidrMask) {
|
||||
// greater Mask means less IpAddresses so -1
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test functions
|
||||
*
|
||||
* @param args where args[0] is the netmask (standard or CIDR notation) and optional args[1] is
|
||||
* the inetAddress to test with this IpV4Subnet
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length != 0) {
|
||||
IpV4Subnet ipV4Subnet = null;
|
||||
try {
|
||||
ipV4Subnet = new IpV4Subnet(args[0]);
|
||||
} catch (UnknownHostException e) {
|
||||
return;
|
||||
}
|
||||
if (args.length > 1) {
|
||||
logger.debug("Is IN: " + args[1] + " " + ipV4Subnet.contains(args[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/** IpV4 only Filter Rule */
|
||||
public class IpV4SubnetFilterRule extends IpV4Subnet implements IpFilterRule {
|
||||
/** Is this IpV4Subnet an ALLOW or DENY rule */
|
||||
private boolean isAllowRule = true;
|
||||
|
||||
/**
|
||||
* Constructor for a ALLOW or DENY ALL
|
||||
*
|
||||
* @param allow True for ALLOW, False for DENY
|
||||
*/
|
||||
public IpV4SubnetFilterRule(boolean allow) {
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
/** @param allow True for ALLOW, False for DENY */
|
||||
public IpV4SubnetFilterRule(boolean allow, InetAddress inetAddress, int cidrNetMask) {
|
||||
super(inetAddress, cidrNetMask);
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
/** @param allow True for ALLOW, False for DENY */
|
||||
public IpV4SubnetFilterRule(boolean allow, InetAddress inetAddress, String netMask) {
|
||||
super(inetAddress, netMask);
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
/** @param allow True for ALLOW, False for DENY */
|
||||
public IpV4SubnetFilterRule(boolean allow, String netAddress) throws UnknownHostException {
|
||||
super(netAddress);
|
||||
isAllowRule = allow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowRule() {
|
||||
return isAllowRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDenyRule() {
|
||||
return !isAllowRule;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelHandler.Sharable;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelState;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
|
||||
/**
|
||||
* Handler that block any new connection if there are already a currently active
|
||||
* channel connected with the same InetAddress (IP).<br>
|
||||
* <br>
|
||||
* <p/>
|
||||
* Take care to not change isBlocked method except if you know what you are doing
|
||||
* since it is used to test if the current closed connection is to be removed
|
||||
* or not from the map of currently connected channel.
|
||||
*/
|
||||
@Sharable
|
||||
public class OneIpFilterHandler extends IpFilteringHandlerImpl {
|
||||
/** HashMap of current remote connected InetAddress */
|
||||
private final ConcurrentMap<InetAddress, Boolean> connectedSet = new ConcurrentHashMap<InetAddress, Boolean>();
|
||||
|
||||
@Override
|
||||
protected boolean accept(ChannelHandlerContext ctx, ChannelEvent e, InetSocketAddress inetSocketAddress)
|
||||
throws Exception {
|
||||
InetAddress inetAddress = inetSocketAddress.getAddress();
|
||||
if (connectedSet.containsKey(inetAddress)) {
|
||||
return false;
|
||||
}
|
||||
connectedSet.put(inetAddress, Boolean.TRUE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
|
||||
super.handleUpstream(ctx, e);
|
||||
// Try to remove entry from Map if already exists
|
||||
if (e instanceof ChannelStateEvent) {
|
||||
ChannelStateEvent evt = (ChannelStateEvent) e;
|
||||
if (evt.getState() == ChannelState.CONNECTED) {
|
||||
if (evt.getValue() == null) {
|
||||
// DISCONNECTED but was this channel blocked or not
|
||||
if (isBlocked(ctx)) {
|
||||
// remove inetsocketaddress from set since this channel was not blocked before
|
||||
InetSocketAddress inetSocketAddress = (InetSocketAddress) e.getChannel().getRemoteAddress();
|
||||
connectedSet.remove(inetSocketAddress.getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
202
src/main/java/org/jboss/netty/handler/ipfilter/PatternRule.java
Normal file
202
src/main/java/org/jboss/netty/handler/ipfilter/PatternRule.java
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* The Class PatternRule represents an IP filter rule using string patterns.
|
||||
* <br>
|
||||
* Rule Syntax:
|
||||
* <br>
|
||||
* <pre>
|
||||
* Rule ::= [n|i]:address n stands for computer name, i for ip address
|
||||
* address ::= <regex> | localhost
|
||||
* regex is a regular expression with '*' as multi character and '?' as single character wild card
|
||||
* </pre>
|
||||
* <br>
|
||||
* Example: allow localhost:
|
||||
* <br>
|
||||
* new PatternRule(true, "n:localhost")
|
||||
* <br>
|
||||
* Example: allow local lan:
|
||||
* <br>
|
||||
* new PatternRule(true, "i:192.168.0.*")
|
||||
* <br>
|
||||
* Example: block all
|
||||
* <br>
|
||||
* new PatternRule(false, "n:*")
|
||||
* <br>
|
||||
*/
|
||||
public class PatternRule implements IpFilterRule, Comparable<Object> {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PatternRule.class);
|
||||
|
||||
private Pattern ipPattern;
|
||||
|
||||
private Pattern namePattern;
|
||||
|
||||
private boolean isAllowRule = true;
|
||||
|
||||
private boolean localhost;
|
||||
|
||||
private String pattern;
|
||||
|
||||
/**
|
||||
* Instantiates a new pattern rule.
|
||||
*
|
||||
* @param allow indicates if this is an allow or block rule
|
||||
* @param pattern the filter pattern
|
||||
*/
|
||||
public PatternRule(boolean allow, String pattern) {
|
||||
this.isAllowRule = allow;
|
||||
this.pattern = pattern;
|
||||
parse(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the pattern.
|
||||
*
|
||||
* @return the pattern
|
||||
*/
|
||||
public String getPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowRule() {
|
||||
return isAllowRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDenyRule() {
|
||||
return !isAllowRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(InetAddress inetAddress) {
|
||||
if (localhost) {
|
||||
if (isLocalhost(inetAddress)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ipPattern != null) {
|
||||
if (ipPattern.matcher(inetAddress.getHostAddress()).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (namePattern != null) {
|
||||
if (namePattern.matcher(inetAddress.getHostName()).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void parse(String pattern) {
|
||||
if (pattern == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] acls = pattern.split(",");
|
||||
|
||||
String ip = "";
|
||||
String name = "";
|
||||
for (String c : acls) {
|
||||
c = c.trim();
|
||||
if (c.equals("n:localhost")) {
|
||||
this.localhost = true;
|
||||
} else if (c.startsWith("n:")) {
|
||||
name = addRule(name, c.substring(2));
|
||||
} else if (c.startsWith("i:")) {
|
||||
ip = addRule(ip, c.substring(2));
|
||||
}
|
||||
}
|
||||
if (ip.length() != 0) {
|
||||
ipPattern = Pattern.compile(ip);
|
||||
}
|
||||
if (name.length() != 0) {
|
||||
namePattern = Pattern.compile(name);
|
||||
}
|
||||
}
|
||||
|
||||
private String addRule(String pattern, String rule) {
|
||||
if (rule == null || rule.length() == 0) {
|
||||
return pattern;
|
||||
}
|
||||
if (pattern.length() != 0) {
|
||||
pattern += "|";
|
||||
}
|
||||
rule = rule.replaceAll("\\.", "\\\\.");
|
||||
rule = rule.replaceAll("\\*", ".*");
|
||||
rule = rule.replaceAll("\\?", ".");
|
||||
pattern += "(" + rule + ")";
|
||||
return pattern;
|
||||
}
|
||||
|
||||
private boolean isLocalhost(InetAddress address) {
|
||||
try {
|
||||
if (address.equals(InetAddress.getLocalHost())) {
|
||||
return true;
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("error getting ip of localhost", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
InetAddress[] addrs = InetAddress.getAllByName("127.0.0.1");
|
||||
for (InetAddress addr : addrs) {
|
||||
if (addr.equals(address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("error getting ip of localhost", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if (o == null) {
|
||||
return -1;
|
||||
}
|
||||
if (!(o instanceof PatternRule)) {
|
||||
return -1;
|
||||
}
|
||||
PatternRule p = (PatternRule) o;
|
||||
if (p.isAllowRule() && !this.isAllowRule) {
|
||||
return -1;
|
||||
}
|
||||
if (this.pattern == null && p.pattern == null) {
|
||||
return 0;
|
||||
}
|
||||
if (this.pattern != null) {
|
||||
return this.pattern.compareTo(p.getPattern());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of a Ip based Filter handlers.<br>
|
||||
* <br><br>
|
||||
* <P>The main goal of this package is to allow to filter connections based on IP rules.
|
||||
* The main interface is <tt>{@link org.jboss.netty.handler.ipfilter.IpFilteringHandler}</tt> which all filters will extend.</P>
|
||||
*
|
||||
* <P>Two IP filtering are proposed:<br>
|
||||
* <ul>
|
||||
* <li> <tt>{@link org.jboss.netty.handler.ipfilter.OneIpFilterHandler}</tt>: This filter proposes to allow only one connection by client's IP Address.
|
||||
* I.E. this filter will prevent two connections from the same client based on its IP address.</li><br><br>
|
||||
*
|
||||
* <li> <tt>{@link org.jboss.netty.handler.ipfilter.IpFilterRuleHandler}</tt>: This filter proposes to allow or block IP range (based on standard notation
|
||||
* or on CIDR notation) when the connection is running. It relies on another class like
|
||||
* <tt>IpV4SubnetFilterRule</tt> (IPV4 support only), <tt>IpSubnetFilterRule</tt> (IPV4 and IPV6 support) or <tt>PatternRule</tt> (string pattern support)
|
||||
* which implements those Ip ranges.</li><br><br>
|
||||
*
|
||||
* </ul></P>
|
||||
*
|
||||
* <P>Standard use could be as follow: The accept method must be overridden (of course you can
|
||||
* override others).</P>
|
||||
*
|
||||
* <P><ul>
|
||||
* <li><tt>accept</tt> method allows to specify your way of choosing if a new connection is
|
||||
* to be allowed or not.</li><br>
|
||||
* In <tt>OneIpFilterHandler</tt> and <tt>IpFilterRuleHandler</tt>,
|
||||
* this method is already implemented.<br>
|
||||
* <br>
|
||||
*
|
||||
* <li><tt>handleRefusedChannel</tt> method is executed when the accept method filters (blocks, so returning false)
|
||||
* the new connection. This method allows you to implement specific actions to be taken before the channel is
|
||||
* closed. After this method is called, the channel is immediately closed.</li><br>
|
||||
* So if you want to send back a message to the client, <b>don't forget to return a respectful ChannelFuture,
|
||||
* otherwise the message could be missed since the channel will be closed immediately after this
|
||||
* call and the waiting on this channelFuture</b> (at least with respect of asynchronous operations).<br><br>
|
||||
* Per default implementation this method invokes an {@link org.jboss.netty.handler.ipfilter.IpFilterListener} or returns null if no listener has been set.
|
||||
* <br><br>
|
||||
*
|
||||
* <li><tt>continues</tt> is called when any event appears after CONNECTED event and only for
|
||||
* blocked channels.</li><br>
|
||||
* It should return True if this new event has to go to next handlers
|
||||
* in the pipeline if any, and False (default) if no events has to be passed to the next
|
||||
* handlers when a channel is blocked. This is intend to prevent any unnecessary action since the connection is refused.<br>
|
||||
* However, you could change its behavior for instance because you don't want that any event
|
||||
* will be blocked by this filter by returning always true or according to some events.<br>
|
||||
* <b>Note that OPENED and BOUND events are still passed to the next entry in the pipeline since
|
||||
* those events come out before the CONNECTED event, so there is no possibility to filter those two events
|
||||
* before the CONNECTED event shows up. Therefore, you might want to let CLOSED and UNBOUND be passed
|
||||
* to the next entry in the pipeline.</b><br><br>
|
||||
* Per default implementation this method invokes an {@link org.jboss.netty.handler.ipfilter.IpFilterListener} or returns false if no listener has been set.
|
||||
* <br><br>
|
||||
*
|
||||
* <li>Finally <tt>handleUpstream</tt> traps the CONNECTED and DISCONNECTED events.</li><br>
|
||||
* If in the CONNECTED events the channel is blocked (<tt>accept</tt> refused the channel),
|
||||
* then any new events on this channel will be blocked.<br>
|
||||
* However, you could change its behavior for instance because you don't want that all events
|
||||
* will be blocked by this filter by testing the result of isBlocked, and if so,
|
||||
* calling <tt>ctx.sendUpstream(e);</tt> after calling the super method or by changing the <tt>continues</tt> method.<br><br>
|
||||
|
||||
* </ul></P><br><br>
|
||||
*
|
||||
* A typical setup for ip filter for TCP/IP socket would be:
|
||||
*
|
||||
* <pre>
|
||||
* {@link org.jboss.netty.channel.ChannelPipeline} pipeline = ...;
|
||||
*
|
||||
* IpFilterRuleHandler firewall = new IpFilterRuleHandler();
|
||||
* firewall.addAll(new IpFilterRuleList("+n:localhost, +c:192.168.0.0/27, -n:*"));
|
||||
* pipeline.addFirst("firewall", firewall);
|
||||
* </pre>
|
||||
*
|
||||
* @apiviz.exclude ^java\.lang\.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
|
@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.traffic;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelState;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
import org.jboss.netty.util.DefaultObjectSizeEstimator;
|
||||
import org.jboss.netty.util.ExternalResourceReleasable;
|
||||
import org.jboss.netty.util.ObjectSizeEstimator;
|
||||
import org.jboss.netty.util.internal.ExecutorUtil;
|
||||
|
||||
/**
|
||||
* AbstractTrafficShapingHandler allows to limit the global bandwidth
|
||||
* (see {@link GlobalTrafficShapingHandler}) or per session
|
||||
* bandwidth (see {@link ChannelTrafficShapingHandler}), as traffic shaping.
|
||||
* It allows too to implement an almost real time monitoring of the bandwidth using
|
||||
* the monitors from {@link TrafficCounter} that will call back every checkInterval
|
||||
* the method doAccounting of this handler.<br>
|
||||
* <br>
|
||||
*
|
||||
* If you want for any particular reasons to stop the monitoring (accounting) or to change
|
||||
* the read/write limit or the check interval, several methods allow that for you:<br>
|
||||
* <ul>
|
||||
* <li><tt>configure</tt> allows you to change read or write limits, or the checkInterval</li>
|
||||
* <li><tt>getTrafficCounter</tt> allows you to have access to the TrafficCounter and so to stop
|
||||
* or start the monitoring, to change the checkInterval directly, or to have access to its values.</li>
|
||||
* <li></li>
|
||||
* </ul>
|
||||
*/
|
||||
public abstract class AbstractTrafficShapingHandler extends
|
||||
SimpleChannelHandler implements ExternalResourceReleasable {
|
||||
/**
|
||||
* Internal logger
|
||||
*/
|
||||
static InternalLogger logger = InternalLoggerFactory
|
||||
.getInstance(AbstractTrafficShapingHandler.class);
|
||||
|
||||
/**
|
||||
* Default delay between two checks: 1s
|
||||
*/
|
||||
public static final long DEFAULT_CHECK_INTERVAL = 1000;
|
||||
|
||||
/**
|
||||
* Default minimal time to wait
|
||||
*/
|
||||
private static final long MINIMAL_WAIT = 10;
|
||||
|
||||
/**
|
||||
* Traffic Counter
|
||||
*/
|
||||
protected TrafficCounter trafficCounter;
|
||||
|
||||
/**
|
||||
* ObjectSizeEstimator
|
||||
*/
|
||||
private ObjectSizeEstimator objectSizeEstimator;
|
||||
|
||||
/**
|
||||
* Executor to associated to any TrafficCounter
|
||||
*/
|
||||
protected Executor executor;
|
||||
|
||||
/**
|
||||
* Limit in B/s to apply to write
|
||||
*/
|
||||
private long writeLimit;
|
||||
|
||||
/**
|
||||
* Limit in B/s to apply to read
|
||||
*/
|
||||
private long readLimit;
|
||||
|
||||
/**
|
||||
* Delay between two performance snapshots
|
||||
*/
|
||||
protected long checkInterval = DEFAULT_CHECK_INTERVAL; // default 1 s
|
||||
|
||||
/**
|
||||
* Boolean associated with the release of this TrafficShapingHandler.
|
||||
* It will be true only once when the releaseExternalRessources is called
|
||||
* to prevent waiting when shutdown.
|
||||
*/
|
||||
final AtomicBoolean release = new AtomicBoolean(false);
|
||||
|
||||
private void init(ObjectSizeEstimator newObjectSizeEstimator,
|
||||
Executor newExecutor, long newWriteLimit, long newReadLimit, long newCheckInterval) {
|
||||
objectSizeEstimator = newObjectSizeEstimator;
|
||||
executor = newExecutor;
|
||||
writeLimit = newWriteLimit;
|
||||
readLimit = newReadLimit;
|
||||
checkInterval = newCheckInterval;
|
||||
//logger.info("TSH: "+writeLimit+":"+readLimit+":"+checkInterval+":"+isPerChannel());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param newTrafficCounter the TrafficCounter to set
|
||||
*/
|
||||
void setTrafficCounter(TrafficCounter newTrafficCounter) {
|
||||
trafficCounter = newTrafficCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using default {@link ObjectSizeEstimator}
|
||||
*
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
* @param writeLimit
|
||||
* 0 or a limit in bytes/s
|
||||
* @param readLimit
|
||||
* 0 or a limit in bytes/s
|
||||
* @param checkInterval
|
||||
* The delay between two computations of performances for
|
||||
* channels or 0 if no stats are to be computed
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(Executor executor, long writeLimit,
|
||||
long readLimit, long checkInterval) {
|
||||
init(new DefaultObjectSizeEstimator(), executor, writeLimit, readLimit,
|
||||
checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using the specified ObjectSizeEstimator
|
||||
*
|
||||
* @param objectSizeEstimator
|
||||
* the {@link ObjectSizeEstimator} that will be used to compute
|
||||
* the size of the message
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
* @param writeLimit
|
||||
* 0 or a limit in bytes/s
|
||||
* @param readLimit
|
||||
* 0 or a limit in bytes/s
|
||||
* @param checkInterval
|
||||
* The delay between two computations of performances for
|
||||
* channels or 0 if no stats are to be computed
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor,
|
||||
long writeLimit, long readLimit, long checkInterval) {
|
||||
init(objectSizeEstimator, executor, writeLimit, readLimit,
|
||||
checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using default {@link ObjectSizeEstimator} and using default Check Interval
|
||||
*
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
* @param writeLimit
|
||||
* 0 or a limit in bytes/s
|
||||
* @param readLimit
|
||||
* 0 or a limit in bytes/s
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(Executor executor, long writeLimit,
|
||||
long readLimit) {
|
||||
init(new DefaultObjectSizeEstimator(), executor, writeLimit, readLimit,
|
||||
DEFAULT_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using the specified ObjectSizeEstimator and using default Check Interval
|
||||
*
|
||||
* @param objectSizeEstimator
|
||||
* the {@link ObjectSizeEstimator} that will be used to compute
|
||||
* the size of the message
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
* @param writeLimit
|
||||
* 0 or a limit in bytes/s
|
||||
* @param readLimit
|
||||
* 0 or a limit in bytes/s
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor,
|
||||
long writeLimit, long readLimit) {
|
||||
init(objectSizeEstimator, executor, writeLimit, readLimit,
|
||||
DEFAULT_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using default {@link ObjectSizeEstimator} and using NO LIMIT and default Check Interval
|
||||
*
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(Executor executor) {
|
||||
init(new DefaultObjectSizeEstimator(), executor, 0, 0,
|
||||
DEFAULT_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using the specified ObjectSizeEstimator and using NO LIMIT and default Check Interval
|
||||
*
|
||||
* @param objectSizeEstimator
|
||||
* the {@link ObjectSizeEstimator} that will be used to compute
|
||||
* the size of the message
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor) {
|
||||
init(objectSizeEstimator, executor, 0, 0, DEFAULT_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using default {@link ObjectSizeEstimator} and using NO LIMIT
|
||||
*
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
* @param checkInterval
|
||||
* The delay between two computations of performances for
|
||||
* channels or 0 if no stats are to be computed
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(Executor executor, long checkInterval) {
|
||||
init(new DefaultObjectSizeEstimator(), executor, 0, 0, checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using the specified ObjectSizeEstimator and using NO LIMIT
|
||||
*
|
||||
* @param objectSizeEstimator
|
||||
* the {@link ObjectSizeEstimator} that will be used to compute
|
||||
* the size of the message
|
||||
* @param executor
|
||||
* created for instance like Executors.newCachedThreadPool
|
||||
* @param checkInterval
|
||||
* The delay between two computations of performances for
|
||||
* channels or 0 if no stats are to be computed
|
||||
*/
|
||||
public AbstractTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor,
|
||||
long checkInterval) {
|
||||
init(objectSizeEstimator, executor, 0, 0, checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the underlying limitations and check interval.
|
||||
*/
|
||||
public void configure(long newWriteLimit, long newReadLimit,
|
||||
long newCheckInterval) {
|
||||
this.configure(newWriteLimit, newReadLimit);
|
||||
this.configure(newCheckInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the underlying limitations.
|
||||
*/
|
||||
public void configure(long newWriteLimit, long newReadLimit) {
|
||||
writeLimit = newWriteLimit;
|
||||
readLimit = newReadLimit;
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.resetAccounting(System.currentTimeMillis() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the check interval.
|
||||
*/
|
||||
public void configure(long newCheckInterval) {
|
||||
checkInterval = newCheckInterval;
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.configure(checkInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called each time the accounting is computed from the TrafficCounters.
|
||||
* This method could be used for instance to implement almost real time accounting.
|
||||
*
|
||||
* @param counter
|
||||
* the TrafficCounter that computes its performance
|
||||
*/
|
||||
protected void doAccounting(TrafficCounter counter) {
|
||||
// NOOP by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to implement setReadable at fix time
|
||||
*/
|
||||
private class ReopenRead implements Runnable {
|
||||
/**
|
||||
* Associated ChannelHandlerContext
|
||||
*/
|
||||
private ChannelHandlerContext ctx;
|
||||
|
||||
/**
|
||||
* Time to wait before clearing the channel
|
||||
*/
|
||||
private long timeToWait;
|
||||
|
||||
/**
|
||||
* @param ctx
|
||||
* the associated channelHandlerContext
|
||||
* @param timeToWait
|
||||
*/
|
||||
protected ReopenRead(ChannelHandlerContext ctx, long timeToWait) {
|
||||
this.ctx = ctx;
|
||||
this.timeToWait = timeToWait;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truly run the waken up of the channel
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (release.get()) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(timeToWait);
|
||||
} catch (InterruptedException e) {
|
||||
// interruption so exit
|
||||
return;
|
||||
}
|
||||
// logger.info("WAKEUP!");
|
||||
if (ctx != null && ctx.getChannel() != null &&
|
||||
ctx.getChannel().isConnected()) {
|
||||
//logger.info(" setReadable TRUE: "+timeToWait);
|
||||
// readSuspended = false;
|
||||
ctx.setAttachment(null);
|
||||
ctx.getChannel().setReadable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the time that should be necessary to wait to respect limit. Can
|
||||
* be negative time
|
||||
*/
|
||||
private long getTimeToWait(long limit, long bytes, long lastTime,
|
||||
long curtime) {
|
||||
long interval = curtime - lastTime;
|
||||
if (interval == 0) {
|
||||
// Time is too short, so just lets continue
|
||||
return 0;
|
||||
}
|
||||
return bytes * 1000 / limit - interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext arg0, MessageEvent arg1)
|
||||
throws Exception {
|
||||
try {
|
||||
long curtime = System.currentTimeMillis();
|
||||
long size = objectSizeEstimator.estimateSize(arg1.getMessage());
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.bytesRecvFlowControl(arg0, size);
|
||||
if (readLimit == 0) {
|
||||
// no action
|
||||
return;
|
||||
}
|
||||
// compute the number of ms to wait before reopening the channel
|
||||
long wait = getTimeToWait(readLimit, trafficCounter
|
||||
.getCurrentReadBytes(), trafficCounter.getLastTime(),
|
||||
curtime);
|
||||
if (wait > MINIMAL_WAIT) { // At least 10ms seems a minimal time in order to
|
||||
Channel channel = arg0.getChannel();
|
||||
// try to limit the traffic
|
||||
if (channel != null && channel.isConnected()) {
|
||||
// Channel version
|
||||
if (executor == null) {
|
||||
// Sleep since no executor
|
||||
//logger.info("Read sleep since no executor for "+wait+" ms for "+this);
|
||||
if (release.get()) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(wait);
|
||||
return;
|
||||
}
|
||||
if (arg0.getAttachment() == null) {
|
||||
// readSuspended = true;
|
||||
arg0.setAttachment(Boolean.TRUE);
|
||||
channel.setReadable(false);
|
||||
//logger.info("Read will wakeup after "+wait+" ms "+this);
|
||||
executor.execute(new ReopenRead(arg0, wait));
|
||||
} else {
|
||||
// should be waiting: but can occurs sometime so as a FIX
|
||||
//logger.info("Read sleep ok but should not be here: "+wait+" "+this);
|
||||
if (release.get()) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(wait);
|
||||
}
|
||||
} else {
|
||||
// Not connected or no channel
|
||||
//logger.info("Read sleep "+wait+" ms for "+this);
|
||||
if (release.get()) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(wait);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// The message is then just passed to the next handler
|
||||
super.messageReceived(arg0, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRequested(ChannelHandlerContext arg0, MessageEvent arg1)
|
||||
throws Exception {
|
||||
try {
|
||||
long curtime = System.currentTimeMillis();
|
||||
long size = objectSizeEstimator.estimateSize(arg1.getMessage());
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.bytesWriteFlowControl(size);
|
||||
if (writeLimit == 0) {
|
||||
return;
|
||||
}
|
||||
// compute the number of ms to wait before continue with the channel
|
||||
long wait = getTimeToWait(writeLimit, trafficCounter
|
||||
.getCurrentWrittenBytes(), trafficCounter.getLastTime(),
|
||||
curtime);
|
||||
if (wait > MINIMAL_WAIT) {
|
||||
// Global or Channel
|
||||
if (release.get()) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(wait);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// The message is then just passed to the next handler
|
||||
super.writeRequested(arg0, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
|
||||
throws Exception {
|
||||
if (e instanceof ChannelStateEvent) {
|
||||
ChannelStateEvent cse = (ChannelStateEvent) e;
|
||||
if (cse.getState() == ChannelState.INTEREST_OPS &&
|
||||
(((Integer) cse.getValue()).intValue() & Channel.OP_READ) != 0) {
|
||||
|
||||
// setReadable(true) requested
|
||||
boolean readSuspended = ctx.getAttachment() != null;
|
||||
if (readSuspended) {
|
||||
// Drop the request silently if this handler has
|
||||
// set the flag.
|
||||
e.getFuture().setSuccess();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
super.handleDownstream(ctx, e);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the current TrafficCounter (if
|
||||
* channel is still connected)
|
||||
*/
|
||||
public TrafficCounter getTrafficCounter() {
|
||||
return trafficCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseExternalResources() {
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.stop();
|
||||
}
|
||||
release.set(true);
|
||||
ExecutorUtil.terminate(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TrafficShaping with Write Limit: " + writeLimit +
|
||||
" Read Limit: " + readLimit + " and Counter: " +
|
||||
(trafficCounter != null? trafficCounter.toString() : "none");
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.traffic;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.handler.execution.ExecutionHandler;
|
||||
import org.jboss.netty.handler.execution.MemoryAwareThreadPoolExecutor;
|
||||
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
|
||||
import org.jboss.netty.util.ObjectSizeEstimator;
|
||||
|
||||
/**
|
||||
* This implementation of the {@link AbstractTrafficShapingHandler} is for channel
|
||||
* traffic shaping, that is to say a per channel limitation of the bandwidth.<br><br>
|
||||
*
|
||||
* The general use should be as follow:<br>
|
||||
* <ul>
|
||||
* <li>Add in your pipeline a new ChannelTrafficShapingHandler, before a recommended {@link ExecutionHandler} (like
|
||||
* {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).<br>
|
||||
* <tt>ChannelTrafficShapingHandler myHandler = new ChannelTrafficShapingHandler(executor);</tt><br>
|
||||
* executor could be created using <tt>Executors.newCachedThreadPool();</tt><br>
|
||||
* <tt>pipeline.addLast("CHANNEL_TRAFFIC_SHAPING", myHandler);</tt><br><br>
|
||||
*
|
||||
* <b>Note that this handler has a Pipeline Coverage of "one" which means a new handler must be created
|
||||
* for each new channel as the counter cannot be shared among all channels.</b> For instance, if you have a
|
||||
* {@link ChannelPipelineFactory}, you should create a new ChannelTrafficShapingHandler in this
|
||||
* {@link ChannelPipelineFactory} each time getPipeline() method is called.<br><br>
|
||||
*
|
||||
* Other arguments can be passed like write or read limitation (in bytes/s where 0 means no limitation)
|
||||
* or the check interval (in millisecond) that represents the delay between two computations of the
|
||||
* bandwidth and so the call back of the doAccounting method (0 means no accounting at all).<br><br>
|
||||
*
|
||||
* A value of 0 means no accounting for checkInterval. If you need traffic shaping but no such accounting,
|
||||
* it is recommended to set a positive value, even if it is high since the precision of the
|
||||
* Traffic Shaping depends on the period where the traffic is computed. The highest the interval,
|
||||
* the less precise the traffic shaping will be. It is suggested as higher value something close
|
||||
* to 5 or 10 minutes.<br>
|
||||
* </li>
|
||||
* <li>When you shutdown your application, release all the external resources like the executor
|
||||
* by calling:<br>
|
||||
* <tt>myHandler.releaseExternalResources();</tt><br>
|
||||
* </li>
|
||||
* </ul><br>
|
||||
*/
|
||||
public class ChannelTrafficShapingHandler extends AbstractTrafficShapingHandler {
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
* @param checkInterval
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(Executor executor, long writeLimit,
|
||||
long readLimit, long checkInterval) {
|
||||
super(executor, writeLimit, readLimit, checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(Executor executor, long writeLimit,
|
||||
long readLimit) {
|
||||
super(executor, writeLimit, readLimit);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
* @param checkInterval
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(Executor executor, long checkInterval) {
|
||||
super(executor, checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(Executor executor) {
|
||||
super(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
* @param checkInterval
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor,
|
||||
long writeLimit, long readLimit, long checkInterval) {
|
||||
super(objectSizeEstimator, executor, writeLimit, readLimit,
|
||||
checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor,
|
||||
long writeLimit, long readLimit) {
|
||||
super(objectSizeEstimator, executor, writeLimit, readLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
* @param checkInterval
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor,
|
||||
long checkInterval) {
|
||||
super(objectSizeEstimator, executor, checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
*/
|
||||
public ChannelTrafficShapingHandler(
|
||||
ObjectSizeEstimator objectSizeEstimator, Executor executor) {
|
||||
super(objectSizeEstimator, executor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
|
||||
throws Exception {
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.stop();
|
||||
trafficCounter = null;
|
||||
}
|
||||
super.channelClosed(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
|
||||
throws Exception {
|
||||
// readSuspended = true;
|
||||
ctx.setAttachment(Boolean.TRUE);
|
||||
ctx.getChannel().setReadable(false);
|
||||
if (trafficCounter == null) {
|
||||
// create a new counter now
|
||||
trafficCounter = new TrafficCounter(this, executor, "ChannelTC" +
|
||||
ctx.getChannel().getId(), checkInterval);
|
||||
}
|
||||
if (trafficCounter != null) {
|
||||
trafficCounter.start();
|
||||
}
|
||||
super.channelConnected(ctx, e);
|
||||
// readSuspended = false;
|
||||
ctx.setAttachment(null);
|
||||
ctx.getChannel().setReadable(true);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.traffic;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.jboss.netty.channel.ChannelHandler.Sharable;
|
||||
import org.jboss.netty.handler.execution.ExecutionHandler;
|
||||
import org.jboss.netty.handler.execution.MemoryAwareThreadPoolExecutor;
|
||||
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
|
||||
import org.jboss.netty.util.ObjectSizeEstimator;
|
||||
|
||||
/**
|
||||
* This implementation of the {@link AbstractTrafficShapingHandler} is for global
|
||||
* traffic shaping, that is to say a global limitation of the bandwidth, whatever
|
||||
* the number of opened channels.<br><br>
|
||||
*
|
||||
* The general use should be as follow:<br>
|
||||
* <ul>
|
||||
* <li>Create your unique GlobalTrafficShapingHandler like:<br><br>
|
||||
* <tt>GlobalTrafficShapingHandler myHandler = new GlobalTrafficShapingHandler(executor);</tt><br><br>
|
||||
* executor could be created using <tt>Executors.newCachedThreadPool();</tt><br>
|
||||
* <tt>pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);</tt><br><br>
|
||||
*
|
||||
* <b>Note that this handler has a Pipeline Coverage of "all" which means only one such handler must be created
|
||||
* and shared among all channels as the counter must be shared among all channels.</b><br><br>
|
||||
*
|
||||
* Other arguments can be passed like write or read limitation (in bytes/s where 0 means no limitation)
|
||||
* or the check interval (in millisecond) that represents the delay between two computations of the
|
||||
* bandwidth and so the call back of the doAccounting method (0 means no accounting at all).<br><br>
|
||||
*
|
||||
* A value of 0 means no accounting for checkInterval. If you need traffic shaping but no such accounting,
|
||||
* it is recommended to set a positive value, even if it is high since the precision of the
|
||||
* Traffic Shaping depends on the period where the traffic is computed. The highest the interval,
|
||||
* the less precise the traffic shaping will be. It is suggested as higher value something close
|
||||
* to 5 or 10 minutes.<br>
|
||||
* </li>
|
||||
* <li>Add it in your pipeline, before a recommended {@link ExecutionHandler} (like
|
||||
* {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).<br>
|
||||
* <tt>pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);</tt><br><br>
|
||||
* </li>
|
||||
* <li>When you shutdown your application, release all the external resources like the executor
|
||||
* by calling:<br>
|
||||
* <tt>myHandler.releaseExternalResources();</tt><br>
|
||||
* </li>
|
||||
* </ul><br>
|
||||
*/
|
||||
@Sharable
|
||||
public class GlobalTrafficShapingHandler extends AbstractTrafficShapingHandler {
|
||||
/**
|
||||
* Create the global TrafficCounter
|
||||
*/
|
||||
void createGlobalTrafficCounter() {
|
||||
TrafficCounter tc = new TrafficCounter(this, executor, "GlobalTC",
|
||||
checkInterval);
|
||||
setTrafficCounter(tc);
|
||||
tc.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
* @param checkInterval
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(Executor executor, long writeLimit,
|
||||
long readLimit, long checkInterval) {
|
||||
super(executor, writeLimit, readLimit, checkInterval);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(Executor executor, long writeLimit,
|
||||
long readLimit) {
|
||||
super(executor, writeLimit, readLimit);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
/**
|
||||
* @param executor
|
||||
* @param checkInterval
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(Executor executor, long checkInterval) {
|
||||
super(executor, checkInterval);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param executor
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(Executor executor) {
|
||||
super(executor);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
* @param checkInterval
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator,
|
||||
Executor executor, long writeLimit, long readLimit,
|
||||
long checkInterval) {
|
||||
super(objectSizeEstimator, executor, writeLimit, readLimit,
|
||||
checkInterval);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
* @param writeLimit
|
||||
* @param readLimit
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator,
|
||||
Executor executor, long writeLimit, long readLimit) {
|
||||
super(objectSizeEstimator, executor, writeLimit, readLimit);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
* @param checkInterval
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator,
|
||||
Executor executor, long checkInterval) {
|
||||
super(objectSizeEstimator, executor, checkInterval);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectSizeEstimator
|
||||
* @param executor
|
||||
*/
|
||||
public GlobalTrafficShapingHandler(ObjectSizeEstimator objectSizeEstimator,
|
||||
Executor executor) {
|
||||
super(objectSizeEstimator, executor);
|
||||
createGlobalTrafficCounter();
|
||||
}
|
||||
}
|
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.traffic;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
* TrafficCounter is associated with {@link AbstractTrafficShapingHandler}.<br>
|
||||
* <br>
|
||||
* A TrafficCounter has for goal to count the traffic in order to enable to limit the traffic or not,
|
||||
* globally or per channel. It compute statistics on read and written bytes at the specified
|
||||
* interval and call back the {@link AbstractTrafficShapingHandler} doAccounting method at every
|
||||
* specified interval. If this interval is set to 0, therefore no accounting will be done and only
|
||||
* statistics will be computed at each receive or write operations.
|
||||
*/
|
||||
public class TrafficCounter {
|
||||
/**
|
||||
* Current written bytes
|
||||
*/
|
||||
private final AtomicLong currentWrittenBytes = new AtomicLong();
|
||||
|
||||
/**
|
||||
* Current read bytes
|
||||
*/
|
||||
private final AtomicLong currentReadBytes = new AtomicLong();
|
||||
|
||||
/**
|
||||
* Long life written bytes
|
||||
*/
|
||||
private final AtomicLong cumulativeWrittenBytes = new AtomicLong();
|
||||
|
||||
/**
|
||||
* Long life read bytes
|
||||
*/
|
||||
private final AtomicLong cumulativeReadBytes = new AtomicLong();
|
||||
|
||||
/**
|
||||
* Last Time where cumulative bytes where reset to zero
|
||||
*/
|
||||
private long lastCumulativeTime;
|
||||
|
||||
/**
|
||||
* Last writing bandwidth
|
||||
*/
|
||||
private long lastWriteThroughput;
|
||||
|
||||
/**
|
||||
* Last reading bandwidth
|
||||
*/
|
||||
private long lastReadThroughput;
|
||||
|
||||
/**
|
||||
* Last Time Check taken
|
||||
*/
|
||||
private final AtomicLong lastTime = new AtomicLong();
|
||||
|
||||
/**
|
||||
* Last written bytes number during last check interval
|
||||
*/
|
||||
private long lastWrittenBytes;
|
||||
|
||||
/**
|
||||
* Last read bytes number during last check interval
|
||||
*/
|
||||
private long lastReadBytes;
|
||||
|
||||
/**
|
||||
* Delay between two captures
|
||||
*/
|
||||
AtomicLong checkInterval = new AtomicLong(
|
||||
AbstractTrafficShapingHandler.DEFAULT_CHECK_INTERVAL);
|
||||
|
||||
// default 1 s
|
||||
|
||||
/**
|
||||
* Name of this Monitor
|
||||
*/
|
||||
final String name;
|
||||
|
||||
/**
|
||||
* The associated TrafficShapingHandler
|
||||
*/
|
||||
private AbstractTrafficShapingHandler trafficShapingHandler;
|
||||
|
||||
/**
|
||||
* Default Executor
|
||||
*/
|
||||
private Executor executor;
|
||||
|
||||
/**
|
||||
* Is Monitor active
|
||||
*/
|
||||
AtomicBoolean monitorActive = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Monitor
|
||||
*/
|
||||
private TrafficMonitoring trafficMonitoring;
|
||||
|
||||
/**
|
||||
* Class to implement monitoring at fix delay
|
||||
*/
|
||||
private static class TrafficMonitoring implements Runnable {
|
||||
/**
|
||||
* The associated TrafficShapingHandler
|
||||
*/
|
||||
private final AbstractTrafficShapingHandler trafficShapingHandler1;
|
||||
|
||||
/**
|
||||
* The associated TrafficCounter
|
||||
*/
|
||||
private final TrafficCounter counter;
|
||||
|
||||
/**
|
||||
* @param trafficShapingHandler
|
||||
* @param counter
|
||||
*/
|
||||
protected TrafficMonitoring(
|
||||
AbstractTrafficShapingHandler trafficShapingHandler,
|
||||
TrafficCounter counter) {
|
||||
trafficShapingHandler1 = trafficShapingHandler;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default run
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.currentThread().setName(counter.name);
|
||||
for (; counter.monitorActive.get();) {
|
||||
long check = counter.checkInterval.get();
|
||||
if (check > 0) {
|
||||
Thread.sleep(check);
|
||||
} else {
|
||||
// Delay goes to 0, so exit
|
||||
return;
|
||||
}
|
||||
long endTime = System.currentTimeMillis();
|
||||
counter.resetAccounting(endTime);
|
||||
if (trafficShapingHandler1 != null) {
|
||||
trafficShapingHandler1.doAccounting(counter);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// End of computations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the monitoring process
|
||||
*/
|
||||
public void start() {
|
||||
synchronized (lastTime) {
|
||||
if (monitorActive.get()) {
|
||||
return;
|
||||
}
|
||||
lastTime.set(System.currentTimeMillis());
|
||||
if (checkInterval.get() > 0) {
|
||||
monitorActive.set(true);
|
||||
trafficMonitoring = new TrafficMonitoring(
|
||||
trafficShapingHandler, this);
|
||||
executor.execute(trafficMonitoring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the monitoring process
|
||||
*/
|
||||
public void stop() {
|
||||
synchronized (lastTime) {
|
||||
if (!monitorActive.get()) {
|
||||
return;
|
||||
}
|
||||
monitorActive.set(false);
|
||||
resetAccounting(System.currentTimeMillis());
|
||||
if (trafficShapingHandler != null) {
|
||||
trafficShapingHandler.doAccounting(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the accounting on Read and Write
|
||||
*
|
||||
* @param newLastTime
|
||||
*/
|
||||
void resetAccounting(long newLastTime) {
|
||||
synchronized (lastTime) {
|
||||
long interval = newLastTime - lastTime.getAndSet(newLastTime);
|
||||
if (interval == 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
lastReadBytes = currentReadBytes.getAndSet(0);
|
||||
lastWrittenBytes = currentWrittenBytes.getAndSet(0);
|
||||
lastReadThroughput = lastReadBytes / interval * 1000;
|
||||
// nb byte / checkInterval in ms * 1000 (1s)
|
||||
lastWriteThroughput = lastWrittenBytes / interval * 1000;
|
||||
// nb byte / checkInterval in ms * 1000 (1s)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with the {@link AbstractTrafficShapingHandler} that hosts it, the executorService to use, its
|
||||
* name, the checkInterval between two computations in millisecond
|
||||
* @param trafficShapingHandler the associated AbstractTrafficShapingHandler
|
||||
* @param executor
|
||||
* Should be a CachedThreadPool for efficiency
|
||||
* @param name
|
||||
* the name given to this monitor
|
||||
* @param checkInterval
|
||||
* the checkInterval in millisecond between two computations
|
||||
*/
|
||||
public TrafficCounter(AbstractTrafficShapingHandler trafficShapingHandler,
|
||||
Executor executor, String name, long checkInterval) {
|
||||
this.trafficShapingHandler = trafficShapingHandler;
|
||||
this.executor = executor;
|
||||
this.name = name;
|
||||
lastCumulativeTime = System.currentTimeMillis();
|
||||
configure(checkInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change checkInterval between
|
||||
* two computations in millisecond
|
||||
*
|
||||
* @param newcheckInterval
|
||||
*/
|
||||
public void configure(long newcheckInterval) {
|
||||
if (checkInterval.get() != newcheckInterval) {
|
||||
checkInterval.set(newcheckInterval);
|
||||
if (newcheckInterval <= 0) {
|
||||
stop();
|
||||
// No more active monitoring
|
||||
lastTime.set(System.currentTimeMillis());
|
||||
} else {
|
||||
// Start if necessary
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes counters for Read.
|
||||
*
|
||||
* @param ctx
|
||||
* the associated channelHandlerContext
|
||||
* @param recv
|
||||
* the size in bytes to read
|
||||
*/
|
||||
void bytesRecvFlowControl(ChannelHandlerContext ctx, long recv) {
|
||||
currentReadBytes.addAndGet(recv);
|
||||
cumulativeReadBytes.addAndGet(recv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes counters for Write.
|
||||
*
|
||||
* @param write
|
||||
* the size in bytes to write
|
||||
*/
|
||||
void bytesWriteFlowControl(long write) {
|
||||
currentWrittenBytes.addAndGet(write);
|
||||
cumulativeWrittenBytes.addAndGet(write);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the current checkInterval between two computations of traffic counter
|
||||
* in millisecond
|
||||
*/
|
||||
public long getCheckInterval() {
|
||||
return checkInterval.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the Read Throughput in bytes/s computes in the last check interval
|
||||
*/
|
||||
public long getLastReadThroughput() {
|
||||
return lastReadThroughput;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the Write Throughput in bytes/s computes in the last check interval
|
||||
*/
|
||||
public long getLastWriteThroughput() {
|
||||
return lastWriteThroughput;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the number of bytes read during the last check Interval
|
||||
*/
|
||||
public long getLastReadBytes() {
|
||||
return lastReadBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the number of bytes written during the last check Interval
|
||||
*/
|
||||
public long getLastWrittenBytes() {
|
||||
return lastWrittenBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the current number of bytes read since the last checkInterval
|
||||
*/
|
||||
public long getCurrentReadBytes() {
|
||||
return currentReadBytes.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the current number of bytes written since the last check Interval
|
||||
*/
|
||||
public long getCurrentWrittenBytes() {
|
||||
return currentWrittenBytes.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Time in millisecond of the last check as of System.currentTimeMillis()
|
||||
*/
|
||||
public long getLastTime() {
|
||||
return lastTime.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cumulativeWrittenBytes
|
||||
*/
|
||||
public long getCumulativeWrittenBytes() {
|
||||
return cumulativeWrittenBytes.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cumulativeReadBytes
|
||||
*/
|
||||
public long getCumulativeReadBytes() {
|
||||
return cumulativeReadBytes.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastCumulativeTime in millisecond as of System.currentTimeMillis()
|
||||
* when the cumulative counters were reset to 0.
|
||||
*/
|
||||
public long getLastCumulativeTime() {
|
||||
return lastCumulativeTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset both read and written cumulative bytes counters and the associated time.
|
||||
*/
|
||||
public void resetCumulativeTime() {
|
||||
lastCumulativeTime = System.currentTimeMillis();
|
||||
cumulativeReadBytes.set(0);
|
||||
cumulativeWrittenBytes.set(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* String information
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Monitor " + name + " Current Speed Read: " +
|
||||
(lastReadThroughput >> 10) + " KB/s, Write: " +
|
||||
(lastWriteThroughput >> 10) + " KB/s Current Read: " +
|
||||
(currentReadBytes.get() >> 10) + " KB Current Write: " +
|
||||
(currentWrittenBytes.get() >> 10) + " KB";
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of a Traffic Shaping Handler and Dynamic Statistics.<br>
|
||||
* <br><br>
|
||||
* <P>The main goal of this package is to allow to shape the traffic (bandwidth limitation),
|
||||
* but also to get statistics on how many bytes are read or written. Both functions can
|
||||
* be active or inactive (traffic or statistics).</P>
|
||||
*
|
||||
* <P>Two classes implement this behavior:<br>
|
||||
* <ul>
|
||||
* <li> <tt>{@link org.jboss.netty.handler.traffic.TrafficCounter}</tt>: this class implements the counters needed by the handlers.
|
||||
* It can be accessed to get some extra information like the read or write bytes since last check, the read and write
|
||||
* bandwidth from last check...</li><br><br>
|
||||
*
|
||||
* <li> <tt>{@link org.jboss.netty.handler.traffic.AbstractTrafficShapingHandler}</tt>: this abstract class implements the kernel
|
||||
* of the traffic shaping. It could be extended to fit your needs. Two classes are proposed as default
|
||||
* implementations: see {@link org.jboss.netty.handler.traffic.ChannelTrafficShapingHandler} and see {@link org.jboss.netty.handler.traffic.GlobalTrafficShapingHandler}
|
||||
* respectively for Channel traffic shaping and Global traffic shaping.</li><br><br>
|
||||
*
|
||||
* The insertion in the pipeline of one of those handlers can be wherever you want, but
|
||||
* <b>it must be placed before any <tt>{@link org.jboss.netty.handler.execution.MemoryAwareThreadPoolExecutor}</tt>
|
||||
* in your pipeline</b>.<br>
|
||||
* <b><i>It is really recommended to have such a</i> <tt>{@link org.jboss.netty.handler.execution.MemoryAwareThreadPoolExecutor}</tt>
|
||||
* <i>(either non ordered or </i> <tt>{@link org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor}</tt>
|
||||
* <i>) in your pipeline</i></b>
|
||||
* when you want to use this feature with some real traffic shaping, since it will allow to relax the constraint on
|
||||
* NioWorker to do other jobs if necessary.<br>
|
||||
* Instead, if you don't, you can have the following situation: if there are more clients
|
||||
* connected and doing data transfer (either in read or write) than NioWorker, your global performance can be under
|
||||
* your specifications or even sometimes it will block for a while which can turn to "timeout" operations.
|
||||
* For instance, let says that you've got 2 NioWorkers, and 10 clients wants to send data to your server.
|
||||
* If you set a bandwidth limitation of 100KB/s for each channel (client), you could have a final limitation of about
|
||||
* 60KB/s for each channel since NioWorkers are stopping by this handler.<br>
|
||||
* When it is used as a read traffic shaper, the handler will set the channel as not readable, so as to relax the
|
||||
* NioWorkers.<br><br>
|
||||
* An {@link org.jboss.netty.util.ObjectSizeEstimator} can be passed at construction to specify what
|
||||
* is the size of the object to be read or write accordingly to the type of
|
||||
* object. If not specified, it will used the {@link org.jboss.netty.util.DefaultObjectSizeEstimator} implementation.<br><br>
|
||||
* </ul></P>
|
||||
*
|
||||
* <P>Standard use could be as follow:</P>
|
||||
*
|
||||
* <P><ul>
|
||||
* <li>To activate or deactivate the traffic shaping, change the value corresponding to your desire as
|
||||
* [Global or per Channel] [Write or Read] Limitation in byte/s.</li><br>
|
||||
* A value of <tt>0</tt>
|
||||
* stands for no limitation, so the traffic shaping is deactivate (on what you specified).<br>
|
||||
* You can either change those values with the method <tt>configure</tt> in {@link org.jboss.netty.handler.traffic.AbstractTrafficShapingHandler}.<br>
|
||||
* <br>
|
||||
*
|
||||
* <li>To activate or deactivate the statistics, you can adjust the delay to a low (suggested not less than 200ms
|
||||
* for efficiency reasons) or a high value (let say 24H in millisecond is huge enough to not get the problem)
|
||||
* or even using <tt>0</tt> which means no computation will be done.</li><br>
|
||||
* If you want to do anything with this statistics, just override the <tt>doAccounting</tt> method.<br>
|
||||
* This interval can be changed either from the method <tt>configure</tt> in {@link org.jboss.netty.handler.traffic.AbstractTrafficShapingHandler}
|
||||
* or directly using the method <tt>configure</tt> of {@link org.jboss.netty.handler.traffic.TrafficCounter}.<br><br>
|
||||
*
|
||||
* </ul></P><br><br>
|
||||
*
|
||||
* <P>So in your application you will create your own TrafficShapingHandler and set the values to fit your needs.</P>
|
||||
* <tt>XXXXXTrafficShapingHandler myHandler = new XXXXXTrafficShapingHandler(executor);</tt><br><br>
|
||||
* where executor could be created using <tt>Executors.newCachedThreadPool();</tt> and XXXXX could be either
|
||||
* Global or Channel<br>
|
||||
* <tt>pipeline.addLast("XXXXX_TRAFFIC_SHAPING", myHandler);</tt><br>
|
||||
* <tt>...</tt><br>
|
||||
* <tt>pipeline.addLast("MemoryExecutor",new ExecutionHandler(memoryAwareThreadPoolExecutor));</tt><br><br>
|
||||
* <P>Note that a new {@link org.jboss.netty.handler.traffic.ChannelTrafficShapingHandler} must be created for each new channel,
|
||||
* but only one {@link org.jboss.netty.handler.traffic.GlobalTrafficShapingHandler} must be created for all channels.</P>
|
||||
*
|
||||
* <P>Note also that you can create different GlobalTrafficShapingHandler if you want to separate classes of
|
||||
* channels (for instance either from business point of view or from bind address point of view).</P>
|
||||
*
|
||||
* @apiviz.exclude ^java\.lang\.
|
||||
*/
|
||||
package org.jboss.netty.handler.traffic;
|
||||
|
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright 2011 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jboss.netty.handler.ipfilter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelConfig;
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelFactory;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelHandler;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.UpstreamMessageEvent;
|
||||
import org.junit.Test;
|
||||
|
||||
public class IpFilterRuleTest extends TestCase
|
||||
{
|
||||
public static boolean accept(IpFilterRuleHandler h, InetSocketAddress addr) throws Exception
|
||||
{
|
||||
return h.accept(new ChannelHandlerContext()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean canHandleDownstream()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandleUpstream()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttachment()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel getChannel()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler getHandler()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendDownstream(ChannelEvent e)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendUpstream(ChannelEvent e)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttachment(Object attachment)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
new UpstreamMessageEvent(new Channel()
|
||||
{
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress localAddress)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture getCloseFuture()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig getConfig()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFactory getFactory()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getId()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInterestOps()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getLocalAddress()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel getParent()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBound()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture setInterestOps(int interestOps)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture setReadable(boolean readable)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture unbind()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object message)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object message, SocketAddress remoteAddress)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Channel o)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
}, h, addr),
|
||||
addr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpFilterRule() throws Exception
|
||||
{
|
||||
IpFilterRuleHandler h = new IpFilterRuleHandler();
|
||||
h.addAll(new IpFilterRuleList("+n:localhost, -n:*"));
|
||||
InetSocketAddress addr = new InetSocketAddress(InetAddress.getLocalHost(), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName("127.0.0.2"), 8080);
|
||||
assertFalse(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
|
||||
h.clear();
|
||||
h.addAll(new IpFilterRuleList("+n:*"+InetAddress.getLocalHost().getHostName().substring(1)+", -n:*"));
|
||||
addr = new InetSocketAddress(InetAddress.getLocalHost(), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName("127.0.0.2"), 8080);
|
||||
assertFalse(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
|
||||
h.clear();
|
||||
h.addAll(new IpFilterRuleList("+c:"+InetAddress.getLocalHost().getHostAddress()+"/32, -n:*"));
|
||||
addr = new InetSocketAddress(InetAddress.getLocalHost(), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName("127.0.0.2"), 8080);
|
||||
assertFalse(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
|
||||
h.clear();
|
||||
h.addAll(new IpFilterRuleList(""));
|
||||
addr = new InetSocketAddress(InetAddress.getLocalHost(), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName("127.0.0.2"), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
|
||||
h.clear();
|
||||
addr = new InetSocketAddress(InetAddress.getLocalHost(), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName("127.0.0.2"), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
addr = new InetSocketAddress(InetAddress.getByName(InetAddress.getLocalHost().getHostName()), 8080);
|
||||
assertTrue(accept(h, addr));
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user