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]

Reply via email to