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