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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7b96daa135 fix: use typed InetSocketAddress APIs instead of parsing 
toString() in TCP proxy (#6454)
7b96daa135 is described below

commit 7b96daa13521b6ecfa85e045ce25d68d7c63e597
Author: wy471x <[email protected]>
AuthorDate: Tue Jul 28 08:30:32 2026 +0800

    fix: use typed InetSocketAddress APIs instead of parsing toString() in TCP 
proxy (#6454)
    
    Replace brittle SocketAddress.toString() substring parsing with proper
    InetSocketAddress.getHostString() and InetAddress.equals() comparison.
    This fixes incorrect IP extraction for IPv6 addresses and makes upstream
    connection matching robust across address formats.
    
    Co-authored-by: Claude Opus 4.7 <[email protected]>
    Co-authored-by: aias00 <[email protected]>
---
 shenyu-protocol/shenyu-protocol-tcp/pom.xml        |   6 ++
 .../shenyu/protocol/tcp/TcpBootstrapServer.java    |   8 +-
 .../tcp/connection/ActivityConnectionObserver.java |  32 ++++++-
 .../protocol/tcp/TcpBootstrapServerTest.java       |  73 ++++++++++++++
 .../connection/ActivityConnectionObserverTest.java | 105 +++++++++++++++++++++
 5 files changed, 219 insertions(+), 5 deletions(-)

diff --git a/shenyu-protocol/shenyu-protocol-tcp/pom.xml 
b/shenyu-protocol/shenyu-protocol-tcp/pom.xml
index e7676a960d..b37d175ba1 100644
--- a/shenyu-protocol/shenyu-protocol-tcp/pom.xml
+++ b/shenyu-protocol/shenyu-protocol-tcp/pom.xml
@@ -55,6 +55,12 @@
             <version>${commons-io.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
 </project>
diff --git 
a/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServer.java
 
b/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServer.java
index 6ae6fc9580..cb7c73d769 100644
--- 
a/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServer.java
+++ 
b/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServer.java
@@ -34,6 +34,7 @@ import reactor.netty.DisposableServer;
 import reactor.netty.resources.LoopResources;
 import reactor.netty.tcp.TcpServer;
 
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.util.List;
 import java.util.Objects;
@@ -94,8 +95,11 @@ public class TcpBootstrapServer implements BootstrapServer {
         if (Objects.isNull(socketAddress)) {
             throw new NullPointerException("remoteAddress is null");
         }
-        String address = socketAddress.toString();
-        return address.substring(1, address.indexOf(':'));
+        if (socketAddress instanceof InetSocketAddress) {
+            return ((InetSocketAddress) socketAddress).getHostString();
+        }
+        LOG.error("Unsupported SocketAddress type: {}", 
socketAddress.getClass().getName());
+        throw new IllegalArgumentException("Unsupported SocketAddress type: " 
+ socketAddress.getClass().getName());
     }
 
     /**
diff --git 
a/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserver.java
 
b/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserver.java
index 9c8b1c5805..7deee777d9 100644
--- 
a/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserver.java
+++ 
b/shenyu-protocol/shenyu-protocol-tcp/src/main/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserver.java
@@ -25,9 +25,15 @@ import org.slf4j.LoggerFactory;
 import reactor.netty.Connection;
 import reactor.netty.ConnectionObserver;
 
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -86,11 +92,31 @@ public class ActivityConnectionObserver implements 
ConnectionObserver {
      * @return boolean
      */
     private boolean in(final List<DiscoveryUpstreamData> removeList, final 
SocketAddress cacheSocketAddress) {
+        if (!(cacheSocketAddress instanceof InetSocketAddress)) {
+            LOG.warn("Unsupported SocketAddress type: {}", 
cacheSocketAddress.getClass().getName());
+            return false;
+        }
+        InetSocketAddress inetAddr = (InetSocketAddress) cacheSocketAddress;
         return removeList.stream().anyMatch(u -> {
-            String cacheUrl = cacheSocketAddress.toString().substring(1);
             String removedUrl = u.getUrl();
-            LOG.info("compare {} , {}", cacheUrl, removedUrl);
-            return StringUtils.equals(cacheUrl, removedUrl);
+            if (inetAddr.isUnresolved() || 
Objects.isNull(inetAddr.getAddress())) {
+                String cacheUrl = inetAddr.getHostString() + ":" + 
inetAddr.getPort();
+                LOG.info("compare {} , {}", cacheUrl, removedUrl);
+                return StringUtils.equals(cacheUrl, removedUrl);
+            }
+            try {
+                URI uri = new URI("tcp://" + removedUrl);
+                if (inetAddr.getPort() != uri.getPort()) {
+                    return false;
+                }
+                InetAddress uriAddr = InetAddress.getByName(uri.getHost());
+                boolean matched = inetAddr.getAddress().equals(uriAddr);
+                LOG.info("compare {} , {} -> {}", inetAddr, removedUrl, 
matched);
+                return matched;
+            } catch (URISyntaxException | UnknownHostException e) {
+                LOG.warn("Failed to match upstream URL: {}", removedUrl, e);
+                return false;
+            }
         });
     }
 }
diff --git 
a/shenyu-protocol/shenyu-protocol-tcp/src/test/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServerTest.java
 
b/shenyu-protocol/shenyu-protocol-tcp/src/test/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServerTest.java
new file mode 100644
index 0000000000..2dc218265a
--- /dev/null
+++ 
b/shenyu-protocol/shenyu-protocol-tcp/src/test/java/org/apache/shenyu/protocol/tcp/TcpBootstrapServerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.protocol.tcp;
+
+import com.google.common.eventbus.EventBus;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test Case For {@link TcpBootstrapServer}.
+ */
+public class TcpBootstrapServerTest {
+
+    private final TcpBootstrapServer server = new TcpBootstrapServer(new 
EventBus());
+
+    @Test
+    public void testGetIpWithIpv4() throws Exception {
+        SocketAddress address = new InetSocketAddress("192.168.1.1", 8080);
+        String ip = invokeGetIp(address);
+        assertEquals("192.168.1.1", ip);
+    }
+
+    @Test
+    public void testGetIpWithIpv6() throws Exception {
+        SocketAddress address = new InetSocketAddress("2001:db8::1", 12345);
+        String ip = invokeGetIp(address);
+        assertTrue(ip.contains("2001:db8"));
+    }
+
+    @Test
+    public void testGetIpWithNull() {
+        InvocationTargetException ex = 
assertThrows(InvocationTargetException.class, () -> invokeGetIp(null));
+        assertTrue(ex.getCause() instanceof NullPointerException);
+    }
+
+    @Test
+    public void testGetIpWithUnsupportedAddressType() {
+        SocketAddress customAddress = new SocketAddress() {
+            private static final long serialVersionUID = 1L;
+        };
+        InvocationTargetException ex = 
assertThrows(InvocationTargetException.class, () -> invokeGetIp(customAddress));
+        assertTrue(ex.getCause() instanceof IllegalArgumentException);
+    }
+
+    private String invokeGetIp(final SocketAddress socketAddress) throws 
Exception {
+        Method method = TcpBootstrapServer.class.getDeclaredMethod("getIp", 
SocketAddress.class);
+        method.setAccessible(true);
+        return (String) method.invoke(server, socketAddress);
+    }
+}
diff --git 
a/shenyu-protocol/shenyu-protocol-tcp/src/test/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserverTest.java
 
b/shenyu-protocol/shenyu-protocol-tcp/src/test/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserverTest.java
new file mode 100644
index 0000000000..bd89cb6097
--- /dev/null
+++ 
b/shenyu-protocol/shenyu-protocol-tcp/src/test/java/org/apache/shenyu/protocol/tcp/connection/ActivityConnectionObserverTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.protocol.tcp.connection;
+
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test Case For {@link ActivityConnectionObserver}.
+ */
+public class ActivityConnectionObserverTest {
+
+    private final ActivityConnectionObserver observer = new 
ActivityConnectionObserver("TestObserver");
+
+    @Test
+    public void testInWithIpv4Match() throws Exception {
+        SocketAddress address = new InetSocketAddress("192.168.1.1", 8080);
+        List<DiscoveryUpstreamData> removeList = Collections.singletonList(
+                upstreamData("192.168.1.1:8080"));
+        assertTrue(invokeIn(removeList, address));
+    }
+
+    @Test
+    public void testInWithIpv4NoMatch() throws Exception {
+        SocketAddress address = new InetSocketAddress("192.168.1.1", 8080);
+        List<DiscoveryUpstreamData> removeList = Collections.singletonList(
+                upstreamData("192.168.1.2:8080"));
+        assertFalse(invokeIn(removeList, address));
+    }
+
+    @Test
+    public void testInWithIpv6Match() throws Exception {
+        SocketAddress address = new InetSocketAddress("2001:db8::1", 12345);
+        List<DiscoveryUpstreamData> removeList = Collections.singletonList(
+                upstreamData("[2001:db8::1]:12345"));
+        assertTrue(invokeIn(removeList, address));
+    }
+
+    @Test
+    public void testInWithIpv6NoMatch() throws Exception {
+        SocketAddress address = new InetSocketAddress("2001:db8::1", 12345);
+        List<DiscoveryUpstreamData> removeList = Collections.singletonList(
+                upstreamData("[2001:db8::2]:12345"));
+        assertFalse(invokeIn(removeList, address));
+    }
+
+    @Test
+    public void testInWithUnresolvedIpv6Match() throws Exception {
+        InetSocketAddress address = 
InetSocketAddress.createUnresolved("2001:db8::1", 12345);
+        List<DiscoveryUpstreamData> removeList = Collections.singletonList(
+                upstreamData("2001:db8::1:12345"));
+        assertTrue(invokeIn(removeList, address));
+    }
+
+    @Test
+    public void testInWithUnsupportedAddressType() throws Exception {
+        SocketAddress customAddress = new SocketAddress() {
+            private static final long serialVersionUID = 1L;
+        };
+        List<DiscoveryUpstreamData> removeList = Collections.singletonList(
+                upstreamData("192.168.1.1:8080"));
+        assertFalse(invokeIn(removeList, customAddress));
+    }
+
+    @Test
+    public void testInWithEmptyRemoveList() throws Exception {
+        SocketAddress address = new InetSocketAddress("192.168.1.1", 8080);
+        List<DiscoveryUpstreamData> removeList = Collections.emptyList();
+        assertFalse(invokeIn(removeList, address));
+    }
+
+    private boolean invokeIn(final List<DiscoveryUpstreamData> removeList, 
final SocketAddress socketAddress) throws Exception {
+        Method method = 
ActivityConnectionObserver.class.getDeclaredMethod("in", List.class, 
SocketAddress.class);
+        method.setAccessible(true);
+        return (boolean) method.invoke(observer, removeList, socketAddress);
+    }
+
+    private DiscoveryUpstreamData upstreamData(final String url) {
+        return DiscoveryUpstreamData.builder().url(url).build();
+    }
+}
\ No newline at end of file

Reply via email to