diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/CIDR.java b/src/main/java/org/jboss/netty/handler/ipfilter/CIDR.java new file mode 100644 index 0000000000..f0e881369f --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/CIDR.java @@ -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 { + /** 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
+ * 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.
+ * 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]}; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/CIDR4.java b/src/main/java/org/jboss/netty/handler/ipfilter/CIDR4.java new file mode 100644 index 0000000000..08b83d8b67 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/CIDR4.java @@ -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; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/CIDR6.java b/src/main/java/org/jboss/netty/handler/ipfilter/CIDR6.java new file mode 100644 index 0000000000..7c7964d5e3 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/CIDR6.java @@ -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); + } +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterListener.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterListener.java new file mode 100644 index 0000000000..0ccda3c290 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterListener.java @@ -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.
+ * 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.
+ * 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.
+ * If one wants to not block events, just overridden this method by returning always true.

+ * 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. + * + * @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); + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRule.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRule.java new file mode 100644 index 0000000000..50926886e6 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRule.java @@ -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(); +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRuleHandler.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRuleHandler.java new file mode 100644 index 0000000000..04b133a5d3 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRuleHandler.java @@ -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.
+ *

+ * 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) .
+ *
+ * The check is done by going from step to step in the underlying array of IpFilterRule.
+ * 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.
+ *
+ *
+ * An empty list means allow all (no limitation).

+ * For efficiency reason, you should not add/remove too frequently IpFilterRules to/from this handler. + * You should prefer to replace an entry (set method) with an ALLOW/DENY ALL IpFilterRule + * if possible.


+ * This handler should be created only once and reused on every pipeline since it handles + * a global status of what is allowed or blocked.

+ *

+ * 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 ipFilterRuleList = new CopyOnWriteArrayList(); + + /** Constructor from a new list of IpFilterRule */ + public IpFilterRuleHandler(List 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 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 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 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 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 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 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 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; + } + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRuleList.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRuleList.java new file mode 100644 index 0000000000..eae1f7b755 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilterRuleList.java @@ -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. + *
+ * Rule List Syntax: + *
+ *

+ * RuleList ::= Rule[,Rule]*
+ * Rule ::= AllowRule | BlockRule
+ * AllowRule ::= +Filter
+ * BlockRule ::= -Filter
+ * Filter ::= PatternFilter | CIDRFilter
+ * PatternFilter ::= @see PatternRule
+ * CIDRFilter ::= c:CIDRFilter
+ * CIDRFilter ::= @see CIDR.newCIDR(String)
+ * 
+ *
+ * Example: allow only localhost: + *
+ * new IPFilterRuleHandler().addAll(new IpFilterRuleList("+n:localhost, -n:*")); + *
+ */ +public class IpFilterRuleList extends ArrayList { + 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); + } + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpFilteringHandler.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilteringHandler.java new file mode 100644 index 0000000000..c4c0e2d373 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilteringHandler.java @@ -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. + *
+ * 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(); +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpFilteringHandlerImpl.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilteringHandlerImpl.java new file mode 100644 index 0000000000..7374dfa01a --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpFilteringHandlerImpl.java @@ -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.
+ * 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.
+ * If one wants to not block events, just overridden this method by returning always true.

+ * 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. + * + * @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; + + } + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpSet.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpSet.java new file mode 100644 index 0000000000..c77c0ee6dd --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpSet.java @@ -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); +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpSubnet.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpSubnet.java new file mode 100644 index 0000000000..f62b16b72d --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpSubnet.java @@ -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.
+ *

+ * 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.
+ *

Example1:
+ * IpV4Subnet ips = new IpV4Subnet("192.168.1.0/24");
+ * System.out.println("Result: "+ ips.contains("192.168.1.123"));
+ * System.out.println("Result: "+ ips.contains(inetAddress2));
+ *
Example1 bis:
+ * IpV4Subnet ips = new IpV4Subnet(inetAddress, 24);
+ * where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123
+ *

Example2:
+ * IpV4Subnet ips = new IpV4Subnet("192.168.1.0/255.255.255.0");
+ * System.out.println("Result: "+ ips.contains("192.168.1.123"));
+ * System.out.println("Result: "+ ips.contains(inetAddress2));
+ *
Example2 bis:
+ * IpV4Subnet ips = new IpV4Subnet(inetAddress, "255.255.255.0");
+ * where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123
+ *
+ * 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).
+ *

