From 32e4315c8374cbff234168bbd7d4510f6052bc19 Mon Sep 17 00:00:00 2001
From: Valera V Harseko <vharseko@3a-systems.ru>
Date: Wed, 21 Oct 2026 08:51:52 +0000
Subject: [PATCH] CVE-2026-102648 DSMLv2 gateway: incomplete internal-address check allows SSRF when anyURI dereferencing is enabled
---
opendj-dsml-servlet/resources/webapp/web.xml | 11 +
opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java | 99 ++++++++++++++
opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java | 17 ++
opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/ByteStringUtility.java | 274 +++++++++++++++++++++++++++++++++++++-
4 files changed, 390 insertions(+), 11 deletions(-)
diff --git a/opendj-dsml-servlet/resources/webapp/web.xml b/opendj-dsml-servlet/resources/webapp/web.xml
index 30b4025..81476b2 100644
--- a/opendj-dsml-servlet/resources/webapp/web.xml
+++ b/opendj-dsml-servlet/resources/webapp/web.xml
@@ -103,6 +103,17 @@
<param-name>ldap.dsml.dereference.anyuri.maxsize</param-name>
<param-value>10485760</param-value>
</context-param>
+ If this network uses NAT64 with a Network-Specific Prefix (RFC 6052), declare
+ it here so that the IPv4 address embedded in a translated IPv6 address goes
+ through the same internal-address filter as the built-in transition prefixes
+ (an NSP is ordinary operator address space and cannot be detected
+ automatically). Comma-separated prefix/length entries; the length must be one
+ of 32, 40, 48, 56, 64 or 96.
+ <context-param>
+ <description>NAT64 Network-Specific Prefixes, e.g. 2001:db8:122:344::/96 (default none)</description>
+ <param-name>ldap.dsml.dereference.anyuri.nat64prefixes</param-name>
+ <param-value>2001:db8:122:344::/96</param-value>
+ </context-param>
-->
<context-param>
diff --git a/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/ByteStringUtility.java b/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/ByteStringUtility.java
index 1febf74..18cf2c7 100644
--- a/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/ByteStringUtility.java
+++ b/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/ByteStringUtility.java
@@ -23,9 +23,11 @@
import java.net.URI;
import java.net.URLConnection;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -66,6 +68,16 @@
private static volatile long maxUriContentLength = 10L * 1024 * 1024;
/**
+ * NAT64 Network-Specific Prefixes (RFC 6052 section 2.2) configured by the
+ * deployer. An NSP is chosen by an operator out of their own address space
+ * and is indistinguishable from an ordinary global address, so it cannot be
+ * detected here; deployments using NAT64 with an NSP must declare it so that
+ * the IPv4 address it embeds goes through the same filter as the built-in
+ * transition prefixes. Empty by default.
+ */
+ private static volatile List<Nat64Prefix> nat64Prefixes = Collections.emptyList();
+
+ /**
* Enables or disables server-side dereferencing of xsd:anyURI values.
*
* @param enabled {@code true} to fetch anyURI values (subject to the scheme
@@ -113,6 +125,30 @@
}
/**
+ * Sets the NAT64 Network-Specific Prefixes from a comma-separated list of
+ * {@code prefix/length} entries (e.g. {@code "2001:db8:122:344::/96"}). The
+ * length must be one of the RFC 6052 section 2.2 prefix lengths: 32, 40, 48,
+ * 56, 64 or 96. An empty string clears the list.
+ *
+ * @param prefixes comma-separated NAT64 prefixes in {@code prefix/length} form.
+ * @throws IllegalArgumentException if an entry is not a valid IPv6 literal
+ * with an RFC 6052 prefix length.
+ */
+ static void setNat64Prefixes(String prefixes)
+ {
+ List<Nat64Prefix> parsed = new ArrayList<>();
+ for (String entry : prefixes.split(","))
+ {
+ String trimmed = entry.trim();
+ if (!trimmed.isEmpty())
+ {
+ parsed.add(Nat64Prefix.valueOf(trimmed));
+ }
+ }
+ nat64Prefixes = Collections.unmodifiableList(parsed);
+ }
+
+ /**
* Returns a ByteString from a DsmlValue Object.
*
* @param obj
@@ -278,8 +314,13 @@
* on behalf of a request: loopback, wildcard, link-local (covers the
* 169.254.169.254 metadata endpoint), site-local/private, multicast, an
* IANA-reserved IPv4 range (including 100.64.0.0/10, home of the Alibaba
- * Cloud metadata endpoint 100.100.100.200), or an IPv6 unique-local address
- * (fc00::/7).
+ * Cloud metadata endpoint 100.100.100.200), an IPv6 unique-local address
+ * (fc00::/7), an address in the NAT64 local-use prefix 64:ff9b:1::/48
+ * (RFC 8215), or an IPv6 transition address (NAT64 well-known prefix
+ * 64:ff9b::/96, any NAT64 prefix declared via
+ * {@link #setNat64Prefixes(String)}, 6to4 2002::/16, Teredo 2001:0000::/32,
+ * IPv4-mapped/-compatible) that embeds an internal IPv4 address
+ * (GHSA-97x5-8pm7-xfp7).
*/
static boolean isInternalAddress(InetAddress address)
{
@@ -292,16 +333,227 @@
byte[] bytes = address.getAddress();
if (bytes.length == 4)
{
- int b0 = bytes[0] & 0xff;
- int b1 = bytes[1] & 0xff;
- return b0 == 0 // 0.0.0.0/8 "this network"
- || (b0 == 100 && (b1 & 0xc0) == 0x40) // 100.64.0.0/10 CGNAT
- || (b0 == 192 && b1 == 0 && (bytes[2] & 0xff) == 0) // 192.0.0.0/24 IETF assignments
- || (b0 == 198 && (b1 & 0xfe) == 18) // 198.18.0.0/15 benchmarking
- || b0 >= 240; // 240.0.0.0/4 reserved + broadcast
+ return isInternalIPv4(bytes);
}
- // IPv6 unique-local addresses (fc00::/7) are not covered by isSiteLocalAddress().
- return bytes.length == 16 && (bytes[0] & 0xfe) == 0xfc;
+ if (bytes.length == 16)
+ {
+ // IPv6 unique-local addresses (fc00::/7) are not covered by isSiteLocalAddress().
+ if ((bytes[0] & 0xfe) == 0xfc)
+ {
+ return true;
+ }
+ // The NAT64 local-use prefix 64:ff9b:1::/48 (RFC 8215) is translation-only
+ // space reserved for use within a site, and the operator chooses the
+ // sub-prefix inside it, so the offset of the embedded IPv4 address is not
+ // knowable here (a /64 sub-prefix puts it in bytes 9..12 rather than
+ // 12..15). RFC 6052 section 3.1 forbids representing non-global IPv4
+ // addresses with the well-known prefix and RFC 8215 exists to provide a
+ // prefix that may be used for exactly that, so this /48 is more likely to
+ // carry an internal target than the well-known prefix is. Nothing in it is
+ // a legitimate fetch target, so block the whole /48 rather than only the
+ // sub-prefixes whose layout can be guessed.
+ if (bytes[0] == 0x00 && bytes[1] == 0x64
+ && (bytes[2] & 0xff) == 0xff && (bytes[3] & 0xff) == 0x9b
+ && bytes[4] == 0x00 && bytes[5] == 0x01)
+ {
+ return true;
+ }
+ // A transition address may embed an internal IPv4 address that would
+ // otherwise bypass the IPv4 filter (GHSA-97x5-8pm7-xfp7).
+ byte[] embedded = embeddedIPv4(bytes);
+ return embedded != null && isInternalIPv4(embedded);
+ }
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if the given four-byte IPv4 address is loopback,
+ * private, link-local, multicast or otherwise reserved/non-routable. Unlike
+ * the {@link InetAddress} predicates, this operates on raw bytes so it can
+ * also classify an IPv4 address extracted from an IPv6 transition address.
+ */
+ private static boolean isInternalIPv4(byte[] v4)
+ {
+ int b0 = v4[0] & 0xff;
+ int b1 = v4[1] & 0xff;
+ int b2 = v4[2] & 0xff;
+ return b0 == 0 // 0.0.0.0/8 "this network"
+ || b0 == 10 // 10.0.0.0/8 private
+ || b0 == 127 // 127.0.0.0/8 loopback
+ || (b0 == 100 && (b1 & 0xc0) == 0x40) // 100.64.0.0/10 CGNAT
+ || (b0 == 169 && b1 == 254) // 169.254.0.0/16 link-local
+ || (b0 == 172 && (b1 & 0xf0) == 0x10) // 172.16.0.0/12 private
+ || (b0 == 192 && b1 == 168) // 192.168.0.0/16 private
+ || (b0 == 192 && b1 == 0 && b2 == 0) // 192.0.0.0/24 IETF assignments
+ || (b0 == 192 && b1 == 88 && b2 == 99) // 192.88.99.0/24 6to4 relay anycast (deprecated, RFC 7526)
+ || (b0 == 198 && (b1 & 0xfe) == 18) // 198.18.0.0/15 benchmarking
+ || (b0 & 0xf0) == 0xe0 // 224.0.0.0/4 multicast
+ || b0 >= 240; // 240.0.0.0/4 reserved + broadcast
+ }
+
+ /**
+ * Extracts the IPv4 address embedded in an IPv6 transition address, or
+ * {@code null} if none is embedded. Covers 6to4 (2002::/16), Teredo
+ * (2001:0000::/32, client address obfuscated), the NAT64 well-known prefix
+ * (64:ff9b::/96), any NAT64 Network-Specific Prefix declared via
+ * {@link #setNat64Prefixes(String)}, and IPv4-mapped/-compatible addresses
+ * (::ffff:0:0/96, ::/96). The NAT64 local-use prefix 64:ff9b:1::/48 (RFC 8215)
+ * is not decoded here: {@link #isInternalAddress(InetAddress)} rejects that
+ * whole /48, whatever sub-prefix layout the operator picked inside it.
+ */
+ private static byte[] embeddedIPv4(byte[] b)
+ {
+ int b0 = b[0] & 0xff;
+ int b1 = b[1] & 0xff;
+ // 6to4: 2002:V4ADDR::/16 -- IPv4 in bytes 2..5.
+ if (b0 == 0x20 && b1 == 0x02)
+ {
+ return new byte[] { b[2], b[3], b[4], b[5] };
+ }
+ // Teredo: 2001:0000::/32 -- client IPv4 in bytes 12..15, obfuscated (one's complement).
+ if (b0 == 0x20 && b1 == 0x01 && (b[2] & 0xff) == 0x00 && (b[3] & 0xff) == 0x00)
+ {
+ return new byte[] { (byte) ~b[12], (byte) ~b[13], (byte) ~b[14], (byte) ~b[15] };
+ }
+ // NAT64 well-known prefix: 64:ff9b::/96 -- IPv4 in bytes 12..15. RFC 6052
+ // defines the well-known prefix with a /96 length only, so this is complete;
+ // the local-use prefix 64:ff9b:1::/48 needs no decoding because
+ // isInternalAddress() rejects that whole /48 outright.
+ if (b0 == 0x00 && b1 == 0x64 && (b[2] & 0xff) == 0xff && (b[3] & 0xff) == 0x9b
+ && isZero(b, 4, 12))
+ {
+ return new byte[] { b[12], b[13], b[14], b[15] };
+ }
+ // IPv4-mapped (::ffff:0:0/96) and IPv4-compatible (::/96) -- IPv4 in bytes 12..15.
+ if (isZero(b, 0, 10)
+ && (((b[10] & 0xff) == 0xff && (b[11] & 0xff) == 0xff) // ::ffff:0:0/96 mapped
+ || ((b[10] & 0xff) == 0x00 && (b[11] & 0xff) == 0x00))) // ::/96 compatible
+ {
+ return new byte[] { b[12], b[13], b[14], b[15] };
+ }
+ // NAT64 Network-Specific Prefixes declared by the deployer (RFC 6052).
+ for (Nat64Prefix prefix : nat64Prefixes)
+ {
+ if (prefix.matches(b))
+ {
+ return prefix.extractIPv4(b);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * A NAT64 translation prefix (RFC 6052 section 2.2): a 16-byte IPv6 prefix
+ * and its length, one of 32, 40, 48, 56, 64 or 96 bits. The prefix length
+ * determines where the translated IPv4 address sits in the IPv6 address
+ * (bits 64..71, the "u" byte, never carry IPv4 bits).
+ */
+ private static final class Nat64Prefix
+ {
+ private final byte[] prefix;
+ private final int length;
+
+ private Nat64Prefix(byte[] prefix, int length)
+ {
+ this.prefix = prefix;
+ this.length = length;
+ }
+
+ /**
+ * Parses a {@code prefix/length} entry, e.g. {@code "2001:db8:122:344::/96"}.
+ *
+ * @throws IllegalArgumentException if the entry is malformed, the address
+ * part is not an IPv6 literal, or the length is not an RFC 6052
+ * prefix length.
+ */
+ static Nat64Prefix valueOf(String entry)
+ {
+ int slash = entry.indexOf('/');
+ if (slash < 0)
+ {
+ throw new IllegalArgumentException(
+ "NAT64 prefix must be in prefix/length form, but was: " + entry);
+ }
+ int length;
+ try
+ {
+ length = Integer.parseInt(entry.substring(slash + 1).trim());
+ }
+ catch (NumberFormatException e)
+ {
+ throw new IllegalArgumentException(
+ "NAT64 prefix length is not a number: " + entry);
+ }
+ if (length != 32 && length != 40 && length != 48 && length != 56 && length != 64
+ && length != 96)
+ {
+ throw new IllegalArgumentException("NAT64 prefix length must be one of "
+ + "32, 40, 48, 56, 64 or 96 (RFC 6052), but was: " + entry);
+ }
+ InetAddress address;
+ try
+ {
+ // The bracketed form only accepts an IP literal, so this cannot
+ // trigger a DNS lookup.
+ address = InetAddress.getByName("[" + entry.substring(0, slash).trim() + "]");
+ }
+ catch (UnknownHostException e)
+ {
+ throw new IllegalArgumentException(
+ "NAT64 prefix is not an IPv6 literal: " + entry);
+ }
+ byte[] bytes = address.getAddress();
+ if (bytes.length != 16)
+ {
+ throw new IllegalArgumentException(
+ "NAT64 prefix is not an IPv6 address: " + entry);
+ }
+ return new Nat64Prefix(bytes, length);
+ }
+
+ /** Returns {@code true} if the first {@code length} bits of {@code b} match this prefix. */
+ boolean matches(byte[] b)
+ {
+ for (int i = 0; i < length / 8; i++)
+ {
+ if (b[i] != prefix[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Extracts the embedded IPv4 address as laid out by RFC 6052 section 2.2
+ * for this prefix length: the 32 IPv4 bits follow the prefix directly,
+ * except that byte 8 (the "u" byte) is skipped.
+ */
+ byte[] extractIPv4(byte[] b)
+ {
+ switch (length)
+ {
+ case 32: return new byte[] { b[4], b[5], b[6], b[7] };
+ case 40: return new byte[] { b[5], b[6], b[7], b[9] };
+ case 48: return new byte[] { b[6], b[7], b[9], b[10] };
+ case 56: return new byte[] { b[7], b[9], b[10], b[11] };
+ case 64: return new byte[] { b[9], b[10], b[11], b[12] };
+ default: return new byte[] { b[12], b[13], b[14], b[15] }; // 96
+ }
+ }
+ }
+
+ /** Returns {@code true} if bytes {@code [from, to)} of {@code b} are all zero. */
+ private static boolean isZero(byte[] b, int from, int to)
+ {
+ for (int i = from; i < to; i++)
+ {
+ if (b[i] != 0)
+ {
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java b/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java
index b1e787e..15fefeb 100644
--- a/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java
+++ b/opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java
@@ -128,6 +128,7 @@
private static final String DEREF_ANYURI = "ldap.dsml.dereference.anyuri";
private static final String DEREF_ANYURI_SCHEMES = "ldap.dsml.dereference.anyuri.schemes";
private static final String DEREF_ANYURI_MAXSIZE = "ldap.dsml.dereference.anyuri.maxsize";
+ private static final String DEREF_ANYURI_NAT64PREFIXES = "ldap.dsml.dereference.anyuri.nat64prefixes";
private static final String MAX_BATCH_REQUESTS = "ldap.dsml.batchrequests.max";
private static final String REQUEST_MAXSIZE = "ldap.dsml.request.maxsize";
private static final String MAX_OPERATIONS = "ldap.dsml.batchrequest.operations.max";
@@ -247,6 +248,22 @@
{
ByteStringUtility.setMaxUriContentLength(positiveValue(DEREF_ANYURI_MAXSIZE, maxSize));
}
+ // NAT64 Network-Specific Prefixes cannot be detected from an address
+ // (an NSP is ordinary operator address space), so deployments using
+ // NAT64 with an NSP must declare it for the embedded IPv4 address to
+ // be filtered like the built-in transition prefixes.
+ String nat64Prefixes = stringValue(config, DEREF_ANYURI_NAT64PREFIXES);
+ if (nat64Prefixes != null && !nat64Prefixes.trim().isEmpty())
+ {
+ try
+ {
+ ByteStringUtility.setNat64Prefixes(nat64Prefixes);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new ServletException(DEREF_ANYURI_NAT64PREFIXES + ": " + e.getMessage());
+ }
+ }
}
// Every batchRequest element of a SOAP body is executed over its own
diff --git a/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java b/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java
index d42c479..bfcda39 100644
--- a/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java
+++ b/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java
@@ -46,6 +46,7 @@
ByteStringUtility.setDereferenceUri(false);
ByteStringUtility.setAllowedUriSchemes("http,https");
ByteStringUtility.setMaxUriContentLength(10L * 1024 * 1024);
+ ByteStringUtility.setNat64Prefixes("");
}
@Test
@@ -103,6 +104,13 @@
{ "http://[fe80::1]/" },
{ "http://[fc00::1]/" },
{ "http://[fd12:3456::1]/" },
+ // IPv6 transition addresses embedding an internal IPv4 (GHSA-97x5-8pm7-xfp7).
+ { "http://[64:ff9b::c0a8:0101]/" }, // NAT64 well-known -> 192.168.1.1
+ { "http://[64:ff9b::0a00:0001]/" }, // NAT64 well-known -> 10.0.0.1
+ { "http://[64:ff9b:1::7f00:1]/" }, // NAT64 local-use (RFC 8215) -> 127.0.0.1
+ { "http://[64:ff9b:1:0:c0:a801:100:0]/" }, // NAT64 local-use, /64 layout -> 192.168.1.1
+ { "http://[2002:0a00:0001::]/" }, // 6to4 -> 10.0.0.1
+ { "http://[2001:0000::3f57:fefe]/" }, // Teredo -> 192.168.1.1 (obfuscated)
};
}
@@ -143,6 +151,10 @@
// 192.0.0.0/24 boundaries
{ "192.0.0.1", true },
{ "192.0.1.1", false },
+ // 192.88.99.0/24 (deprecated 6to4 relay anycast, RFC 7526) boundaries
+ { "192.88.98.255", false },
+ { "192.88.99.1", true },
+ { "192.88.100.0", false },
// 198.18.0.0/15 boundaries
{ "198.17.255.255", false },
{ "198.18.0.0", true },
@@ -156,6 +168,33 @@
{ "fc00::1", true },
{ "fdff::1", true },
{ "fe00::1", false },
+ // IPv6 transition addresses embedding an internal IPv4 (GHSA-97x5-8pm7-xfp7).
+ { "64:ff9b::c0a8:0101", true }, // NAT64 well-known -> 192.168.1.1
+ { "64:ff9b::0a00:0001", true }, // NAT64 well-known -> 10.0.0.1
+ { "64:ff9b::7f00:0001", true }, // NAT64 well-known -> 127.0.0.1
+ { "64:ff9b::0808:0808", false }, // NAT64 well-known -> 8.8.8.8 (public)
+ // The NAT64 local-use prefix 64:ff9b:1::/48 (RFC 8215) is blocked outright,
+ // whichever RFC 6052 sub-prefix layout the operator picked inside it.
+ { "64:ff9b:1::7f00:1", true }, // /96 layout -> 127.0.0.1
+ { "64:ff9b:1::a00:1", true }, // /96 layout -> 10.0.0.1
+ { "64:ff9b:1:abcd::c0a8:101", true }, // /96 layout in a subnet of the /48 -> 192.168.1.1
+ { "64:ff9b:1:0:c0:a801:100:0", true }, // /64 layout -> 192.168.1.1
+ { "64:ff9b:1:0:a:102:300:0", true }, // /64 layout -> 10.1.2.3
+ { "64:ff9b:1:7f00:0:100::", true }, // /48 layout -> 127.0.0.1
+ { "64:ff9b:1::808:808", true }, // 8.8.8.8: public, but not reachable through this prefix
+ { "64:ff9b:1:ffff:ffff:ffff:ffff:ffff", true }, // no layout matches, still in the /48
+ // 64:ff9b:1::/48 boundaries.
+ { "64:ff9b:0:1::7f00:1", false }, // 64:ff9b:0:1, not the local-use prefix
+ { "64:ff9b:2::7f00:1", false }, // outside 64:ff9b::/96 and 64:ff9b:1::/48
+ { "64:ff9c:1::7f00:1", false }, // 64:ff9c:1, not the local-use prefix
+ { "2002:0a00:0001::", true }, // 6to4 -> 10.0.0.1
+ { "2002:c0a8:0101::", true }, // 6to4 -> 192.168.1.1
+ { "2002:c058:6301::", true }, // 6to4 -> 192.88.99.1 (blocked IPv4 via transition)
+ { "2002:0808:0808::", false }, // 6to4 -> 8.8.8.8 (public)
+ { "2001:0000::3f57:fefe", true }, // Teredo -> 192.168.1.1 (obfuscated)
+ { "2001:0000::f7f7:f7f7", false },// Teredo -> 8.8.8.8 (public, obfuscated)
+ // A regular global-unicast address under 2001::/16 must not be misread as Teredo/6to4.
+ { "2001:4860:4860::8844", false },
};
}
@@ -168,6 +207,66 @@
"isInternalAddress(" + literal + ")");
}
+ @Test
+ public void testNat64NetworkSpecificPrefixIsNotBlockedByDefault() throws Exception
+ {
+ // An NSP is ordinary operator address space: without configuration it
+ // cannot be told apart from a global address, so it must stay allowed.
+ assertFalse(isInternal("2001:db8:122:344::c0a8:101"));
+ }
+
+ @Test
+ public void testConfiguredNat64PrefixFiltersEmbeddedIPv4() throws Exception
+ {
+ ByteStringUtility.setNat64Prefixes("2001:db8:122:344::/96");
+ assertTrue(isInternal("2001:db8:122:344::c0a8:101")); // -> 192.168.1.1
+ assertTrue(isInternal("2001:db8:122:344::7f00:1")); // -> 127.0.0.1
+ assertFalse(isInternal("2001:db8:122:344::808:808")); // -> 8.8.8.8 (public)
+ assertFalse(isInternal("2001:db8:122:345::c0a8:101")); // outside the prefix
+ }
+
+ @Test
+ public void testConfiguredNat64PrefixWith64BitLength() throws Exception
+ {
+ // RFC 6052 /64 format: the IPv4 address sits in bytes 9..12, after the "u" byte.
+ ByteStringUtility.setNat64Prefixes("2001:db8:122:344::/64");
+ assertTrue(isInternal("2001:db8:122:344:a:0:100:0")); // -> 10.0.0.1
+ assertFalse(isInternal("2001:db8:122:344:8:808:800:0"));// -> 8.8.8.8 (public)
+ }
+
+ @Test
+ public void testMultipleNat64PrefixesAreSupported() throws Exception
+ {
+ ByteStringUtility.setNat64Prefixes(" 2001:db8:122:344::/96 , 2001:db8:9::/96 ");
+ assertTrue(isInternal("2001:db8:122:344::a00:1")); // -> 10.0.0.1
+ assertTrue(isInternal("2001:db8:9::a9fe:a9fe")); // -> 169.254.169.254
+ assertFalse(isInternal("2001:db8:9::101:101")); // -> 1.1.1.1 (public)
+ }
+
+ @DataProvider
+ public Object[][] invalidNat64Prefixes()
+ {
+ return new Object[][] {
+ { "2001:db8::" }, // no /length
+ { "2001:db8::/95" }, // not an RFC 6052 prefix length
+ { "2001:db8::/abc" }, // length is not a number
+ { "not-an-address/96" }, // not an IP literal (must not trigger DNS)
+ { "192.0.2.0/96" }, // IPv4, not IPv6
+ };
+ }
+
+ @Test(dataProvider = "invalidNat64Prefixes",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidNat64PrefixesAreRejected(String prefixes)
+ {
+ ByteStringUtility.setNat64Prefixes(prefixes);
+ }
+
+ private boolean isInternal(String literal) throws Exception
+ {
+ return ByteStringUtility.isInternalAddress(InetAddress.getByName(literal));
+ }
+
@DataProvider
public Object[][] redirectCodes()
{
--
Gitblit v1.10.0