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.InetSocketAddress;
25  import java.net.SocketAddress;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.Iterator;
30  import java.util.List;
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  @Sharable
55  public class IpSubnetFilter extends AbstractRemoteAddressFilter<InetSocketAddress> {
56  
57      private final boolean acceptIfNotFound;
58      private final List<IpSubnetFilterRule> ipv4Rules;
59      private final List<IpSubnetFilterRule> ipv6Rules;
60      private final IpFilterRuleType ipFilterRuleTypeIPv4;
61      private final IpFilterRuleType ipFilterRuleTypeIPv6;
62  
63      /**
64       * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array. </p>
65       * <p> {@code acceptIfNotFound} is set to {@code true}. </p>
66       *
67       * @param rules {@link IpSubnetFilterRule} as an array
68       */
69      public IpSubnetFilter(IpSubnetFilterRule... rules) {
70          this(true, Arrays.asList(ObjectUtil.checkNotNull(rules, "rules")));
71      }
72  
73      /**
74       * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as array
75       * and specify if we'll accept a connection if we don't find it in the rule(s). </p>
76       *
77       * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
78       * @param rules            {@link IpSubnetFilterRule} as an array
79       */
80      public IpSubnetFilter(boolean acceptIfNotFound, IpSubnetFilterRule... rules) {
81          this(acceptIfNotFound, Arrays.asList(ObjectUtil.checkNotNull(rules, "rules")));
82      }
83  
84      /**
85       * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}. </p>
86       * <p> {@code acceptIfNotFound} is set to {@code true}. </p>
87       *
88       * @param rules {@link IpSubnetFilterRule} as a {@link List}
89       */
90      public IpSubnetFilter(List<IpSubnetFilterRule> rules) {
91          this(true, rules);
92      }
93  
94      /**
95       * <p> Create new {@link IpSubnetFilter} Instance with specified {@link IpSubnetFilterRule} as {@link List}
96       * and specify if we'll accept a connection if we don't find it in the rule(s). </p>
97       *
98       * @param acceptIfNotFound {@code true} if we'll accept connection if not found in rule(s).
99       * @param rules            {@link IpSubnetFilterRule} as a {@link List}
100      */
101     public IpSubnetFilter(boolean acceptIfNotFound, List<IpSubnetFilterRule> rules) {
102         ObjectUtil.checkNotNull(rules, "rules");
103         this.acceptIfNotFound = acceptIfNotFound;
104 
105         int numAcceptIPv4 = 0;
106         int numRejectIPv4 = 0;
107         int numAcceptIPv6 = 0;
108         int numRejectIPv6 = 0;
109 
110         List<IpSubnetFilterRule> unsortedIPv4Rules = new ArrayList<IpSubnetFilterRule>();
111         List<IpSubnetFilterRule> unsortedIPv6Rules = new ArrayList<IpSubnetFilterRule>();
112 
113         // Iterate over rules and check for `null` rule.
114         for (IpSubnetFilterRule ipSubnetFilterRule : rules) {
115             ObjectUtil.checkNotNull(ipSubnetFilterRule, "rule");
116 
117             if (ipSubnetFilterRule.getFilterRule() instanceof IpSubnetFilterRule.Ip4SubnetFilterRule) {
118                 unsortedIPv4Rules.add(ipSubnetFilterRule);
119 
120                 if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
121                     numAcceptIPv4++;
122                 } else {
123                     numRejectIPv4++;
124                 }
125             } else {
126                 unsortedIPv6Rules.add(ipSubnetFilterRule);
127 
128                 if (ipSubnetFilterRule.ruleType() == IpFilterRuleType.ACCEPT) {
129                     numAcceptIPv6++;
130                 } else {
131                     numRejectIPv6++;
132                 }
133             }
134         }
135 
136         /*
137          * If Number of ACCEPT rule is 0 and number of REJECT rules is more than 0,
138          * then all rules are of "REJECT" type.
139          *
140          * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.REJECT`.
141          *
142          * If Number of ACCEPT rules are more than 0 and number of REJECT rules is 0,
143          * then all rules are of "ACCEPT" type.
144          *
145          * In this case, we'll set `ipFilterRuleTypeIPv4` to `IpFilterRuleType.ACCEPT`.
146          */
147         if (numAcceptIPv4 == 0 && numRejectIPv4 > 0) {
148             ipFilterRuleTypeIPv4 = IpFilterRuleType.REJECT;
149         } else if (numAcceptIPv4 > 0 && numRejectIPv4 == 0) {
150             ipFilterRuleTypeIPv4 = IpFilterRuleType.ACCEPT;
151         } else {
152             ipFilterRuleTypeIPv4 = null;
153         }
154 
155         if (numAcceptIPv6 == 0 && numRejectIPv6 > 0) {
156             ipFilterRuleTypeIPv6 = IpFilterRuleType.REJECT;
157         } else if (numAcceptIPv6 > 0 && numRejectIPv6 == 0) {
158             ipFilterRuleTypeIPv6 = IpFilterRuleType.ACCEPT;
159         } else {
160             ipFilterRuleTypeIPv6 = null;
161         }
162 
163         this.ipv4Rules = sortAndFilter(unsortedIPv4Rules);
164         this.ipv6Rules = sortAndFilter(unsortedIPv6Rules);
165     }
166 
167     @Override
168     protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) {
169         if (remoteAddress.getAddress() instanceof Inet4Address) {
170             int indexOf = Collections.binarySearch(ipv4Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
171             if (indexOf >= 0) {
172                 if (ipFilterRuleTypeIPv4 == null) {
173                     return ipv4Rules.get(indexOf).ruleType() == IpFilterRuleType.ACCEPT;
174                 } else {
175                     return ipFilterRuleTypeIPv4 == IpFilterRuleType.ACCEPT;
176                 }
177             }
178         } else {
179             int indexOf = Collections.binarySearch(ipv6Rules, remoteAddress, IpSubnetFilterRuleComparator.INSTANCE);
180             if (indexOf >= 0) {
181                 if (ipFilterRuleTypeIPv6 == null) {
182                     return ipv6Rules.get(indexOf).ruleType() == IpFilterRuleType.ACCEPT;
183                 } else {
184                     return ipFilterRuleTypeIPv6 == IpFilterRuleType.ACCEPT;
185                 }
186             }
187         }
188 
189         return acceptIfNotFound;
190     }
191 
192     /**
193      * <ol>
194      *     <li> Sort the list </li>
195      *     <li> Remove over-lapping subnet </li>
196      *     <li> Sort the list again </li>
197      * </ol>
198      */
199     @SuppressWarnings("ConstantConditions")
200     private static List<IpSubnetFilterRule> sortAndFilter(List<IpSubnetFilterRule> rules) {
201         Collections.sort(rules);
202         Iterator<IpSubnetFilterRule> iterator = rules.iterator();
203         List<IpSubnetFilterRule> toKeep = new ArrayList<IpSubnetFilterRule>();
204 
205         IpSubnetFilterRule parentRule = iterator.hasNext() ? iterator.next() : null;
206         if (parentRule != null) {
207             toKeep.add(parentRule);
208         }
209 
210         while (iterator.hasNext()) {
211 
212             // Grab a potential child rule.
213             IpSubnetFilterRule childRule = iterator.next();
214 
215             // If parentRule matches childRule, then there's no need to keep the child rule.
216             // Otherwise, the rules are distinct and we need both.
217             if (!parentRule.matches(new InetSocketAddress(childRule.getIpAddress(), 1))) {
218                 toKeep.add(childRule);
219                 // Then we'll keep the child rule around as the parent for the next round.
220                 parentRule = childRule;
221             }
222         }
223 
224         return toKeep;
225     }
226 }