diff --git a/DotNetty.sln.DotSettings b/DotNetty.sln.DotSettings
index f023462bc..b406f68dc 100644
--- a/DotNetty.sln.DotSettings
+++ b/DotNetty.sln.DotSettings
@@ -74,6 +74,7 @@
Copyright (c) Microsoft. All rights reserved.
Licensed under the MIT license. See LICENSE file in the project root for full license information.
GC
+ IP
$object$_On$event$
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj
index 76862a01d..fbcb452d5 100644
--- a/src/DotNetty.Common/DotNetty.Common.csproj
+++ b/src/DotNetty.Common/DotNetty.Common.csproj
@@ -35,6 +35,7 @@
+
diff --git a/src/DotNetty.Common/Internal/SocketUtils.cs b/src/DotNetty.Common/Internal/SocketUtils.cs
new file mode 100644
index 000000000..d4b34a98b
--- /dev/null
+++ b/src/DotNetty.Common/Internal/SocketUtils.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Common.Internal
+{
+ using System.Net;
+ using System.Net.Sockets;
+
+ public class SocketUtils
+ {
+ public static IPAddress AddressByName(string hostname)
+ {
+ if (string.IsNullOrEmpty(hostname))
+ {
+ bool isIPv6Supported = Socket.OSSupportsIPv6;
+ if (isIPv6Supported)
+ {
+ return IPAddress.IPv6Loopback;
+ }
+ else
+ {
+ return IPAddress.Loopback;
+ }
+ }
+ if (hostname == "0.0.0.0")
+ {
+ return IPAddress.Any;
+ }
+ if (hostname == "::0" || hostname == "::")
+ {
+ return IPAddress.IPv6Any;
+ }
+ if (IPAddress.TryParse(hostname, out IPAddress parseResult))
+ {
+ return parseResult;
+ }
+ IPHostEntry hostEntry = Dns.GetHostEntryAsync(hostname).Result;
+ return hostEntry.AddressList[0];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Handlers/IPFilter/AbstractRemoteAddressFilter.cs b/src/DotNetty.Handlers/IPFilter/AbstractRemoteAddressFilter.cs
new file mode 100644
index 000000000..2661f5d91
--- /dev/null
+++ b/src/DotNetty.Handlers/IPFilter/AbstractRemoteAddressFilter.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+namespace DotNetty.Handlers.IPFilter
+{
+ using System;
+ using System.Net;
+ using System.Threading.Tasks;
+ using DotNetty.Transport.Channels;
+
+ ///
+ /// This class provides the functionality to either accept or reject new 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 to decided whether you want to accept or reject
+ /// a connection from the remote address.
+ /// Furthermore overriding 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 for details.
+ ///
+ public abstract class AbstractRemoteAddressFilter: ChannelHandlerAdapter where T:EndPoint
+ {
+ public override void ChannelRegistered(IChannelHandlerContext ctx)
+ {
+ this.HandleNewChannel(ctx);
+ ctx.FireChannelRegistered();
+ }
+
+ public override void ChannelActive(IChannelHandlerContext ctx)
+ {
+ if (!this.HandleNewChannel(ctx))
+ {
+ throw new ArgumentException(nameof(ctx),"cannot determine to accept or reject a channel: " + ctx.Channel);
+ }
+ else
+ {
+ ctx.FireChannelActive();
+ }
+ }
+
+ bool HandleNewChannel(IChannelHandlerContext ctx)
+ {
+ var 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 (this.Accept(ctx, remoteAddress))
+ {
+ this.ChannelAccepted(ctx, remoteAddress);
+ }
+ else
+ {
+ Task rejectedTask = this.ChannelRejected(ctx, remoteAddress);
+ if (rejectedTask != null)
+ {
+ rejectedTask.ContinueWith(_ =>
+ {
+ ctx.CloseAsync();
+ });
+ }
+ else
+ {
+ ctx.CloseAsync();
+ }
+ }
+ return true;
+ }
+
+ ///
+ /// This method is called immediately after a gets registered.
+ ///
+ /// Return true if connections from this IP address and port should be accepted. False otherwise.
+ protected abstract bool Accept(IChannelHandlerContext ctx, T remoteAddress);
+
+ ///
+ /// This method is called if gets accepted by
+ /// . You should override it if you would like to handle
+ /// (e.g. respond to) accepted addresses.
+ ///
+ protected virtual void ChannelAccepted(IChannelHandlerContext ctx, T remoteAddress) { }
+
+
+ ///
+ /// This method is called if gets rejected by
+ /// . You should override it if you would like to handle
+ /// (e.g. respond to) rejected addresses.
+ ///
+ /// A if you perform I/O operations, so that
+ /// the can be closed once it completes. Null otherwise.
+ ///
+ ///
+ protected virtual Task ChannelRejected(IChannelHandlerContext ctx, T remoteAddress)
+ {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Handlers/IPFilter/IIPFilterRule.cs b/src/DotNetty.Handlers/IPFilter/IIPFilterRule.cs
new file mode 100644
index 000000000..85ce2aaba
--- /dev/null
+++ b/src/DotNetty.Handlers/IPFilter/IIPFilterRule.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+namespace DotNetty.Handlers.IPFilter
+{
+ using System.Net;
+
+ ///
+ /// Implement this interface to create new rules.
+ ///
+ public interface IIPFilterRule
+ {
+ ///
+ /// This method should return true if remoteAddress is valid according to your criteria. False otherwise.
+ ///
+ bool Matches(IPEndPoint remoteAddress);
+
+ ///
+ /// This method should return if all
+ /// for which
+ /// returns true should the accepted. If you want to exclude all of those IP addresses then
+ /// should be returned.
+ ///
+ IPFilterRuleType RuleType { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Handlers/IPFilter/IPFilterRuleType.cs b/src/DotNetty.Handlers/IPFilter/IPFilterRuleType.cs
new file mode 100644
index 000000000..1edba8d02
--- /dev/null
+++ b/src/DotNetty.Handlers/IPFilter/IPFilterRuleType.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Handlers.IPFilter
+{
+ ///
+ /// Used in to decide if a matching IP Address should be allowed or denied to connect.
+ ///
+ public enum IPFilterRuleType
+ {
+ Accept,
+ Reject
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Handlers/IPFilter/IPSubnetFilterRule.cs b/src/DotNetty.Handlers/IPFilter/IPSubnetFilterRule.cs
new file mode 100644
index 000000000..5744711d4
--- /dev/null
+++ b/src/DotNetty.Handlers/IPFilter/IPSubnetFilterRule.cs
@@ -0,0 +1,198 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Handlers.IPFilter
+{
+ using System;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Sockets;
+ using System.Numerics;
+ using DotNetty.Common.Internal;
+
+ ///
+ /// Use this class to create rules for that group IP addresses into subnets.
+ /// Supports both, IPv4 and IPv6.
+ ///
+ public class IPSubnetFilterRule : IIPFilterRule
+ {
+ readonly IIPFilterRule filterRule;
+
+ public IPSubnetFilterRule(string ipAddress, int cidrPrefix, IPFilterRuleType ruleType)
+ {
+ this.filterRule = SelectFilterRule(SocketUtils.AddressByName(ipAddress), cidrPrefix, ruleType);
+ }
+
+ public IPSubnetFilterRule(IPAddress ipAddress, int cidrPrefix, IPFilterRuleType ruleType)
+ {
+ this.filterRule = SelectFilterRule(ipAddress, cidrPrefix, ruleType);
+ }
+
+ public IPFilterRuleType RuleType => this.filterRule.RuleType;
+
+ public bool Matches(IPEndPoint remoteAddress)
+ {
+ return this.filterRule.Matches(remoteAddress);
+ }
+
+ static IIPFilterRule SelectFilterRule(IPAddress ipAddress, int cidrPrefix, IPFilterRuleType ruleType)
+ {
+ if (ipAddress == null)
+ {
+ throw new ArgumentNullException(nameof(ipAddress));
+ }
+
+ if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
+ {
+ return new IP4SubnetFilterRule(ipAddress, cidrPrefix, ruleType);
+ }
+ else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ return new IP6SubnetFilterRule(ipAddress, cidrPrefix, ruleType);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(ipAddress), "Only IPv4 and IPv6 addresses are supported");
+ }
+ }
+
+ private class IP4SubnetFilterRule : IIPFilterRule
+ {
+ readonly int networkAddress;
+ readonly int subnetMask;
+
+ public IP4SubnetFilterRule(IPAddress ipAddress, int cidrPrefix, IPFilterRuleType ruleType)
+ {
+ if (cidrPrefix < 0 || cidrPrefix > 32)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(cidrPrefix),
+ string.Format(
+ "IPv4 requires the subnet prefix to be in range of " +
+ "[0,32]. The prefix was: {0}",
+ cidrPrefix));
+ }
+
+ this.subnetMask = PrefixToSubnetMask(cidrPrefix);
+ this.networkAddress = GetNetworkAddress(ipAddress, this.subnetMask);
+ this.RuleType = ruleType;
+ }
+
+ public IPFilterRuleType RuleType { get; }
+
+ public bool Matches(IPEndPoint remoteAddress)
+ {
+ if (remoteAddress.AddressFamily == AddressFamily.InterNetwork)
+ {
+ return GetNetworkAddress(remoteAddress.Address, this.subnetMask) == this.networkAddress;
+ }
+ return false;
+ }
+
+ static int GetNetworkAddress(IPAddress ipAddress, int subnetMask)
+ {
+ return IpToInt(ipAddress) & subnetMask;
+ }
+
+ 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);
+ }
+
+ static int IpToInt(IPAddress ipAddress)
+ {
+ byte[] octets = ipAddress.GetAddressBytes();
+ if (octets.Length != 4)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ipAddress), "Octets count must be equal 4 for IPv4 address.");
+ }
+
+ return (octets[0] & 0xff) << 24 |
+ (octets[1] & 0xff) << 16 |
+ (octets[2] & 0xff) << 8 |
+ octets[3] & 0xff;
+ }
+ }
+
+ private class IP6SubnetFilterRule : IIPFilterRule
+ {
+ readonly BigInteger networkAddress;
+ readonly BigInteger subnetMask;
+
+ public IP6SubnetFilterRule(IPAddress ipAddress, int cidrPrefix, IPFilterRuleType ruleType)
+ {
+ if (cidrPrefix < 0 || cidrPrefix > 128)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(cidrPrefix),
+ string.Format(
+ "IPv6 requires the subnet prefix to be in range of " +
+ "[0,128]. The prefix was: {0}",
+ cidrPrefix));
+ }
+
+ this.subnetMask = CidrToSubnetMask((byte)cidrPrefix);
+ this.networkAddress = GetNetworkAddress(ipAddress, this.subnetMask);
+ this.RuleType = ruleType;
+ }
+
+ public IPFilterRuleType RuleType { get; }
+
+ public bool Matches(IPEndPoint remoteAddress)
+ {
+ if (remoteAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ return this.networkAddress == GetNetworkAddress(remoteAddress.Address, this.subnetMask);
+ }
+ return false;
+ }
+
+ static BigInteger CidrToSubnetMask(byte cidr)
+ {
+ var mask = new BigInteger(
+ new byte[]
+ {
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x00
+ });
+
+
+ BigInteger masked = cidr == 0 ? 0 : mask << (128 - cidr);
+ byte[] m = masked.ToByteArray();
+ var bmask = new byte[16];
+ int copy = m.Length > 16 ? 16 : m.Length;
+ Array.Copy(m, 0, bmask, 0, copy);
+ byte[] resBytes = bmask.Reverse().ToArray();
+ return new BigInteger(resBytes);
+ }
+
+ static BigInteger IpToInt(IPAddress ipAddress)
+ {
+ byte[] octets = ipAddress.GetAddressBytes();
+ if (octets.Length != 16)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ipAddress), "Octets count must be equal 16 for IPv6 address.");
+ }
+ return new BigInteger(octets);
+ }
+
+ static BigInteger GetNetworkAddress(IPAddress ipAddress, BigInteger subnetMask)
+ {
+ return IpToInt(ipAddress) & subnetMask;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Handlers/IPFilter/RuleBasedIPFilter.cs b/src/DotNetty.Handlers/IPFilter/RuleBasedIPFilter.cs
new file mode 100644
index 000000000..409519729
--- /dev/null
+++ b/src/DotNetty.Handlers/IPFilter/RuleBasedIPFilter.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+namespace DotNetty.Handlers.IPFilter
+{
+ using System;
+ using System.Net;
+ using DotNetty.Transport.Channels;
+
+ ///
+ /// This class allows one to filter new s based on the
+ /// 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 s, you should override
+ /// .
+ ///
+ public class RuleBasedIPFilter : AbstractRemoteAddressFilter
+ {
+ readonly IIPFilterRule[] rules;
+
+ public RuleBasedIPFilter(params IIPFilterRule[] rules)
+ {
+ this.rules = rules ?? throw new ArgumentNullException(nameof(rules));
+ }
+
+ protected override bool Accept(IChannelHandlerContext ctx, IPEndPoint remoteAddress)
+ {
+ foreach (IIPFilterRule rule in this.rules) {
+ if (rule == null) {
+ break;
+ }
+ if (rule.Matches(remoteAddress)) {
+ return rule.RuleType == IPFilterRuleType.Accept;
+ }
+ }
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Handlers/IPFilter/UniqueIPFilter.cs b/src/DotNetty.Handlers/IPFilter/UniqueIPFilter.cs
new file mode 100644
index 000000000..042e6ad10
--- /dev/null
+++ b/src/DotNetty.Handlers/IPFilter/UniqueIPFilter.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+namespace DotNetty.Handlers.IPFilter
+{
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.Net;
+ using DotNetty.Transport.Channels;
+
+ ///
+ /// This class allows one to ensure that at all times for every IP address there is at most one
+ /// connected to the server.
+ ///
+ public class UniqueIPFilter : AbstractRemoteAddressFilter
+ {
+ const byte Filler = 0;
+ //using dictionary as set. value always equals Filler.
+ readonly IDictionary connected = new ConcurrentDictionary();
+
+ protected override bool Accept(IChannelHandlerContext ctx, IPEndPoint remoteAddress)
+ {
+ IPAddress remoteIp = remoteAddress.Address;
+ if (this.connected.ContainsKey(remoteIp))
+ {
+ return false;
+ }
+ else
+ {
+ this.connected.Add(remoteIp, Filler);
+ ctx.Channel.CloseCompletion.ContinueWith(_ =>
+ {
+ this.connected.Remove(remoteIp);
+ });
+ }
+ return true;
+ }
+
+ public override bool IsSharable => true;
+ }
+}
\ No newline at end of file
diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs
index 6401d5c1f..ef548389d 100644
--- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs
+++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs
@@ -208,6 +208,8 @@ protected AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, IEventE
public IChannel Channel => this.pipeline.Channel;
+ public IChannelPipeline Pipeline => this.pipeline;
+
public IByteBufferAllocator Allocator => this.Channel.Allocator;
public abstract IChannelHandler Handler { get; }
diff --git a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs
index 77433b364..31695aff2 100644
--- a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs
+++ b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs
@@ -70,6 +70,11 @@ public interface IChannelHandlerContext : IAttributeMap
Task WriteAsync(object message); // todo: optimize: add flag saying if handler is interested in task, do not produce task if it isn't needed
IChannelHandlerContext Flush();
+
+ ///
+ /// Return the assigned
+ ///
+ IChannelPipeline Pipeline { get; }
Task WriteAndFlushAsync(object message);
diff --git a/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs b/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs
new file mode 100644
index 000000000..04c0e7585
--- /dev/null
+++ b/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs
@@ -0,0 +1,246 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Handlers.Tests
+{
+ using System;
+ using System.Net;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using DotNetty.Buffers;
+ using DotNetty.Handlers.IPFilter;
+ using DotNetty.Tests.Common;
+ using DotNetty.Transport.Channels;
+ using DotNetty.Transport.Channels.Embedded;
+ using Xunit;
+ using Xunit.Abstractions;
+
+ public class IPSubnetFilterTest : TestBase
+ {
+ public IPSubnetFilterTest(ITestOutputHelper output)
+ : base(output)
+ {
+ }
+
+ [Fact]
+ public void TestIpv4DefaultRoute()
+ {
+ var rule = new IPSubnetFilterRule("0.0.0.0", 0, IPFilterRuleType.Accept);
+ Assert.True(rule.Matches(CreateIPEndPoint("91.114.240.43")));
+ Assert.True(rule.Matches(CreateIPEndPoint("10.0.0.3")));
+ Assert.True(rule.Matches(CreateIPEndPoint("192.168.93.2")));
+ }
+
+ [Fact]
+ public void TestIpv4SubnetMaskCorrectlyHandlesIpv6()
+ {
+ var rule = new IPSubnetFilterRule("0.0.0.0", 0, IPFilterRuleType.Accept);
+ Assert.False(rule.Matches(CreateIPEndPoint("2001:db8:abcd:0000::1")));
+ }
+
+ [Fact]
+ public void TestIpv6SubnetMaskCorrectlyHandlesIpv4()
+ {
+ var rule = new IPSubnetFilterRule("::", 0, IPFilterRuleType.Accept);
+ Assert.False(rule.Matches(CreateIPEndPoint("91.114.240.43")));
+ }
+
+ [Fact]
+ public void TestIp4SubnetFilterRule()
+ {
+ var rule = new IPSubnetFilterRule("192.168.56.1", 24, IPFilterRuleType.Accept);
+ for (int i = 0; i <= 255; i++)
+ {
+ Assert.True(rule.Matches(CreateIPEndPoint(string.Format("192.168.56.{0}", i))));
+ }
+ Assert.False(rule.Matches(CreateIPEndPoint("192.168.57.1")));
+
+ rule = new IPSubnetFilterRule("91.114.240.1", 23, IPFilterRuleType.Accept);
+ Assert.True(rule.Matches(CreateIPEndPoint("91.114.240.43")));
+ Assert.True(rule.Matches(CreateIPEndPoint("91.114.240.255")));
+ Assert.True(rule.Matches(CreateIPEndPoint("91.114.241.193")));
+ Assert.True(rule.Matches(CreateIPEndPoint("91.114.241.254")));
+ Assert.False(rule.Matches(CreateIPEndPoint("91.115.241.2")));
+ }
+
+ [Fact]
+ public void TestIp6SubnetFilterRule()
+ {
+ var rule = new IPSubnetFilterRule("2001:db8:abcd:0000::", 52, IPFilterRuleType.Accept);
+ Assert.True(rule.RuleType == IPFilterRuleType.Accept);
+ Assert.True(rule.Matches(CreateIPEndPoint("2001:db8:abcd:0000::1")));
+ Assert.True(rule.Matches(CreateIPEndPoint("2001:db8:abcd:0fff:ffff:ffff:ffff:ffff")));
+ Assert.False(rule.Matches(CreateIPEndPoint("2001:db8:abcd:1000::")));
+
+
+ rule = new IPSubnetFilterRule("2001:db8:1234:c000::", 50, IPFilterRuleType.Reject);
+ Assert.True(rule.RuleType == IPFilterRuleType.Reject);
+ Assert.True(rule.Matches(CreateIPEndPoint("2001:db8:1234:c000::")));
+ Assert.True(rule.Matches(CreateIPEndPoint("2001:db8:1234:ffff:ffff:ffff:1111:ffff")));
+ Assert.True(rule.Matches(CreateIPEndPoint("2001:db8:1234:ffff:ffff:ffff:ffff:ffff")));
+ Assert.False(rule.Matches(CreateIPEndPoint("2001:db8:1234:bfff:ffff:ffff:ffff:ffff")));
+ Assert.False(rule.Matches(CreateIPEndPoint("2001:db8:1234:8000::")));
+ Assert.False(rule.Matches(CreateIPEndPoint("2001:db7:1234:c000::")));
+ }
+
+ [Fact]
+ public void TestIPFilterRuleHandler()
+ {
+
+ IIPFilterRule filter0 = new TestIPFilterRule(TestIPFilterRuleHandlerConstants.IP1);
+ RuleBasedIPFilter denyHandler = new TestDenyFilter(TestIPFilterRuleHandlerConstants.IP1, filter0);
+ EmbeddedChannel chDeny = new TestIpFilterRuleHandlerChannel1(denyHandler);
+ var output = chDeny.ReadOutbound();
+ Assert.Equal(7, output.ReadableBytes);
+ for (byte i = 1; i <= 7; i++)
+ {
+ Assert.Equal(i, output.ReadByte());
+ }
+ //waiting finish of ContinueWith for chDeny.ChannelRejected
+ Thread.Sleep(300);
+ Assert.False(chDeny.Active);
+ Assert.False(chDeny.Open);
+ RuleBasedIPFilter allowHandler = new TestAllowFilter(filter0);
+ EmbeddedChannel chAllow = new TestIpFilterRuleHandlerChannel2(allowHandler);
+ Assert.True(chAllow.Active);
+ Assert.True(chAllow.Open);
+ }
+
+ [Fact]
+ public void TestUniqueIPFilterHandler() {
+ var handler = new UniqueIPFilter();
+
+ EmbeddedChannel ch1 = new TestUniqueIPFilterHandlerChannel1(handler);
+ Assert.True(ch1.Active);
+ EmbeddedChannel ch2 = new TestUniqueIPFilterHandlerChannel2(handler);
+ Assert.True(ch2.Active);
+ EmbeddedChannel ch3 =new TestUniqueIPFilterHandlerChannel1( handler);
+ Assert.False(ch3.Active);
+
+ // false means that no data is left to read/write
+ Assert.False(ch1.Finish());
+
+ //waiting finish of ContinueWith for ch1.CloseCompletion
+ Thread.Sleep(300);
+
+ EmbeddedChannel ch4 = new TestUniqueIPFilterHandlerChannel1(handler);
+ Assert.True(ch4.Active);
+ }
+
+ #region private
+
+ private static class TestIPFilterRuleHandlerConstants
+ {
+ public const string IP1 = "192.168.57.1";
+ public const string IP2 = "192.168.57.2";
+ }
+
+ private static class TestUniqueIPFilterHandlerConstants
+ {
+ public const string IP1 = "91.92.93.1";
+ public const string IP2 = "91.92.93.2";
+ }
+
+
+ private class TestIPFilterRule : IIPFilterRule
+ {
+ readonly string ip;
+
+ public TestIPFilterRule(string ip)
+ {
+ this.ip = ip;
+ }
+
+ public bool Matches(IPEndPoint remoteAddress)
+ {
+ return this.ip.Equals(remoteAddress.Address.ToString());
+ }
+
+ public IPFilterRuleType RuleType => IPFilterRuleType.Reject;
+ }
+
+ private class TestDenyFilter : RuleBasedIPFilter
+ {
+ readonly string ip;
+ readonly byte[] message = { 1, 2, 3, 4, 5, 6, 7 };
+
+ public TestDenyFilter(string ip, IIPFilterRule rule)
+ : base(rule)
+ {
+ this.ip = ip;
+ }
+
+ protected override Task ChannelRejected(IChannelHandlerContext ctx, IPEndPoint remoteAddress)
+ {
+ Assert.True(ctx.Channel.Active);
+ Assert.True(ctx.Channel.IsWritable);
+ Assert.Equal(this.ip, remoteAddress.Address.ToString());
+ return ctx.WriteAndFlushAsync(Unpooled.WrappedBuffer(this.message));
+ }
+ }
+
+ private class TestAllowFilter : RuleBasedIPFilter
+ {
+ public TestAllowFilter(IIPFilterRule rule)
+ : base(rule)
+ {
+ }
+
+ protected override Task ChannelRejected(IChannelHandlerContext ctx, IPEndPoint remoteAddress)
+ {
+ throw new InvalidOperationException("This code must be skipped during test execution.");
+ }
+ }
+
+ private class TestUniqueIPFilterHandlerChannel1 : EmbeddedChannel
+ {
+ public TestUniqueIPFilterHandlerChannel1(params IChannelHandler[] handlers):base(handlers)
+ {
+ }
+
+ protected override EndPoint RemoteAddressInternal => this.Active
+ ? CreateIPEndPoint(TestUniqueIPFilterHandlerConstants.IP1, 5421)
+ : null;
+ }
+
+ private class TestUniqueIPFilterHandlerChannel2 : EmbeddedChannel
+ {
+ public TestUniqueIPFilterHandlerChannel2(params IChannelHandler[] handlers):base(handlers)
+ {
+ }
+
+ protected override EndPoint RemoteAddressInternal => this.Active
+ ? CreateIPEndPoint(TestUniqueIPFilterHandlerConstants.IP2, 5421)
+ : null;
+ }
+
+ private class TestIpFilterRuleHandlerChannel1 : EmbeddedChannel
+ {
+ public TestIpFilterRuleHandlerChannel1(params IChannelHandler[] handlers):base(handlers)
+ {
+ }
+
+ protected override EndPoint RemoteAddressInternal => this.Active
+ ? CreateIPEndPoint(TestIPFilterRuleHandlerConstants.IP1, 5421)
+ : null;
+ }
+
+ private class TestIpFilterRuleHandlerChannel2 : EmbeddedChannel
+ {
+ public TestIpFilterRuleHandlerChannel2(params IChannelHandler[] handlers):base(handlers)
+ {
+ }
+
+ protected override EndPoint RemoteAddressInternal => this.Active
+ ? CreateIPEndPoint(TestIPFilterRuleHandlerConstants.IP2, 5421)
+ : null;
+ }
+
+ static IPEndPoint CreateIPEndPoint(string ipAddress, int port = 1234)
+ {
+ return new IPEndPoint(IPAddress.Parse(ipAddress), port);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/test/DotNetty.Handlers.Tests/IdleStateHandlerTest.cs b/test/DotNetty.Handlers.Tests/IdleStateHandlerTest.cs
index 787b9641f..08a3971a2 100644
--- a/test/DotNetty.Handlers.Tests/IdleStateHandlerTest.cs
+++ b/test/DotNetty.Handlers.Tests/IdleStateHandlerTest.cs
@@ -3,7 +3,6 @@
namespace DotNetty.Handlers.Tests
{
- using System.Threading.Tasks;
using DotNetty.Tests.Common;
using Xunit;
using Xunit.Abstractions;