This is an automated email from the ASF dual-hosted git repository.
Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 52b8fdcb5d8 Add isPrivateIp scalar function for IP address
classification (#18828)
52b8fdcb5d8 is described below
commit 52b8fdcb5d8028f2014feffbc73a358abd804d9d
Author: Akanksha kedia <[email protected]>
AuthorDate: Tue Jun 23 01:09:26 2026 +0530
Add isPrivateIp scalar function for IP address classification (#18828)
---
.../common/function/scalar/IpAddressFunctions.java | 52 +++++++++++++++
.../function/scalar/IpAddressFunctionsTest.java | 78 ++++++++++++++++++++++
2 files changed, 130 insertions(+)
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/IpAddressFunctions.java
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/IpAddressFunctions.java
index 9790f1a6ace..446e6e74b1c 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/IpAddressFunctions.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/IpAddressFunctions.java
@@ -38,6 +38,25 @@ public class IpAddressFunctions {
private IpAddressFunctions() {
}
+ // RFC 1918 private IPv4 ranges (10/8, 172.16/12, 192.168/16) used by
isPrivateIp().
+ // Initialised once at class-load; the literals are always valid so no
runtime failure is possible.
+ private static final IPAddress IPV4_PRIVATE_10;
+ private static final IPAddress IPV4_PRIVATE_172;
+ private static final IPAddress IPV4_PRIVATE_192;
+ // IPv6 Unique Local Address range fc00::/7 (covers fd00::/8 ULA and
fc00::/8)
+ private static final IPAddress IPV6_ULA;
+
+ static {
+ try {
+ IPV4_PRIVATE_10 = new
IPAddressString("10.0.0.0/8").toAddress().toPrefixBlock();
+ IPV4_PRIVATE_172 = new
IPAddressString("172.16.0.0/12").toAddress().toPrefixBlock();
+ IPV4_PRIVATE_192 = new
IPAddressString("192.168.0.0/16").toAddress().toPrefixBlock();
+ IPV6_ULA = new IPAddressString("fc00::/7").toAddress().toPrefixBlock();
+ } catch (AddressStringException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
/**
* Validates IP prefix prefixStr and returns IPAddress if validated
*/
@@ -333,4 +352,37 @@ public class IpAddressFunctions {
public static String ipHostmask(String cidr) {
return buildMask(getPrefix(cidr), true);
}
+
+ /**
+ * Returns whether the given IP address belongs to a private or reserved
range.
+ *
+ * <p>The following ranges are considered private:
+ * <ul>
+ * <li>IPv4 RFC 1918: {@code 10.0.0.0/8}, {@code 172.16.0.0/12}, {@code
192.168.0.0/16}</li>
+ * <li>IPv4 loopback: {@code 127.0.0.0/8}</li>
+ * <li>IPv4 link-local: {@code 169.254.0.0/16}</li>
+ * <li>IPv6 loopback: {@code ::1}</li>
+ * <li>IPv6 link-local: {@code fe80::/10}</li>
+ * <li>IPv6 Unique Local Address (ULA): {@code fc00::/7}</li>
+ * </ul>
+ *
+ * @param ipString IP address string without a prefix length (e.g., {@code
"192.168.1.1"})
+ * @return {@code true} if the address is private or reserved
+ * @throws IllegalArgumentException if the string is not a valid
non-prefixed IP address
+ */
+ @ScalarFunction(names = {"isPrivateIp", "is_private_ip"})
+ public static boolean isPrivateIp(String ipString) {
+ IPAddress ip = getAddress(ipString);
+ if (ip.isPrefixed()) {
+ throw new IllegalArgumentException("IP Address " + ipString + " should
not be prefixed.");
+ }
+ // isLoopback() covers 127.0.0.0/8 (IPv4) and ::1 (IPv6)
+ // isLinkLocal() covers 169.254.0.0/16 (IPv4) and fe80::/10 (IPv6)
+ return ip.isLoopback()
+ || ip.isLinkLocal()
+ || IPV4_PRIVATE_10.contains(ip)
+ || IPV4_PRIVATE_172.contains(ip)
+ || IPV4_PRIVATE_192.contains(ip)
+ || IPV6_ULA.contains(ip);
+ }
}
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/IpAddressFunctionsTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/IpAddressFunctionsTest.java
index adb24fec3f1..1e1e5ba7c0f 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/IpAddressFunctionsTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/IpAddressFunctionsTest.java
@@ -548,4 +548,82 @@ public class IpAddressFunctionsTest {
}
}
}
+
+ // ==================== Tests for isPrivateIp ====================
+
+ @Test
+ public void testIsPrivateIpRfc1918() {
+ // 10.0.0.0/8
+ assertTrue(IpAddressFunctions.isPrivateIp("10.0.0.1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("10.255.255.255"));
+ assertTrue(IpAddressFunctions.isPrivateIp("10.128.0.1"));
+
+ // 172.16.0.0/12
+ assertTrue(IpAddressFunctions.isPrivateIp("172.16.0.1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("172.31.255.255"));
+ assertTrue(IpAddressFunctions.isPrivateIp("172.20.10.5"));
+ assertFalse(IpAddressFunctions.isPrivateIp("172.32.0.1")); // just outside
/12
+
+ // 192.168.0.0/16
+ assertTrue(IpAddressFunctions.isPrivateIp("192.168.0.1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("192.168.255.255"));
+ assertTrue(IpAddressFunctions.isPrivateIp("192.168.100.200"));
+ }
+
+ @Test
+ public void testIsPrivateIpLoopback() {
+ // IPv4 loopback 127.0.0.0/8
+ assertTrue(IpAddressFunctions.isPrivateIp("127.0.0.1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("127.255.255.255"));
+
+ // IPv6 loopback
+ assertTrue(IpAddressFunctions.isPrivateIp("::1"));
+ }
+
+ @Test
+ public void testIsPrivateIpLinkLocal() {
+ // IPv4 link-local 169.254.0.0/16
+ assertTrue(IpAddressFunctions.isPrivateIp("169.254.0.1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("169.254.255.255"));
+
+ // IPv6 link-local fe80::/10
+ assertTrue(IpAddressFunctions.isPrivateIp("fe80::1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("fe80::abcd:1234"));
+ }
+
+ @Test
+ public void testIsPrivateIpIPv6Ula() {
+ // fc00::/7 covers fc::/8 and fd::/8
+ assertTrue(IpAddressFunctions.isPrivateIp("fd00::1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("fc00::1"));
+ assertTrue(IpAddressFunctions.isPrivateIp("fdab:cdef:1234::1"));
+ }
+
+ @Test
+ public void testIsPrivateIpPublicAddresses() {
+ // Public IPv4 — must NOT be private
+ assertFalse(IpAddressFunctions.isPrivateIp("8.8.8.8"));
+ assertFalse(IpAddressFunctions.isPrivateIp("1.1.1.1"));
+ assertFalse(IpAddressFunctions.isPrivateIp("203.0.113.1"));
+ assertFalse(IpAddressFunctions.isPrivateIp("198.18.0.1")); // TEST-NET-3
is public
+
+ // Public IPv6 — must NOT be private
+ assertFalse(IpAddressFunctions.isPrivateIp("2001:db8::1")); //
documentation prefix
+ assertFalse(IpAddressFunctions.isPrivateIp("2606:4700::1")); // Cloudflare
+ }
+
+ @Test
+ public void testIsPrivateIpInvalidInputs() {
+ // CIDR notation not accepted
+ assertThrows(IllegalArgumentException.class,
+ () -> IpAddressFunctions.isPrivateIp("10.0.0.0/8"));
+ assertThrows(IllegalArgumentException.class,
+ () -> IpAddressFunctions.isPrivateIp("fc00::/7"));
+
+ // Invalid formats
+ assertThrows(IllegalArgumentException.class,
+ () -> IpAddressFunctions.isPrivateIp("not-an-ip"));
+ assertThrows(IllegalArgumentException.class,
+ () -> IpAddressFunctions.isPrivateIp("999.999.999.999"));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]