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