View Javadoc
1   /*
2    * Copyright 2014 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.util.NetUtil;
19  import io.netty.util.internal.ObjectUtil;
20  import io.netty.util.internal.SocketUtils;
21  
22  import java.math.BigInteger;
23  import java.net.Inet4Address;
24  import java.net.Inet6Address;
25  import java.net.InetAddress;
26  import java.net.InetSocketAddress;
27  import java.net.UnknownHostException;
28  
29  /**
30   * Use this class to create rules for {@link RuleBasedIpFilter} that group IP addresses into subnets.
31   * Supports both, IPv4 and IPv6.
32   */
33  public final class IpSubnetFilterRule implements IpFilterRule, Comparable<IpSubnetFilterRule> {
34  
35      private final IpFilterRule filterRule;
36      private final String ipAddress;
37  
38      /**
39       * Create a new {@link IpSubnetFilterRule} instance
40       *
41       * @param ipAddressWithCidr IP Address with CIDR notation, e.g. (192.168.0.0/16) or (2001:db8::/32)
42       * @param ruleType {@link IpFilterRuleType} to use
43       */
44      public IpSubnetFilterRule(String ipAddressWithCidr, IpFilterRuleType ruleType) {
45          try {
46              String[] ipAndCidr = ipAddressWithCidr.split("/");
47              if (ipAndCidr.length != 2) {
48                  throw new IllegalArgumentException("ipAddressWithCidr: " + ipAddressWithCidr +
49                          " (expected: \"<ip-address>/<mask-size>\")");
50              }
51  
52              ipAddress = ipAndCidr[0];
53              int cidrPrefix = Integer.parseInt(ipAndCidr[1]);
54              filterRule = selectFilterRule(SocketUtils.addressByName(ipAddress), cidrPrefix, ruleType);
55          } catch (UnknownHostException e) {
56              throw new IllegalArgumentException("ipAddressWithCidr", e);
57          }
58      }
59  
60      /**
61       * Create a new {@link IpSubnetFilterRule} instance
62       *
63       * @param ipAddress IP Address as {@link String}
64       * @param cidrPrefix CIDR Prefix
65       * @param ruleType {@link IpFilterRuleType} to use
66       */
67      public IpSubnetFilterRule(String ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
68          try {
69              this.ipAddress = ipAddress;
70              filterRule = selectFilterRule(SocketUtils.addressByName(ipAddress), cidrPrefix, ruleType);
71          } catch (UnknownHostException e) {
72              throw new IllegalArgumentException("ipAddress", e);
73          }
74      }
75  
76      /**
77       * Create a new {@link IpSubnetFilterRule} instance
78       *
79       * @param ipAddress IP Address as {@link InetAddress}
80       * @param cidrPrefix CIDR Prefix
81       * @param ruleType {@link IpFilterRuleType} to use
82       */
83      public IpSubnetFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
84          this.ipAddress = ipAddress.getHostAddress();
85          filterRule = selectFilterRule(ipAddress, cidrPrefix, ruleType);
86      }
87  
88      private static IpFilterRule selectFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
89          ObjectUtil.checkNotNull(ipAddress, "ipAddress");
90          ObjectUtil.checkNotNull(ruleType, "ruleType");
91  
92          if (ipAddress instanceof Inet4Address) {
93              return new Ip4SubnetFilterRule((Inet4Address) ipAddress, cidrPrefix, ruleType);
94          } else if (ipAddress instanceof Inet6Address) {
95              return new Ip6SubnetFilterRule((Inet6Address) ipAddress, cidrPrefix, ruleType);
96          } else {
97              throw new IllegalArgumentException("Only IPv4 and IPv6 addresses are supported");
98          }
99      }
100 
101     @Override
102     public boolean matches(InetSocketAddress remoteAddress) {
103         return filterRule.matches(remoteAddress);
104     }
105 
106     @Override
107     public IpFilterRuleType ruleType() {
108         return filterRule.ruleType();
109     }
110 
111     /**
112      * Get IP Address of this rule
113      */
114     String getIpAddress() {
115         return ipAddress;
116     }
117 
118     /**
119      * {@link Ip4SubnetFilterRule} or {@link Ip6SubnetFilterRule}
120      */
121     IpFilterRule getFilterRule() {
122         return filterRule;
123     }
124 
125     @Override
126     public int compareTo(IpSubnetFilterRule ipSubnetFilterRule) {
127         if (filterRule instanceof Ip4SubnetFilterRule) {
128             return compareInt(((Ip4SubnetFilterRule) filterRule).networkAddress,
129                     ((Ip4SubnetFilterRule) ipSubnetFilterRule.filterRule).networkAddress);
130         } else {
131             return ((Ip6SubnetFilterRule) filterRule).networkAddress
132                     .compareTo(((Ip6SubnetFilterRule) ipSubnetFilterRule.filterRule).networkAddress);
133         }
134     }
135 
136     /**
137      * It'll compare IP address with {@link Ip4SubnetFilterRule#networkAddress} or
138      * {@link Ip6SubnetFilterRule#networkAddress}.
139      *
140      * @param inetSocketAddress {@link InetSocketAddress} to match
141      * @return 0 if IP Address match else difference index.
142      */
143     int compareTo(InetSocketAddress inetSocketAddress) {
144         if (filterRule instanceof Ip4SubnetFilterRule) {
145             Ip4SubnetFilterRule ip4SubnetFilterRule = (Ip4SubnetFilterRule) filterRule;
146             return compareInt(ip4SubnetFilterRule.networkAddress, NetUtil.ipv4AddressToInt((Inet4Address)
147                     inetSocketAddress.getAddress()) & ip4SubnetFilterRule.subnetMask);
148         } else {
149             Ip6SubnetFilterRule ip6SubnetFilterRule = (Ip6SubnetFilterRule) filterRule;
150             return ip6SubnetFilterRule.networkAddress
151                     .compareTo(Ip6SubnetFilterRule.ipToInt((Inet6Address) inetSocketAddress.getAddress())
152                             .and(ip6SubnetFilterRule.networkAddress));
153         }
154     }
155 
156     /**
157      * Equivalent to {@link Integer#compare(int, int)}
158      */
159     private static int compareInt(int x, int y) {
160         return (x < y) ? -1 : ((x == y) ? 0 : 1);
161     }
162 
163     static final class Ip4SubnetFilterRule implements IpFilterRule {
164 
165         private final int networkAddress;
166         private final int subnetMask;
167         private final IpFilterRuleType ruleType;
168 
169         private Ip4SubnetFilterRule(Inet4Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
170             if (cidrPrefix < 0 || cidrPrefix > 32) {
171                 throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of "
172                         + "[0,32]. The prefix was: %d", cidrPrefix));
173             }
174 
175             subnetMask = prefixToSubnetMask(cidrPrefix);
176             networkAddress = NetUtil.ipv4AddressToInt(ipAddress) & subnetMask;
177             this.ruleType = ruleType;
178         }
179 
180         @Override
181         public boolean matches(InetSocketAddress remoteAddress) {
182             final InetAddress inetAddress = remoteAddress.getAddress();
183             if (inetAddress instanceof Inet4Address) {
184                 int ipAddress = NetUtil.ipv4AddressToInt((Inet4Address) inetAddress);
185                 return (ipAddress & subnetMask) == networkAddress;
186             }
187             return false;
188         }
189 
190         @Override
191         public IpFilterRuleType ruleType() {
192             return ruleType;
193         }
194 
195         private static int prefixToSubnetMask(int cidrPrefix) {
196             /*
197              * Perform the shift on a long and downcast it to int afterwards.
198              * This is necessary to handle a cidrPrefix of zero correctly.
199              * The left shift operator on an int only uses the five least
200              * significant bits of the right-hand operand. Thus -1 << 32 evaluates
201              * to -1 instead of 0. The left shift operator applied on a long
202              * uses the six least significant bits.
203              *
204              * Also see https://github.com/netty/netty/issues/2767
205              */
206             return (int) (-1L << 32 - cidrPrefix);
207         }
208     }
209 
210     static final class Ip6SubnetFilterRule implements IpFilterRule {
211 
212         private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
213 
214         private final BigInteger networkAddress;
215         private final BigInteger subnetMask;
216         private final IpFilterRuleType ruleType;
217 
218         private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
219             if (cidrPrefix < 0 || cidrPrefix > 128) {
220                 throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of "
221                         + "[0,128]. The prefix was: %d", cidrPrefix));
222             }
223 
224             subnetMask = prefixToSubnetMask(cidrPrefix);
225             networkAddress = ipToInt(ipAddress).and(subnetMask);
226             this.ruleType = ruleType;
227         }
228 
229         @Override
230         public boolean matches(InetSocketAddress remoteAddress) {
231             final InetAddress inetAddress = remoteAddress.getAddress();
232             if (inetAddress instanceof Inet6Address) {
233                 BigInteger ipAddress = ipToInt((Inet6Address) inetAddress);
234                 return ipAddress.and(subnetMask).equals(subnetMask) || ipAddress.and(subnetMask).equals(networkAddress);
235             }
236             return false;
237         }
238 
239         @Override
240         public IpFilterRuleType ruleType() {
241             return ruleType;
242         }
243 
244         private static BigInteger ipToInt(Inet6Address ipAddress) {
245             byte[] octets = ipAddress.getAddress();
246             assert octets.length == 16;
247 
248             return new BigInteger(octets);
249         }
250 
251         private static BigInteger prefixToSubnetMask(int cidrPrefix) {
252             return MINUS_ONE.shiftLeft(128 - cidrPrefix);
253         }
254     }
255 }