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      public IpSubnetFilterRule(String ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
39          try {
40              this.ipAddress = ipAddress;
41              filterRule = selectFilterRule(SocketUtils.addressByName(ipAddress), cidrPrefix, ruleType);
42          } catch (UnknownHostException e) {
43              throw new IllegalArgumentException("ipAddress", e);
44          }
45      }
46  
47      public IpSubnetFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
48          this.ipAddress = ipAddress.getHostAddress();
49          filterRule = selectFilterRule(ipAddress, cidrPrefix, ruleType);
50      }
51  
52      private static IpFilterRule selectFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
53          ObjectUtil.checkNotNull(ipAddress, "ipAddress");
54          ObjectUtil.checkNotNull(ruleType, "ruleType");
55  
56          if (ipAddress instanceof Inet4Address) {
57              return new Ip4SubnetFilterRule((Inet4Address) ipAddress, cidrPrefix, ruleType);
58          } else if (ipAddress instanceof Inet6Address) {
59              return new Ip6SubnetFilterRule((Inet6Address) ipAddress, cidrPrefix, ruleType);
60          } else {
61              throw new IllegalArgumentException("Only IPv4 and IPv6 addresses are supported");
62          }
63      }
64  
65      @Override
66      public boolean matches(InetSocketAddress remoteAddress) {
67          return filterRule.matches(remoteAddress);
68      }
69  
70      @Override
71      public IpFilterRuleType ruleType() {
72          return filterRule.ruleType();
73      }
74  
75      /**
76       * Get IP Address of this rule
77       */
78      String getIpAddress() {
79          return ipAddress;
80      }
81  
82      /**
83       * {@link Ip4SubnetFilterRule} or {@link Ip6SubnetFilterRule}
84       */
85      IpFilterRule getFilterRule() {
86          return filterRule;
87      }
88  
89      @Override
90      public int compareTo(IpSubnetFilterRule ipSubnetFilterRule) {
91          if (filterRule instanceof Ip4SubnetFilterRule) {
92              return compareInt(((Ip4SubnetFilterRule) filterRule).networkAddress,
93                      ((Ip4SubnetFilterRule) ipSubnetFilterRule.filterRule).networkAddress);
94          } else {
95              return ((Ip6SubnetFilterRule) filterRule).networkAddress
96                      .compareTo(((Ip6SubnetFilterRule) ipSubnetFilterRule.filterRule).networkAddress);
97          }
98      }
99  
100     /**
101      * It'll compare IP address with {@link Ip4SubnetFilterRule#networkAddress} or
102      * {@link Ip6SubnetFilterRule#networkAddress}.
103      *
104      * @param inetSocketAddress {@link InetSocketAddress} to match
105      * @return 0 if IP Address match else difference index.
106      */
107     int compareTo(InetSocketAddress inetSocketAddress) {
108         if (filterRule instanceof Ip4SubnetFilterRule) {
109             Ip4SubnetFilterRule ip4SubnetFilterRule = (Ip4SubnetFilterRule) filterRule;
110             return compareInt(ip4SubnetFilterRule.networkAddress, NetUtil.ipv4AddressToInt((Inet4Address)
111                     inetSocketAddress.getAddress()) & ip4SubnetFilterRule.subnetMask);
112         } else {
113             Ip6SubnetFilterRule ip6SubnetFilterRule = (Ip6SubnetFilterRule) filterRule;
114             return ip6SubnetFilterRule.networkAddress
115                     .compareTo(Ip6SubnetFilterRule.ipToInt((Inet6Address) inetSocketAddress.getAddress())
116                             .and(ip6SubnetFilterRule.networkAddress));
117         }
118     }
119 
120     /**
121      * Equivalent to {@link Integer#compare(int, int)}
122      */
123     private static int compareInt(int x, int y) {
124         return (x < y) ? -1 : ((x == y) ? 0 : 1);
125     }
126 
127     static final class Ip4SubnetFilterRule implements IpFilterRule {
128 
129         private final int networkAddress;
130         private final int subnetMask;
131         private final IpFilterRuleType ruleType;
132 
133         private Ip4SubnetFilterRule(Inet4Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
134             if (cidrPrefix < 0 || cidrPrefix > 32) {
135                 throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of "
136                         + "[0,32]. The prefix was: %d", cidrPrefix));
137             }
138 
139             subnetMask = prefixToSubnetMask(cidrPrefix);
140             networkAddress = NetUtil.ipv4AddressToInt(ipAddress) & subnetMask;
141             this.ruleType = ruleType;
142         }
143 
144         @Override
145         public boolean matches(InetSocketAddress remoteAddress) {
146             final InetAddress inetAddress = remoteAddress.getAddress();
147             if (inetAddress instanceof Inet4Address) {
148                 int ipAddress = NetUtil.ipv4AddressToInt((Inet4Address) inetAddress);
149                 return (ipAddress & subnetMask) == networkAddress;
150             }
151             return false;
152         }
153 
154         @Override
155         public IpFilterRuleType ruleType() {
156             return ruleType;
157         }
158 
159         private static int prefixToSubnetMask(int cidrPrefix) {
160             /*
161              * Perform the shift on a long and downcast it to int afterwards.
162              * This is necessary to handle a cidrPrefix of zero correctly.
163              * The left shift operator on an int only uses the five least
164              * significant bits of the right-hand operand. Thus -1 << 32 evaluates
165              * to -1 instead of 0. The left shift operator applied on a long
166              * uses the six least significant bits.
167              *
168              * Also see https://github.com/netty/netty/issues/2767
169              */
170             return (int) (-1L << 32 - cidrPrefix);
171         }
172     }
173 
174     static final class Ip6SubnetFilterRule implements IpFilterRule {
175 
176         private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
177 
178         private final BigInteger networkAddress;
179         private final BigInteger subnetMask;
180         private final IpFilterRuleType ruleType;
181 
182         private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
183             if (cidrPrefix < 0 || cidrPrefix > 128) {
184                 throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of "
185                         + "[0,128]. The prefix was: %d", cidrPrefix));
186             }
187 
188             subnetMask = prefixToSubnetMask(cidrPrefix);
189             networkAddress = ipToInt(ipAddress).and(subnetMask);
190             this.ruleType = ruleType;
191         }
192 
193         @Override
194         public boolean matches(InetSocketAddress remoteAddress) {
195             final InetAddress inetAddress = remoteAddress.getAddress();
196             if (inetAddress instanceof Inet6Address) {
197                 BigInteger ipAddress = ipToInt((Inet6Address) inetAddress);
198                 return ipAddress.and(subnetMask).equals(subnetMask) || ipAddress.and(subnetMask).equals(networkAddress);
199             }
200             return false;
201         }
202 
203         @Override
204         public IpFilterRuleType ruleType() {
205             return ruleType;
206         }
207 
208         private static BigInteger ipToInt(Inet6Address ipAddress) {
209             byte[] octets = ipAddress.getAddress();
210             assert octets.length == 16;
211 
212             return new BigInteger(octets);
213         }
214 
215         private static BigInteger prefixToSubnetMask(int cidrPrefix) {
216             return MINUS_ONE.shiftLeft(128 - cidrPrefix);
217         }
218     }
219 }