From a0bde17effbedc21d8492e7f539e0824f01867a6 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 19 Aug 2015 16:21:00 +0200 Subject: [PATCH] Backport ipfilter handler Motivation: The ipfilter handler does not exists in 4.0 yet. Modifications: Backport the ipfilter from 4.1 to 4.0. Result: It's possible to use the ipfilter handler in 4.0 as well. --- .../ipfilter/AbstractRemoteAddressFilter.java | 109 +++++++++++ .../netty/handler/ipfilter/IpFilterRule.java | 36 ++++ .../handler/ipfilter/IpFilterRuleType.java | 24 +++ .../handler/ipfilter/IpSubnetFilterRule.java | 169 ++++++++++++++++++ .../handler/ipfilter/RuleBasedIpFilter.java | 60 +++++++ .../handler/ipfilter/UniqueIpFilter.java | 54 ++++++ .../netty/handler/ipfilter/package-info.java | 20 +++ .../handler/ipfilter/IpSubnetFilterTest.java | 143 +++++++++++++++ 8 files changed, 615 insertions(+) create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/AbstractRemoteAddressFilter.java create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/IpFilterRule.java create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleType.java create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/RuleBasedIpFilter.java create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java create mode 100644 handler/src/main/java/io/netty/handler/ipfilter/package-info.java create mode 100644 handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java diff --git a/handler/src/main/java/io/netty/handler/ipfilter/AbstractRemoteAddressFilter.java b/handler/src/main/java/io/netty/handler/ipfilter/AbstractRemoteAddressFilter.java new file mode 100644 index 0000000000..a0a2c1e12d --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ipfilter/AbstractRemoteAddressFilter.java @@ -0,0 +1,109 @@ +/* + * 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 java.net.SocketAddress; + +/** + * 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(ChannelHandlerContext, SocketAddress)} to decided whether you want to accept or reject + * a connection from the remote address. + *

