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    *   http://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 java.math.BigInteger;
19  import java.net.Inet4Address;
20  import java.net.Inet6Address;
21  import java.net.InetAddress;
22  import java.net.InetSocketAddress;
23  import java.net.UnknownHostException;
24  
25  /**
26   * Use this class to create rules for {@link RuleBasedIpFilter} that group IP addresses into subnets.
27   * Supports both, IPv4 and IPv6.
28   */
29  public final class IpSubnetFilterRule implements IpFilterRule {
30  
31      private final IpFilterRule filterRule;
32  
33      public IpSubnetFilterRule(String ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
34          try {
35              filterRule = selectFilterRule(InetAddress.getByName(ipAddress), cidrPrefix, ruleType);
36          } catch (UnknownHostException e) {
37              throw new IllegalArgumentException("ipAddress", e);
38          }
39      }
40  
41      public IpSubnetFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
42          filterRule = selectFilterRule(ipAddress, cidrPrefix, ruleType);
43      }
44  
45      private static IpFilterRule selectFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
46          if (ipAddress == null) {
47              throw new NullPointerException("ipAddress");
48          }
49  
50          if (ruleType == null) {
51              throw new NullPointerException("ruleType");
52          }
53  
54          if (ipAddress instanceof Inet4Address) {
55              return new Ip4SubnetFilterRule((Inet4Address) ipAddress, cidrPrefix, ruleType);
56          } else if (ipAddress instanceof Inet6Address) {
57              return new Ip6SubnetFilterRule((Inet6Address) ipAddress, cidrPrefix, ruleType);
58          } else {
59              throw new IllegalArgumentException("Only IPv4 and IPv6 addresses are supported");
60          }
61      }
62  
63      @Override
64      public boolean matches(InetSocketAddress remoteAddress) {
65          return filterRule.matches(remoteAddress);
66      }
67  
68      @Override
69      public IpFilterRuleType ruleType() {
70          return filterRule.ruleType();
71      }
72  
73      private static final class Ip4SubnetFilterRule implements IpFilterRule {
74  
75          private final int networkAddress;
76          private final int subnetMask;
77          private final IpFilterRuleType ruleType;
78  
79          private Ip4SubnetFilterRule(Inet4Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
80              if (cidrPrefix < 0 || cidrPrefix > 32) {
81                  throw new IllegalArgumentException(String.format("IPv4 requires the subnet prefix to be in range of " +
82                                                                      "[0,32]. The prefix was: %d", cidrPrefix));
83              }
84  
85              subnetMask = prefixToSubnetMask(cidrPrefix);
86              networkAddress = ipToInt(ipAddress) & subnetMask;
87              this.ruleType = ruleType;
88          }
89  
90          @Override
91          public boolean matches(InetSocketAddress remoteAddress) {
92              int ipAddress = ipToInt((Inet4Address) remoteAddress.getAddress());
93  
94              return (ipAddress & subnetMask) == networkAddress;
95          }
96  
97          @Override
98          public IpFilterRuleType ruleType() {
99              return ruleType;
100         }
101 
102         private static int ipToInt(Inet4Address ipAddress) {
103             byte[] octets = ipAddress.getAddress();
104             assert octets.length == 4;
105 
106             return (octets[0] & 0xff) << 24 |
107                    (octets[1] & 0xff) << 16 |
108                    (octets[2] & 0xff) << 8 |
109                     octets[3] & 0xff;
110         }
111 
112         private static int prefixToSubnetMask(int cidrPrefix) {
113             /**
114              * Perform the shift on a long and downcast it to int afterwards.
115              * This is necessary to handle a cidrPrefix of zero correctly.
116              * The left shift operator on an int only uses the five least
117              * significant bits of the right-hand operand. Thus -1 << 32 evaluates
118              * to -1 instead of 0. The left shift operator applied on a long
119              * uses the six least significant bits.
120              *
121              * Also see https://github.com/netty/netty/issues/2767
122              */
123             return (int) ((-1L << 32 - cidrPrefix) & 0xffffffff);
124         }
125     }
126 
127     private static final class Ip6SubnetFilterRule implements IpFilterRule {
128 
129         private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
130 
131         private final BigInteger networkAddress;
132         private final BigInteger subnetMask;
133         private final IpFilterRuleType ruleType;
134 
135         private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
136             if (cidrPrefix < 0 || cidrPrefix > 128) {
137                 throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of " +
138                                                                     "[0,128]. The prefix was: %d", cidrPrefix));
139             }
140 
141             subnetMask = prefixToSubnetMask(cidrPrefix);
142             networkAddress = ipToInt(ipAddress).and(subnetMask);
143             this.ruleType = ruleType;
144         }
145 
146         @Override
147         public boolean matches(InetSocketAddress remoteAddress) {
148             BigInteger ipAddress = ipToInt((Inet6Address) remoteAddress.getAddress());
149 
150             return ipAddress.and(subnetMask).equals(networkAddress);
151         }
152 
153         @Override
154         public IpFilterRuleType ruleType() {
155             return ruleType;
156         }
157 
158         private static BigInteger ipToInt(Inet6Address ipAddress) {
159             byte[] octets = ipAddress.getAddress();
160             assert octets.length == 16;
161 
162             return new BigInteger(octets);
163         }
164 
165         private static BigInteger prefixToSubnetMask(int cidrPrefix) {
166             return MINUS_ONE.shiftLeft(128 - cidrPrefix);
167         }
168     }
169 }