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.subnetMask));
153         }
154     }
155 
156     /**
157      * {@code true} if every address matched by {@code other} is also matched by this rule.
158      */
159     boolean contains(IpSubnetFilterRule other) {
160         if (filterRule instanceof Ip4SubnetFilterRule) {
161             Ip4SubnetFilterRule parent = (Ip4SubnetFilterRule) filterRule;
162             Ip4SubnetFilterRule child = (Ip4SubnetFilterRule) other.filterRule;
163             return (parent.subnetMask & child.subnetMask) == parent.subnetMask &&
164                     (child.networkAddress & parent.subnetMask) == parent.networkAddress;
165         }
166         Ip6SubnetFilterRule parent = (Ip6SubnetFilterRule) filterRule;
167         Ip6SubnetFilterRule child = (Ip6SubnetFilterRule) other.filterRule;
168         return parent.subnetMask.and(child.subnetMask).equals(parent.subnetMask) &&
169                 child.networkAddress.and(parent.subnetMask).equals(parent.networkAddress);
170     }
171 
172     /**
173      * IPv4 addresses are compared as unsigned values, so 128.0.0.0 and above sort after 127.255.255.255.
174      */
175     private static int compareInt(int x, int y) {
176         return Integer.compare(x ^ Integer.MIN_VALUE, y ^ Integer.MIN_VALUE);
177     }
178 
179     @Override
180     public String toString() {
181         return "IpSubnetFilterRule(" + ipAddress + ", " + ruleType() + ')';
182     }
183 
184     static final class Ip4SubnetFilterRule implements IpFilterRule {
185 
186         private final int networkAddress;
187         private final int subnetMask;
188         private final IpFilterRuleType ruleType;
189 
190         private Ip4SubnetFilterRule(Inet4Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
191             if (cidrPrefix < 0 || cidrPrefix > 32) {
192                 throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of "
193                         + "[0,32]. The prefix was: %d", cidrPrefix));
194             }
195 
196             subnetMask = prefixToSubnetMask(cidrPrefix);
197             networkAddress = NetUtil.ipv4AddressToInt(ipAddress) & subnetMask;
198             this.ruleType = ruleType;
199         }
200 
201         @Override
202         public boolean matches(InetSocketAddress remoteAddress) {
203             final InetAddress inetAddress = remoteAddress.getAddress();
204             if (inetAddress instanceof Inet4Address) {
205                 int ipAddress = NetUtil.ipv4AddressToInt((Inet4Address) inetAddress);
206                 return (ipAddress & subnetMask) == networkAddress;
207             }
208             return false;
209         }
210 
211         @Override
212         public IpFilterRuleType ruleType() {
213             return ruleType;
214         }
215 
216         private static int prefixToSubnetMask(int cidrPrefix) {
217             /*
218              * Perform the shift on a long and downcast it to int afterwards.
219              * This is necessary to handle a cidrPrefix of zero correctly.
220              * The left shift operator on an int only uses the five least
221              * significant bits of the right-hand operand. Thus -1 << 32 evaluates
222              * to -1 instead of 0. The left shift operator applied on a long
223              * uses the six least significant bits.
224              *
225              * Also see https://github.com/netty/netty/issues/2767
226              */
227             return (int) (-1L << 32 - cidrPrefix);
228         }
229     }
230 
231     static final class Ip6SubnetFilterRule implements IpFilterRule {
232 
233         private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
234 
235         private final BigInteger networkAddress;
236         private final BigInteger subnetMask;
237         private final IpFilterRuleType ruleType;
238 
239         private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
240             if (cidrPrefix < 0 || cidrPrefix > 128) {
241                 throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of "
242                         + "[0,128]. The prefix was: %d", cidrPrefix));
243             }
244 
245             subnetMask = prefixToSubnetMask(cidrPrefix);
246             networkAddress = ipToInt(ipAddress).and(subnetMask);
247             this.ruleType = ruleType;
248         }
249 
250         @Override
251         public boolean matches(InetSocketAddress remoteAddress) {
252             final InetAddress inetAddress = remoteAddress.getAddress();
253             if (inetAddress instanceof Inet6Address) {
254                 BigInteger ipAddress = ipToInt((Inet6Address) inetAddress);
255                 return ipAddress.and(subnetMask).equals(subnetMask) || ipAddress.and(subnetMask).equals(networkAddress);
256             }
257             return false;
258         }
259 
260         @Override
261         public IpFilterRuleType ruleType() {
262             return ruleType;
263         }
264 
265         private static BigInteger ipToInt(Inet6Address ipAddress) {
266             byte[] octets = ipAddress.getAddress();
267             assert octets.length == 16;
268 
269             return new BigInteger(1, octets);
270         }
271 
272         private static BigInteger prefixToSubnetMask(int cidrPrefix) {
273             return MINUS_ONE.shiftLeft(128 - cidrPrefix);
274         }
275     }
276 }