+ * Furthermore overriding {@link #channelRejected(ChannelHandlerContext, SocketAddress)} 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 RuleBasedIpFilter} for details. + */ +public abstract class AbstractRemoteAddressFilter extends ChannelInboundHandlerAdapter { + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + handleNewChannel(ctx); + ctx.fireChannelRegistered(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + if (!handleNewChannel(ctx)) { + throw new IllegalStateException("cannot determine to accept or reject a channel: " + ctx.channel()); + } else { + ctx.fireChannelActive(); + } + } + + private boolean handleNewChannel(ChannelHandlerContext ctx) throws Exception { + @SuppressWarnings("unchecked") + T remoteAddress = (T) ctx.channel().remoteAddress(); + + // If the remote address is not available yet, defer the decision. + if (remoteAddress == null) { + return false; + } + + // No need to keep this handler in the pipeline anymore because the decision is going to be made now. + // Also, this will prevent the subsequent events from being handled by this handler. + ctx.pipeline().remove(this); + + if (accept(ctx, remoteAddress)) { + channelAccepted(ctx, remoteAddress); + } else { + ChannelFuture rejectedFuture = channelRejected(ctx, remoteAddress); + if (rejectedFuture != null) { + rejectedFuture.addListener(ChannelFutureListener.CLOSE); + } else { + ctx.close(); + } + } + + return true; + } + + /** + * 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(ChannelHandlerContext ctx, T remoteAddress) throws Exception; + + /** + * This method is called if {@code remoteAddress} gets accepted by + * {@link #accept(ChannelHandlerContext, SocketAddress)}. You should override it if you would like to handle + * (e.g. respond to) accepted addresses. + */ + @SuppressWarnings("UnusedParameters") + protected void channelAccepted(ChannelHandlerContext ctx, T remoteAddress) { } + + /** + * This method is called if {@code remoteAddress} gets rejected by + * {@link #accept(ChannelHandlerContext, SocketAddress)}. 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. + */ + @SuppressWarnings("UnusedParameters") + protected ChannelFuture channelRejected(ChannelHandlerContext ctx, T remoteAddress) { + return null; + } +} 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..c8f39fa54c --- /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 remoteAddress is valid according to your criteria. False otherwise. + */ + boolean matches(InetSocketAddress remoteAddress); + + /** + * @return This method should return {@link IpFilterRuleType#Accept} 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#Reject} should be returned. + */ + IpFilterRuleType ruleType(); +} 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..a3a9588c2f --- /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 { + Accept, + Reject +} 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..6b5c8f4b38 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java @@ -0,0 +1,169 @@ +/* + * 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 RuleBasedIpFilter} 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 remoteAddress) { + return filterRule.matches(remoteAddress); + } + + @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 remoteAddress) { + int ipAddress = ipToInt((Inet4Address) remoteAddress.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) { + /** + * Perform the shift on a long and downcast it to int afterwards. + * This is necessary to handle a cidrPrefix of zero correctly. + * The left shift operator on an int only uses the five least + * significant bits of the right-hand operand. Thus -1 << 32 evaluates + * to -1 instead of 0. The left shift operator applied on a long + * uses the six least significant bits. + * + * Also see https://github.com/netty/netty/issues/2767 + */ + return (int) ((-1L << 32 - cidrPrefix) & 0xffffffff); + } + } + + 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 remoteAddress) { + BigInteger ipAddress = ipToInt((Inet6Address) remoteAddress.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/RuleBasedIpFilter.java b/handler/src/main/java/io/netty/handler/ipfilter/RuleBasedIpFilter.java new file mode 100644 index 0000000000..e8473c5db2 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ipfilter/RuleBasedIpFilter.java @@ -0,0 +1,60 @@ +/* + * 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.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * 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 #channelRejected(ChannelHandlerContext, SocketAddress)}. + */ +@Sharable +public class RuleBasedIpFilter extends AbstractRemoteAddressFilter { + + private final IpFilterRule[] rules; + + public RuleBasedIpFilter(IpFilterRule... rules) { + if (rules == null) { + throw new NullPointerException("rules"); + } + + this.rules = rules; + } + + @Override + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { + for (IpFilterRule rule : rules) { + if (rule == null) { + break; + } + + if (rule.matches(remoteAddress)) { + return rule.ruleType() == IpFilterRuleType.Accept; + } + } + + return true; + } +} diff --git a/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java b/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java new file mode 100644 index 0000000000..ac346481d7 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java @@ -0,0 +1,54 @@ +/* + * 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.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.internal.ConcurrentSet; + +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 UniqueIpFilter extends AbstractRemoteAddressFilter { + + private final Set connected = new ConcurrentSet(); + + @Override + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { + final InetAddress remoteIp = remoteAddress.getAddress(); + if (connected.contains(remoteIp)) { + return false; + } else { + connected.add(remoteIp); + ctx.channel().closeFuture().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + connected.remove(remoteIp); + } + }); + return true; + } + } +} 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..7394855b43 --- /dev/null +++ b/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java @@ -0,0 +1,143 @@ +/* + * 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 testIpv4DefaultRoute() { + IpSubnetFilterRule rule = new IpSubnetFilterRule("0.0.0.0", 0, IpFilterRuleType.Accept); + Assert.assertTrue(rule.matches(newSockAddress("91.114.240.43"))); + Assert.assertTrue(rule.matches(newSockAddress("10.0.0.3"))); + Assert.assertTrue(rule.matches(newSockAddress("192.168.93.2"))); + } + + @Test + public void testIp4SubnetFilterRule() throws Exception { + IpSubnetFilterRule rule = new IpSubnetFilterRule("192.168.56.1", 24, IpFilterRuleType.Accept); + 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.Accept); + 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.Accept); + 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 remoteAddress) { + return "192.168.57.1".equals(remoteAddress.getHostName()); + } + + @Override + public IpFilterRuleType ruleType() { + return IpFilterRuleType.Reject; + } + }; + + RuleBasedIpFilter denyHandler = new RuleBasedIpFilter(filter0) { + private final byte[] message = {1, 2, 3, 4, 5, 6, 7}; + + @Override + protected ChannelFuture channelRejected(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) { + Assert.assertTrue(ctx.channel().isActive()); + Assert.assertTrue(ctx.channel().isWritable()); + Assert.assertEquals("192.168.57.1", remoteAddress.getHostName()); + + return ctx.writeAndFlush(Unpooled.wrappedBuffer(message)); + } + }; + EmbeddedChannel chDeny = newEmbeddedInetChannel("192.168.57.1", denyHandler); + ByteBuf out = (ByteBuf) 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()); + + RuleBasedIpFilter allowHandler = new RuleBasedIpFilter(filter0) { + @Override + protected ChannelFuture channelRejected(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) { + Assert.fail(); + return null; + } + }; + EmbeddedChannel chAllow = newEmbeddedInetChannel("192.168.57.2", allowHandler); + Assert.assertTrue(chAllow.isActive()); + Assert.assertTrue(chAllow.isOpen()); + } + + @Test + public void testUniqueIpFilterHandler() { + UniqueIpFilter handler = new UniqueIpFilter(); + + 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); + } +}