diff --git a/handler/src/main/java/io/netty/handler/ipfilter/AbstractIpFilterHandler.java b/handler/src/main/java/io/netty/handler/ipfilter/AbstractIpFilterHandler.java deleted file mode 100644 index 8ad7eb2c1b..0000000000 --- a/handler/src/main/java/io/netty/handler/ipfilter/AbstractIpFilterHandler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.ChannelHandlerAdapter; -import io.netty.channel.ChannelHandlerContext; -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 ChannelHandlerAdapter { - 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/AbstractRemoteAddressFilter.java b/handler/src/main/java/io/netty/handler/ipfilter/AbstractRemoteAddressFilter.java new file mode 100644 index 0000000000..a3523a7b0f --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ipfilter/AbstractRemoteAddressFilter.java @@ -0,0 +1,114 @@ +/* + * 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.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; + +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 ChannelHandlerAdapter { + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + try { + handleNewChannel(ctx); + } finally { + ctx.fireChannelRegistered(); + } + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + try { + if (!handleNewChannel(ctx)) { + throw new IllegalStateException("cannot determine to accept or reject a channel: " + ctx.channel()); + } + } finally { + 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/IpFilterDecision.java b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterDecision.java deleted file mode 100644 index 910e750c08..0000000000 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterDecision.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; - -/** - * This enum is used in {@link AbstractIpFilterHandler} to keep the state between the calls to - * {@link ChannelHandler#channelRegistered(ChannelHandlerContext)} and - * {@link ChannelHandler#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 index 8a5759ef23..2883734a20 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRule.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRule.java @@ -22,15 +22,15 @@ import java.net.InetSocketAddress; */ public interface IpFilterRule { /** - * @return This method should return true if ipAndPort is valid according to your criteria. False otherwise. + * @return This method should return true if remoteAddress is valid according to your criteria. False otherwise. */ - boolean matches(InetSocketAddress ipAndPort); + boolean matches(InetSocketAddress remoteAddress); /** - * @return This method should return {@link IpFilterRuleType#ALLOW} if all + * @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#DENY} should be returned. + * {@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 index 9d78130388..7c2deedc9a 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleType.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleType.java @@ -19,6 +19,6 @@ 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 + 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 index 2eebfdcce7..2e6545012b 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java @@ -23,10 +23,11 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; /** - * Use this class to create rules for {@link IpFilterRuleHandler} that group IP addresses into subnets. + * 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) { @@ -60,8 +61,8 @@ public final class IpSubnetFilterRule implements IpFilterRule { } @Override - public boolean matches(InetSocketAddress ipAndPort) { - return filterRule.matches(ipAndPort); + public boolean matches(InetSocketAddress remoteAddress) { + return filterRule.matches(remoteAddress); } @Override @@ -87,8 +88,8 @@ public final class IpSubnetFilterRule implements IpFilterRule { } @Override - public boolean matches(InetSocketAddress ipAndPort) { - int ipAddress = ipToInt((Inet4Address) ipAndPort.getAddress()); + public boolean matches(InetSocketAddress remoteAddress) { + int ipAddress = ipToInt((Inet4Address) remoteAddress.getAddress()); return (ipAddress & subnetMask) == networkAddress; } @@ -133,8 +134,8 @@ public final class IpSubnetFilterRule implements IpFilterRule { } @Override - public boolean matches(InetSocketAddress ipAndPort) { - BigInteger ipAddress = ipToInt((Inet6Address) ipAndPort.getAddress()); + public boolean matches(InetSocketAddress remoteAddress) { + BigInteger ipAddress = ipToInt((Inet6Address) remoteAddress.getAddress()); return ipAddress.and(subnetMask).equals(networkAddress); } diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleHandler.java b/handler/src/main/java/io/netty/handler/ipfilter/RuleBasedIpFilter.java similarity index 67% rename from handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleHandler.java rename to handler/src/main/java/io/netty/handler/ipfilter/RuleBasedIpFilter.java index ded2bcf6f5..66ffa4545f 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleHandler.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/RuleBasedIpFilter.java @@ -15,11 +15,12 @@ */ package io.netty.handler.ipfilter; -import io.netty.channel.ChannelFuture; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.Channel; + import java.net.InetSocketAddress; +import java.net.SocketAddress; /** * This class allows one to filter new {@link Channel}s based on the @@ -27,36 +28,33 @@ import java.net.InetSocketAddress; * will be accepted. * * If you would like to explicitly take action on rejected {@link Channel}s, you should override - * {@link #rejected(ChannelHandlerContext, InetSocketAddress)}. + * {@link #channelRejected(ChannelHandlerContext, SocketAddress)}. */ @Sharable -public class IpFilterRuleHandler extends AbstractIpFilterHandler { +public class RuleBasedIpFilter extends AbstractRemoteAddressFilter { + private final IpFilterRule[] rules; - public IpFilterRuleHandler(IpFilterRule... rules) { + public RuleBasedIpFilter(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 { + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { for (IpFilterRule rule : rules) { - if (rule.matches(ipAndPort)) { - return rule.ruleType() == IpFilterRuleType.ALLOW; + if (rule == null) { + break; + } + + if (rule.matches(remoteAddress)) { + return rule.ruleType() == IpFilterRuleType.ACCEPT; } } + return true; } - - @Override - protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) { - return null; - } } diff --git a/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilterHandler.java b/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java similarity index 65% rename from handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilterHandler.java rename to handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java index a6dd4487a4..ac346481d7 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilterHandler.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/UniqueIpFilter.java @@ -15,11 +15,12 @@ */ 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 io.netty.channel.Channel; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -30,30 +31,24 @@ import java.util.Set; * {@link Channel} connected to the server. */ @ChannelHandler.Sharable -public class UniqueIpFilterHandler extends AbstractIpFilterHandler { +public class UniqueIpFilter extends AbstractRemoteAddressFilter { + private final Set connected = new ConcurrentSet(); @Override - protected boolean accept(InetSocketAddress ipAndPort) throws Exception { - InetAddress ipAddress = ipAndPort.getAddress(); - if (connected.contains(ipAddress)) { + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { + final InetAddress remoteIp = remoteAddress.getAddress(); + if (connected.contains(remoteIp)) { return false; } else { - connected.add(ipAddress); + connected.add(remoteIp); + ctx.channel().closeFuture().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + connected.remove(remoteIp); + } + }); 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/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java b/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java index 8ca53ad326..6c3caf6ce1 100644 --- a/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java +++ b/handler/src/test/java/io/netty/handler/ipfilter/IpSubnetFilterTest.java @@ -31,13 +31,13 @@ public class IpSubnetFilterTest { @Test public void testIp4SubnetFilterRule() throws Exception { - IpSubnetFilterRule rule = new IpSubnetFilterRule("192.168.56.1", 24, IpFilterRuleType.ALLOW); + 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.ALLOW); + 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"))); @@ -49,7 +49,7 @@ public class IpSubnetFilterTest { public void testIp6SubnetFilterRule() { IpSubnetFilterRule rule; - rule = new IpSubnetFilterRule("2001:db8:abcd:0000::", 52, IpFilterRuleType.ALLOW); + 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::"))); @@ -59,24 +59,24 @@ public class IpSubnetFilterTest { public void testIpFilterRuleHandler() throws Exception { IpFilterRule filter0 = new IpFilterRule() { @Override - public boolean matches(InetSocketAddress ipAndPort) { - return "192.168.57.1".equals(ipAndPort.getHostName()); + public boolean matches(InetSocketAddress remoteAddress) { + return "192.168.57.1".equals(remoteAddress.getHostName()); } @Override public IpFilterRuleType ruleType() { - return IpFilterRuleType.DENY; + return IpFilterRuleType.REJECT; } }; - IpFilterRuleHandler denyHandler = new IpFilterRuleHandler(filter0) { + RuleBasedIpFilter denyHandler = new RuleBasedIpFilter(filter0) { private final byte[] message = {1, 2, 3, 4, 5, 6, 7}; @Override - protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) { + protected ChannelFuture channelRejected(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) { Assert.assertTrue(ctx.channel().isActive()); Assert.assertTrue(ctx.channel().isWritable()); - Assert.assertEquals("192.168.57.1", ipAndPort.getHostName()); + Assert.assertEquals("192.168.57.1", remoteAddress.getHostName()); return ctx.writeAndFlush(Unpooled.wrappedBuffer(message)); } @@ -90,9 +90,9 @@ public class IpSubnetFilterTest { Assert.assertFalse(chDeny.isActive()); Assert.assertFalse(chDeny.isOpen()); - IpFilterRuleHandler allowHandler = new IpFilterRuleHandler(filter0) { + RuleBasedIpFilter allowHandler = new RuleBasedIpFilter(filter0) { @Override - protected ChannelFuture rejected(ChannelHandlerContext ctx, InetSocketAddress ipAndPort) { + protected ChannelFuture channelRejected(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) { Assert.fail(); return null; } @@ -104,7 +104,7 @@ public class IpSubnetFilterTest { @Test public void testUniqueIpFilterHandler() { - UniqueIpFilterHandler handler = new UniqueIpFilterHandler(); + UniqueIpFilter handler = new UniqueIpFilter(); EmbeddedChannel ch1 = newEmbeddedInetChannel("91.92.93.1", handler); Assert.assertTrue(ch1.isActive());