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

jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new 1c0b0c7069d [Pipe] Prevent unexpected OPC UA endpoint redirects 
(#18259) (#18269)
1c0b0c7069d is described below

commit 1c0b0c7069d535607402650a8b316bf1f0927376
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 22 11:55:51 2026 +0800

    [Pipe] Prevent unexpected OPC UA endpoint redirects (#18259) (#18269)
    
    * [Pipe] Prevent unexpected OPC UA endpoint redirects
    
    * [Pipe] Add advertised host for internal OPC UA server
---
 .../db/pipe/sink/protocol/opcua/OpcUaSink.java     |  29 +++-
 .../sink/protocol/opcua/client/ClientRunner.java   |  88 ++++++++++-
 .../protocol/opcua/client/IoTDBOpcUaClient.java    |  12 +-
 .../protocol/opcua/server/OpcUaKeyStoreLoader.java |  21 ++-
 .../sink/protocol/opcua/server/OpcUaNameSpace.java |   2 +
 .../protocol/opcua/server/OpcUaServerBuilder.java  | 141 ++++++++++++++++--
 .../protocol/opcua/client/ClientRunnerTest.java    | 147 ++++++++++++++++++
 .../opcua/server/OpcUaServerBuilderTest.java       | 164 +++++++++++++++++++++
 .../pipe/config/constant/PipeSinkConstant.java     |   9 ++
 9 files changed, 579 insertions(+), 34 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java
index 654395efe8d..d6cf17c07ad 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java
@@ -63,6 +63,9 @@ import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CON
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USERNAME_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USER_DEFAULT_VALUE;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USER_KEY;
+import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ADVERTISED_HOST_KEY;
+import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ALLOW_ENDPOINT_REDIRECT_DEFAULT_VALUE;
+import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ALLOW_ENDPOINT_REDIRECT_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEBOUNCE_TIME_MS_DEFAULT_VALUE;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEBOUNCE_TIME_MS_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEFAULT_QUALITY_BAD_VALUE;
@@ -103,6 +106,8 @@ import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CON
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_PASSWORD_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_USERNAME_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_USER_KEY;
+import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_ADVERTISED_HOST_KEY;
+import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_ALLOW_ENDPOINT_REDIRECT_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_DEBOUNCE_TIME_MS_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_DEFAULT_QUALITY_KEY;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY;
@@ -238,6 +243,9 @@ public class OpcUaSink implements PipeConnector {
         parameters.getIntOrDefault(
             Arrays.asList(CONNECTOR_OPC_UA_HTTPS_BIND_PORT_KEY, 
SINK_OPC_UA_HTTPS_BIND_PORT_KEY),
             CONNECTOR_OPC_UA_HTTPS_BIND_PORT_DEFAULT_VALUE);
+    final String advertisedHost =
+        parameters.getStringByKeys(
+            CONNECTOR_OPC_UA_ADVERTISED_HOST_KEY, 
SINK_OPC_UA_ADVERTISED_HOST_KEY);
 
     final String user =
         parameters.getStringOrDefault(
@@ -300,6 +308,7 @@ public class OpcUaSink implements PipeConnector {
                             new OpcUaServerBuilder()
                                 .setTcpBindPort(tcpBindPort)
                                 .setHttpsBindPort(httpsBindPort)
+                                .setAdvertisedHost(advertisedHost)
                                 .setUser(user)
                                 .setPassword(password)
                                 .setSecurityDir(securityDir)
@@ -315,6 +324,7 @@ public class OpcUaSink implements PipeConnector {
                         oldValue
                             .getRight()
                             .checkEquals(
+                                advertisedHost,
                                 user,
                                 password,
                                 securityDir,
@@ -368,6 +378,12 @@ public class OpcUaSink implements PipeConnector {
         parameters.getLongOrDefault(
             Arrays.asList(CONNECTOR_OPC_UA_TIMEOUT_SECONDS_KEY, 
SINK_OPC_UA_TIMEOUT_SECONDS_KEY),
             CONNECTOR_OPC_UA_TIMEOUT_SECONDS_DEFAULT_VALUE);
+    final boolean allowEndpointRedirect =
+        parameters.getBooleanOrDefault(
+            Arrays.asList(
+                CONNECTOR_OPC_UA_ALLOW_ENDPOINT_REDIRECT_KEY,
+                SINK_OPC_UA_ALLOW_ENDPOINT_REDIRECT_KEY),
+            CONNECTOR_OPC_UA_ALLOW_ENDPOINT_REDIRECT_DEFAULT_VALUE);
 
     synchronized (CLIENT_KEY_TO_REFERENCE_COUNT_AND_CLIENT_MAP) {
       client =
@@ -387,11 +403,20 @@ public class OpcUaSink implements PipeConnector {
                                       SINK_OPC_UA_HISTORIZING_KEY),
                                   CONNECTOR_OPC_UA_HISTORIZING_DEFAULT_VALUE));
                       final ClientRunner runner =
-                          new ClientRunner(result, securityDir, password, 
userName, timeoutSeconds);
+                          new ClientRunner(
+                              result,
+                              securityDir,
+                              password,
+                              userName,
+                              timeoutSeconds,
+                              allowEndpointRedirect);
                       runner.run();
                       return new Pair<>(new AtomicInteger(0), result);
                     }
-                    oldValue.getRight().checkEquals(userName, password, 
securityDir, policy);
+                    oldValue
+                        .getRight()
+                        .checkEquals(
+                            userName, password, securityDir, policy, 
allowEndpointRedirect);
                     return oldValue;
                   })
               .getRight();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java
index 9e3b46d463a..5ed25f47935 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java
@@ -26,7 +26,10 @@ import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
 import 
org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator;
 import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager;
 import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.transport.TransportProfile;
 import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+import org.eclipse.milo.opcua.stack.core.util.EndpointUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,7 +39,10 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.Security;
+import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
+import java.util.Optional;
 
 import static 
org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
 
@@ -53,6 +59,7 @@ public class ClientRunner {
   private final Path securityDir;
   private final String password;
   private final long timeoutSeconds;
+  private final boolean allowEndpointRedirect;
 
   // For conflict checking
   private final String user;
@@ -63,11 +70,22 @@ public class ClientRunner {
       final String password,
       final String user,
       final long timeoutSeconds) {
+    this(configurableUaClient, securityDir, password, user, timeoutSeconds, 
false);
+  }
+
+  public ClientRunner(
+      final IoTDBOpcUaClient configurableUaClient,
+      final String securityDir,
+      final String password,
+      final String user,
+      final long timeoutSeconds,
+      final boolean allowEndpointRedirect) {
     this.configurableUaClient = configurableUaClient;
     this.securityDir = Paths.get(securityDir);
     this.password = password;
     this.user = user;
     this.timeoutSeconds = timeoutSeconds;
+    this.allowEndpointRedirect = allowEndpointRedirect;
     configurableUaClient.setRunner(this);
   }
 
@@ -92,7 +110,12 @@ public class ClientRunner {
 
     return OpcUaClient.create(
         configurableUaClient.getNodeUrl(),
-        endpoints -> 
endpoints.stream().filter(configurableUaClient.endpointFilter()).findFirst(),
+        endpoints ->
+            selectEndpoint(
+                endpoints,
+                configurableUaClient.getNodeUrl(),
+                configurableUaClient.getSecurityPolicy(),
+                allowEndpointRedirect),
         configBuilder ->
             configBuilder
                 .setApplicationName(LocalizedText.english("Apache IoTDB OPC UA 
client"))
@@ -108,6 +131,65 @@ public class ClientRunner {
                 .build());
   }
 
+  static Optional<EndpointDescription> selectEndpoint(
+      final List<EndpointDescription> endpoints,
+      final String configuredNodeUrl,
+      final SecurityPolicy securityPolicy,
+      final boolean allowEndpointRedirect) {
+    final String configuredScheme = 
normalizeScheme(EndpointUtil.getScheme(configuredNodeUrl));
+
+    return endpoints.stream()
+        .filter(endpoint -> 
securityPolicy.getUri().equals(endpoint.getSecurityPolicyUri()))
+        .filter(endpoint -> matchesConfiguredTransport(endpoint, 
configuredScheme))
+        .findFirst()
+        .map(
+            advertisedEndpoint -> {
+              final EndpointDescription effectiveEndpoint =
+                  allowEndpointRedirect
+                      ? advertisedEndpoint
+                      : EndpointUtil.updateUrl(
+                          advertisedEndpoint,
+                          EndpointUtil.getHost(configuredNodeUrl),
+                          EndpointUtil.getPort(configuredNodeUrl));
+              logger.info(
+                  "OPC UA endpoint selected: configured={}, advertised={}, 
effective={}, allowEndpointRedirect={}.",
+                  configuredNodeUrl,
+                  advertisedEndpoint.getEndpointUrl(),
+                  effectiveEndpoint.getEndpointUrl(),
+                  allowEndpointRedirect);
+              return effectiveEndpoint;
+            });
+  }
+
+  private static boolean matchesConfiguredTransport(
+      final EndpointDescription endpoint, final String configuredScheme) {
+    if (Objects.isNull(configuredScheme)
+        || !Objects.equals(
+            configuredScheme, 
normalizeScheme(EndpointUtil.getScheme(endpoint.getEndpointUrl())))) {
+      return false;
+    }
+
+    final String transportProfileUri = endpoint.getTransportProfileUri();
+    if (Objects.isNull(transportProfileUri)) {
+      return true;
+    }
+    try {
+      return Objects.equals(
+          configuredScheme,
+          
normalizeScheme(TransportProfile.fromUri(transportProfileUri).getScheme()));
+    } catch (final IllegalArgumentException ignored) {
+      // Preserve compatibility with servers that advertise a custom transport 
profile. The
+      // endpoint URL scheme still has to match the configured URL.
+      return true;
+    }
+  }
+
+  private static String normalizeScheme(final String scheme) {
+    return Objects.nonNull(scheme) && scheme.equalsIgnoreCase("opc.https")
+        ? "https"
+        : Objects.isNull(scheme) ? null : scheme.toLowerCase(Locale.ROOT);
+  }
+
   public void run() {
     try {
       final OpcUaClient client = createClient();
@@ -134,7 +216,8 @@ public class ClientRunner {
       final String user,
       final String password,
       final Path securityDir,
-      final SecurityPolicy securityPolicy) {
+      final SecurityPolicy securityPolicy,
+      final boolean allowEndpointRedirect) {
     checkEquals("user", this.user, user);
     checkEquals("password", this.password, password);
     checkEquals(
@@ -142,6 +225,7 @@ public class ClientRunner {
         
FileSystems.getDefault().getPath(this.securityDir.toAbsolutePath().toString()),
         
FileSystems.getDefault().getPath(securityDir.toAbsolutePath().toString()));
     checkEquals("securityPolicy", configurableUaClient.getSecurityPolicy(), 
securityPolicy);
+    checkEquals("allow endpoint redirect", this.allowEndpointRedirect, 
allowEndpointRedirect);
   }
 
   private void checkEquals(final String attrName, Object thisAttr, Object 
thatAttr) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java
index 7595b75747e..5151c7d963e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java
@@ -51,7 +51,6 @@ import 
org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
 import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesItem;
 import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResponse;
 import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResult;
-import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
 import org.eclipse.milo.opcua.stack.core.types.structured.ObjectAttributes;
 import org.eclipse.milo.opcua.stack.core.types.structured.VariableAttributes;
 import org.slf4j.Logger;
@@ -64,7 +63,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Predicate;
 
 import static 
org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.convertToOpcDataType;
 import static 
org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.timestampToUtc;
@@ -306,10 +304,6 @@ public class IoTDBOpcUaClient {
     return nodeUrl;
   }
 
-  Predicate<EndpointDescription> endpointFilter() {
-    return e -> getSecurityPolicy().getUri().equals(e.getSecurityPolicyUri());
-  }
-
   SecurityPolicy getSecurityPolicy() {
     return securityPolicy;
   }
@@ -364,7 +358,9 @@ public class IoTDBOpcUaClient {
       final String user,
       final String password,
       final String securityDir,
-      final SecurityPolicy securityPolicy) {
-    runner.checkEquals(user, password, Paths.get(securityDir), securityPolicy);
+      final SecurityPolicy securityPolicy,
+      final boolean allowEndpointRedirect) {
+    runner.checkEquals(
+        user, password, Paths.get(securityDir), securityPolicy, 
allowEndpointRedirect);
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java
index 16f176a3c53..640b127d1f9 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java
@@ -21,7 +21,7 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.server;
 
 import org.apache.iotdb.commons.utils.FileUtils;
 
-import com.google.common.collect.Sets;
+import com.google.common.net.InetAddresses;
 import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
 import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
 import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
@@ -40,22 +40,21 @@ import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
+import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.UUID;
-import java.util.regex.Pattern;
 
 class OpcUaKeyStoreLoader {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(OpcUaKeyStoreLoader.class);
 
-  private static final Pattern IP_ADDR_PATTERN =
-      
Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
-
   private static final String SERVER_ALIAS = "server-ai";
 
   private X509Certificate serverCertificate;
   private KeyPair serverKeyPair;
 
-  OpcUaKeyStoreLoader load(final Path baseDir, final char[] password) throws 
Exception {
+  OpcUaKeyStoreLoader load(
+      final Path baseDir, final char[] password, final Set<String> 
advertisedHostnames)
+      throws Exception {
     final KeyStore keyStore = KeyStore.getInstance("PKCS12");
 
     final File serverKeyStore = baseDir.resolve("iotdb-server.pfx").toFile();
@@ -89,14 +88,14 @@ class OpcUaKeyStoreLoader {
               .setApplicationUri(applicationUri);
 
       // Get as many hostnames and IP addresses as we can list in the 
certificate.
-      final Set<String> hostnames =
-          Sets.union(
-              Sets.newHashSet(HostnameUtil.getHostname()),
-              HostnameUtil.getHostnames("0.0.0.0", false));
+      final Set<String> hostnames = new LinkedHashSet<>();
+      hostnames.add(HostnameUtil.getHostname());
+      hostnames.addAll(HostnameUtil.getHostnames("0.0.0.0", false));
+      hostnames.addAll(advertisedHostnames);
 
       hostnames.forEach(
           hostname -> {
-            if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
+            if (InetAddresses.isInetAddress(hostname)) {
               builder.addIpAddress(hostname);
             } else {
               builder.addDnsName(hostname);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java
index 713a87b2e35..64fd9fce310 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java
@@ -601,6 +601,7 @@ public class OpcUaNameSpace extends 
ManagedNamespaceWithLifecycle {
   /////////////////////////////// Conflict detection 
///////////////////////////////
 
   public void checkEquals(
+      final String advertisedHost,
       final String user,
       final String password,
       final String securityDir,
@@ -608,6 +609,7 @@ public class OpcUaNameSpace extends 
ManagedNamespaceWithLifecycle {
       final Set<SecurityPolicy> securityPolicies,
       final long debounceTimeMs) {
     builder.checkEquals(
+        advertisedHost,
         user,
         password,
         Paths.get(securityDir),
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java
index 281d6eae77e..adc505b8716 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.server;
 
 import org.apache.iotdb.pipe.api.exception.PipeException;
 
+import com.google.common.net.InetAddresses;
 import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
 import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
 import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator;
@@ -77,9 +78,11 @@ public class OpcUaServerBuilder implements Closeable {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(OpcUaServerBuilder.class);
 
   private static final String WILD_CARD_ADDRESS = "0.0.0.0";
+  private static final int BACKSLASH = 0x5c;
 
   private int tcpBindPort;
   private int httpsBindPort;
+  private String advertisedHost;
   private String user;
   private String password;
   private Path securityDir;
@@ -98,6 +101,53 @@ public class OpcUaServerBuilder implements Closeable {
     return this;
   }
 
+  /** Configures the host published in endpoint URLs without changing the 
wildcard bind address. */
+  public OpcUaServerBuilder setAdvertisedHost(final String advertisedHost) {
+    this.advertisedHost = normalizeAdvertisedHost(advertisedHost);
+    return this;
+  }
+
+  private static String normalizeAdvertisedHost(final String advertisedHost) {
+    if (Objects.isNull(advertisedHost)) {
+      return null;
+    }
+
+    String normalizedAdvertisedHost = advertisedHost.trim();
+    if (normalizedAdvertisedHost.isEmpty()) {
+      throw invalidAdvertisedHost();
+    }
+    final boolean bracketed =
+        normalizedAdvertisedHost.startsWith("[") || 
normalizedAdvertisedHost.endsWith("]");
+    if (bracketed) {
+      if (!normalizedAdvertisedHost.startsWith("[") || 
!normalizedAdvertisedHost.endsWith("]")) {
+        throw invalidAdvertisedHost();
+      }
+      normalizedAdvertisedHost =
+          normalizedAdvertisedHost.substring(1, 
normalizedAdvertisedHost.length() - 1);
+    }
+
+    final boolean isIpAddress = 
InetAddresses.isInetAddress(normalizedAdvertisedHost);
+    if (normalizedAdvertisedHost.isEmpty()
+        || (bracketed && !isIpAddress)
+        || normalizedAdvertisedHost.chars().anyMatch(Character::isWhitespace)
+        || normalizedAdvertisedHost.contains("/")
+        || normalizedAdvertisedHost.indexOf(BACKSLASH) >= 0
+        || normalizedAdvertisedHost.contains("?")
+        || normalizedAdvertisedHost.contains("#")
+        || normalizedAdvertisedHost.contains("@")
+        || normalizedAdvertisedHost.contains("[")
+        || normalizedAdvertisedHost.contains("]")
+        || (!isIpAddress && normalizedAdvertisedHost.contains(":"))) {
+      throw invalidAdvertisedHost();
+    }
+    return normalizedAdvertisedHost;
+  }
+
+  private static IllegalArgumentException invalidAdvertisedHost() {
+    return new IllegalArgumentException(
+        "The advertised host must be a hostname or IP address without a 
scheme, port, or path.");
+  }
+
   public OpcUaServerBuilder setUser(final String user) {
     this.user = user;
     return this;
@@ -146,8 +196,10 @@ public class OpcUaServerBuilder implements Closeable {
     LoggerFactory.getLogger(OpcUaServerBuilder.class)
         .info("Security pki dir: {}", pkiDir.getAbsolutePath());
 
+    final Set<String> endpointHostnames = getEndpointHostnames();
+    final Set<String> certificateHostnames = 
getCertificateHostnames(endpointHostnames);
     final OpcUaKeyStoreLoader loader =
-        new OpcUaKeyStoreLoader().load(securityDir, password.toCharArray());
+        new OpcUaKeyStoreLoader().load(securityDir, password.toCharArray(), 
certificateHostnames);
 
     final DefaultCertificateManager certificateManager =
         new DefaultCertificateManager(loader.getServerKeyPair(), 
loader.getServerCertificate());
@@ -164,8 +216,15 @@ public class OpcUaServerBuilder implements Closeable {
 
     final SelfSignedHttpsCertificateBuilder httpsCertificateBuilder =
         new SelfSignedHttpsCertificateBuilder(httpsKeyPair);
-    httpsCertificateBuilder.setCommonName(HostnameUtil.getHostname());
-    
HostnameUtil.getHostnames(WILD_CARD_ADDRESS).forEach(httpsCertificateBuilder::addDnsName);
+    
httpsCertificateBuilder.setCommonName(certificateHostnames.iterator().next());
+    certificateHostnames.forEach(
+        hostname -> {
+          if (InetAddresses.isInetAddress(hostname)) {
+            httpsCertificateBuilder.addIpAddress(hostname);
+          } else {
+            httpsCertificateBuilder.addDnsName(hostname);
+          }
+        });
     final X509Certificate httpsCertificate = httpsCertificateBuilder.build();
 
     final DefaultServerCertificateValidator certificateValidator =
@@ -188,6 +247,12 @@ public class OpcUaServerBuilder implements Closeable {
                     new UaRuntimeException(
                         StatusCodes.Bad_ConfigurationError, "No certificate 
found"));
 
+    if (Objects.nonNull(advertisedHost) && 
!isAdvertisedHostInCertificate(certificate)) {
+      LOGGER.warn(
+          "Advertised host {} is not present in the loaded OPC UA server 
certificate subject alternative names. Secured clients may reject it; replace 
or regenerate the certificate and establish trust again.",
+          advertisedHost);
+    }
+
     final String applicationUri =
         CertificateUtil.getSanUri(certificate)
             .orElseThrow(
@@ -197,7 +262,7 @@ public class OpcUaServerBuilder implements Closeable {
                         "Certificate is missing the application URI"));
 
     final Set<EndpointConfiguration> endpointConfigurations =
-        createEndpointConfigurations(certificate, tcpBindPort, httpsBindPort);
+        createEndpointConfigurations(certificate, tcpBindPort, httpsBindPort, 
endpointHostnames);
 
     serverConfig =
         OpcUaServerConfig.builder()
@@ -231,19 +296,71 @@ public class OpcUaServerBuilder implements Closeable {
     return server;
   }
 
-  private Set<EndpointConfiguration> createEndpointConfigurations(
-      final X509Certificate certificate, final int tcpBindPort, final int 
httpsBindPort) {
+  private Set<String> getEndpointHostnames() {
+    if (Objects.nonNull(advertisedHost)) {
+      final Set<String> hostnames = new LinkedHashSet<>();
+      hostnames.add(toEndpointHostname(advertisedHost));
+      return hostnames;
+    }
+    final Set<String> hostnames = new LinkedHashSet<>();
+    hostnames.add(toEndpointHostname(HostnameUtil.getHostname()));
+    HostnameUtil.getHostnames(WILD_CARD_ADDRESS).stream()
+        .map(OpcUaServerBuilder::toEndpointHostname)
+        .forEach(hostnames::add);
+    return hostnames;
+  }
+
+  private static Set<String> getCertificateHostnames(final Set<String> 
endpointHostnames) {
+    final Set<String> certificateHostnames = new LinkedHashSet<>();
+    endpointHostnames.stream()
+        .map(OpcUaServerBuilder::removeIpv6Brackets)
+        .forEach(certificateHostnames::add);
+    return certificateHostnames;
+  }
+
+  private static String toEndpointHostname(final String hostname) {
+    return InetAddresses.isInetAddress(hostname) && hostname.indexOf(':') >= 0
+        ? '[' + hostname + ']'
+        : hostname;
+  }
+
+  private static String removeIpv6Brackets(final String hostname) {
+    return hostname.startsWith("[") && hostname.endsWith("]")
+        ? hostname.substring(1, hostname.length() - 1)
+        : hostname;
+  }
+
+  private boolean isAdvertisedHostInCertificate(final X509Certificate 
certificate) {
+    if (InetAddresses.isInetAddress(advertisedHost)) {
+      return CertificateUtil.getSanIpAddresses(certificate).stream()
+          .filter(InetAddresses::isInetAddress)
+          .map(InetAddresses::forString)
+          .anyMatch(InetAddresses.forString(advertisedHost)::equals);
+    }
+    return CertificateUtil.getSanDnsNames(certificate).stream()
+        .anyMatch(hostname -> hostname.equalsIgnoreCase(advertisedHost));
+  }
+
+  Set<EndpointConfiguration> createEndpointConfigurations(
+      final X509Certificate certificate,
+      final int tcpBindPort,
+      final int httpsBindPort,
+      final Set<String> hostnames) {
     final Set<EndpointConfiguration> endpointConfigurations = new 
LinkedHashSet<>();
+    final Set<String> effectiveHostnames = new LinkedHashSet<>();
+    if (Objects.nonNull(advertisedHost)) {
+      effectiveHostnames.add(toEndpointHostname(advertisedHost));
+    } else {
+      hostnames.stream()
+          .map(OpcUaServerBuilder::toEndpointHostname)
+          .forEach(effectiveHostnames::add);
+    }
 
     final List<String> bindAddresses = newArrayList();
     bindAddresses.add(WILD_CARD_ADDRESS);
 
-    final Set<String> hostnames = new LinkedHashSet<>();
-    hostnames.add(HostnameUtil.getHostname());
-    hostnames.addAll(HostnameUtil.getHostnames(WILD_CARD_ADDRESS));
-
     for (final String bindAddress : bindAddresses) {
-      for (final String hostname : hostnames) {
+      for (final String hostname : effectiveHostnames) {
         final EndpointConfiguration.Builder builder =
             EndpointConfiguration.newBuilder()
                 .setBindAddress(bindAddress)
@@ -320,12 +437,14 @@ public class OpcUaServerBuilder implements Closeable {
   /////////////////////////////// Conflict detection 
///////////////////////////////
 
   void checkEquals(
+      final String advertisedHost,
       final String user,
       final String password,
       final Path securityDir,
       final boolean enableAnonymousAccess,
       final Set<SecurityPolicy> securityPolicies,
       final long debounceTimeMs) {
+    checkEquals("advertised host", this.advertisedHost, 
normalizeAdvertisedHost(advertisedHost));
     checkEquals("user", this.user, user);
     checkEquals("password", this.password, password);
     checkEquals(
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunnerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunnerTest.java
new file mode 100644
index 00000000000..c760f661a71
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunnerTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.iotdb.db.pipe.sink.protocol.opcua.client;
+
+import org.apache.iotdb.pipe.api.exception.PipeException;
+
+import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
+import org.eclipse.milo.opcua.stack.core.Stack;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class ClientRunnerTest {
+
+  private static final String CONFIGURED_NODE_URL = 
"opc.tcp://10.60.80.65:12686/iotdb";
+  private static final SecurityPolicy SECURITY_POLICY = 
SecurityPolicy.Basic256Sha256;
+
+  @Test
+  public void testConfiguredHostAndPortAreUsedByDefault() {
+    final EndpointDescription advertisedEndpoint =
+        createEndpoint(
+            "opc.tcp://fwq03-15:4840/server-path",
+            SECURITY_POLICY,
+            Stack.TCP_UASC_UABINARY_TRANSPORT_URI);
+
+    final EndpointDescription effectiveEndpoint =
+        ClientRunner.selectEndpoint(
+                Arrays.asList(advertisedEndpoint), CONFIGURED_NODE_URL, 
SECURITY_POLICY, false)
+            .orElseThrow(AssertionError::new);
+
+    Assert.assertEquals(
+        "opc.tcp://10.60.80.65:12686/server-path", 
effectiveEndpoint.getEndpointUrl());
+    Assert.assertNotSame(advertisedEndpoint, effectiveEndpoint);
+    Assert.assertSame(
+        advertisedEndpoint.getServerCertificate(), 
effectiveEndpoint.getServerCertificate());
+    Assert.assertEquals(advertisedEndpoint.getSecurityMode(), 
effectiveEndpoint.getSecurityMode());
+    Assert.assertEquals(
+        advertisedEndpoint.getSecurityPolicyUri(), 
effectiveEndpoint.getSecurityPolicyUri());
+    Assert.assertSame(
+        advertisedEndpoint.getUserIdentityTokens(), 
effectiveEndpoint.getUserIdentityTokens());
+    Assert.assertEquals(
+        advertisedEndpoint.getTransportProfileUri(), 
effectiveEndpoint.getTransportProfileUri());
+  }
+
+  @Test
+  public void testAdvertisedEndpointIsUsedWhenRedirectIsAllowed() {
+    final EndpointDescription advertisedEndpoint =
+        createEndpoint(
+            "opc.tcp://fwq03-15:4840/server-path",
+            SECURITY_POLICY,
+            Stack.TCP_UASC_UABINARY_TRANSPORT_URI);
+
+    final EndpointDescription effectiveEndpoint =
+        ClientRunner.selectEndpoint(
+                Arrays.asList(advertisedEndpoint), CONFIGURED_NODE_URL, 
SECURITY_POLICY, true)
+            .orElseThrow(AssertionError::new);
+
+    Assert.assertSame(advertisedEndpoint, effectiveEndpoint);
+  }
+
+  @Test
+  public void testEndpointSelectionMatchesConfiguredTransport() {
+    final EndpointDescription wrongSchemeEndpoint =
+        createEndpoint(
+            "https://wrong-scheme:12686/iotdb";,
+            SECURITY_POLICY,
+            Stack.HTTPS_UABINARY_TRANSPORT_URI);
+    final EndpointDescription wrongTransportEndpoint =
+        createEndpoint(
+            "opc.tcp://wrong-transport:12686/iotdb",
+            SECURITY_POLICY,
+            Stack.HTTPS_UABINARY_TRANSPORT_URI);
+    final EndpointDescription matchingEndpoint =
+        createEndpoint(
+            "opc.tcp://matching:12686/iotdb",
+            SECURITY_POLICY,
+            Stack.TCP_UASC_UABINARY_TRANSPORT_URI);
+
+    final EndpointDescription selectedEndpoint =
+        ClientRunner.selectEndpoint(
+                Arrays.asList(wrongSchemeEndpoint, wrongTransportEndpoint, 
matchingEndpoint),
+                CONFIGURED_NODE_URL,
+                SECURITY_POLICY,
+                true)
+            .orElseThrow(AssertionError::new);
+
+    Assert.assertSame(matchingEndpoint, selectedEndpoint);
+  }
+
+  @Test
+  public void testAllowEndpointRedirectParticipatesInConflictDetection() {
+    final String securityDir = "target/opcua-client-runner-test";
+    final IoTDBOpcUaClient client =
+        new IoTDBOpcUaClient(
+            CONFIGURED_NODE_URL, SECURITY_POLICY, AnonymousProvider.INSTANCE, 
false);
+    final ClientRunner runner = new ClientRunner(client, securityDir, 
"password", null, 10, false);
+
+    runner.checkEquals(null, "password", Paths.get(securityDir), 
SECURITY_POLICY, false);
+    final PipeException exception =
+        Assert.assertThrows(
+            PipeException.class,
+            () ->
+                runner.checkEquals(
+                    null, "password", Paths.get(securityDir), SECURITY_POLICY, 
true));
+    Assert.assertTrue(exception.getMessage().contains("allow endpoint 
redirect"));
+  }
+
+  private static EndpointDescription createEndpoint(
+      final String endpointUrl,
+      final SecurityPolicy securityPolicy,
+      final String transportProfileUri) {
+    return new EndpointDescription(
+        endpointUrl,
+        null,
+        ByteString.of(new byte[] {1, 2, 3}),
+        MessageSecurityMode.SignAndEncrypt,
+        securityPolicy.getUri(),
+        new UserTokenPolicy[0],
+        transportProfileUri,
+        Unsigned.ubyte(1));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilderTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilderTest.java
new file mode 100644
index 00000000000..97938f56d09
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilderTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.iotdb.db.pipe.sink.protocol.opcua.server;
+
+import org.apache.iotdb.pipe.api.exception.PipeException;
+
+import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.util.CertificateUtil;
+import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class OpcUaServerBuilderTest {
+
+  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Test
+  public void testDetectedHostsArePublishedByDefault() {
+    final Set<SecurityPolicy> securityPolicies = 
Collections.singleton(SecurityPolicy.None);
+    final Set<String> detectedHostnames =
+        new LinkedHashSet<>(Arrays.asList("opc-server", "10.60.80.65"));
+    final OpcUaServerBuilder builder =
+        new OpcUaServerBuilder().setSecurityPolicies(securityPolicies);
+
+    final Set<EndpointConfiguration> endpoints =
+        builder.createEndpointConfigurations(null, 12686, 8443, 
detectedHostnames);
+
+    for (final String hostname : detectedHostnames) {
+      Assert.assertEquals(
+          2,
+          endpoints.stream()
+              .filter(endpoint -> endpoint.getPath().equals("/iotdb"))
+              .filter(endpoint -> endpoint.getHostname().equals(hostname))
+              .filter(endpoint -> endpoint.getSecurityPolicy() == 
SecurityPolicy.None)
+              .count());
+    }
+    Assert.assertEquals(Collections.singleton(SecurityPolicy.None), 
securityPolicies);
+  }
+
+  @Test
+  public void testOnlyExplicitAdvertisedHostIsPublished() {
+    final Set<String> detectedHostnames =
+        new LinkedHashSet<>(Arrays.asList("opc-server", "10.60.80.65"));
+    final OpcUaServerBuilder builder =
+        new OpcUaServerBuilder()
+            .setAdvertisedHost("opc.example.com")
+            .setSecurityPolicies(Collections.singleton(SecurityPolicy.None));
+
+    final Set<EndpointConfiguration> endpoints =
+        builder.createEndpointConfigurations(null, 12686, 8443, 
detectedHostnames);
+
+    Assert.assertEquals(
+        Collections.singleton("opc.example.com"),
+        
endpoints.stream().map(EndpointConfiguration::getHostname).collect(Collectors.toSet()));
+    Assert.assertTrue(
+        endpoints.stream().allMatch(endpoint -> 
"0.0.0.0".equals(endpoint.getBindAddress())));
+  }
+
+  @Test
+  public void testIpv6AdvertisedHostIsNormalizedAndEndpointUrlIsRejected() {
+    final OpcUaServerBuilder builder =
+        new OpcUaServerBuilder()
+            .setAdvertisedHost("[2001:db8::1]")
+            .setSecurityPolicies(Collections.singleton(SecurityPolicy.None));
+
+    final Set<EndpointConfiguration> endpoints =
+        builder.createEndpointConfigurations(
+            null, 12686, 8443, Collections.singleton("opc-server"));
+
+    Assert.assertEquals(
+        Collections.singleton("[2001:db8::1]"),
+        
endpoints.stream().map(EndpointConfiguration::getHostname).collect(Collectors.toSet()));
+    Assert.assertTrue(
+        endpoints.stream()
+            .map(EndpointConfiguration::getEndpointUrl)
+            .allMatch(endpointUrl -> 
endpointUrl.contains("://[2001:db8::1]:")));
+    Assert.assertThrows(
+        IllegalArgumentException.class,
+        () -> 
builder.setAdvertisedHost("opc.tcp://opc.example.com:12686/iotdb"));
+  }
+
+  @Test
+  public void testNewCertificateContainsAdvertisedHost() throws Exception {
+    final String advertisedHost = "opc.example.com";
+    final Path securityDir = temporaryFolder.newFolder("security").toPath();
+
+    try (final OpcUaServerBuilder builder =
+        new OpcUaServerBuilder()
+            .setTcpBindPort(12686)
+            .setHttpsBindPort(8443)
+            .setAdvertisedHost(advertisedHost)
+            .setUser("root")
+            .setPassword("root")
+            .setSecurityDir(securityDir.toString())
+            .setEnableAnonymousAccess(true)
+            .setSecurityPolicies(Collections.singleton(SecurityPolicy.None))
+            .setDebounceTimeMs(50)) {
+      final OpcUaServer server = builder.build();
+      final Set<EndpointConfiguration> endpoints = 
server.getConfig().getEndpoints();
+
+      Assert.assertEquals(
+          Collections.singleton(advertisedHost),
+          
endpoints.stream().map(EndpointConfiguration::getHostname).collect(Collectors.toSet()));
+      Assert.assertTrue(
+          endpoints.stream()
+              .map(EndpointConfiguration::getCertificate)
+              .allMatch(
+                  certificate ->
+                      
CertificateUtil.getSanDnsNames(certificate).contains(advertisedHost)));
+    }
+  }
+
+  @Test
+  public void testAdvertisedHostParticipatesInConflictDetection() {
+    final Path securityDir = temporaryFolder.getRoot().toPath();
+    final Set<SecurityPolicy> securityPolicies = 
Collections.singleton(SecurityPolicy.None);
+    final OpcUaServerBuilder builder =
+        new OpcUaServerBuilder()
+            .setAdvertisedHost("opc.example.com")
+            .setUser("root")
+            .setPassword("root")
+            .setSecurityDir(securityDir.toString())
+            .setEnableAnonymousAccess(true)
+            .setSecurityPolicies(securityPolicies)
+            .setDebounceTimeMs(50);
+
+    builder.checkEquals("opc.example.com", "root", "root", securityDir, true, 
securityPolicies, 50);
+    final PipeException exception =
+        Assert.assertThrows(
+            PipeException.class,
+            () ->
+                builder.checkEquals(
+                    "other.example.com", "root", "root", securityDir, true, 
securityPolicies, 50));
+
+    Assert.assertTrue(exception.getMessage().contains("advertised host"));
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java
index e170e8e7828..f5727634d19 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java
@@ -189,6 +189,10 @@ public class PipeSinkConstant {
   public static final String SINK_OPC_UA_HTTPS_BIND_PORT_KEY = 
"sink.opcua.https.port";
   public static final int CONNECTOR_OPC_UA_HTTPS_BIND_PORT_DEFAULT_VALUE = 
8443;
 
+  public static final String CONNECTOR_OPC_UA_ADVERTISED_HOST_KEY =
+      "connector.opcua.advertised-host";
+  public static final String SINK_OPC_UA_ADVERTISED_HOST_KEY = 
"sink.opcua.advertised-host";
+
   public static final String CONNECTOR_OPC_UA_SECURITY_DIR_KEY = 
"connector.opcua.security.dir";
   public static final String SINK_OPC_UA_SECURITY_DIR_KEY = 
"sink.opcua.security.dir";
   public static final String CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE =
@@ -223,6 +227,11 @@ public class PipeSinkConstant {
 
   public static final String CONNECTOR_OPC_UA_NODE_URL_KEY = 
"connector.opcua.node-url";
   public static final String SINK_OPC_UA_NODE_URL_KEY = "sink.opcua.node-url";
+  public static final String CONNECTOR_OPC_UA_ALLOW_ENDPOINT_REDIRECT_KEY =
+      "connector.opcua.allow-endpoint-redirect";
+  public static final String SINK_OPC_UA_ALLOW_ENDPOINT_REDIRECT_KEY =
+      "sink.opcua.allow-endpoint-redirect";
+  public static final boolean 
CONNECTOR_OPC_UA_ALLOW_ENDPOINT_REDIRECT_DEFAULT_VALUE = false;
 
   public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_KEY =
       "connector.opcua.security-policy";


Reply via email to