Example1:
+ * IpSubnet ips = new IpSubnet("1fff:0:0a88:85a3:0:0:0:0/24");
+ * IpSubnet ips = new IpSubnet("1fff:0:0a88:85a3::/24");
+ * System.out.println("Result: "+ ips.contains("1fff:0:0a88:85a3:0:0:ac1f:8001"));
+ * System.out.println("Result: "+ ips.contains(inetAddress2));
+ *
Example1 bis:
+ * IpSubnet ips = new IpSubnet(inetAddress, 24);
+ * where inetAddress2 is 1fff:0:0a88:85a3:0:0:ac1f:8001
+ */ +public class IpSubnet implements IpSet, Comparable { + + 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
+ * i.e.:
+ * IpSubnet subnet = new IpSubnet("10.10.10.0/24"); or
+ * IpSubnet subnet = new IpSubnet("10.10.10.0/255.255.255.0"); or
+ * 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])); + } + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpSubnetFilterRule.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpSubnetFilterRule.java new file mode 100644 index 0000000000..137a7f62fd --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpSubnetFilterRule.java @@ -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.
+ *
+ * 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; + } + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpV4Subnet.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpV4Subnet.java new file mode 100644 index 0000000000..ca6faf5250 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpV4Subnet.java @@ -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.
+ * 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.
+ *

Example1:
+ * IpV4Subnet ips = new IpV4Subnet("192.168.1.0/24");
+ * System.out.println("Result: "+ ips.contains("192.168.1.123"));
+ * System.out.println("Result: "+ ips.contains(inetAddress2));
+ *
Example1 bis:
+ * IpV4Subnet ips = new IpV4Subnet(inetAddress, 24);
+ * where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123
+ *

Example2:
+ * IpV4Subnet ips = new IpV4Subnet("192.168.1.0/255.255.255.0");
+ * System.out.println("Result: "+ ips.contains("192.168.1.123"));
+ * System.out.println("Result: "+ ips.contains(inetAddress2));
+ *
Example2 bis:
+ * IpV4Subnet ips = new IpV4Subnet(inetAddress, "255.255.255.0");
+ * where inetAddress is 192.168.1.0 and inetAddress2 is 192.168.1.123
+ */ +public class IpV4Subnet implements IpSet, Comparable { + + 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
+ * 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.
+ * i.e.: setNetAddress("1.1.1.1/24"); or
+ * setNetAddress("1.1.1.1/255.255.255.0");
+ * + * @param netAddress a network address as string. + */ + private void setNetAddress(String netAddress) throws UnknownHostException { + Vector vec = new Vector(); + 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.
+ * 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.
+ * 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
+ * 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])); + } + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/IpV4SubnetFilterRule.java b/src/main/java/org/jboss/netty/handler/ipfilter/IpV4SubnetFilterRule.java new file mode 100644 index 0000000000..afb511428d --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/IpV4SubnetFilterRule.java @@ -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; + } + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/OneIpFilterHandler.java b/src/main/java/org/jboss/netty/handler/ipfilter/OneIpFilterHandler.java new file mode 100644 index 0000000000..aa68f477d3 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/OneIpFilterHandler.java @@ -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).
+ *
+ *

+ * 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 connectedSet = new ConcurrentHashMap(); + + @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()); + } + } + } + } + } + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/PatternRule.java b/src/main/java/org/jboss/netty/handler/ipfilter/PatternRule.java new file mode 100644 index 0000000000..7b362b141c --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/PatternRule.java @@ -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. + *
+ * Rule Syntax: + *
+ *

+ * 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
+ * 
+ *
+ * Example: allow localhost: + *
+ * new PatternRule(true, "n:localhost") + *
+ * Example: allow local lan: + *
+ * new PatternRule(true, "i:192.168.0.*") + *
+ * Example: block all + *
+ * new PatternRule(false, "n:*") + *
+ */ +public class PatternRule implements IpFilterRule, Comparable { + 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; + } + +} diff --git a/src/main/java/org/jboss/netty/handler/ipfilter/package-info.java b/src/main/java/org/jboss/netty/handler/ipfilter/package-info.java new file mode 100644 index 0000000000..9e833baa13 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ipfilter/package-info.java @@ -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.
+ *

