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.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 }