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

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


The following commit(s) were added to refs/heads/master by this push:
     new bdff9801bb2 HDDS-16308. Validate advertised addresses before startup, 
registration, and publication (#11272)
bdff9801bb2 is described below

commit bdff9801bb2bc4769bf6d1f1decd6a80908e1c72
Author: rjgoyln <[email protected]>
AuthorDate: Sat Oct 10 05:57:45 2026 +0800

    HDDS-16308. Validate advertised addresses before startup, registration, and 
publication (#11272)
---
 .../hdds/scm/client/TestHddsClientUtils.java       |  40 ++++-
 .../java/org/apache/hadoop/hdds/HddsUtils.java     | 152 +++++++++++++++++
 .../java/org/apache/hadoop/hdds/NodeDetails.java   |   3 +-
 .../org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java |   6 +
 .../java/org/apache/hadoop/hdds/TestHddsUtils.java | 190 +++++++++++++++++++++
 .../apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java |  78 +++++++++
 .../common/states/datanode/InitDatanodeState.java  |   8 +-
 .../container/common/TestDatanodeStateMachine.java |  12 ++
 .../apache/hadoop/hdds/utils/HddsServerUtil.java   |   5 +
 .../hadoop/hdds/scm/ha/SCMHANodeDetails.java       |   4 +-
 .../apache/hadoop/hdds/scm/ha/SCMNodeDetails.java  |   6 -
 .../hadoop/hdds/scm/TestHddsServerUtils.java       |  50 ++++++
 .../hadoop/hdds/scm/ha/TestSCMConfiguration.java   |  29 ++++
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   8 +
 .../java/org/apache/hadoop/ozone/TestOmUtils.java  |  38 ++++-
 ...doopRpcOMFollowerReadFailoverProxyProvider.java |   2 +-
 .../ozone/om/ha/TestOMFailoverProxyProvider.java   |   2 +-
 .../hadoop/ozone/om/helpers/TestOMNodeDetails.java |  19 +++
 .../ozone/om/TestOzoneManagerConfiguration.java    |  14 +-
 .../apache/hadoop/ozone/om/ha/OMHANodeDetails.java |   4 +-
 .../org/apache/hadoop/ozone/recon/ReconServer.java |   2 +
 21 files changed, 641 insertions(+), 31 deletions(-)

diff --git 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
index 647ae727465..98013d891c1 100644
--- 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
+++ 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
@@ -184,11 +184,6 @@ public void testClientFallbackToScmNamesWithPort() {
 
   @Test
   public void testClientAddressIPv6() {
-    // Bare IPv6 literal without port: port falls back to the default and the
-    // host must be re-bracketed before the address string is parsed.
-    checkScmClientAddr(OZONE_SCM_CLIENT_ADDRESS_KEY, "2001:db8::1",
-        "2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
-
     // Bracketed IPv6 literal with explicit port.
     checkScmClientAddr(OZONE_SCM_CLIENT_ADDRESS_KEY, "[2001:db8::1]:9876",
         "2001:db8:0:0:0:0:0:1", 9876);
@@ -199,12 +194,26 @@ public void testClientAddressIPv6() {
         "2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
   }
 
+  /**
+   * A bare literal used to resolve to the default port (HDDS-15773). It is
+   * rejected since HDDS-16308, because the same text also reads as a shorter
+   * host with the trailing group for a port.
+   */
   @Test
-  public void testClientFallbackToScmNamesIPv6() {
-    // Bare IPv6 literal in ozone.scm.names.
-    checkScmClientAddr(OZONE_SCM_NAMES, "2001:db8::1",
-        "2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
+  public void testClientAddressRejectsBareIPv6Literal() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "2001:db8::1");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsUtils.getScmAddressForClients(conf));
+
+    assertThat(e.getMessage())
+        .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+        .contains("[2001:db8::1]");
+  }
 
+  @Test
+  public void testClientFallbackToScmNamesIPv6() {
     // On the ozone.scm.names fallback path an inline port is ignored and the
     // default client port is used instead (same semantics as
     // testClientFallbackToScmNamesWithPort).
@@ -212,6 +221,19 @@ public void testClientFallbackToScmNamesIPv6() {
         "2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
   }
 
+  @Test
+  public void testClientFallbackToScmNamesRejectsBareIPv6Literal() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_NAMES, "2001:db8::1");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsUtils.getScmAddressForClients(conf));
+
+    assertThat(e.getMessage())
+        .contains(OZONE_SCM_NAMES)
+        .contains("[2001:db8::1]");
+  }
+
   @Test
   @SuppressWarnings("StringSplitter")
   public void testBlockClientFallbackToClientWithPort() {
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
index 93a84f7c6f7..e9079b8b8bb 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
@@ -34,6 +34,7 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.net.HostAndPort;
+import com.google.common.net.InetAddresses;
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.ServiceException;
 import jakarta.annotation.Nonnull;
@@ -42,6 +43,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.UndeclaredThrowableException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.nio.file.Path;
@@ -138,6 +140,11 @@ public static Collection<InetSocketAddress> 
getScmAddressForClients(
       String address = conf.getTrimmed(OZONE_SCM_CLIENT_ADDRESS_KEY);
       int port = -1;
 
+      // Only the bracket rule applies: SCM rewrites this property with its
+      // bound host, a wildcard by default, and a process sharing its
+      // configuration reads that back here.
+      validateHostPortAuthority(OZONE_SCM_CLIENT_ADDRESS_KEY, address);
+
       if (address == null) {
         // fall back to ozone.scm.names for non-ha
         Collection<String> scmAddresses =
@@ -155,6 +162,7 @@ public static Collection<InetSocketAddress> 
getScmAddressForClients(
         }
 
         address = scmAddresses.iterator().next();
+        validateAdvertisedAddress(OZONE_SCM_NAMES, address);
 
         port = conf.getInt(OZONE_SCM_CLIENT_PORT_KEY,
             OZONE_SCM_CLIENT_PORT_DEFAULT);
@@ -243,6 +251,144 @@ public static String getHostPortString(String host, int 
port) {
     return HostAndPort.fromParts(host, port).toString();
   }
 
+  /**
+   * Rejects a host configured as an advertised endpoint that cannot identify
+   * this node to a peer. A wildcard ({@code 0.0.0.0}, {@code ::}) names every
+   * local interface instead of one reachable endpoint, a link-local literal is
+   * only meaningful on a single link, and the zone identifier of a scoped
+   * literal ({@code fe80::1%eth0}) names an interface on the host that wrote
+   * it, so it cannot be resolved by a peer nor encoded in the SAN extension of
+   * an X.509 certificate (see RFC-5280). A DNS name is accepted without being
+   * resolved, and so is loopback, which a single-host deployment legitimately
+   * advertises.
+   *
+   * @param key the property the host was configured under
+   * @param host a hostname or an IP literal, bracketed or not
+   * @throws ConfigurationException if the host cannot be advertised
+   */
+  public static void validateAdvertisedHost(String key, String host) {
+    if (host == null || host.isEmpty()) {
+      return;
+    }
+
+    // A host-only property can still be written with brackets, and the 
brackets
+    // are stripped on the way to the address, so unwrap them here or the
+    // literal inside escapes every check below.
+    final String literal = host.startsWith("[") && host.endsWith("]")
+        ? host.substring(1, host.length() - 1)
+        : host;
+
+    // Judged on the text, like HddsServerUtil.isScopedOrMaskingIPv6Address,
+    // because the scope makes the literal unresolvable here:
+    // InetAddresses.forString("fe80::1%eth0") throws unless this host happens
+    // to own an interface by that name.
+    if (literal.indexOf('%') >= 0 || literal.indexOf('/') >= 0) {
+      throw new ConfigurationException(String.format(
+          "%s = %s carries a zone identifier or prefix length, which cannot be 
advertised: it names an interface "
+              + "on this host, so a peer cannot resolve it and it cannot be 
encoded in an X.509 certificate.",
+          key, host));
+    }
+
+    if (!InetAddresses.isInetAddress(literal)) {
+      return;
+    }
+
+    final InetAddress address = InetAddresses.forString(literal);
+    if (address.isAnyLocalAddress()) {
+      throw new ConfigurationException(String.format(
+          "%s = %s is a wildcard address, which cannot be advertised. 
Configure the address this node is reachable "
+              + "at, and listen on every interface through the matching bind 
host property.",
+          key, host));
+    }
+    if (address.isLinkLocalAddress()) {
+      throw new ConfigurationException(String.format(
+          "%s = %s is a link-local address, which is only reachable on one 
link and cannot be advertised.",
+          key, host));
+    }
+  }
+
+  /**
+   * Rejects a configured advertised address, both for the textual form of its
+   * authority and for the host it names.
+   *
+   * @param key the property the address was configured under
+   * @param value host or host:port
+   * @throws ConfigurationException if the address cannot be advertised
+   * @see #validateAdvertisedHost(String, String)
+   */
+  public static void validateAdvertisedAddress(String key, String value) {
+    // The host first: a wildcard is rejected outright, so it must not be told
+    // to add brackets that leave it rejected anyway.
+    getHostName(value).ifPresent(host -> validateAdvertisedHost(key, host));
+    validateHostPortAuthority(key, value);
+  }
+
+  /**
+   * Rejects an advertised address configured under any of the given 
properties.
+   * A property holding a comma-separated list is checked entry by entry, and a
+   * property that is not set is skipped.
+   *
+   * @param conf the configuration to read
+   * @param keys the properties to check
+   * @throws ConfigurationException if any configured address cannot be 
advertised
+   * @see #validateAdvertisedAddress(String, String)
+   */
+  public static void validateAdvertisedAddressConfig(ConfigurationSource conf, 
String... keys) {
+    for (final String key : keys) {
+      for (final String value : conf.getTrimmedStringCollection(key)) {
+        validateAdvertisedAddress(key, value);
+      }
+    }
+  }
+
+  /**
+   * Rejects an unbracketed IPv6 literal configured under a property that a 
port
+   * may follow. Both readings of {@code 2001:db8::1:9862} are valid IPv6
+   * literals - the host {@code 2001:db8::1} on port 9862, or the whole literal
+   * on the property's default port - and nothing in the text tells them apart,
+   * so one of them is used silently. Brackets remove the ambiguity, and are
+   * needed for the single-colon form too, since a bare literal there is read 
as
+   * a host and a port.
+   *
+   * @param key the property the value was configured under
+   * @param value host or host:port
+   * @throws ConfigurationException if the host is an unbracketed IPv6 literal
+   */
+  private static void validateHostPortAuthority(String key, String value) {
+    if (value == null || value.isEmpty() || value.startsWith("[")) {
+      return;
+    }
+
+    final String host = HostAndPort.fromString(value).getHost();
+    final int lastColon = host.lastIndexOf(':');
+    if (lastColon < 0) {
+      return;
+    }
+
+    final String shorterHost = host.substring(0, lastColon);
+    final String trailingGroup = host.substring(lastColon + 1);
+    if (isPortNumber(trailingGroup) && 
InetAddresses.isInetAddress(shorterHost)) {
+      throw new ConfigurationException(String.format(
+          "%s = %s is an unbracketed IPv6 literal. Write %s for host %s with 
port %s, or %s to use the whole "
+              + "literal as the host.",
+          key, value, getHostPortString(shorterHost, 
Integer.parseInt(trailingGroup)), shorterHost, trailingGroup,
+          HostAndPort.fromHost(host)));
+    }
+    throw new ConfigurationException(String.format(
+        "%s = %s is an unbracketed IPv6 literal. Write %s; a port may follow 
this property, so the host has to be "
+            + "bracketed.",
+        key, value, HostAndPort.fromHost(host)));
+  }
+
+  private static boolean isPortNumber(String value) {
+    try {
+      final int port = Integer.parseInt(value);
+      return port > 0 && port <= 65535;
+    } catch (NumberFormatException e) {
+      return false;
+    }
+  }
+
   /**
    * Parse a Ratis role string produced by
    * {@code SCMRatisServerImpl.getRatisRoles()} into its constituent fields.
@@ -337,11 +483,14 @@ public static OptionalInt getNumberFromConfigKeys(
    * @return first port number component found from the given keys, or absent.
    * @throws IllegalArgumentException if any values are not in the 'host'
    *             or host:port format.
+   * @throws ConfigurationException if any value holds an unbracketed IPv6
+   *             literal, which cannot be told apart from a host and a port.
    */
   public static OptionalInt getPortNumberFromConfigKeys(
       ConfigurationSource conf, String... keys) {
     for (final String key : keys) {
       final String value = conf.getTrimmed(key);
+      validateHostPortAuthority(key, value);
       final OptionalInt hostPort = getHostPort(value);
       if (hostPort.isPresent()) {
         return hostPort;
@@ -360,10 +509,13 @@ public static OptionalInt getPortNumberFromConfigKeys(
    * @return the hostname (NB: may not be a FQDN)
    * @throws UnknownHostException if the hdds.datanode.dns.interface
    *    option is used and the hostname can not be determined
+   * @throws ConfigurationException if the configured hostname cannot be
+   *    advertised to SCM
    */
   public static String getHostName(ConfigurationSource conf)
       throws UnknownHostException {
     String name = conf.get(HDDS_DATANODE_HOST_NAME_KEY);
+    validateAdvertisedHost(HDDS_DATANODE_HOST_NAME_KEY, name);
     if (name == null) {
       String dnsInterface = conf.get(
           CommonConfigurationKeysPublic.HADOOP_SECURITY_DNS_INTERFACE_KEY);
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
index ae871a8a86f..93f9d62ca56 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
@@ -111,7 +111,8 @@ public int getRatisPort() {
   }
 
   public String getRpcAddressString() {
-    return NetUtils.getHostPortString(getRpcAddress());
+    final InetSocketAddress addr = getRpcAddress();
+    return HddsUtils.getHostPortString(addr.getHostName(), addr.getPort());
   }
 
   public String getHttpAddress() {
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
index 98b138de1ce..f3a7b59a0e4 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
@@ -96,6 +96,7 @@ public static List<SCMNodeInfo> 
buildNodeInfo(ConfigurationSource conf) {
         if (scmAddress == null) {
           throw new ConfigurationException(addressKey + "is not defined");
         }
+        HddsUtils.validateAdvertisedHost(addressKey, scmAddress);
 
         // Get port from Address Key if defined, else fall back to port key.
         int scmClientPort = getPort(conf, scmServiceId, scmNodeId,
@@ -126,6 +127,11 @@ public static List<SCMNodeInfo> 
buildNodeInfo(ConfigurationSource conf) {
     } else {
       scmServiceId = SCM_DUMMY_SERVICE_ID;
 
+      // The *.address properties double as the non-HA SCM's listen addresses,
+      // which it rewrites with its bound host (a wildcard by default) once its
+      // RPC servers start, so only ozone.scm.names is checked for the host.
+      HddsUtils.validateAdvertisedAddressConfig(conf, OZONE_SCM_NAMES);
+
       // Following current approach of fall back to
       // OZONE_SCM_CLIENT_ADDRESS_KEY to figure out hostname.
 
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
index 065dc8b6829..fc2da4832a6 100644
--- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
@@ -19,9 +19,18 @@
 
 import static 
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_CLIENT_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_CLIENT_BIND_HOST_KEY;
+import static 
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_HOST_NAME_KEY;
 import static org.apache.hadoop.hdds.HddsUtils.processForLogging;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedAddress;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedAddressConfig;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedHost;
+import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY;
+import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_BIND_HOST_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_NAMES;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_PIPELINE_OWNER_CONTAINER_COUNT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
@@ -34,6 +43,7 @@
 import java.util.Optional;
 import java.util.OptionalInt;
 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.ScmConfigKeys;
 import org.apache.hadoop.ozone.ha.ConfUtils;
@@ -41,6 +51,7 @@
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Testing HddsUtils.
@@ -265,4 +276,183 @@ void testRedactSensitivePropsForLogging() {
     /* Verify that non-sensitive properties retain their value */
     assertEquals(processedConf.get("ozone.normal.config"), ORIGINAL_VALUE);
   }
+
+  @ParameterizedTest
+  @MethodSource("ambiguousIPv6Authorities")
+  void getPortNumberFromConfigKeysRejectsAmbiguousIPv6Authority(String value, 
String host, String port) {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, value);
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsUtils.getPortNumberFromConfigKeys(conf, 
OZONE_SCM_CLIENT_ADDRESS_KEY));
+
+    // Both readings have to be spelled out, since the configured text does not
+    // say which one was meant.
+    assertThat(e.getMessage())
+        .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+        .contains(value)
+        .contains("[" + host + "]:" + port)
+        .contains("[" + value + "]");
+  }
+
+  static List<Arguments> ambiguousIPv6Authorities() {
+    return Arrays.asList(
+        Arguments.of("2001:db8::1:9862", "2001:db8::1", "9862"),
+        Arguments.of("fd00::1:2", "fd00::1", "2")
+    );
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"::1", "2001:db8::1", "2001:db8::1:abcd", 
"fe80::1%eth0"})
+  void getPortNumberFromConfigKeysRejectsBareIPv6Literal(String value) {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, value);
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsUtils.getPortNumberFromConfigKeys(conf, 
OZONE_SCM_CLIENT_ADDRESS_KEY));
+
+    assertThat(e.getMessage())
+        .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+        .contains("[" + value + "]");
+  }
+
+  @ParameterizedTest
+  @MethodSource("unambiguousAuthorities")
+  void getPortNumberFromConfigKeysAcceptsUnambiguousAuthority(String value, 
OptionalInt expectedPort) {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, value);
+
+    assertEquals(expectedPort,
+        HddsUtils.getPortNumberFromConfigKeys(conf, 
OZONE_SCM_CLIENT_ADDRESS_KEY));
+  }
+
+  static List<Arguments> unambiguousAuthorities() {
+    return Arrays.asList(
+        Arguments.of("[2001:db8::1]:9862", OptionalInt.of(9862)),
+        Arguments.of("[2001:db8::1:9862]:9862", OptionalInt.of(9862)),
+        Arguments.of("[2001:db8::1]", OptionalInt.empty()),
+        Arguments.of("192.0.2.1:9862", OptionalInt.of(9862)),
+        Arguments.of("scm1.example.com:9862", OptionalInt.of(9862)),
+        Arguments.of("scm1.example.com", OptionalInt.empty())
+    );
+  }
+
+  /**
+   * A bind host names no port, so the bracket rule must leave it alone: an
+   * explicit IPv6 listener is configured as a bare {@code ::}.
+   */
+  @Test
+  void getHostNameFromConfigKeysAcceptsBareIPv6BindHost() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_CLIENT_BIND_HOST_KEY, "::");
+
+    assertEquals(Optional.of("::"),
+        HddsUtils.getHostNameFromConfigKeys(conf, 
OZONE_SCM_CLIENT_BIND_HOST_KEY));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"scm1.example.com", "192.0.2.1", "2001:db8::1", 
"localhost", "127.0.0.1", "::1"})
+  void validateAdvertisedHostAcceptsReachableHost(String host) {
+    assertDoesNotThrow(() -> 
validateAdvertisedHost(OZONE_SCM_CLIENT_ADDRESS_KEY, host));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"0.0.0.0", "::", "169.254.1.1", "fe80::1", 
"fe80::1%eth0", "2001:db8::1/64",
+      "[::]", "[fe80::1]", "[fe80::1%eth0]"})
+  void validateAdvertisedHostRejectsUnadvertisableHost(String host) {
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> validateAdvertisedHost(OZONE_SCM_CLIENT_ADDRESS_KEY, host));
+
+    assertThat(e.getMessage())
+        .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+        .contains(host);
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"0.0.0.0:9860", "[::]:9860", "[fe80::1%eth0]:9860"})
+  void validateAdvertisedAddressRejectsUnadvertisableHostWithPort(String 
value) {
+    assertThrows(ConfigurationException.class,
+        () -> validateAdvertisedAddress(OZONE_SCM_CLIENT_ADDRESS_KEY, value));
+  }
+
+  @Test
+  void getHostNameRejectsUnadvertisableDatanodeHostname() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(HDDS_DATANODE_HOST_NAME_KEY, "0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsUtils.getHostName(conf));
+
+    assertThat(e.getMessage()).contains(HDDS_DATANODE_HOST_NAME_KEY);
+  }
+
+  @Test
+  void getHostNameAcceptsConfiguredDatanodeHostname() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(HDDS_DATANODE_HOST_NAME_KEY, "dn1.example.com");
+
+    assertEquals("dn1.example.com", HddsUtils.getHostName(conf));
+  }
+
+  /**
+   * SCM rewrites its client address with the bound host, so a process that
+   * shares its configuration reads the wildcard back.
+   */
+  @Test
+  void getScmAddressForClientsAcceptsWildcardListenAddress() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "0.0.0.0:9860");
+
+    InetSocketAddress addr = 
HddsUtils.getScmAddressForClients(conf).iterator().next();
+
+    assertEquals("0.0.0.0", addr.getHostString());
+    assertEquals(9860, addr.getPort());
+  }
+
+  @Test
+  void getScmAddressForClientsRejectsUnadvertisableScmName() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_NAMES, "0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsUtils.getScmAddressForClients(conf));
+
+    assertThat(e.getMessage()).contains(OZONE_SCM_NAMES);
+  }
+
+  /**
+   * A host that can never be advertised has to be named as such, rather than
+   * be asked for brackets that leave it rejected anyway.
+   */
+  @Test
+  void validateAdvertisedAddressReportsTheHostBeforeTheBrackets() {
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> validateAdvertisedAddress(OZONE_SCM_CLIENT_ADDRESS_KEY, "::"));
+
+    assertThat(e.getMessage()).contains("wildcard").doesNotContain("bracket");
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"scm1.example.com", "192.0.2.1", 
"[2001:db8::1]:9860", "127.0.0.1:9860"})
+  void validateAdvertisedAddressAcceptsReachableAddress(String value) {
+    assertDoesNotThrow(() -> 
validateAdvertisedAddress(OZONE_SCM_CLIENT_ADDRESS_KEY, value));
+  }
+
+  @Test
+  void validateAdvertisedAddressConfigSkipsUnsetProperty() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+
+    assertDoesNotThrow(() -> validateAdvertisedAddressConfig(conf, 
OZONE_SCM_CLIENT_ADDRESS_KEY));
+  }
+
+  @Test
+  void validateAdvertisedAddressConfigChecksEveryListEntry() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_NAMES, "scm1.example.com,0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> validateAdvertisedAddressConfig(conf, 
OZONE_SCM_CLIENT_ADDRESS_KEY, OZONE_SCM_NAMES));
+
+    assertThat(e.getMessage()).contains(OZONE_SCM_NAMES).contains("0.0.0.0");
+  }
 }
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
index 8706f0f7f03..60111c2f333 100644
--- 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
@@ -27,9 +27,11 @@
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_DEFAULT;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_NAMES;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_DEFAULT;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_KEY;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -162,4 +164,80 @@ public void testNonHAWithRestDefaults() {
     assertEquals("localhost:" + OZONE_SCM_DATANODE_PORT_DEFAULT,
         scmNodeInfos.get(0).getScmDatanodeAddress());
   }
