1 /*
2 * Copyright 2020 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.ipfilter;
17
18 import io.netty.channel.Channel;
19 import io.netty.channel.ChannelHandler.Sharable;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.util.internal.ObjectUtil;
22
23 import java.net.Inet4Address;
24 import java.net.Inet6Address;
25 import java.net.InetSocketAddress;
26 import java.net.SocketAddress;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.Iterator;
32 import java.util.List;
33
34 /**
35 * <p>
36 * This class allows one to filter new {@link Channel}s based on the
37 * {@link IpSubnetFilter}s passed to its constructor. If no rules are provided, all connections
38 * will be accepted since {@code acceptIfNotFound} is {@code true} by default.
39 * </p>
40 *
41 * <p>
42 * If you would like to explicitly take action on rejected {@link Channel}s, you should override
43 * {@link AbstractRemoteAddressFilter#channelRejected(ChannelHandlerContext, SocketAddress)}.
44 * </p>
45 *
46 * <p>
47 * Few Points to keep in mind:
48 * <ol>
49 * <li> Since {@link IpSubnetFilter} uses Binary search algorithm, it's a good
50 * idea to insert IP addresses in incremental order. </li>
51 * <li> Remove any over-lapping CIDR. </li>
52 * </ol>
53 * </p>
54 *
55 */
56 @Sharable
57 public class IpSubnetFilter extends AbstractRemoteAddressFilter<InetSocketAddress> {
58
59 private final boolean acceptIfNotFound;
60 private final IpSubnetFilterRule[] ipv4Rules;
61 private final IpSubnetFilterRule[] ipv6Rules;
62 private final IpFilterRuleType ipFilterRuleTypeIPv4;
63 private final IpFilterRuleType ipFilterRuleTypeIPv6;
64
65 /**
66 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array. </p>
67 * <p> {@code acceptIfNotFound} is set to {@code true}. </p>
68 *
69 * @param rules {@link IpSubnetFilterRule} as an array
70 */
71 public IpSubnetFilter(IpSubnetFilterRule... rules) {
72 this(true, Arrays.asList(ObjectUtil.checkNotNull(rules, "rules")));
73 }
74
75 /**
76 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array
77 * and specify if we'll accept a connection if we don't find it in the rule(s). </p>
78 *
79 * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
80 * @param rules {@link IpSubnetFilterRule} as an array
81 */
82 public IpSubnetFilter(boolean acceptIfNotFound, IpSubnetFilterRule... rules) {
83 this(acceptIfNotFound, Arrays.asList(ObjectUtil.checkNotNull(rules, "rules")));
84 }
85
86 /**
87 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}. </p>
88 * <p> {@code acceptIfNotFound} is set to {@code true}. </p>
89 *
90 * @param rules {@link IpSubnetFilterRule} as a {@link List}
91 */
92 public IpSubnetFilter(List<IpSubnetFilterRule> rules) {
93 this(true, rules);
94 }
95
96 /**
97 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}
98 * and specify if we'll accept a connection if we don't find it in the rule(s). </p>
99 *
100 * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
101 * @param rules {@link IpSubnetFilterRule} as a {@link List}
102 */
103 public IpSubnetFilter(boolean acceptIfNotFound, List<IpSubnetFilterRule> rules) {
104 ObjectUtil.checkNotNull(rules, "rules");
105 this.acceptIfNotFound = acceptIfNotFound;
106
107 int numAcceptIPv4 = 0;
108 int numRejectIPv4 = 0;
109 int numAcceptIPv6 = 0;
110 int numRejectIPv6 = 0;
111
112 List<IpSubnetFilterRule> unsortedIPv4Rules = new ArrayList<IpSubnetFilterRule>();
113 List<IpSubnetFilterRule> unsortedIPv6Rules = new ArrayList<IpSubnetFilterRule>();
114
115 // Iterate over rules and check for `null` rule.
116 for (IpSubnetFilterRule ipSubnetFilterRule : rules) {
117 ObjectUtil.checkNotNull(ipSubnetFilterRule, "rule");
118
119 if (ipSubnetFilterRule.getFilterRule() instanceof IpSubnetFilterRule.Ip4SubnetFilterRule) {
120 unsortedIPv4Rules.add(ipSubnetFilterRule);
121
122 if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
123 numAcceptIPv4++;
124 } else {
125 numRejectIPv4++;
126 }
127 } else {
128 unsortedIPv6Rules.add(ipSubnetFilterRule);
129
130 if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
131 numAcceptIPv6++;
132 } else {
133 numRejectIPv6++;
134 }
135 }
136 }
137
138 /*
139 * If Number of ACCEPT rule is 0 and number of REJECT rules is more than 0,
140 * then all rules are of "REJECT" type.
141 *
142 * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.REJECT`.
143 *
144 * If Number of ACCEPT rules are more than 0 and number of REJECT rules is 0,
145 * then all rules are of "ACCEPT" type.
146 *
147 * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.ACCEPT`.
148 */
149 if (numAcceptIPv4 == 0 && numRejectIPv4 > 0) {
150 ipFilterRuleTypeIPv4 = IpFilterRuleType.REJECT;
151 } else if (numAcceptIPv4 > 0 && numRejectIPv4 == 0) {
152 ipFilterRuleTypeIPv4 = IpFilterRuleType.ACCEPT;
153 } else {
154 ipFilterRuleTypeIPv4 = null;
155 }
156
157 if (numAcceptIPv6 == 0 && numRejectIPv6 > 0) {
158 ipFilterRuleTypeIPv6 = IpFilterRuleType.REJECT;
159 } else if (numAcceptIPv6 > 0 && numRejectIPv6 == 0) {
160 ipFilterRuleTypeIPv6 = IpFilterRuleType.ACCEPT;
161 } else {
162 ipFilterRuleTypeIPv6 = null;
163 }
164
165 this.ipv4Rules = unsortedIPv4Rules.isEmpty() ? null : sortAndFilter(unsortedIPv4Rules);
166 this.ipv6Rules = unsortedIPv6Rules.isEmpty() ? null : sortAndFilter(unsortedIPv6Rules);
167 }
168
169 @Override
170 protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) {
171 if (ipv4Rules != null && remoteAddress.getAddress() instanceof Inet4Address) {
172 int indexOf = Arrays.binarySearch(ipv4Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
173 if (indexOf >= 0) {
174 if (ipFilterRuleTypeIPv4 == null) {
175 return ipv4Rules[indexOf].ruleType() == IpFilterRuleType.ACCEPT;
176 } else {
177 return ipFilterRuleTypeIPv4 == IpFilterRuleType.ACCEPT;
178 }
179 }
180 } else if (ipv6Rules != null && remoteAddress.getAddress() instanceof Inet6Address) {
181 int indexOf = Arrays.binarySearch(ipv6Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
182 if (indexOf >= 0) {
183 if (ipFilterRuleTypeIPv6 == null) {
184 return ipv6Rules[indexOf].ruleType() == IpFilterRuleType.ACCEPT;
185 } else {
186 return ipFilterRuleTypeIPv6 == IpFilterRuleType.ACCEPT;
187 }
188 }
189 }
190
191 return acceptIfNotFound;
192 }
193
194 /**
195 * <ol>
196 * <li> Sort the list </li>
197 * <li> Remove over-lapping subnet </li>
198 * <li> Sort the list again </li>
199 * </ol>
200 */
201 @SuppressWarnings("ZeroLengthArrayAllocation")
202 private static IpSubnetFilterRule[] sortAndFilter(List<IpSubnetFilterRule> rules) {
203 // Equal network addresses: broader subnet first, so it becomes the parent of the narrower one.
204 Collections.sort(rules, new Comparator<IpSubnetFilterRule>() {
205 @Override
206 public int compare(IpSubnetFilterRule a, IpSubnetFilterRule b) {
207 int cmp = a.compareTo(b);
208 if (cmp != 0) {
209 return cmp;
210 }
211 return a.contains(b) ? (b.contains(a) ? 0 : -1) : 1;
212 }
213 });
214 Iterator<IpSubnetFilterRule> iterator = rules.iterator();
215 List<IpSubnetFilterRule> toKeep = new ArrayList<IpSubnetFilterRule>();
216
217 IpSubnetFilterRule parentRule = iterator.hasNext() ? iterator.next() : null;
218 if (parentRule != null) {
219 toKeep.add(parentRule);
220 }
221
222 while (iterator.hasNext()) {
223
224 // Grab a potential child rule.
225 IpSubnetFilterRule childRule = iterator.next();
226
227 // If parentRule matches childRule, then there's no need to keep the child rule.
228 // Otherwise, the rules are distinct and we need both.
229 if (parentRule.contains(childRule)) {
230 // The child rule is a subset of the parent rule. If they don't share the same rule type
231 // we can't safely drop the child rule, as doing so would silently change the outcome for
232 // addresses covered by the child but not otherwise expressible with the parent's ruleType.
233 // This binary-search based design can't represent longest-prefix-match semantics, so reject
234 // the unsupported configuration instead of failing open.
235 if (childRule.ruleType() != parentRule.ruleType()) {
236 throw new IllegalArgumentException(
237 "IpSubnetFilter does not support overlapping rules with different ruleTypes: "
238 + parentRule + " (" + parentRule.ruleType() + ") overlaps "
239 + childRule + " (" + childRule.ruleType() + "). "
240 + "Use RuleBasedIpFilter for mixed ACCEPT/REJECT carve-outs.");
241 }
242 continue;
243 }
244
245 toKeep.add(childRule);
246 // Then we'll keep the child rule around as the parent for the next round.
247 parentRule = childRule;
248 }
249
250 return toKeep.toArray(new IpSubnetFilterRule[0]);
251 }
252 }