+ *

The main goal of this package is to allow to filter connections based on IP rules. + * The main interface is {@link org.jboss.netty.handler.ipfilter.IpFilteringHandler} which all filters will extend.

+ * + *

Two IP filtering are proposed:
+ *

    + *
  • {@link org.jboss.netty.handler.ipfilter.OneIpFilterHandler}: 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.


  • + * + *
  • {@link org.jboss.netty.handler.ipfilter.IpFilterRuleHandler}: 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 + * IpV4SubnetFilterRule (IPV4 support only), IpSubnetFilterRule (IPV4 and IPV6 support) or PatternRule (string pattern support) + * which implements those Ip ranges.


  • + * + *

+ * + *

Standard use could be as follow: The accept method must be overridden (of course you can + * override others).

+ * + *

    + *
  • accept method allows to specify your way of choosing if a new connection is + * to be allowed or not.

  • + * In OneIpFilterHandler and IpFilterRuleHandler, + * this method is already implemented.
    + *
    + * + *
  • handleRefusedChannel 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.

  • + * So if you want to send back a message to the client, 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 (at least with respect of asynchronous operations).

    + * Per default implementation this method invokes an {@link org.jboss.netty.handler.ipfilter.IpFilterListener} or returns null if no listener has been set. + *

    + * + *
  • continues is called when any event appears after CONNECTED event and only for + * blocked channels.

  • + * 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.
    + * 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.
    + * 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.

    + * Per default implementation this method invokes an {@link org.jboss.netty.handler.ipfilter.IpFilterListener} or returns false if no listener has been set. + *

    + * + *
  • Finally handleUpstream traps the CONNECTED and DISCONNECTED events.

  • + * If in the CONNECTED events the channel is blocked (accept refused the channel), + * then any new events on this channel will be blocked.
    + * 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 ctx.sendUpstream(e); after calling the super method or by changing the continues method.

    + + *



+ * + * A typical setup for ip filter for TCP/IP socket would be: + * + *
+ * {@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);
+ * 
+ * + * @apiviz.exclude ^java\.lang\. + */ +package org.jboss.netty.handler.ipfilter; + + diff --git a/src/main/java/org/jboss/netty/handler/traffic/AbstractTrafficShapingHandler.java b/src/main/java/org/jboss/netty/handler/traffic/AbstractTrafficShapingHandler.java new file mode 100644 index 0000000000..f7fe35d60e --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/traffic/AbstractTrafficShapingHandler.java @@ -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.
+ *
+ * + * 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:
+ *
    + *
  • configure allows you to change read or write limits, or the checkInterval
  • + *
  • getTrafficCounter 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.
  • + *
  • + *
+ */ +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"); + } +} diff --git a/src/main/java/org/jboss/netty/handler/traffic/ChannelTrafficShapingHandler.java b/src/main/java/org/jboss/netty/handler/traffic/ChannelTrafficShapingHandler.java new file mode 100644 index 0000000000..9ec72afd5a --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/traffic/ChannelTrafficShapingHandler.java @@ -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.

+ * + * The general use should be as follow:
+ *
    + *
  • Add in your pipeline a new ChannelTrafficShapingHandler, before a recommended {@link ExecutionHandler} (like + * {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).
    + * ChannelTrafficShapingHandler myHandler = new ChannelTrafficShapingHandler(executor);
    + * executor could be created using Executors.newCachedThreadPool();
    + * pipeline.addLast("CHANNEL_TRAFFIC_SHAPING", myHandler);

    + * + * 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. For instance, if you have a + * {@link ChannelPipelineFactory}, you should create a new ChannelTrafficShapingHandler in this + * {@link ChannelPipelineFactory} each time getPipeline() method is called.

    + * + * 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).

    + * + * 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.
    + *
  • + *
  • When you shutdown your application, release all the external resources like the executor + * by calling:
    + * myHandler.releaseExternalResources();
    + *
  • + *

+ */ +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); + } + +} diff --git a/src/main/java/org/jboss/netty/handler/traffic/GlobalTrafficShapingHandler.java b/src/main/java/org/jboss/netty/handler/traffic/GlobalTrafficShapingHandler.java new file mode 100644 index 0000000000..7f54e28f29 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/traffic/GlobalTrafficShapingHandler.java @@ -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.

