From 83da141d54720a1066411d831d081562323b2d41 Mon Sep 17 00:00:00 2001 From: Frank591 Date: Thu, 9 Nov 2017 18:56:14 -0500 Subject: [PATCH 1/2] #296 port io.netty.handler.ipfilter --- DotNetty.sln.DotSettings | 1 + src/DotNetty.Common/DotNetty.Common.csproj | 1 + src/DotNetty.Common/Internal/SocketUtils.cs | 41 +++ .../IPFilter/AbstractRemoteAddressFilter.cs | 103 ++++++++ .../IPFilter/IIPFilterRule.cs | 25 ++ .../IPFilter/IPFilterRuleType.cs | 14 + .../IPFilter/IPSubnetFilterRule.cs | 198 ++++++++++++++ .../IPFilter/RuleBasedIPFilter.cs | 39 +++ .../IPFilter/UniqueIPFilter.cs | 40 +++ .../Channels/AbstractChannelHandlerContext.cs | 2 + .../Channels/IChannelHandlerContext.cs | 5 + .../IPSubnetFilterTest.cs | 244 ++++++++++++++++++ .../IdleStateHandlerTest.cs | 1 - 13 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 src/DotNetty.Common/Internal/SocketUtils.cs create mode 100644 src/DotNetty.Handlers/IPFilter/AbstractRemoteAddressFilter.cs create mode 100644 src/DotNetty.Handlers/IPFilter/IIPFilterRule.cs create mode 100644 src/DotNetty.Handlers/IPFilter/IPFilterRuleType.cs create mode 100644 src/DotNetty.Handlers/IPFilter/IPSubnetFilterRule.cs create mode 100644 src/DotNetty.Handlers/IPFilter/RuleBasedIPFilter.cs create mode 100644 src/DotNetty.Handlers/IPFilter/UniqueIPFilter.cs create mode 100644 test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs 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..fbdf60926 --- /dev/null +++ b/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs @@ -0,0 +1,244 @@ +// 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()); + } + 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; From 6b061a810d4e25a0a245178e122c33ed71d83138 Mon Sep 17 00:00:00 2001 From: Frank591 Date: Mon, 13 Nov 2017 17:40:09 -0500 Subject: [PATCH 2/2] #296 fix TestIPFilterRuleHandler --- test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs b/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs index fbdf60926..04c0e7585 100644 --- a/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs +++ b/test/DotNetty.Handlers.Tests/IPSubnetFilterTest.cs @@ -96,6 +96,8 @@ public void TestIPFilterRuleHandler() { 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);