diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilter.java b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilter.java
new file mode 100644
index 0000000000..9774f4b646
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilter.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2020 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.ipfilter;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.internal.ObjectUtil;
+
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * This class allows one to filter new {@link Channel}s based on the
+ * {@link IpSubnetFilter}s passed to its constructor. If no rules are provided, all connections
+ * will be accepted since {@code acceptIfNotFound} is {@code true} by default.
+ *
+ *
+ *
+ * If you would like to explicitly take action on rejected {@link Channel}s, you should override
+ * {@link AbstractRemoteAddressFilter#channelRejected(ChannelHandlerContext, SocketAddress)}.
+ *
+ *
+ *
+ * Few Points to keep in mind:
+ *
+ *
Since {@link IpSubnetFilter} uses Binary search algorithm, it's a good
+ * idea to insert IP addresses in incremental order.
+ *
Remove any over-lapping CIDR.
+ *
+ *
+ *
+ */
+@Sharable
+public class IpSubnetFilter extends AbstractRemoteAddressFilter {
+
+ private final boolean acceptIfNotFound;
+ private final List ipv4Rules;
+ private final List ipv6Rules;
+ private final IpFilterRuleType ipFilterRuleTypeIPv4;
+ private final IpFilterRuleType ipFilterRuleTypeIPv6;
+
+ /**
+ *
Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array.
+ *
{@code acceptIfNotFound} is set to {@code true}.
+ *
+ * @param rules {@link IpSubnetFilterRule} as an array
+ */
+ public IpSubnetFilter(IpSubnetFilterRule... rules) {
+ this(true, Arrays.asList(ObjectUtil.checkNotNull(rules, "rules")));
+ }
+
+ /**
+ *
Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array
+ * and specify if we'll accept a connection if we don't find it in the rule(s).
+ *
+ * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
+ * @param rules {@link IpSubnetFilterRule} as an array
+ */
+ public IpSubnetFilter(boolean acceptIfNotFound, IpSubnetFilterRule... rules) {
+ this(acceptIfNotFound, Arrays.asList(ObjectUtil.checkNotNull(rules, "rules")));
+ }
+
+ /**
+ *
Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}.
+ *
{@code acceptIfNotFound} is set to {@code true}.
+ *
+ * @param rules {@link IpSubnetFilterRule} as a {@link List}
+ */
+ public IpSubnetFilter(List rules) {
+ this(true, rules);
+ }
+
+ /**
+ *
Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}
+ * and specify if we'll accept a connection if we don't find it in the rule(s).
+ *
+ * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
+ * @param rules {@link IpSubnetFilterRule} as a {@link List}
+ */
+ public IpSubnetFilter(boolean acceptIfNotFound, List rules) {
+ ObjectUtil.checkNotNull(rules, "rules");
+ this.acceptIfNotFound = acceptIfNotFound;
+
+ int numAcceptIPv4 = 0;
+ int numRejectIPv4 = 0;
+ int numAcceptIPv6 = 0;
+ int numRejectIPv6 = 0;
+
+ List unsortedIPv4Rules = new ArrayList();
+ List unsortedIPv6Rules = new ArrayList();
+
+ // Iterate over rules and check for `null` rule.
+ for (IpSubnetFilterRule ipSubnetFilterRule : rules) {
+ ObjectUtil.checkNotNull(ipSubnetFilterRule, "rule");
+
+ if (ipSubnetFilterRule.getFilterRule() instanceof IpSubnetFilterRule.Ip4SubnetFilterRule) {
+ unsortedIPv4Rules.add(ipSubnetFilterRule);
+
+ if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
+ numAcceptIPv4++;
+ } else {
+ numRejectIPv4++;
+ }
+ } else {
+ unsortedIPv6Rules.add(ipSubnetFilterRule);
+
+ if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
+ numAcceptIPv6++;
+ } else {
+ numRejectIPv6++;
+ }
+ }
+ }
+
+ /*
+ * If Number of ACCEPT rule is 0 and number of REJECT rules is more than 0,
+ * then all rules are of "REJECT" type.
+ *
+ * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.REJECT`.
+ *
+ * If Number of ACCEPT rules are more than 0 and number of REJECT rules is 0,
+ * then all rules are of "ACCEPT" type.
+ *
+ * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.ACCEPT`.
+ */
+ if (numAcceptIPv4 == 0 && numRejectIPv4 > 0) {
+ ipFilterRuleTypeIPv4 = IpFilterRuleType.REJECT;
+ } else if (numAcceptIPv4 > 0 && numRejectIPv4 == 0) {
+ ipFilterRuleTypeIPv4 = IpFilterRuleType.ACCEPT;
+ } else {
+ ipFilterRuleTypeIPv4 = null;
+ }
+
+ if (numAcceptIPv6 == 0 && numRejectIPv6 > 0) {
+ ipFilterRuleTypeIPv6 = IpFilterRuleType.REJECT;
+ } else if (numAcceptIPv6 > 0 && numRejectIPv6 == 0) {
+ ipFilterRuleTypeIPv6 = IpFilterRuleType.ACCEPT;
+ } else {
+ ipFilterRuleTypeIPv6 = null;
+ }
+
+ this.ipv4Rules = sortAndFilter(unsortedIPv4Rules);
+ this.ipv6Rules = sortAndFilter(unsortedIPv6Rules);
+ }
+
+ @Override
+ protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) {
+ if (remoteAddress.getAddress() instanceof Inet4Address) {
+ int indexOf = Collections.binarySearch(ipv4Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
+ if (indexOf >= 0) {
+ if (ipFilterRuleTypeIPv4 == null) {
+ return ipv4Rules.get(indexOf).ruleType() == IpFilterRuleType.ACCEPT;
+ } else {
+ return ipFilterRuleTypeIPv4 == IpFilterRuleType.ACCEPT;
+ }
+ }
+ } else {
+ int indexOf = Collections.binarySearch(ipv6Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
+ if (indexOf >= 0) {
+ if (ipFilterRuleTypeIPv6 == null) {
+ return ipv6Rules.get(indexOf).ruleType() == IpFilterRuleType.ACCEPT;
+ } else {
+ return ipFilterRuleTypeIPv6 == IpFilterRuleType.ACCEPT;
+ }
+ }
+ }
+
+ return acceptIfNotFound;
+ }
+
+ /**
+ *
+ *
Sort the list
+ *
Remove over-lapping subnet
+ *
Sort the list again
+ *
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static List sortAndFilter(List rules) {
+ Collections.sort(rules);
+ Iterator iterator = rules.iterator();
+ List toKeep = new ArrayList();
+
+ IpSubnetFilterRule parentRule = iterator.hasNext() ? iterator.next() : null;
+ if (parentRule != null) {
+ toKeep.add(parentRule);
+ }
+
+ while (iterator.hasNext()) {
+
+ // Grab a potential child rule.
+ IpSubnetFilterRule childRule = iterator.next();
+
+ // If parentRule matches childRule, then there's no need to keep the child rule.
+ // Otherwise, the rules are distinct and we need both.
+ if (!parentRule.matches(new InetSocketAddress(childRule.getIpAddress(), 1))) {
+ toKeep.add(childRule);
+ // Then we'll keep the child rule around as the parent for the next round.
+ parentRule = childRule;
+ }
+ }
+
+ return toKeep;
+ }
+}
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 bc6b1ffaca..e85e0713f0 100644
--- a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRule.java
@@ -15,6 +15,7 @@
*/
package io.netty.handler.ipfilter;
+import io.netty.util.NetUtil;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.SocketUtils;
@@ -29,12 +30,14 @@ import java.net.UnknownHostException;
* Use this class to create rules for {@link RuleBasedIpFilter} that group IP addresses into subnets.
* Supports both, IPv4 and IPv6.
*/
-public final class IpSubnetFilterRule implements IpFilterRule {
+public final class IpSubnetFilterRule implements IpFilterRule, Comparable {
private final IpFilterRule filterRule;
+ private final String ipAddress;
public IpSubnetFilterRule(String ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
try {
+ this.ipAddress = ipAddress;
filterRule = selectFilterRule(SocketUtils.addressByName(ipAddress), cidrPrefix, ruleType);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("ipAddress", e);
@@ -42,6 +45,7 @@ public final class IpSubnetFilterRule implements IpFilterRule {
}
public IpSubnetFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
+ this.ipAddress = ipAddress.getHostAddress();
filterRule = selectFilterRule(ipAddress, cidrPrefix, ruleType);
}
@@ -68,7 +72,59 @@ public final class IpSubnetFilterRule implements IpFilterRule {
return filterRule.ruleType();
}
- private static final class Ip4SubnetFilterRule implements IpFilterRule {
+ /**
+ * Get IP Address of this rule
+ */
+ String getIpAddress() {
+ return ipAddress;
+ }
+
+ /**
+ * {@link Ip4SubnetFilterRule} or {@link Ip6SubnetFilterRule}
+ */
+ IpFilterRule getFilterRule() {
+ return filterRule;
+ }
+
+ @Override
+ public int compareTo(IpSubnetFilterRule ipSubnetFilterRule) {
+ if (filterRule instanceof Ip4SubnetFilterRule) {
+ return compareInt(((Ip4SubnetFilterRule) filterRule).networkAddress,
+ ((Ip4SubnetFilterRule) ipSubnetFilterRule.filterRule).networkAddress);
+ } else {
+ return ((Ip6SubnetFilterRule) filterRule).networkAddress
+ .compareTo(((Ip6SubnetFilterRule) ipSubnetFilterRule.filterRule).networkAddress);
+ }
+ }
+
+ /**
+ * It'll compare IP address with {@link Ip4SubnetFilterRule#networkAddress} or
+ * {@link Ip6SubnetFilterRule#networkAddress}.
+ *
+ * @param inetSocketAddress {@link InetSocketAddress} to match
+ * @return 0 if IP Address match else difference index.
+ */
+ int compareTo(InetSocketAddress inetSocketAddress) {
+ if (filterRule instanceof Ip4SubnetFilterRule) {
+ Ip4SubnetFilterRule ip4SubnetFilterRule = (Ip4SubnetFilterRule) filterRule;
+ return compareInt(ip4SubnetFilterRule.networkAddress, NetUtil.ipv4AddressToInt((Inet4Address)
+ inetSocketAddress.getAddress()) & ip4SubnetFilterRule.subnetMask);
+ } else {
+ Ip6SubnetFilterRule ip6SubnetFilterRule = (Ip6SubnetFilterRule) filterRule;
+ return ip6SubnetFilterRule.networkAddress
+ .compareTo(Ip6SubnetFilterRule.ipToInt((Inet6Address) inetSocketAddress.getAddress())
+ .and(ip6SubnetFilterRule.networkAddress));
+ }
+ }
+
+ /**
+ * Equivalent to {@link Integer#compare(int, int)}
+ */
+ private static int compareInt(int x, int y) {
+ return (x < y) ? -1 : ((x == y) ? 0 : 1);
+ }
+
+ static final class Ip4SubnetFilterRule implements IpFilterRule {
private final int networkAddress;
private final int subnetMask;
@@ -76,12 +132,12 @@ public final class IpSubnetFilterRule implements IpFilterRule {
private Ip4SubnetFilterRule(Inet4Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
if (cidrPrefix < 0 || cidrPrefix > 32) {
- throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of " +
- "[0,32]. The prefix was: %d", cidrPrefix));
+ throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of "
+ + "[0,32]. The prefix was: %d", cidrPrefix));
}
subnetMask = prefixToSubnetMask(cidrPrefix);
- networkAddress = ipToInt(ipAddress) & subnetMask;
+ networkAddress = NetUtil.ipv4AddressToInt(ipAddress) & subnetMask;
this.ruleType = ruleType;
}
@@ -89,7 +145,7 @@ public final class IpSubnetFilterRule implements IpFilterRule {
public boolean matches(InetSocketAddress remoteAddress) {
final InetAddress inetAddress = remoteAddress.getAddress();
if (inetAddress instanceof Inet4Address) {
- int ipAddress = ipToInt((Inet4Address) inetAddress);
+ int ipAddress = NetUtil.ipv4AddressToInt((Inet4Address) inetAddress);
return (ipAddress & subnetMask) == networkAddress;
}
return false;
@@ -100,18 +156,8 @@ public final class IpSubnetFilterRule implements IpFilterRule {
return ruleType;
}
- private static int ipToInt(Inet4Address ipAddress) {
- byte[] octets = ipAddress.getAddress();
- assert octets.length == 4;
-
- return (octets[0] & 0xff) << 24 |
- (octets[1] & 0xff) << 16 |
- (octets[2] & 0xff) << 8 |
- octets[3] & 0xff;
- }
-
private static int prefixToSubnetMask(int cidrPrefix) {
- /**
+ /*
* Perform the shift on a long and downcast it to int afterwards.
* This is necessary to handle a cidrPrefix of zero correctly.
* The left shift operator on an int only uses the five least
@@ -125,7 +171,7 @@ public final class IpSubnetFilterRule implements IpFilterRule {
}
}
- private static final class Ip6SubnetFilterRule implements IpFilterRule {
+ static final class Ip6SubnetFilterRule implements IpFilterRule {
private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
@@ -135,8 +181,8 @@ public final class IpSubnetFilterRule implements IpFilterRule {
private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
if (cidrPrefix < 0 || cidrPrefix > 128) {
- throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of " +
- "[0,128]. The prefix was: %d", cidrPrefix));
+ throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of "
+ + "[0,128]. The prefix was: %d", cidrPrefix));
}
subnetMask = prefixToSubnetMask(cidrPrefix);
diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRuleComparator.java b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRuleComparator.java
new file mode 100644
index 0000000000..aaf2695ddf
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ipfilter/IpSubnetFilterRuleComparator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.ipfilter;
+
+import java.net.InetSocketAddress;
+import java.util.Comparator;
+
+/**
+ * This comparator is only used for searching.
+ */
+final class IpSubnetFilterRuleComparator implements Comparator