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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 61d1b178d fix(nameserver): preserve named IPv6 zone identifiers (#4522)
61d1b178d is described below

commit 61d1b178d8ab95a7963471fc63b7c9ae6b975488
Author: jokerzsd <[email protected]>
AuthorDate: Mon Sep 21 21:00:07 2026 +0800

    fix(nameserver): preserve named IPv6 zone identifiers (#4522)
    
    `NamesrvAddrParser.normalizeSegment` lowercased the whole bracketed IPv6 
literal, zone identifier included, so registering `[FE80::ABCD%ProdNIC]:9876` 
persisted `%prodnic` in `rmq_nameserver.namesrv_addr`. The address reaches that 
line because `commons-validator`'s `isValidInet6Address` splits off the `%zone` 
suffix and validates it separately, so a zoned literal passes 
`isValidIpv6Literal`. A named zone is a local interface name that the JDK 
matches against `NetworkInterface.getName( [...]
    
    The literal is now split at the first `%`: only the address part is 
lowercased and the zone keeps its exact spelling. The DNS branch still 
lowercases hostnames, which are case-insensitive, and 
`NameServerConfigDiffService.canonicalAddressKey` already had its own `%` 
guard. The registry create and update tests became parameterized so both cover 
a named-zone address end to end.
    
    Fixes #4520
---
 .../cluster/nameserver/NamesrvAddrParser.java      |  6 ++++-
 .../nameserver/NameserverRegistryServiceTest.java  | 30 ++++++++++++++--------
 .../cluster/nameserver/NamesrvAddrParserTest.java  | 12 +++++++++
 3 files changed, 37 insertions(+), 11 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParser.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParser.java
index a064f5969..9bebcfd4d 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParser.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParser.java
@@ -65,7 +65,11 @@ public final class NamesrvAddrParser {
             if (!isValidIpv6Literal(ipv6)) {
                 throw new BusinessException(400, "namesrvAddr segment has a 
malformed IPv6 literal: " + segment);
             }
-            normalizedHost = "[" + ipv6.toLowerCase(Locale.ROOT) + "]";
+            int zoneStart = ipv6.indexOf('%');
+            String address = zoneStart < 0 ? ipv6 : ipv6.substring(0, 
zoneStart);
+            // Named zones identify local interfaces and must retain their 
exact spelling.
+            String zone = zoneStart < 0 ? "" : ipv6.substring(zoneStart);
+            normalizedHost = "[" + address.toLowerCase(Locale.ROOT) + zone + 
"]";
         } else {
             if (host.isEmpty()) {
                 throw new BusinessException(400, "namesrvAddr segment is 
missing a host: " + segment);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
index 29146acf5..29a50f398 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
@@ -22,6 +22,8 @@ import 
org.apache.rocketmq.studio.persistence.entity.RmqNameserver;
 import org.apache.rocketmq.studio.persistence.mapper.RmqNameserverMapper;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -153,8 +155,12 @@ class NameserverRegistryServiceTest {
         verify(nameserverMapper, never()).insert(any(RmqNameserver.class));
     }
 
-    @Test
-    void createShouldNormalizeAddrBeforePersistTest() {
+    @ParameterizedTest
+    @CsvSource({
+        "' NS1:9876 ; ns2:9876 ', 'ns1:9876,ns2:9876'",
+        "'[FE80::ABCD%ProdNIC]:9876', '[fe80::abcd%ProdNIC]:9876'"
+    })
+    void createShouldNormalizeAddrBeforePersistTest(String input, String 
expected) {
         when(nameserverMapper.selectCount(any())).thenReturn(0L);
         
when(nameserverMapper.insert(any(RmqNameserver.class))).thenAnswer(invocation 
-> {
             RmqNameserver entity = invocation.getArgument(0);
@@ -164,17 +170,17 @@ class NameserverRegistryServiceTest {
         RmqNameserver stored = new RmqNameserver();
         stored.setId(11L);
         stored.setName("prod");
-        stored.setNamesrvAddr("ns1:9876,ns2:9876");
+        stored.setNamesrvAddr(expected);
         when(nameserverMapper.selectById(11L)).thenReturn(stored);
 
         service.create(CreateNameserverRegistryDTO.builder()
                 .name("prod")
-                .namesrvAddr(" NS1:9876 ; ns2:9876 ")
+                .namesrvAddr(input)
                 .build());
 
         ArgumentCaptor<RmqNameserver> captor = 
ArgumentCaptor.forClass(RmqNameserver.class);
         verify(nameserverMapper).insert(captor.capture());
-        
assertThat(captor.getValue().getNamesrvAddr()).isEqualTo("ns1:9876,ns2:9876");
+        assertThat(captor.getValue().getNamesrvAddr()).isEqualTo(expected);
     }
 
     @Test
@@ -220,8 +226,12 @@ class NameserverRegistryServiceTest {
                 .hasMessageContaining("deleted concurrently");
     }
 
-    @Test
-    void updateShouldPersistAndReturnStoredEntryTest() {
+    @ParameterizedTest
+    @CsvSource({
+        "rocketmq1-nameserver.svc:9876, rocketmq1-nameserver.svc:9876",
+        "[FE80::ABCD%ProdNIC]:9876, [fe80::abcd%ProdNIC]:9876"
+    })
+    void updateShouldPersistAndReturnStoredEntryTest(String input, String 
expected) {
         RmqNameserver existing = new RmqNameserver();
         existing.setId(1L);
         existing.setName("rocketmq1");
@@ -232,13 +242,13 @@ class NameserverRegistryServiceTest {
         NameserverRegistryVO updated = 
service.update(UpdateNameserverRegistryDTO.builder()
                 .id(1L)
                 .name("rocketmq1")
-                .namesrvAddr("rocketmq1-nameserver.svc:9876")
+                .namesrvAddr(input)
                 .k8sNamespace("rocketmq1")
                 .build());
 
-        
assertThat(updated.getNamesrvAddr()).isEqualTo("rocketmq1-nameserver.svc:9876");
+        assertThat(updated.getNamesrvAddr()).isEqualTo(expected);
         verify(nameserverMapper).updateById(existing);
-        
assertThat(existing.getNamesrvAddr()).isEqualTo("rocketmq1-nameserver.svc:9876");
+        assertThat(existing.getNamesrvAddr()).isEqualTo(expected);
     }
 
     @Test
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParserTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParserTest.java
index 24ffd8687..b5180fc39 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParserTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NamesrvAddrParserTest.java
@@ -59,6 +59,18 @@ class NamesrvAddrParserTest {
         
assertThat(NamesrvAddrParser.normalize("[2001:DB8::1]:9876")).isEqualTo("[2001:db8::1]:9876");
     }
 
+    @Test
+    void preservesNamedIpv6ZoneWhileNormalizingAddressAndSeparatorsTest() {
+        assertThat(NamesrvAddrParser.normalize(" [FE80::ABCD%ProdNIC]:9876 ; 
NS1.Example.COM:9876 "))
+                .isEqualTo("[fe80::abcd%ProdNIC]:9876,ns1.example.com:9876");
+    }
+
+    @Test
+    void preservesNumericIpv6ZoneTest() {
+        assertThat(NamesrvAddrParser.normalize("[FE80::ABCD%12]:9876"))
+                .isEqualTo("[fe80::abcd%12]:9876");
+    }
+
     @Test
     void rejectsBlankInputTest() {
         assertThatThrownBy(() -> NamesrvAddrParser.normalize("   "))

Reply via email to