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.netty5.handler.ipfilter;
17
18 import io.netty5.channel.Channel;
19 import io.netty5.channel.ChannelHandlerContext;
20
21 import java.net.Inet4Address;
22 import java.net.InetSocketAddress;
23 import java.net.SocketAddress;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Iterator;
28 import java.util.List;
29
30 import static java.util.Objects.requireNonNull;
31
32 /**
33 * <p>
34 * This class allows one to filter new {@link Channel}s based on the
35 * {@link IpSubnetFilter}s passed to its constructor. If no rules are provided, all connections
36 * will be accepted since {@code acceptIfNotFound} is {@code true} by default.
37 * </p>
38 *
39 * <p>
40 * If you would like to explicitly take action on rejected {@link Channel}s, you should override
41 * {@link AbstractRemoteAddressFilter#channelRejected(ChannelHandlerContext, SocketAddress)}.
42 * </p>
43 *
44 * <p>
45 * Few Points to keep in mind:
46 * <ol>
47 * <li> Since {@link IpSubnetFilter} uses Binary search algorithm, it's a good
48 * idea to insert IP addresses in incremental order. </li>
49 * <li> Remove any over-lapping CIDR. </li>
50 * </ol>
51 * </p>
52 *
53 */
54 public class IpSubnetFilter extends AbstractRemoteAddressFilter<InetSocketAddress> {
55
56 private final boolean acceptIfNotFound;
57 private final List<IpSubnetFilterRule> ipv4Rules;
58 private final List<IpSubnetFilterRule> ipv6Rules;
59 private final IpFilterRuleType ipFilterRuleTypeIPv4;
60 private final IpFilterRuleType ipFilterRuleTypeIPv6;
61
62 /**
63 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array. </p>
64 * <p> {@code acceptIfNotFound} is set to {@code true}. </p>
65 *
66 * @param rules {@link IpSubnetFilterRule} as an array
67 */
68 public IpSubnetFilter(IpSubnetFilterRule... rules) {
69 this(true, Arrays.asList(requireNonNull(rules, "rules")));
70 }
71
72 /**
73 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array
74 * and specify if we'll accept a connection if we don't find it in the rule(s). </p>
75 *
76 * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
77 * @param rules {@link IpSubnetFilterRule} as an array
78 */
79 public IpSubnetFilter(boolean acceptIfNotFound, IpSubnetFilterRule... rules) {
80 this(acceptIfNotFound, Arrays.asList(requireNonNull(rules, "rules")));
81 }
82
83 /**
84 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}. </p>
85 * <p> {@code acceptIfNotFound} is set to {@code true}. </p>
86 *
87 * @param rules {@link IpSubnetFilterRule} as a {@link List}
88 */
89 public IpSubnetFilter(List<IpSubnetFilterRule> rules) {
90 this(true, rules);
91 }
92
93 /**
94 * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}
95 * and specify if we'll accept a connection if we don't find it in the rule(s). </p>
96 *
97 * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
98 * @param rules {@link IpSubnetFilterRule} as a {@link List}
99 */
100 public IpSubnetFilter(boolean acceptIfNotFound, List<IpSubnetFilterRule> rules) {
101 requireNonNull(rules, "rules");
102 this.acceptIfNotFound = acceptIfNotFound;
103
104 int numAcceptIPv4 = 0;
105 int numRejectIPv4 = 0;
106 int numAcceptIPv6 = 0;
107 int numRejectIPv6 = 0;
108
109 List<IpSubnetFilterRule> unsortedIPv4Rules = new ArrayList<>();
110 List<IpSubnetFilterRule> unsortedIPv6Rules = new ArrayList<>();
111
112 // Iterate over rules and check for `null` rule.
113 for (IpSubnetFilterRule ipSubnetFilterRule : rules) {
114 requireNonNull(ipSubnetFilterRule, "rule");
115
116 if (ipSubnetFilterRule.getFilterRule() instanceof IpSubnetFilterRule.Ip4SubnetFilterRule) {
117 unsortedIPv4Rules.add(ipSubnetFilterRule);
118
119 if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
120 numAcceptIPv4++;
121 } else {
122 numRejectIPv4++;
123 }
124 } else {
125 unsortedIPv6Rules.add(ipSubnetFilterRule);
126
127 if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
128 numAcceptIPv6++;
129 } else {
130 numRejectIPv6++;
131 }
132 }
133 }
134
135 /*
136 * If Number of ACCEPT rule is 0 and number of REJECT rules is more than 0,
137 * then all rules are of "REJECT" type.
138 *
139 * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.REJECT`.
140 *
141 * If Number of ACCEPT rules are more than 0 and number of REJECT rules is 0,
142 * then all rules are of "ACCEPT" type.
143 *
144 * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.ACCEPT`.
145 */
146 if (numAcceptIPv4 == 0 && numRejectIPv4 > 0) {
147 ipFilterRuleTypeIPv4 = IpFilterRuleType.REJECT;
148 } else if (numAcceptIPv4 > 0 && numRejectIPv4 == 0) {
149 ipFilterRuleTypeIPv4 = IpFilterRuleType.ACCEPT;
150 } else {
151 ipFilterRuleTypeIPv4 = null;
152 }
153
154 if (numAcceptIPv6 == 0 && numRejectIPv6 > 0) {
155 ipFilterRuleTypeIPv6 = IpFilterRuleType.REJECT;
156 } else if (numAcceptIPv6 > 0 && numRejectIPv6 == 0) {
157 ipFilterRuleTypeIPv6 = IpFilterRuleType.ACCEPT;
158 } else {
159 ipFilterRuleTypeIPv6 = null;
160 }
161
162 this.ipv4Rules = sortAndFilter(unsortedIPv4Rules);
163 this.ipv6Rules = sortAndFilter(unsortedIPv6Rules);
164 }
165
166 @Override
167 public boolean isSharable() {
168 return true;
169 }
170
171 @Override
172 protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) {
173 if (remoteAddress.getAddress() instanceof Inet4Address) {
174 int indexOf = Collections.binarySearch(ipv4Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
175 if (indexOf >= 0) {
176 if (ipFilterRuleTypeIPv4 == null) {
177 return ipv4Rules.get(indexOf).ruleType() == IpFilterRuleType.ACCEPT;
178 } else {
179 return ipFilterRuleTypeIPv4 == IpFilterRuleType.ACCEPT;
180 }
181 }
182 } else {
183 int indexOf = Collections.binarySearch(ipv6Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
184 if (indexOf >= 0) {
185 if (ipFilterRuleTypeIPv6 == null) {
186 return ipv6Rules.get(indexOf).ruleType() == IpFilterRuleType.ACCEPT;
187 } else {
188 return ipFilterRuleTypeIPv6 == IpFilterRuleType.ACCEPT;
189 }
190 }
191 }
192
193 return acceptIfNotFound;
194 }
195
196 /**
197 * <ol>
198 * <li> Sort the list </li>
199 * <li> Remove over-lapping subnet </li>
200 * <li> Sort the list again </li>
201 * </ol>
202 */
203 @SuppressWarnings("ConstantConditions")
204 private static List<IpSubnetFilterRule> sortAndFilter(List<IpSubnetFilterRule> rules) {
205 Collections.sort(rules);
206 Iterator<IpSubnetFilterRule> iterator = rules.iterator();
207 List<IpSubnetFilterRule> toKeep = new ArrayList<>();
208
209 IpSubnetFilterRule parentRule = iterator.hasNext() ? iterator.next() : null;
210 if (parentRule != null) {
211 toKeep.add(parentRule);
212 }
213
214 while (iterator.hasNext()) {
215
216 // Grab a potential child rule.
217 IpSubnetFilterRule childRule = iterator.next();
218
219 // If parentRule matches childRule, then there's no need to keep the child rule.
220 // Otherwise, the rules are distinct, and we need both.
221 if (!parentRule.matches(new InetSocketAddress(childRule.getIpAddress(), 1))) {
222 toKeep.add(childRule);
223 // Then we'll keep the child rule around as the parent for the next round.
224 parentRule = childRule;
225 }
226 }
227
228 return toKeep;
229 }
230 }