+ * + * The general use should be as follow:
+ *
    + *
  • Create your unique GlobalTrafficShapingHandler like:

    + * GlobalTrafficShapingHandler myHandler = new GlobalTrafficShapingHandler(executor);

    + * executor could be created using Executors.newCachedThreadPool();
    + * pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);

    + * + * 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.

    + * + * 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).

    + * + * 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.
    + *
  • + *
  • Add it in your pipeline, before a recommended {@link ExecutionHandler} (like + * {@link OrderedMemoryAwareThreadPoolExecutor} or {@link MemoryAwareThreadPoolExecutor}).
    + * pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", myHandler);

    + *
  • + *
  • When you shutdown your application, release all the external resources like the executor + * by calling:
    + * myHandler.releaseExternalResources();
    + *
  • + *

+ */ +@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(); + } +} diff --git a/src/main/java/org/jboss/netty/handler/traffic/TrafficCounter.java b/src/main/java/org/jboss/netty/handler/traffic/TrafficCounter.java new file mode 100644 index 0000000000..521664e173 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/traffic/TrafficCounter.java @@ -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}.
+ *
+ * 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"; + } +} diff --git a/src/main/java/org/jboss/netty/handler/traffic/package-info.java b/src/main/java/org/jboss/netty/handler/traffic/package-info.java new file mode 100644 index 0000000000..25321804cb --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/traffic/package-info.java @@ -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.
+ *

+ *

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).

+ * + *

Two classes implement this behavior:
+ *

    + *
  • {@link org.jboss.netty.handler.traffic.TrafficCounter}: 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...


  • + * + *
  • {@link org.jboss.netty.handler.traffic.AbstractTrafficShapingHandler}: 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.


  • + * + * The insertion in the pipeline of one of those handlers can be wherever you want, but + * it must be placed before any {@link org.jboss.netty.handler.execution.MemoryAwareThreadPoolExecutor} + * in your pipeline.
    + * It is really recommended to have such a {@link org.jboss.netty.handler.execution.MemoryAwareThreadPoolExecutor} + * (either non ordered or {@link org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor} + * ) in your pipeline + * 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.
    + * 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.
    + * When it is used as a read traffic shaper, the handler will set the channel as not readable, so as to relax the + * NioWorkers.

    + * 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.

    + *

+ * + *

Standard use could be as follow:

+ * + *

    + *
  • 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.

  • + * A value of 0 + * stands for no limitation, so the traffic shaping is deactivate (on what you specified).
    + * You can either change those values with the method configure in {@link org.jboss.netty.handler.traffic.AbstractTrafficShapingHandler}.
    + *
    + * + *
  • 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 0 which means no computation will be done.

  • + * If you want to do anything with this statistics, just override the doAccounting method.
    + * This interval can be changed either from the method configure in {@link org.jboss.netty.handler.traffic.AbstractTrafficShapingHandler} + * or directly using the method configure of {@link org.jboss.netty.handler.traffic.TrafficCounter}.

    + * + *



+ * + *

So in your application you will create your own TrafficShapingHandler and set the values to fit your needs.

+ * XXXXXTrafficShapingHandler myHandler = new XXXXXTrafficShapingHandler(executor);

+ * where executor could be created using Executors.newCachedThreadPool(); and XXXXX could be either + * Global or Channel
+ * pipeline.addLast("XXXXX_TRAFFIC_SHAPING", myHandler);
+ * ...
+ * pipeline.addLast("MemoryExecutor",new ExecutionHandler(memoryAwareThreadPoolExecutor));

+ *

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.

+ * + *

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).

+ * + * @apiviz.exclude ^java\.lang\. + */ +package org.jboss.netty.handler.traffic; + diff --git a/src/test/java/org/jboss/netty/handler/ipfilter/IpFilterRuleTest.java b/src/test/java/org/jboss/netty/handler/ipfilter/IpFilterRuleTest.java new file mode 100644 index 0000000000..cb2793ebae --- /dev/null +++ b/src/test/java/org/jboss/netty/handler/ipfilter/IpFilterRuleTest.java @@ -0,0 +1,347 @@ +/* + * 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 io.netty.handler.ipfilter; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import junit.framework.TestCase; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.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; + } + + @Override + public Object getAttachment() { + return null; + } + + @Override + public void setAttachment(Object attachment) { + + } + + }, 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)); + + } + +}