View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.net.util;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.math.BigInteger;
26  import java.net.Inet6Address;
27  import java.net.InetAddress;
28  import java.net.UnknownHostException;
29  
30  import org.apache.commons.net.util.SubnetUtils6.SubnetInfo;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Tests {@link SubnetUtils6}.
35   */
36  class SubnetUtils6Test {
37  
38      private static final BigInteger TWO = BigInteger.valueOf(2);
39  
40      private static void assertEquivalentSubnets(final String... cidrs) {
41          final SubnetInfo reference = new SubnetUtils6(cidrs[0]).getInfo();
42          for (int i = 1; i < cidrs.length; i++) {
43              final SubnetInfo other = new SubnetUtils6(cidrs[i]).getInfo();
44              assertEquals(reference.getNetworkAddress(), other.getNetworkAddress(),
45                  cidrs[0] + " vs " + cidrs[i] + " network");
46              assertEquals(reference.getHighAddress(), other.getHighAddress(),
47                  cidrs[0] + " vs " + cidrs[i] + " high");
48              assertEquals(reference.getAddress(), other.getAddress(),
49                  cidrs[0] + " vs " + cidrs[i] + " address");
50          }
51      }
52  
53      @Test
54      void testBasicCidr128() {
55          final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/128");
56          final SubnetInfo info = utils.getInfo();
57  
58          assertEquals(128, info.getPrefixLength());
59          assertEquals("2001:db8:0:0:0:0:0:1", info.getNetworkAddress());
60          assertEquals("2001:db8:0:0:0:0:0:1", info.getHighAddress());
61          assertEquals(BigInteger.ONE, info.getAddressCount());
62      }
63  
64      @Test
65      void testBasicCidr64() {
66          final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/64");
67          final SubnetInfo info = utils.getInfo();
68  
69          assertEquals(64, info.getPrefixLength());
70          assertEquals("2001:db8:0:0:0:0:0:1", info.getAddress());
71          assertEquals("2001:db8:0:0:0:0:0:0", info.getNetworkAddress());
72          assertEquals("2001:db8:0:0:ffff:ffff:ffff:ffff", info.getHighAddress());
73          // 2^64 addresses
74          assertEquals(TWO.pow(64), info.getAddressCount());
75      }
76  
77      @Test
78      void testCidr0() {
79          final SubnetUtils6 utils = new SubnetUtils6("::/0");
80          final SubnetInfo info = utils.getInfo();
81  
82          assertEquals(0, info.getPrefixLength());
83          assertEquals("0:0:0:0:0:0:0:0", info.getNetworkAddress());
84          // 2^128 addresses
85          assertEquals(TWO.pow(128), info.getAddressCount());
86      }
87  
88      @Test
89      void testCidr48() {
90          final SubnetUtils6 utils = new SubnetUtils6("2001:db8:abcd::/48");
91          final SubnetInfo info = utils.getInfo();
92  
93          assertEquals(48, info.getPrefixLength());
94          assertEquals("2001:db8:abcd:0:0:0:0:0", info.getNetworkAddress());
95          assertEquals("2001:db8:abcd:ffff:ffff:ffff:ffff:ffff", info.getHighAddress());
96      }
97  
98      @Test
99      void testCompressedAddress() {
100         final SubnetUtils6 utils = new SubnetUtils6("fe80::1/10");
101         final SubnetInfo info = utils.getInfo();
102 
103         assertEquals(10, info.getPrefixLength());
104         assertTrue(info.isInRange("fe80::1"));
105         assertTrue(info.isInRange("fe80::ffff"));
106         assertTrue(info.isInRange("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
107         assertFalse(info.isInRange("fec0::1")); // Outside /10 range
108     }
109 
110     @Test
111     void testConstructorWithSeparateArgs() {
112         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1", 64);
113         final SubnetInfo info = utils.getInfo();
114 
115         assertEquals(64, info.getPrefixLength());
116         assertEquals("2001:db8:0:0:0:0:0:0", info.getNetworkAddress());
117     }
118 
119     @Test
120     void testFullAddress() {
121         final SubnetUtils6 utils = new SubnetUtils6("2001:0db8:0000:0000:0000:0000:0000:0001/64");
122         final SubnetInfo info = utils.getInfo();
123 
124         assertEquals(64, info.getPrefixLength());
125         assertEquals("2001:db8:0:0:0:0:0:0", info.getNetworkAddress());
126     }
127 
128     @Test
129     void testGetCidrSignature() {
130         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/64");
131         final SubnetInfo info = utils.getInfo();
132 
133         assertEquals("2001:db8:0:0:0:0:0:1/64", info.getCidrSignature());
134     }
135 
136     @Test
137     void testGetLowAddress() {
138         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::100/120");
139         final SubnetInfo info = utils.getInfo();
140 
141         // getLowAddress returns the network address (same as getNetworkAddress)
142         assertEquals(info.getNetworkAddress(), info.getLowAddress());
143         assertEquals("2001:db8:0:0:0:0:0:100", info.getLowAddress());
144     }
145 
146     @Test
147     void testHighBitAddress() {
148         final SubnetUtils6 utils = new SubnetUtils6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
149         final SubnetInfo info = utils.getInfo();
150 
151         assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", info.getAddress());
152         assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", info.getNetworkAddress());
153         assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", info.getHighAddress());
154         assertEquals(BigInteger.ONE, info.getAddressCount());
155     }
156 
157     @Test
158     void testInvalidCidr() {
159         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6(null));
160         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1"));
161         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/"));
162         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/129"));
163         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/-1"));
164         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/abc"));
165         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("not-an-address/64"));
166     }
167 
168     @Test
169     void testInvalidIPv4Address() {
170         // IPv4 addresses should be rejected
171         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("192.168.1.1/24"));
172     }
173 
174     @Test
175     void testInvalidTwoArgConstructor() {
176         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1", 129));
177         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1", -1));
178         assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("not-an-address", 64));
179     }
180 
181     @Test
182     void testIsInRange() {
183         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
184         final SubnetInfo info = utils.getInfo();
185 
186         // Addresses in range
187         assertTrue(info.isInRange("2001:db8::1"));
188         assertTrue(info.isInRange("2001:db8::"));
189         assertTrue(info.isInRange("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"));
190         assertTrue(info.isInRange("2001:db8:1234:5678:9abc:def0:1234:5678"));
191 
192         // Addresses out of range
193         assertFalse(info.isInRange("2001:db9::1"));
194         assertFalse(info.isInRange("2001:db7::1"));
195         assertFalse(info.isInRange("2002:db8::1"));
196         assertFalse(info.isInRange("::1"));
197     }
198 
199     @Test
200     void testIsInRangeWithBigInteger() throws UnknownHostException {
201         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
202         final SubnetInfo info = utils.getInfo();
203 
204         // Test with null
205         assertFalse(info.isInRange((BigInteger) null));
206 
207         final BigInteger inRange = new BigInteger(1, InetAddress.getByName("2001:db8::1").getAddress());
208         assertTrue(info.isInRange(inRange));
209         final BigInteger outOfRange = new BigInteger(1, InetAddress.getByName("2001:db9::1").getAddress());
210         assertFalse(info.isInRange(outOfRange));
211     }
212 
213     @Test
214     void testIsInRangeWithByteArray() throws UnknownHostException {
215         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
216         final SubnetInfo info = utils.getInfo();
217 
218         // Test with null
219         assertFalse(info.isInRange((byte[]) null));
220 
221         // Test with wrong length
222         assertFalse(info.isInRange(new byte[4]));
223         assertFalse(info.isInRange(new byte[15]));
224         assertFalse(info.isInRange(new byte[17]));
225 
226         assertTrue(info.isInRange(InetAddress.getByName("2001:db8::1").getAddress()));
227         assertFalse(info.isInRange(InetAddress.getByName("2001:db9::1").getAddress()));
228     }
229 
230     @Test
231     void testIsInRangeWithInet6Address() throws UnknownHostException {
232         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
233         final SubnetInfo info = utils.getInfo();
234 
235         // Test with actual Inet6Address
236         final Inet6Address addr = (Inet6Address) InetAddress.getByName("2001:db8::1");
237         assertTrue(info.isInRange(addr));
238 
239         final Inet6Address addrOutside = (Inet6Address) InetAddress.getByName("2001:db9::1");
240         assertFalse(info.isInRange(addrOutside));
241 
242         // Test with null
243         assertFalse(info.isInRange((Inet6Address) null));
244     }
245 
246     @Test
247     void testIsInRangeWithInvalidString() {
248         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
249         final SubnetInfo info = utils.getInfo();
250 
251         assertThrows(IllegalArgumentException.class, () -> info.isInRange("not-an-address"));
252         assertThrows(IllegalArgumentException.class, () -> info.isInRange("192.168.1.1"));
253     }
254 
255     @Test
256     void testLinkLocalAddress() {
257         final SubnetUtils6 utils = new SubnetUtils6("fe80::/10");
258         final SubnetInfo info = utils.getInfo();
259 
260         assertTrue(info.isInRange("fe80::1"));
261         assertTrue(info.isInRange("fe80::1:2:3:4"));
262         assertFalse(info.isInRange("::1")); // Loopback is not link-local
263     }
264 
265     @Test
266     void testLoopbackAddress() {
267         final SubnetUtils6 utils = new SubnetUtils6("::1/128");
268         final SubnetInfo info = utils.getInfo();
269 
270         assertEquals(128, info.getPrefixLength());
271         assertTrue(info.isInRange("::1"));
272         assertFalse(info.isInRange("::2"));
273     }
274 
275     // All examples below are from https://datatracker.ietf.org/doc/html/rfc5952 to verify properly
276     /**
277      * RFC 5952 Section 1: all representations of the same address must parse identically.
278      */
279     @Test
280     void testRfc5952Section1EquivalentRepresentations() {
281         assertEquivalentSubnets(
282                 "2001:db8:0:0:1:0:0:1/128",
283                 "2001:0db8:0:0:1:0:0:1/128",
284                 "2001:db8::1:0:0:1/128",
285                 "2001:db8::0:1:0:0:1/128",
286                 "2001:0db8::1:0:0:1/128",
287                 "2001:db8:0:0:1::1/128",
288                 "2001:db8:0000:0:1::1/128",
289                 "2001:DB8:0:0:1::1/128"
290         );
291     }
292 
293     /**
294      * RFC 5952 Section 2.1: leading zeros in each 16-bit group must not affect parsing.
295      */
296     @Test
297     void testRfc5952Section21LeadingZeros() {
298         assertEquivalentSubnets(
299                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001/128",
300                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:001/128",
301                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:01/128",
302                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:1/128"
303         );
304     }
305 
306     /**
307      * RFC 5952 Section 2.2:  various :: compression positions must resolve to the same address.
308      */
309     @Test
310     void testRfc5952Section22ZeroCompression() {
311         assertEquivalentSubnets(
312                 "2001:db8:0:0:0:0:0:1/128",
313                 "2001:db8:0:0:0::1/128",
314                 "2001:db8:0:0::1/128",
315                 "2001:db8:0::1/128",
316                 "2001:db8::1/128"
317         );
318     }
319 
320     /**
321      * RFC 5952 Section 2.3:  uppercase, lowercase, and mixed-case hex digits must parse identically.
322      */
323     @Test
324     void testRfc5952Section23CaseInsensitivity() {
325         assertEquivalentSubnets(
326                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa/128",
327                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA/128",
328                 "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa/128",
329                 "2001:DB8:AAAA:BBBB:CCCC:DDDD:EEEE:AAAA/128"
330         );
331     }
332 
333     /**
334      * RFC 5952 Section 4.1:  canonical form suppresses leading zeros.
335      * Verifies that {@code 2001:0db8::0001} and {@code 2001:db8::1} produce the same output.
336      */
337     @Test
338     void testRfc5952Section41CanonicalLeadingZeros() {
339         final SubnetInfo a = new SubnetUtils6("2001:0db8::0001/128").getInfo();
340         final SubnetInfo b = new SubnetUtils6("2001:db8::1/128").getInfo();
341         assertEquals(a.getAddress(), b.getAddress());
342     }
343 
344     /**
345      * RFC 5952 Section 4.2.1: :: must compress the longest possible run.
346      * Both forms represent the same address.
347      */
348     @Test
349     void testRfc5952Section421MaximumShortening() {
350         final SubnetInfo a = new SubnetUtils6("2001:db8::0:1/128").getInfo();
351         final SubnetInfo b = new SubnetUtils6("2001:db8::1/128").getInfo();
352         assertEquals(a.getAddress(), b.getAddress());
353     }
354 
355     /**
356      * RFC 5952 Section 4.2.3:  when two zero runs of equal length exist, the first must be compressed.
357      * Both input forms must parse to the same address.
358      */
359     @Test
360     void testRfc5952Section423FirstLongestRunCompressed() {
361         assertEquivalentSubnets(
362                 "2001:db8:0:0:1:0:0:1/128",
363                 "2001:db8::1:0:0:1/128",
364                 "2001:db8:0:0:1::1/128"
365         );
366     }
367 
368     @Test
369     void testRfc5952Section5SpecialAddresses() {
370         final SubnetInfo loopback = new SubnetUtils6("::1/128").getInfo();
371         assertEquals("0:0:0:0:0:0:0:1", loopback.getAddress());
372 
373         final SubnetInfo unspecified = new SubnetUtils6("::/128").getInfo();
374         assertEquals("0:0:0:0:0:0:0:0", unspecified.getAddress());
375     }
376 
377     @Test
378     void testToString() {
379         final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/64");
380         final SubnetInfo info = utils.getInfo();
381         final String str = utils.toString();
382 
383         assertNotNull(str);
384         assertTrue(str.contains("CIDR Signature"));
385         assertTrue(str.contains("Network"));
386         assertTrue(str.contains("First address"));
387         assertTrue(str.contains("Last address"));
388         assertTrue(str.contains("Address Count"));
389 
390         // note: The CIDR signature from toString can be fed back into the constructor
391         final String cidr = info.getCidrSignature();
392         final SubnetUtils6 roundTrip = new SubnetUtils6(cidr);
393         final SubnetInfo roundTripInfo = roundTrip.getInfo();
394         assertEquals(info.getPrefixLength(), roundTripInfo.getPrefixLength());
395         assertEquals(info.getNetworkAddress(), roundTripInfo.getNetworkAddress());
396         assertEquals(info.getHighAddress(), roundTripInfo.getHighAddress());
397         assertEquals(info.getAddressCount(), roundTripInfo.getAddressCount());
398         assertEquals(info.getCidrSignature(), roundTripInfo.getCidrSignature());
399     }
400 
401     @Test
402     void testUniqueLocalAddress() {
403         // ULA range is fc00::/7
404         final SubnetUtils6 utils = new SubnetUtils6("fd00::/8");
405         final SubnetInfo info = utils.getInfo();
406 
407         assertTrue(info.isInRange("fd00::1"));
408         assertTrue(info.isInRange("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
409         assertFalse(info.isInRange("fc00::1")); // fc00::/8 is different from fd00::/8
410     }
411 }