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              final InetAddress inetAddress = remoteAddress.getAddress();
95              if (inetAddress instanceof Inet4Address) {
96                  int ipAddress = ipToInt((Inet4Address) inetAddress);
97                  return (ipAddress & subnetMask) == networkAddress;
98              }
99              return false;
100         }
101 
102         @Override
103         public IpFilterRuleType ruleType() {
104             return ruleType;
105         }
106 
107         private static int ipToInt(Inet4Address ipAddress) {
108             byte[] octets = ipAddress.getAddress();
109             assert octets.length == 4;
110 
111             return (octets[0] & 0xff) << 24 |
112                    (octets[1] & 0xff) << 16 |
113                    (octets[2] & 0xff) << 8 |
114                     octets[3] & 0xff;
115         }
116 
117         private static int prefixToSubnetMask(int cidrPrefix) {
118             /**
119              * Perform the shift on a long and downcast it to int afterwards.
120              * This is necessary to handle a cidrPrefix of zero correctly.
121              * The left shift operator on an int only uses the five least
122              * significant bits of the right-hand operand. Thus -1 << 32 evaluates
123              * to -1 instead of 0. The left shift operator applied on a long
124              * uses the six least significant bits.
125              *
126              * Also see https://github.com/netty/netty/issues/2767
127              */
128             return (int) ((-1L << 32 - cidrPrefix) & 0xffffffff);
129         }
130     }
131 
132     private static final class Ip6SubnetFilterRule implements IpFilterRule {
133 
134         private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
135 
136         private final BigInteger networkAddress;
137         private final BigInteger subnetMask;
138         private final IpFilterRuleType ruleType;
139 
140         private Ip6SubnetFilterRule(Inet6Address ipAddress, int cidrPrefix, IpFilterRuleType ruleType) {
141             if (cidrPrefix < 0 || cidrPrefix > 128) {
142                 throw new IllegalArgumentException(String.format("IPv6 requires the subnet prefix to be in range of " +
143                                                                     "[0,128]. The prefix was: %d", cidrPrefix));
144             }
145 
146             subnetMask = prefixToSubnetMask(cidrPrefix);
147             networkAddress = ipToInt(ipAddress).and(subnetMask);
148             this.ruleType = ruleType;
149         }
150 
151         @Override
152         public boolean matches(InetSocketAddress remoteAddress) {
153             final InetAddress inetAddress = remoteAddress.getAddress();
154             if (inetAddress instanceof Inet6Address) {
155                 BigInteger ipAddress = ipToInt((Inet6Address) inetAddress);
156                 return ipAddress.and(subnetMask).equals(networkAddress);
157             }
158             return false;
159         }
160 
161         @Override
162         public IpFilterRuleType ruleType() {
163             return ruleType;
164         }
165 
166         private static BigInteger ipToInt(Inet6Address ipAddress) {
167             byte[] octets = ipAddress.getAddress();
168             assert octets.length == 16;
169 
170             return new BigInteger(octets);
171         }
172 
173         private static BigInteger prefixToSubnetMask(int cidrPrefix) {
174             return MINUS_ONE.shiftLeft(128 - cidrPrefix);
175         }
176     }
177 }