This is an automated email from the ASF dual-hosted git repository.

kezhuw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 641cf0085 ZOOKEEPER-4240: Add IPV6 support for ZooKeeper ACL
641cf0085 is described below

commit 641cf0085d0bb84ed8f23214d1c2ce201c8f7f11
Author: Abhishek Kothalikar <99398985+kabhish...@users.noreply.github.com>
AuthorDate: Tue Aug 19 23:29:36 2025 +0530

    ZOOKEEPER-4240: Add IPV6 support for ZooKeeper ACL
    
    Reviewers: anmolnar, anmolnar, kezhuw
    Author: kabhishek4
    Closes #2280 from kabhishek4/ZOOKEEPER-4240
---
 .../server/auth/IPAuthenticationProvider.java      | 104 ++++++++++++-
 .../server/auth/IPAuthenticationProviderTest.java  |  90 +++++++++++
 .../java/org/apache/zookeeper/test/ACLTest.java    | 164 +++++++++++++++++++++
 3 files changed, 355 insertions(+), 3 deletions(-)

diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
index 26c14a4e8..0b4ef9a70 100644
--- 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
@@ -22,15 +22,26 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.StringTokenizer;
+import java.util.regex.Pattern;
 import javax.servlet.http.HttpServletRequest;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.Id;
 import org.apache.zookeeper.server.ServerCnxn;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class IPAuthenticationProvider implements AuthenticationProvider {
+    private static final Logger LOG = 
LoggerFactory.getLogger(IPAuthenticationProvider.class);
     public static final String X_FORWARDED_FOR_HEADER_NAME = "X-Forwarded-For";
 
     public static final String USE_X_FORWARDED_FOR_KEY = 
"zookeeper.IPAuthenticationProvider.usexforwardedfor";
+    private static final int IPV6_BYTE_LENGTH = 16; // IPv6 address is 128 
bits = 16 bytes
+    private static final int IPV6_SEGMENT_COUNT = 8; // IPv6 address has 8 
segments
+    private static final int IPV6_SEGMENT_BYTE_LENGTH = 2; // Each segment has 
up to two bytes
+    private static final int IPV6_SEGMENT_HEX_LENGTH = 4; // Each segment has 
up to 4 hex digits
+
+    private static final Pattern IPV6_PATTERN = Pattern.compile(":");
+    private static final Pattern IPV4_PATTERN = Pattern.compile("\\.");
 
     public String getScheme() {
         return "ip";
@@ -55,9 +66,14 @@ public List<Id> handleAuthentication(HttpServletRequest 
request, byte[] authData
     // This is a bit weird but we need to return the address and the number of
     // bytes (to distinguish between IPv4 and IPv6
     private byte[] addr2Bytes(String addr) {
-        byte[] b = v4addr2Bytes(addr);
-        // TODO Write the v6addr2Bytes
-        return b;
+        if (IPV6_PATTERN.matcher(addr).find()) {
+            return v6addr2Bytes(addr);
+        } else if (IPV4_PATTERN.matcher(addr).find()) {
+            return v4addr2Bytes(addr);
+        } else {
+            LOG.warn("Input string does not resemble an IPv4 or IPv6 address: 
{}", addr);
+            return null;
+        }
     }
 
     private byte[] v4addr2Bytes(String addr) {
@@ -81,6 +97,83 @@ private byte[] v4addr2Bytes(String addr) {
         return b;
     }
 
+    /**
+     * Validates an IPv6 address string and converts it into a byte array.
+     *
+     * @param ipv6Addr The IPv6 address string to validate.
+     * @return A byte array representing the IPv6 address if valid, or null if 
the address
+     * is invalid or cannot be parsed.
+     */
+    static byte[] v6addr2Bytes(String ipv6Addr) {
+        try {
+            return parseV6addr(ipv6Addr);
+        } catch (IllegalArgumentException e) {
+            LOG.warn("Fail to parse {} as IPv6 address: {}", ipv6Addr, 
e.getMessage());
+            return null;
+        }
+    }
+
+    static byte[] parseV6addr(String ipv6Addr) {
+        // Split the address by "::" to handle zero compression, -1 to keep 
trailing empty strings
+        String[] parts = ipv6Addr.split("::", -1);
+
+        String[] segments1 = new String[0];
+        String[] segments2 = new String[0];
+
+        // Case 1: No "::" (full address)
+        if (parts.length == 1) {
+            segments1 = parts[0].split(":", -1);
+            if (segments1.length != IPV6_SEGMENT_COUNT) {
+                String reason = "wrong number of segments";
+                throw new IllegalArgumentException(reason);
+            }
+        } else if (parts.length == 2) {
+            // Case 2: "::" is present
+            // Handle cases like "::1" or "1::"
+            if (!parts[0].isEmpty()) {
+                segments1 = parts[0].split(":", -1);
+            }
+            if (!parts[1].isEmpty()) {
+                segments2 = parts[1].split(":", -1);
+            }
+
+            // Check if the total number of explicit segments exceeds 8
+            if (segments1.length + segments2.length >= IPV6_SEGMENT_COUNT) {
+                String reason = "too many segments";
+                throw new IllegalArgumentException(reason);
+            }
+        } else {
+            // Case 3: Invalid number of parts after splitting by "::" (should 
be 1 or 2)
+            String reason = "too many '::'";
+            throw new IllegalArgumentException(reason);
+        }
+
+        byte[] result = new byte[IPV6_BYTE_LENGTH];
+        // Process segments before "::"
+        parseV6Segment(result, 0, segments1);
+        // Process segments after "::"
+        parseV6Segment(result, IPV6_BYTE_LENGTH - segments2.length * 
IPV6_SEGMENT_BYTE_LENGTH, segments2);
+
+        return result;
+    }
+
+    private static void parseV6Segment(byte[] addr, int i, String[] segments) {
+        for (String segment : segments) {
+            if (segment.isEmpty()) {
+                throw new IllegalArgumentException("empty segment");
+            } else if (segment.length() > IPV6_SEGMENT_HEX_LENGTH) {
+                throw new IllegalArgumentException("segment too long");
+            }
+            try {
+                int value = Integer.parseInt(segment, 16);
+                addr[i++] = (byte) ((value >> 8) & 0xFF);
+                addr[i++] = (byte) (value & 0xFF);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("invalid hexadecimal 
characters in segment: " + segment);
+            }
+        }
+    }
+
     private void mask(byte[] b, int bits) {
         int start = bits / 8;
         int startMask = (1 << (8 - (bits % 8))) - 1;
@@ -93,6 +186,7 @@ private void mask(byte[] b, int bits) {
     }
 
     public boolean matches(String id, String aclExpr) {
+        LOG.trace("id: '{}' aclExpr:  {}", id, aclExpr);
         String[] parts = aclExpr.split("/", 2);
         byte[] aclAddr = addr2Bytes(parts[0]);
         if (aclAddr == null) {
@@ -115,6 +209,10 @@ public boolean matches(String id, String aclExpr) {
             return false;
         }
         mask(remoteAddr, bits);
+        // Check if id and acl expression are of different formats (ipv6 or 
iv4) return false
+        if (remoteAddr.length != aclAddr.length) {
+            return false;
+        }
         for (int i = 0; i < remoteAddr.length; i++) {
             if (remoteAddr[i] != aclAddr[i]) {
                 return false;
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
index c1a1d1f52..1586cac39 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
@@ -19,13 +19,23 @@
 
 import static 
org.apache.zookeeper.server.auth.IPAuthenticationProvider.USE_X_FORWARDED_FOR_KEY;
 import static 
org.apache.zookeeper.server.auth.IPAuthenticationProvider.X_FORWARDED_FOR_HEADER_NAME;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import java.util.stream.Stream;
 import javax.servlet.http.HttpServletRequest;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 public class IPAuthenticationProviderTest {
 
@@ -96,4 +106,84 @@ public void testGetClientIPAddressMissingXForwardedFor() {
     // Assert
     assertEquals("192.168.1.1", clientIp);
   }
+
+  @Test
+  public void testParsingOfIPv6Address() {
+    //Full IPv6 address
+    String ipv6Full = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
+    byte[] expectedFull = {
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x85, (byte) 0xa3, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x8a, (byte) 0x2e,
+            (byte) 0x03, (byte) 0x70, (byte) 0x73, (byte) 0x34
+    };
+    byte[] actualFull = IPAuthenticationProvider.v6addr2Bytes(ipv6Full);
+    assertNotNull(actualFull, "Full IPv6 address should not return null");
+    assertArrayEquals(expectedFull, actualFull, "Full IPv6 address conversion 
mismatch");
+
+    //Compressed IPv6 address (double colon)
+    String ipv6Compressed = "2001:db8::8a2e:370:7334";
+    byte[] expectedCompressed = {
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x8a, (byte) 0x2e,
+            (byte) 0x03, (byte) 0x70, (byte) 0x73, (byte) 0x34
+    };
+    byte[] actualCompressed = 
IPAuthenticationProvider.v6addr2Bytes(ipv6Compressed);
+    assertNotNull(actualCompressed, "Compressed IPv6 address should not return 
null");
+    assertArrayEquals(expectedCompressed, actualCompressed, "Compressed IPv6 
address conversion mismatch");
+
+    //Shortened IPv6 address
+    String ipv6Shortened = "2001:db8::1";
+    byte[] expectedShortened = {
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
+    };
+    byte[] actualShortened = 
IPAuthenticationProvider.v6addr2Bytes(ipv6Shortened);
+    assertNotNull(actualShortened, "Shortened IPv6 address should not return 
null");
+    assertArrayEquals(expectedShortened, actualShortened, "Shortened IPv6 
address conversion mismatch");
+
+    //Loopback address
+    String ipv6Loopback = "::1";
+    byte[] expectedLoopback = {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
+    };
+    byte[] actualLoopback = 
IPAuthenticationProvider.v6addr2Bytes(ipv6Loopback);
+    assertNotNull(actualLoopback, "Loopback IPv6 address should not return 
null");
+    assertArrayEquals(expectedLoopback, actualLoopback, "Loopback IPv6 address 
conversion mismatch");
+  }
+
+  private static Stream<Arguments> invalidIPv6Addresses() {
+    return Stream.of(
+      Arguments.of("1", "wrong number of segments"),
+      Arguments.of("1:2", "wrong number of segments"),
+      Arguments.of("1::2:", "empty segment"),
+      Arguments.of(":1::2:", "empty segment"),
+      Arguments.of("1:2:3:4:5:6:7:8:", "wrong number of segments"),
+      Arguments.of("1:2:3:4:5:6:7:8:9", "wrong number of segments"),
+      Arguments.of("1:2::3:4:5:6:7:8", "too many segments"),
+      Arguments.of("1::2::", "too many '::'"),
+      Arguments.of("1:abcdf::", "segment too long"),
+      Arguments.of("efgh::", "invalid hexadecimal characters in segment"),
+      Arguments.of("1:: ", "invalid hexadecimal characters in segment"),
+      Arguments.of(" 1::", "invalid hexadecimal characters in segment")
+    );
+  }
+
+  @ParameterizedTest(name = "address = \"{0}\"")
+  @MethodSource("invalidIPv6Addresses")
+  public void testParsingOfInvalidIPv6Address(String ipv6Address, String 
expectedMessage) {
+    try {
+      IPAuthenticationProvider.parseV6addr(ipv6Address);
+      fail("expect failure");
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString(expectedMessage));
+    }
+    assertNull(IPAuthenticationProvider.v6addr2Bytes(ipv6Address));
+  }
 }
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
index e66592e49..d1f7c00de 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
@@ -23,11 +23,15 @@
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Properties;
 import java.util.concurrent.CountDownLatch;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -39,6 +43,7 @@
 import org.apache.zookeeper.ZKTestCase;
 import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.ZooDefs.Perms;
 import org.apache.zookeeper.ZooKeeper;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Id;
@@ -48,6 +53,7 @@
 import org.apache.zookeeper.server.SyncRequestProcessor;
 import org.apache.zookeeper.server.ZooKeeperServer;
 import org.apache.zookeeper.server.auth.IPAuthenticationProvider;
+import org.apache.zookeeper.server.embedded.ZooKeeperServerEmbedded;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.slf4j.Logger;
@@ -69,6 +75,38 @@ public void testIPAuthenticationIsValidCIDR() throws 
Exception {
         assertFalse(prov.isValid("10.0.0.1/-1"), "testing netmask too low");
     }
 
+    @Test
+    public void testIPAuthenticationIsValidIpv6CIDR() throws Exception {
+        IPAuthenticationProvider prov = new IPAuthenticationProvider();
+        assertTrue(prov.isValid("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 
"full address no netmask");
+        assertTrue(prov.isValid("2001:db8:85a3::8a2e:370:7334"), "compressed 
zeros");
+        assertTrue(prov.isValid("::1"), "loopback with compression");
+        assertTrue(prov.isValid("1::"), "Start with compression");
+        assertTrue(prov.isValid("2001:db8::/4"), "end with compression");
+        assertTrue(prov.isValid("0:0:0:0:0:0:0::/8"), "all zeros");
+        assertTrue(prov.isValid("2001:db8:85a3:0:0:0:0::/32"), "Explicit 
zeros");
+        assertTrue(prov.isValid("1234:5678:9abc:def0:1234:5678:9abc:def0"), 
"max hex value");
+        
assertFalse(prov.isValid("2001:db8:85a3:0000:0000:8a2e:0370:7334:extra"), "too 
many address segments");
+        assertFalse(prov.isValid("2001:db8:85a3:0000:0000:8a2e:0370"), "too 
few address segments");
+        assertFalse(prov.isValid("2001:db8:85a3::8a2e::0370:7334"), "multiple 
'::' not valid");
+        assertFalse(prov.isValid("2001:db8:85a3:G::8a2e:0370:7334"), "Invalid 
hex character");
+        assertFalse(prov.isValid(""), "empty string");
+        assertFalse(prov.isValid("2001:db8:85a3:0:0:0:0:1:2"), "too many 
segments post compression");
+        assertFalse(prov.isValid("2001:db8:85a3::8a2e:0370:7334:"), "trailing 
colon");
+        assertFalse(prov.isValid(":2001:db8:85a3::8a2e:0370:7334"), "Leading 
colon");
+        assertFalse(prov.isValid("::FFFF:192.168.1.1"), "IPv4-mapped");
+        assertTrue(prov.isValid("2001:db8:1234::/64"), "IPv6 address for 
multiple clients");
+    }
+
+    @Test
+    public void testIPAuthenticationIsValidIpv6Mask() throws Exception {
+        IPAuthenticationProvider prov = new IPAuthenticationProvider();
+        assertTrue(prov.matches("2001:db8:1234::", "2001:db8:1234::/64"));
+        assertTrue(prov.matches("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 
"2001:0db8:85a3:0000:0000:8a2e:0370::/2"));
+        assertFalse(prov.matches("22001:db8:85a3:0:0:0:0::0", 
"2001:db8:85a3:0:0:0:0::/32"));
+        assertFalse(prov.matches("2001:db8::/4", "2001:db8::/4"));
+    }
+
     @Test
     public void testNettyIpAuthDefault(@TempDir File tmpDir) throws Exception {
         String HOSTPORT = "127.0.0.1:" + PortAssignment.unique();
@@ -101,6 +139,132 @@ public void testNettyIpAuthDefault(@TempDir File tmpDir) 
throws Exception {
         }
     }
 
+    @Test
+    public void testAuthWithIPV6Server(@TempDir File tmpDir) throws Exception {
+        Properties properties = new Properties();
+        properties.setProperty("clientPortAddress", "::1");
+        properties.setProperty("clientPort", "0");
+
+        ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded.builder()
+            .baseDir(tmpDir.toPath())
+            .configuration(properties)
+            .build();
+        server.start();
+
+        String connectionString = server.getConnectionString();
+        String port = 
connectionString.substring(connectionString.lastIndexOf(':') + 1);
+
+        String hostport = String.format("[::1]:%s", port);
+        assertTrue(ClientBase.waitForServerUp(hostport, CONNECTION_TIMEOUT), 
"waiting for server being up");
+
+        // given: ipv6 client
+        ZooKeeper zk = ClientBase.createZKClient(hostport);
+
+        // when: invalid ipv6 network acl
+        // then: InvalidACL
+        assertThrows(KeeperException.InvalidACLException.class, () ->
+            zk.create("/invalid-ipv6-network-acl", null, 
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "::1/256"))), 
CreateMode.PERSISTENT));
+
+        // given: ipv4 network acl
+        zk.create("/unmatched-ipv4-network-acl", null, 
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "127.0.0.1/16"))), 
CreateMode.PERSISTENT);
+        // when: access with v6 ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/unmatched-ipv4-network-acl", null, -1));
+
+        // given: prefix matched ipv4 acl
+        zk.create("/prefix-matched-ipv4-acl", null, 
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "0.0.0.1/16"))), 
CreateMode.PERSISTENT);
+        // when: access with v6 ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/prefix-matched-ipv4-acl", null, -1));
+
+        // given: ipv6 with network acl
+        zk.create("/ipv6-network-acl", null, Collections.singletonList(new 
ACL(Perms.ALL, new Id("ip", "::1/64"))), CreateMode.PERSISTENT);
+        // when: access with valid ip
+        // then: ok
+        zk.setData("/ipv6-network-acl", null, -1);
+
+        // given: ipv6 acl
+        zk.create("/ipv6-acl", null, Collections.singletonList(new 
ACL(Perms.ALL, new Id("ip", "::1"))), CreateMode.PERSISTENT);
+        // when: access with valid ip
+        // then: ok
+        zk.setData("/ipv6-acl", null, -1);
+
+        // given: mismatched ipv6 with network acl
+        zk.create("/mismatched-ipv6-network-acl", null, 
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "0000:0001::/32"))), 
CreateMode.PERSISTENT);
+        // when: access with invalid ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/mismatched-ipv6-network-acl", null, -1));
+
+        // given: mismatched ipv6 acl
+        zk.create("/mismatched-ipv6-acl", null, Collections.singletonList(new 
ACL(Perms.ALL, new Id("ip", "::2"))), CreateMode.PERSISTENT);
+        // when: access with invalid ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/mismatched-ipv6-acl", null, -1));
+
+        server.close();
+    }
+
+    @Test
+    public void testAuthWithIPV4Server(@TempDir File tmpDir) throws Exception {
+        Properties properties = new Properties();
+        properties.setProperty("clientPortAddress", "127.0.0.1");
+        properties.setProperty("clientPort", "0");
+
+        ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded.builder()
+            .baseDir(tmpDir.toPath())
+            .configuration(properties)
+            .build();
+        server.start();
+
+        assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 
CONNECTION_TIMEOUT), "waiting for server being up");
+
+        // given: ipv4 client
+        ZooKeeper zk = ClientBase.createZKClient(server.getConnectionString());
+
+        // when: invalid ipv4 network acl
+        // then: InvalidACL
+        assertThrows(KeeperException.InvalidACLException.class, () ->
+            zk.create("/invalid-ipv4-network-acl", new byte[]{}, 
Arrays.asList(new ACL(Perms.ALL, new Id("ip", "127.0.0.1/64"))), 
CreateMode.PERSISTENT));
+
+        // given: ipv6 acl
+        zk.create("/mismatched-ipv6-acl", new byte[]{}, Arrays.asList(new 
ACL(Perms.ALL, new Id("ip", "::1"))), CreateMode.PERSISTENT);
+        // when: access with v4 ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/mismatched-ipv6-acl", null, -1));
+
+        // given: prefix matched ipv6 network acl
+        zk.create("/prefix-matched-ipv6-network-acl", new byte[]{}, 
Arrays.asList(new ACL(Perms.ALL, new Id("ip", "7f::/16"))), 
CreateMode.PERSISTENT);
+        // when: access with v4 ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/prefix-matched-ipv6-network-acl", null, -1));
+
+        // given: ipv4 with network acl
+        zk.create("/matched-ipv4-network-acl", new byte[]{}, Arrays.asList(new 
ACL(Perms.ALL, new Id("ip", "127.0.0.1/16"))), CreateMode.PERSISTENT);
+        // when: access with valid ip
+        // then: ok
+        zk.setData("/matched-ipv4-network-acl", null, -1);
+
+        // given: matched ipv4 acl
+        zk.create("/matched-ipv4-acl", new byte[]{}, Arrays.asList(new 
ACL(Perms.ALL, new Id("ip", "127.0.0.1"))), CreateMode.PERSISTENT);
+        // when: access with valid ip
+        // then: ok
+        zk.setData("/matched-ipv4-acl", null, -1);
+
+        // given: mismatched ipv4 network acl
+        zk.create("/mismatched-ipv4-network-acl", new byte[]{}, 
Arrays.asList(new ACL(Perms.ALL, new Id("ip", "192.168.0.2/16"))), 
CreateMode.PERSISTENT);
+        // when: access with invalid ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/mismatched-ipv4-network-acl", null, -1));
+
+        // given: mismatched ipv4 acl
+        zk.create("/mismatched-ipv4-acl", new byte[]{}, Arrays.asList(new 
ACL(Perms.ALL, new Id("ip", "127.0.0.2"))), CreateMode.PERSISTENT);
+        // when: access with invalid ip
+        // then: NoAuth
+        assertThrows(KeeperException.NoAuthException.class, () -> 
zk.setData("/mismatched-ipv4-acl", null, -1));
+
+        server.close();
+    }
+
     @Test
     public void testDisconnectedAddAuth(@TempDir File tmpDir) throws Exception 
{
         ClientBase.setupTestEnv();

Reply via email to