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;