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