diff --git a/handler/src/main/java/io/netty/handler/ipfilter/AbstractIpFilterHandler.java b/handler/src/main/java/io/netty/handler/ipfilter/AbstractIpFilterHandler.java
new file mode 100644
index 0000000000..92fb0e6f44
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/AbstractIpFilterHandler.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014 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 io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+
+import java.net.InetSocketAddress;
+
+/**
+ * This class provides the functionality to either accept or reject new {@link Channel}s
+ * based on their IP address.
+ *
+ * You should inherit from this class if you would like to implement your own IP-based filter. Basically you have to
+ * implement {@link #accept(InetSocketAddress)} to decided whether you want to allow or deny a connection from an IP
+ * address. Furthermore overriding {@link #rejected(ChannelHandlerContext, InetSocketAddress)} gives you the flexibility
+ * to respond to rejected (denied) connections. If you do not want to send a response, just have it return null.
+ * Take a look at {@link IpFilterRuleHandler} for details.
+ */
+public abstract class AbstractIpFilterHandler extends ChannelInboundHandlerAdapter {
+ private final AttributeKey decisionKey = AttributeKey.valueOf(getClass().getName());
+
+ @Override
+ public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
+ final InetSocketAddress ipAddress = (InetSocketAddress) ctx.channel().remoteAddress();
+
+ if (!accept(ipAddress)) {
+ // the channel might be active already
+ if (ctx.channel().isActive()) {
+ handleRejected(ctx);
+ } else {
+ // if the channel is not active yet, store the decision for later use
+ // in #channelActive(ChannelHandlerContext ctx)
+ Attribute decision = ctx.attr(decisionKey);
+ decision.set(IpFilterDecision.REJECTED);
+
+ super.channelRegistered(ctx);
+ }
+ } else {
+ super.channelRegistered(ctx);
+ }
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ final IpFilterDecision decision = ctx.attr(decisionKey).get();
+
+ if (decision == IpFilterDecision.REJECTED) {
+ handleRejected(ctx);
+ } else {
+ super.channelActive(ctx);
+ }
+ }
+
+ private void handleRejected(ChannelHandlerContext ctx) {
+ final InetSocketAddress ipAddress = (InetSocketAddress) ctx.channel().remoteAddress();
+
+ ChannelFuture rejectedFuture = rejected(ctx, ipAddress);
+ if (rejectedFuture != null) {
+ rejectedFuture.addListener(ChannelFutureListener.CLOSE);
+ } else {
+ ctx.close();
+ }
+ }
+
+ /**
+ * This method is called immediately after a {@link io.netty.channel.Channel} gets registered.
+ *
+ * @return Return true if connections from this IP address and port should be accepted. False otherwise.
+ */
+ protected abstract boolean accept(InetSocketAddress ipAndPort) throws Exception;
+
+ /**
+ * This method is called if ipAndPort gets rejected by {@link #accept(InetSocketAddress)}.
+ * You should override it if you would like to handle (e.g. respond to) rejected addresses.
+ *
+ * @return A {@link ChannelFuture} if you perform I/O operations, so that
+ * the {@link Channel} can be closed once it completes. Null otherwise.
+ */
+ protected abstract ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort);
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterDecision.java b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterDecision.java
new file mode 100644
index 0000000000..95e5c5b0c2
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterDecision.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 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 io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandler;
+
+/**
+ * This enum is used in {@link AbstractIpFilterHandler} to keep the state between the calls to
+ * {@link ChannelInboundHandler#channelRegistered(ChannelHandlerContext)} and
+ * {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)}.
+ */
+public enum IpFilterDecision {
+ REJECTED
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRule.java b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRule.java
new file mode 100644
index 0000000000..8a5759ef23
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRule.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 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.InetSocketAddress;
+
+/**
+ * Implement this interface to create new rules.
+ */
+public interface IpFilterRule {
+ /**
+ * @return This method should return true if ipAndPort is valid according to your criteria. False otherwise.
+ */
+ boolean matches(InetSocketAddress ipAndPort);
+
+ /**
+ * @return This method should return {@link IpFilterRuleType#ALLOW} if all
+ * {@link IpFilterRule#matches(InetSocketAddress)} for which {@link #matches(InetSocketAddress)}
+ * returns true should the accepted. If you want to exclude all of those IP addresses then
+ * {@link IpFilterRuleType#DENY} should be returned.
+ */
+ IpFilterRuleType ruleType();
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleHandler.java b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleHandler.java
new file mode 100644
index 0000000000..ded2bcf6f5
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 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 io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.Channel;
+import java.net.InetSocketAddress;
+
+/**
+ * This class allows one to filter new {@link Channel}s based on the
+ * {@link IpFilterRule}s passed to its constructor. If no rules are provided, all connections
+ * will be accepted.
+ *
+ * If you would like to explicitly take action on rejected {@link Channel}s, you should override
+ * {@link #rejected(ChannelHandlerContext, InetSocketAddress)}.
+ */
+@Sharable
+public class IpFilterRuleHandler extends AbstractIpFilterHandler {
+ private final IpFilterRule[] rules;
+
+ public IpFilterRuleHandler(IpFilterRule... rules) {
+ if (rules == null) {
+ throw new NullPointerException("rules");
+ }
+
+ if (rules.length == 0) {
+ throw new IllegalArgumentException("You have to provide at least one rule.");
+ }
+
+ this.rules = rules;
+ }
+
+ @Override
+ protected boolean accept(InetSocketAddress ipAndPort) throws Exception {
+ for (IpFilterRule rule : rules) {
+ if (rule.matches(ipAndPort)) {
+ return rule.ruleType() == IpFilterRuleType.ALLOW;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) {
+ return null;
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleType.java b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleType.java
new file mode 100644
index 0000000000..9d78130388
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleType.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014 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;
+
+/**
+ * Used in {@link IpFilterRule} to decide if a matching IP Address should be allowed or denied to connect.
+ */
+public enum IpFilterRuleType {
+ ALLOW,
+ DENY
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java
new file mode 100644
index 0000000000..2eebfdcce7
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2014 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.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Use this class to create rules for {@link IpFilterRuleHandler} that group IP addresses into subnets.
+ * Supports both, IPv4 and IPv6.
+ */
+public final class IpSubnetFilterRule implements IpFilterRule {
+ private final IpFilterRule filterRule;
+
+ public IpSubnetFilterRule(String ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
+ try {
+ filterRule = selectFilterRule(InetAddress.getByName(ipAddress), cidrPrefix, ruleType);
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("ipAddress", e);
+ }
+ }
+
+ public IpSubnetFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
+ filterRule = selectFilterRule(ipAddress, cidrPrefix, ruleType);
+ }
+
+ private static IpFilterRule selectFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
+ if (ipAddress == null) {
+ throw new NullPointerException("ipAddress");
+ }
+
+ if (ruleType == null) {
+ throw new NullPointerException("ruleType");
+ }
+
+ if (ipAddress instanceof Inet4Address) {
+ return new Ip4SubnetFilterRule((Inet4Address) ipAddress, cidrPrefix, ruleType);
+ } else if (ipAddress instanceof Inet6Address) {
+ return new Ip6SubnetFilterRule((Inet6Address) ipAddress, cidrPrefix, ruleType);
+ } else {
+ throw new IllegalArgumentException("Only IPv4 and IPv6 addresses are supported");
+ }
+ }
+
+ @Override
+ public boolean matches(InetSocketAddress ipAndPort) {
+ return filterRule.matches(ipAndPort);
+ }
+
+ @Override
+ public IpFilterRuleType ruleType() {
+ return filterRule.ruleType();
+ }
+
+ private static final class Ip4SubnetFilterRule implements IpFilterRule {
+
+ private final int networkAddress;
+ private final int subnetMask;
+ private final IpFilterRuleType ruleType;
+
+ private Ip4SubnetFilterRule(Inet4Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
+ if (cidrPrefix < 0 || cidrPrefix > 32) {
+ throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of " +
+ "[0,32]. The prefix was: %d", cidrPrefix));
+ }
+
+ subnetMask = prefixToSubnetMask(cidrPrefix);
+ networkAddress = ipToInt(ipAddress) & subnetMask;
+ this.ruleType = ruleType;
+ }
+
+ @Override
+ public boolean matches(InetSocketAddress ipAndPort) {
+ int ipAddress = ipToInt((Inet4Address) ipAndPort.getAddress());
+
+ return (ipAddress & subnetMask) == networkAddress;
+ }
+
+ @Override
+ public IpFilterRuleType ruleType() {
+ return ruleType;
+ }
+
+ private static int ipToInt(Inet4Address ipAddress) {
+ byte[] octets = ipAddress.getAddress();
+ assert octets.length == 4;
+
+ return (octets[0] & 0xff) << 24 |
+ (octets[1] & 0xff) << 16 |
+ (octets[2] & 0xff) << 8 |
+ octets[3] & 0xff;
+ }
+
+ private static int prefixToSubnetMask(int cidrPrefix) {
+ return -1 << 32 - cidrPrefix;
+ }
+ }
+
+ private static final class Ip6SubnetFilterRule implements IpFilterRule {
+
+ private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
+
+ private final BigInteger networkAddress;
+ private final BigInteger subnetMask;
+ private final IpFilterRuleType ruleType;
+
+ private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
+ if (cidrPrefix < 0 || cidrPrefix > 128) {
+ throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of " +
+ "[0,128]. The prefix was: %d", cidrPrefix));
+ }
+
+ subnetMask = prefixToSubnetMask(cidrPrefix);
+ networkAddress = ipToInt(ipAddress).and(subnetMask);
+ this.ruleType = ruleType;
+ }
+
+ @Override
+ public boolean matches(InetSocketAddress ipAndPort) {
+ BigInteger ipAddress = ipToInt((Inet6Address) ipAndPort.getAddress());
+
+ return ipAddress.and(subnetMask).equals(networkAddress);
+ }
+
+ @Override
+ public IpFilterRuleType ruleType() {
+ return ruleType;
+ }
+
+ private static BigInteger ipToInt(Inet6Address ipAddress) {
+ byte[] octets = ipAddress.getAddress();
+ assert octets.length == 16;
+
+ return new BigInteger(octets);
+ }
+
+ private static BigInteger prefixToSubnetMask(int cidrPrefix) {
+ return MINUS_ONE.shiftLeft(128 - cidrPrefix);
+ }
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilterHandler.java b/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilterHandler.java
new file mode 100644
index 0000000000..a6dd4487a4
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilterHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 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 io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.internal.ConcurrentSet;
+import io.netty.channel.Channel;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Set;
+
+/**
+ * This class allows one to ensure that at all times for every IP address there is at most one
+ * {@link Channel} connected to the server.
+ */
+@ChannelHandler.Sharable
+public class UniqueIpFilterHandler extends AbstractIpFilterHandler {
+ private final Set connected = new ConcurrentSet();
+
+ @Override
+ protected boolean accept(InetSocketAddress ipAndPort) throws Exception {
+ InetAddress ipAddress = ipAndPort.getAddress();
+ if (connected.contains(ipAddress)) {
+ return false;
+ } else {
+ connected.add(ipAddress);
+ return true;
+ }
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ InetAddress ipAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress();
+ connected.remove(ipAddress);
+
+ super.channelInactive(ctx);
+ }
+
+ @Override
+ protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) {
+ return null;
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/package-info.java b/handler/src/main/java/io/netty/handler/ipfilter/package-info.java
new file mode 100644
index 0000000000..c97c430f58
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 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 to filter IP addresses (allow/deny).
+ */
+package io.netty.handler.ipfilter;
diff --git a/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java b/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java
new file mode 100644
index 0000000000..8ca53ad326
--- /dev/null
+++ b/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2014 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 io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.embedded.EmbeddedChannel;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+public class IpSubnetFilterTest {
+
+ @Test
+ public void testIp4SubnetFilterRule() throws Exception {
+ IpSubnetFilterRule rule = new IpSubnetFilterRule("192.168.56.1", 24, IpFilterRuleType.ALLOW);
+ for (int i = 0; i <= 255; i++) {
+ Assert.assertTrue(rule.matches(newSockAddress(String.format("192.168.56.%d", i))));
+ }
+ Assert.assertFalse(rule.matches(newSockAddress("192.168.57.1")));
+
+ rule = new IpSubnetFilterRule("91.114.240.1", 23, IpFilterRuleType.ALLOW);
+ Assert.assertTrue(rule.matches(newSockAddress("91.114.240.43")));
+ Assert.assertTrue(rule.matches(newSockAddress("91.114.240.255")));
+ Assert.assertTrue(rule.matches(newSockAddress("91.114.241.193")));
+ Assert.assertTrue(rule.matches(newSockAddress("91.114.241.254")));
+ Assert.assertFalse(rule.matches(newSockAddress("91.115.241.2")));
+ }
+
+ @Test
+ public void testIp6SubnetFilterRule() {
+ IpSubnetFilterRule rule;
+
+ rule = new IpSubnetFilterRule("2001:db8:abcd:0000::", 52, IpFilterRuleType.ALLOW);
+ Assert.assertTrue(rule.matches(newSockAddress("2001:db8:abcd:0000::1")));
+ Assert.assertTrue(rule.matches(newSockAddress("2001:db8:abcd:0fff:ffff:ffff:ffff:ffff")));
+ Assert.assertFalse(rule.matches(newSockAddress("2001:db8:abcd:1000::")));
+ }
+
+ @Test
+ public void testIpFilterRuleHandler() throws Exception {
+ IpFilterRule filter0 = new IpFilterRule() {
+ @Override
+ public boolean matches(InetSocketAddress ipAndPort) {
+ return "192.168.57.1".equals(ipAndPort.getHostName());
+ }
+
+ @Override
+ public IpFilterRuleType ruleType() {
+ return IpFilterRuleType.DENY;
+ }
+ };
+
+ IpFilterRuleHandler denyHandler = new IpFilterRuleHandler(filter0) {
+ private final byte[] message = {1, 2, 3, 4, 5, 6, 7};
+
+ @Override
+ protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) {
+ Assert.assertTrue(ctx.channel().isActive());
+ Assert.assertTrue(ctx.channel().isWritable());
+ Assert.assertEquals("192.168.57.1", ipAndPort.getHostName());
+
+ return ctx.writeAndFlush(Unpooled.wrappedBuffer(message));
+ }
+ };
+ EmbeddedChannel chDeny = newEmbeddedInetChannel("192.168.57.1", denyHandler);
+ ByteBuf out = chDeny.readOutbound();
+ Assert.assertEquals(7, out.readableBytes());
+ for (byte i = 1; i <= 7; i++) {
+ Assert.assertEquals(i, out.readByte());
+ }
+ Assert.assertFalse(chDeny.isActive());
+ Assert.assertFalse(chDeny.isOpen());
+
+ IpFilterRuleHandler allowHandler = new IpFilterRuleHandler(filter0) {
+ @Override
+ protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) {
+ Assert.fail();
+ return null;
+ }
+ };
+ EmbeddedChannel chAllow = newEmbeddedInetChannel("192.168.57.2", allowHandler);
+ Assert.assertTrue(chAllow.isActive());
+ Assert.assertTrue(chAllow.isOpen());
+ }
+
+ @Test
+ public void testUniqueIpFilterHandler() {
+ UniqueIpFilterHandler handler = new UniqueIpFilterHandler();
+
+ EmbeddedChannel ch1 = newEmbeddedInetChannel("91.92.93.1", handler);
+ Assert.assertTrue(ch1.isActive());
+ EmbeddedChannel ch2 = newEmbeddedInetChannel("91.92.93.2", handler);
+ Assert.assertTrue(ch2.isActive());
+ EmbeddedChannel ch3 = newEmbeddedInetChannel("91.92.93.1", handler);
+ Assert.assertFalse(ch3.isActive());
+
+ // false means that no data is left to read/write
+ Assert.assertFalse(ch1.finish());
+
+ EmbeddedChannel ch4 = newEmbeddedInetChannel("91.92.93.1", handler);
+ Assert.assertTrue(ch4.isActive());
+ }
+
+ private static EmbeddedChannel newEmbeddedInetChannel(final String ipAddress, ChannelHandler... handlers) {
+ return new EmbeddedChannel(handlers) {
+ @Override
+ protected SocketAddress remoteAddress0() {
+ return isActive()? new InetSocketAddress(ipAddress, 5421) : null;
+ }
+ };
+ }
+
+ private static InetSocketAddress newSockAddress(String ipAddress) {
+ return new InetSocketAddress(ipAddress, 1234);
+ }
+}