+
+  @Test
+  public void testSCMHANodeInfoRejectsWildcardSCMAddress() {
+    for (String nodeId : nodes) {
+      conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+          scmServiceId, nodeId), "localhost");
+    }
+    String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+        scmServiceId, "scm1");
+    conf.set(addressKey, "0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> SCMNodeInfo.buildNodeInfo(conf));
+
+    assertThat(e.getMessage()).contains(addressKey).contains("0.0.0.0");
+  }
+
+  /**
+   * The SCM address property names a host and takes its ports from separate
+   * properties, so a bare IPv6 literal is unambiguous there.
+   */
+  @Test
+  public void testSCMHANodeInfoAcceptsBareIPv6SCMAddress() {
+    for (String nodeId : nodes) {
+      conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+          scmServiceId, nodeId), "2001:db8::1");
+    }
+
+    List<SCMNodeInfo> scmNodeInfos = SCMNodeInfo.buildNodeInfo(conf);
+
+    assertEquals("[2001:db8::1]:" + OZONE_SCM_CLIENT_PORT_DEFAULT,
+        scmNodeInfos.get(0).getScmClientAddress());
+  }
+
+  @Test
+  public void testSCMHANodeInfoRejectsBracketedWildcardSCMAddress() {
+    for (String nodeId : nodes) {
+      conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+          scmServiceId, nodeId), "localhost");
+    }
+    String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+        scmServiceId, "scm1");
+    conf.set(addressKey, "[::]");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> SCMNodeInfo.buildNodeInfo(conf));
+
+    assertThat(e.getMessage()).contains(addressKey).contains("[::]");
+  }
+
+  /**
+   * A non-HA SCM rewrites its address properties with the bound host, so a
+   * process that shares its configuration reads the wildcard back.
+   */
+  @Test
+  public void testNonHAAcceptsWildcardListenAddress() {
+    OzoneConfiguration config = new OzoneConfiguration();
+    config.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "0.0.0.0:9860");
+    config.set(OZONE_SCM_DATANODE_ADDRESS_KEY, "0.0.0.0:9861");
+
+    List<SCMNodeInfo> scmNodeInfos = SCMNodeInfo.buildNodeInfo(config);
+
+    assertEquals("0.0.0.0:9860", scmNodeInfos.get(0).getScmClientAddress());
+    assertEquals("0.0.0.0:9861", scmNodeInfos.get(0).getScmDatanodeAddress());
+  }
+
+  @Test
+  public void testNonHARejectsWildcardScmNames() {
+    OzoneConfiguration config = new OzoneConfiguration();
+    config.set(OZONE_SCM_NAMES, "0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> SCMNodeInfo.buildNodeInfo(config));
+
+    assertThat(e.getMessage()).contains(OZONE_SCM_NAMES).contains("0.0.0.0");
+  }
 }
diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
index d0c0a60b03e..c8801ecc5c5 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
@@ -29,6 +29,7 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.protocol.DatanodeDetails;
 import org.apache.hadoop.hdds.scm.net.HostAndPort;
@@ -76,11 +77,13 @@ public InitDatanodeState(ConfigurationSource conf,
   @Override
   public DatanodeStateMachine.DatanodeStates call() throws Exception {
     final Collection<HostAndPort> addresses;
+    final HostAndPort reconAddress;
     try {
       addresses = HddsServerUtil.getSCMAddressForDatanodes(conf);
-    } catch (IllegalArgumentException e) {
+      reconAddress = getReconAddressForDatanodes(conf);
+    } catch (IllegalArgumentException | ConfigurationException e) {
       if (!Strings.isNullOrEmpty(e.getMessage())) {
-        LOG.error("Failed to get SCM addresses: {}", e.getMessage());
+        LOG.error("Failed to get SCM or Recon addresses: {}", e.getMessage());
       }
       return DatanodeStateMachine.DatanodeStates.SHUTDOWN;
     }
@@ -103,7 +106,6 @@ public DatanodeStateMachine.DatanodeStates call() throws 
Exception {
         connectionManager.addSCMServer(addr, context.getThreadNamePrefix());
         this.context.addEndpoint(addr);
       }
-      final HostAndPort reconAddress = getReconAddressForDatanodes(conf);
       if (reconAddress != null) {
         connectionManager.addReconServer(reconAddress,
             context.getThreadNamePrefix());
diff --git 
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
 
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
index e7ffc64c5a6..44229eed1bf 100644
--- 
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
+++ 
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
@@ -50,6 +50,7 @@
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.conf.ReconfigurationHandler;
 import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.recon.ReconConfigKeys;
 import org.apache.hadoop.hdds.scm.ScmConfigKeys;
 import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
 import org.apache.hadoop.ipc_.RPC;
@@ -511,6 +512,17 @@ public void 
testDatanodeStateMachineWithInvalidConfiguration()
     /** Port out of range **/
     confList.add(Maps.immutableEntry(
         ScmConfigKeys.OZONE_SCM_NAMES, "scm:123456"));
+    /** Unbracketed IPv6 literal **/
+    confList.add(Maps.immutableEntry(
+        ScmConfigKeys.OZONE_SCM_NAMES, "::1"));
+    /** Wildcard **/
+    confList.add(Maps.immutableEntry(
+        ScmConfigKeys.OZONE_SCM_NAMES, "0.0.0.0"));
+
+    // Invalid ozone.recon.address
+    /** Wildcard **/
+    confList.add(Maps.immutableEntry(
+        ReconConfigKeys.OZONE_RECON_ADDRESS_KEY, "0.0.0.0:9891"));
 
     confList.forEach((entry) -> {
       OzoneConfiguration perTestConf = new OzoneConfiguration(conf);
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
index 7ffe7c7a643..1d51c8b7365 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
@@ -31,6 +31,8 @@
 import static org.apache.hadoop.hdds.HddsUtils.getHostPortString;
 import static org.apache.hadoop.hdds.HddsUtils.getPortNumberFromConfigKeys;
 import static org.apache.hadoop.hdds.HddsUtils.getScmServiceId;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedAddress;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedHost;
 import static 
org.apache.hadoop.hdds.recon.ReconConfigKeys.OZONE_RECON_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.recon.ReconConfigKeys.OZONE_RECON_DATANODE_PORT_DEFAULT;
 import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_KEY;
@@ -893,6 +895,7 @@ public static Collection<HostAndPort> 
getSCMAddressForDatanodes(ConfigurationSou
 
       final Collection<HostAndPort> addresses = new HashSet<>(names.size());
       for (String address : names) {
+        validateAdvertisedAddress(OZONE_SCM_NAMES, address);
         Optional<String> hostname = getHostName(address);
         if (!hostname.isPresent()) {
           throw new IllegalArgumentException("Invalid hostname for SCM: "
@@ -931,6 +934,7 @@ public static List<ScmNodeAddress> 
getSCMAddressForDatanodes(
         LOG.warn("The SCM address configuration {} is not defined, return 
nothing", addressKey);
         return null;
       }
+      validateAdvertisedHost(addressKey, scmAddress);
 
       int scmDatanodePort = SCMNodeInfo.getPort(conf, scmServiceId, scmNodeId,
           OZONE_SCM_DATANODE_ADDRESS_KEY, OZONE_SCM_DATANODE_PORT_KEY,
@@ -953,6 +957,7 @@ public static HostAndPort getReconAddressForDatanodes(
     if (StringUtils.isEmpty(name)) {
       return null;
     }
+    validateAdvertisedAddress(OZONE_RECON_ADDRESS_KEY, name);
     Optional<String> hostname = getHostName(name);
     if (!hostname.isPresent()) {
       throw new IllegalArgumentException("Invalid hostname for Recon: "
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
index 60a4ca7325f..50aea32c8b8 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
@@ -215,6 +215,7 @@ public static SCMHANodeDetails 
loadSCMHAConfig(OzoneConfiguration conf,
               "%s. SCM RPC Address should be set for all nodes in a SCM " +
               "service.", rpcAddrKey);
         }
+        HddsUtils.validateAdvertisedHost(rpcAddrKey, rpcAddrStr);
         isSCMddressSet = true;
 
         String ratisPortKey = 
ConfUtils.addKeySuffixes(OZONE_SCM_RATIS_PORT_KEY,
@@ -266,7 +267,8 @@ public static SCMHANodeDetails 
loadSCMHAConfig(OzoneConfiguration conf,
         LOG.info("Found matching SCM address with SCMServiceId: {}, " +
                 "SCMNodeId: {}, RPC Address: {} and Ratis port: {}",
             localScmServiceId, localScmNodeId,
-            NetUtils.getHostPortString(localRpcAddress), localRatisPort);
+            HddsUtils.getHostPortString(localRpcAddress.getHostName(), 
localRpcAddress.getPort()),
+            localRatisPort);
 
         // Set SCM node specific config keys.
         ConfUtils.setNodeSpecificConfigs(nodeSpecificConfigKeys, conf,
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
index 39fa7e97d03..d819b19432b 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
@@ -19,7 +19,6 @@
 
 import java.net.InetSocketAddress;
 import org.apache.hadoop.hdds.NodeDetails;
-import org.apache.hadoop.net.NetUtils;
 
 /**
  * Construct SCM node details.
@@ -147,11 +146,6 @@ public SCMNodeDetails build() {
     }
   }
 
-  @Override
-  public String getRpcAddressString() {
-    return NetUtils.getHostPortString(getRpcAddress());
-  }
-
   public InetSocketAddress getClientProtocolServerAddress() {
     return clientProtocolServerAddress;
   }
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
index b19b59320c7..e452ef51dbb 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
@@ -17,6 +17,8 @@
 
 package org.apache.hadoop.hdds.scm;
 
+import static 
org.apache.hadoop.hdds.recon.ReconConfigKeys.OZONE_RECON_ADDRESS_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_ID_DIR;
@@ -33,14 +35,17 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
+import java.util.Collections;
 import java.util.concurrent.TimeUnit;
 import org.apache.commons.io.FileUtils;
 import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.ha.SCMNodeInfo;
 import org.apache.hadoop.hdds.server.ServerUtils;
 import org.apache.hadoop.hdds.utils.HddsServerUtil;
 import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.ozone.ha.ConfUtils;
 import org.apache.hadoop.test.PathUtils;
 import org.junit.jupiter.api.Test;
 
@@ -265,4 +270,49 @@ public void testGetDatanodeIdFilePath() {
       FileUtils.deleteQuietly(metaDir);
     }
   }
+
+  @Test
+  public void testGetSCMAddressForDatanodesRejectsWildcardSCMName() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_SCM_NAMES, "0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsServerUtil.getSCMAddressForDatanodes(conf));
+
+    assertThat(e.getMessage()).contains(OZONE_SCM_NAMES).contains("0.0.0.0");
+  }
+
+  @Test
+  public void testGetSCMAddressForDatanodesRejectsWildcardHASCMAddress() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    final String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+        "scmservice", "scm1");
+    conf.set(addressKey, "::");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsServerUtil.getSCMAddressForDatanodes(conf, "scmservice",
+            Collections.singleton("scm1")));
+
+    assertThat(e.getMessage()).contains(addressKey).contains("::");
+  }
+
+  @Test
+  public void testGetReconAddressForDatanodesRejectsWildcardAddress() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_RECON_ADDRESS_KEY, "0.0.0.0:9891");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> HddsServerUtil.getReconAddressForDatanodes(conf));
+
+    assertThat(e.getMessage()).contains(OZONE_RECON_ADDRESS_KEY);
+  }
+
+  @Test
+  public void testGetReconAddressForDatanodesAcceptsHostname() {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OZONE_RECON_ADDRESS_KEY, "recon.example.com:9891");
+
+    assertEquals("recon.example.com:9891",
+        
HddsServerUtil.getReconAddressForDatanodes(conf).getHostAndPortString());
+  }
 }
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
index 15715d72b61..17c743beeab 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
@@ -36,13 +36,16 @@
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_BIND_HOST_KEY;
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_KEY;
 import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_METADATA_DIRS;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
 import java.net.InetSocketAddress;
 import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.ScmConfigKeys;
 import org.apache.hadoop.hdds.scm.ScmRatisServerConfig;
@@ -57,6 +60,8 @@
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 
 /**
  * Test for SCM HA-related configuration.
@@ -211,6 +216,30 @@ public void testSCMConfig() throws Exception {
         RaftServerConfigKeys.Log.Appender.WAIT_TIME_MIN_KEY);
   }
 
+  @ParameterizedTest
+  @CsvSource({"scm1, 0.0.0.0", "scm2, 169.254.1.1"})
+  void testSCMConfigRejectsUnadvertisableAddress(String nodeId, String 
address) {
+    String scmServiceId = "scmserviceId";
+    conf.set(ScmConfigKeys.OZONE_SCM_SERVICE_IDS_KEY, scmServiceId);
+    conf.set(ScmConfigKeys.OZONE_SCM_NODES_KEY + "." + scmServiceId,
+        "scm1,scm2,scm3");
+    conf.set(ScmConfigKeys.OZONE_SCM_NODE_ID_KEY, "scm1");
+    for (String node : new String[] {"scm1", "scm2", "scm3"}) {
+      conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY, scmServiceId,
+          node), "localhost");
+    }
+    String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+        scmServiceId, nodeId);
+    conf.set(addressKey, address);
+    SCMStorageConfig scmStorageConfig = mock(SCMStorageConfig.class);
+    
when(scmStorageConfig.getState()).thenReturn(Storage.StorageState.NOT_INITIALIZED);
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> SCMHANodeDetails.loadSCMHAConfig(conf, scmStorageConfig));
+
+    assertThat(e.getMessage()).contains(addressKey).contains(address);
+  }
+
   @Test
   public void testSamePortConfig() throws Exception {
     String scmServiceId = "scmserviceId";
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 0e457553e79..25453776b7f 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -21,6 +21,7 @@
 import static org.apache.hadoop.hdds.HddsUtils.getHostNameFromConfigKeys;
 import static org.apache.hadoop.hdds.HddsUtils.getHostPortString;
 import static org.apache.hadoop.hdds.HddsUtils.getPortNumberFromConfigKeys;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedHost;
 import static org.apache.hadoop.ozone.OzoneConsts.DOUBLE_SLASH_OM_KEY_PREFIX;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_INDICATOR;
@@ -161,12 +162,19 @@ public static String getOmRpcAddress(ConfigurationSource 
conf) {
    * @param conf configuration
    * @param confKey configuration key to lookup address from
    * @return Target InetSocketAddress for the OM RPC server.
+   * @throws ConfigurationException if the configured host cannot be advertised
+   *         to the other OMs of the service
    */
   public static String getOmRpcAddress(ConfigurationSource conf,
       String confKey) {
     final Optional<String> host = getHostNameFromConfigKeys(conf, confKey);
 
     if (host.isPresent()) {
+      // Without a service and node suffix this is the non-HA OM's own RPC
+      // address, which defaults to a wildcard.
+      if (!OZONE_OM_ADDRESS_KEY.equals(confKey)) {
+        validateAdvertisedHost(confKey, host.get());
+      }
       return getHostPortString(host.get(),
           getPortNumberFromConfigKeys(conf, confKey)
               .orElse(OZONE_OM_PORT_DEFAULT));
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
index 8abfba48a13..889d2d3b950 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
@@ -24,6 +24,7 @@
 import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
 import static 
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_INTERNAL_SERVICE_ID;
 import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_NODES_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_PORT_DEFAULT;
 import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SERVICE_IDS_KEY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -43,6 +44,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.ozone.ha.ConfUtils;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
@@ -455,7 +457,7 @@ void testOmAddressesBracketIPv6Literals() {
     assertEquals("[2001:db8::1]:9999", OmUtils.getOmRpcAddress(conf));
 
     String peerAddressKey = OZONE_OM_ADDRESS_KEY + ".omservice.om1";
-    conf.set(peerAddressKey, "2001:db8::2");
+    conf.set(peerAddressKey, "[2001:db8::2]");
     assertEquals("[2001:db8::2]:" + OMConfigKeys.OZONE_OM_PORT_DEFAULT,
         OmUtils.getOmRpcAddress(conf, peerAddressKey));
 
@@ -483,4 +485,38 @@ public void testResolveOmHostAcceptsIpv6Literal() {
       }
     }
   }
+
+  @Test
+  void getOmRpcAddressRejectsWildcardPeerAddress() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    String rpcAddrKey = ConfUtils.addKeySuffixes(OZONE_OM_ADDRESS_KEY,
+        "omservice", "om1");
+    conf.set(rpcAddrKey, "0.0.0.0");
+
+    ConfigurationException e = assertThrows(ConfigurationException.class,
+        () -> OmUtils.getOmRpcAddress(conf, rpcAddrKey));
+
+    assertThat(e.getMessage()).contains(rpcAddrKey).contains("0.0.0.0");
+  }
+
+  @Test
+  void getOmRpcAddressAcceptsPeerHostname() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    String rpcAddrKey = ConfUtils.addKeySuffixes(OZONE_OM_ADDRESS_KEY,
+        "omservice", "om1");
+    conf.set(rpcAddrKey, "om1.example.com");
+
+    assertEquals("om1.example.com:" + OZONE_OM_PORT_DEFAULT,
+        OmUtils.getOmRpcAddress(conf, rpcAddrKey));
+  }
+
+  /**
+   * The non-HA client failover proxy reads the unsuffixed property, which is
+   * the non-HA OM's own RPC address and ships as a wildcard.
+   */
+  @Test
+  void getOmRpcAddressKeepsWildcardDefault() {
+    assertEquals("0.0.0.0:" + OZONE_OM_PORT_DEFAULT,
+        OmUtils.getOmRpcAddress(new OzoneConfiguration(), 
OZONE_OM_ADDRESS_KEY));
+  }
 }
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
index 06c95559b28..ec42e895c07 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
@@ -512,7 +512,7 @@ private void setupProxyProvider(int omNodeCount, 
OzoneConfiguration config) thro
     for (int i = 0; i < omNodeCount; i++) {
       String nodeId = NODE_ID_BASE_STR + (i + 1); // 1-th indexed
       config.set(ConfUtils.addKeySuffixes(OZONE_OM_ADDRESS_KEY, OM_SERVICE_ID,
-          nodeId),  "0.0.0.0:" + i);
+          nodeId),  "localhost:" + i);
       allNodeIds.add(nodeId);
       omNodeIds[i] = nodeId;
       omNodeAnswers[i] = new OMAnswer();
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
index 4a91b44ee34..08085743270 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
@@ -49,7 +49,7 @@
 public class TestOMFailoverProxyProvider {
   private static final String OM_SERVICE_ID = "om-service-test1";
   private static final String NODE_ID_BASE_STR = "omNode-";
-  private static final String DUMMY_NODE_ADDR = "0.0.0.0:8080";
+  private static final String DUMMY_NODE_ADDR = "localhost:8080";
   private HadoopRpcOMFailoverProxyProvider<OzoneManagerProtocolPB> provider;
   private long waitBetweenRetries;
   private int numNodes = 3;
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
index 4b3eaef0018..46125e761ca 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
@@ -450,4 +450,23 @@ public void testSetRatisAddress() {
     assertEquals("192.168.1.100", nodeDetails.getHostAddress());
     assertEquals(9873, nodeDetails.getRatisPort());
   }
+
+  /**
+   * An advertised peer identity has to bracket an IPv6 literal, or a reader
+   * takes the last group of the address for the port.
+   */
+  @Test
+  public void testRpcAddressStringBracketsIPv6Literal() {
+    OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+        .setOMServiceId(OM_SERVICE_ID)
+        .setOMNodeId(OM_NODE_ID)
+        .setRpcAddress(InetSocketAddress.createUnresolved("2001:db8::1", 
RPC_PORT))
+        .setRatisPort(RATIS_PORT)
+        .setHttpAddress(HTTP_ADDRESS)
+        .setHttpsAddress(HTTPS_ADDRESS)
+        .setIsListener(false)
+        .build();
+
+    assertEquals("[2001:db8::1]:" + RPC_PORT, 
nodeDetails.getRpcAddressString());
+  }
 }
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
index d6b9d51e921..17ffa2fef40 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
@@ -112,7 +112,7 @@ public void testDefaultPortIfNotSpecified() throws 
Exception {
     String omNode1RpcAddrKey = getOMAddrKeyWithSuffix(serviceID, omNode1Id);
     String omNode2RpcAddrKey = getOMAddrKeyWithSuffix(serviceID, omNode2Id);
 
-    conf.set(omNode1RpcAddrKey, "0.0.0.0");
+    conf.set(omNode1RpcAddrKey, "localhost");
     conf.set(omNode2RpcAddrKey, "122.0.0.122");
 
     // Set omNode1 as the current node. omNode1 address does not have a port
@@ -121,7 +121,7 @@ public void testDefaultPortIfNotSpecified() throws 
Exception {
 
     startCluster();
     OzoneManager om = cluster.getOzoneManager();
-    assertEquals("0.0.0.0",
+    assertEquals("localhost",
         om.getOmRpcServerAddr().getHostName());
     assertEquals(OMConfigKeys.OZONE_OM_PORT_DEFAULT,
         om.getOmRpcServerAddr().getPort());
@@ -188,7 +188,7 @@ public void testThreeNodeOMservice() throws Exception {
 
     // Set node2 to localhost and the other two nodes to dummy addresses
     conf.set(omNode1RpcAddrKey, "123.0.0.123:9862");
-    conf.set(omNode2RpcAddrKey, "0.0.0.0:9862");
+    conf.set(omNode2RpcAddrKey, "localhost:9862");
     conf.set(omNode3RpcAddrKey, "124.0.0.124:9862");
 
     conf.setInt(omNode3RatisPortKey, 9898);
@@ -217,7 +217,7 @@ public void testThreeNodeOMservice() throws Exception {
             OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
         break;
       case omNode2Id :
-        expectedPeerAddress = "0.0.0.0:" +
+        expectedPeerAddress = "localhost:" +
             OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
         break;
       case omNode3Id :
@@ -263,7 +263,7 @@ public void testOMHAWithUnresolvedAddresses() throws 
Exception {
 
     // Set node2 to localhost and the other two nodes to dummy addresses
     conf.set(omNode1RpcAddrKey, node1Hostname + ":9862");
-    conf.set(omNode2RpcAddrKey, "0.0.0.0:9862");
+    conf.set(omNode2RpcAddrKey, "localhost:9862");
     conf.set(omNode3RpcAddrKey, node3Hostname + ":9804");
 
     conf.setInt(omNode3RatisPortKey, 9898);
@@ -300,7 +300,7 @@ public void testOMHAWithUnresolvedAddresses() throws 
Exception {
             OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
         break;
       case omNode2Id :
-        expectedPeerAddress = "0.0.0.0:" +
+        expectedPeerAddress = "localhost:" +
             OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
         break;
       case omNode3Id :
@@ -425,7 +425,7 @@ public void testMultipleOMServiceIds() throws Exception {
     conf.set(getOMAddrKeyWithSuffix(om2ServiceId, omNode1Id),
         "125.0.0.126:9862");
     conf.set(getOMAddrKeyWithSuffix(om2ServiceId, omNode2Id),
-        "0.0.0.0:9862");
+        "localhost:9862");
     conf.set(getOMAddrKeyWithSuffix(om2ServiceId, omNode3Id),
         "126.0.0.127:9862");
 
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
index da0ac79be26..7c68a51cc57 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
@@ -35,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.net.NetUtils;
 import org.apache.hadoop.ozone.OmUtils;
@@ -203,7 +204,8 @@ public static OMHANodeDetails 
loadOMHAConfig(OzoneConfiguration conf) {
         LOG.info("Found matching OM address with OMServiceId: {}, " +
                 "OMNodeId: {}, RPC Address: {} ,Ratis port: {} and isListener: 
{}",
             localOMServiceId, localOMNodeId,
-            NetUtils.getHostPortString(localRpcAddress), localRatisPort, 
localIsListener);
+            HddsUtils.getHostPortString(localRpcAddress.getHostName(), 
localRpcAddress.getPort()),
+            localRatisPort, localIsListener);
 
         ConfUtils.setNodeSpecificConfigs(genericConfigKeys, conf,
             localOMServiceId, localOMNodeId, LOG);
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
index b8b413d2c20..28b0e60609a 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
@@ -33,6 +33,7 @@
 import java.util.Collection;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.cli.GenericCli;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import 
org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB;
@@ -114,6 +115,7 @@ public Void call() throws Exception {
     HddsServerUtil.startupShutdownMessage(OzoneVersionInfo.OZONE_VERSION_INFO,
             ReconServer.class, originalArgs, LOG, configuration);
     ConfigurationProvider.setConfiguration(configuration);
+    HddsUtils.validateAdvertisedAddressConfig(configuration, 
ReconConfigKeys.OZONE_RECON_ADDRESS_KEY);
 
     try {
       reconAdmins = createReconAdmins(configuration);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to