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

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


The following commit(s) were added to refs/heads/master by this push:
     new cdd854f23f fix: Add strict hostname resolution and loopback check for 
advertised address (#4595)
cdd854f23f is described below

commit cdd854f23fe27a670cde126a5419fd9c222ad482
Author: Zixuan Liu <node...@gmail.com>
AuthorDate: Wed Apr 23 02:45:19 2025 +0800

    fix: Add strict hostname resolution and loopback check for advertised 
address (#4595)
    
    * fix: Add strict hostname resolution and loopback check for advertised 
address
    
    * Fix style
    
    * Fix test
---
 .../org/apache/bookkeeper/bookie/BookieImpl.java   | 65 ++++++++++--------
 .../bookkeeper/conf/ServerConfiguration.java       | 10 +++
 .../bookkeeper/bookie/BookieSocketAddressTest.java | 77 ++++++++++++++++++++++
 .../bookkeeper/bookie/CookieIndexDirTest.java      |  3 +-
 .../bookie/datainteg/CookieValidationTest.java     |  7 +-
 .../bookie/datainteg/DataIntegrityCheckTest.java   |  3 +-
 6 files changed, 131 insertions(+), 34 deletions(-)

diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieImpl.java 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieImpl.java
index 4ca771a40b..111f5134e7 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieImpl.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieImpl.java
@@ -254,49 +254,56 @@ public class BookieImpl implements Bookie {
      */
     public static BookieSocketAddress getBookieAddress(ServerConfiguration 
conf)
             throws UnknownHostException {
+        String hostAddress;
         // Advertised address takes precedence over the listening interface 
and the
         // useHostNameAsBookieID settings
         if (conf.getAdvertisedAddress() != null && 
conf.getAdvertisedAddress().trim().length() > 0) {
-            String hostAddress = conf.getAdvertisedAddress().trim();
-            return new BookieSocketAddress(hostAddress, conf.getBookiePort());
-        }
-
-        String iface = conf.getListeningInterface();
-        if (iface == null) {
-            iface = "default";
-        }
-
-        String hostAddress = DNS.getDefaultIP(iface);
-        if (conf.getUseHostNameAsBookieID()) {
-            try {
-                hostAddress = 
InetAddress.getByName(hostAddress).getCanonicalHostName();
-            } catch (Exception e) {
-                UnknownHostException unknownHostException =
-                        new UnknownHostException("Unable to resolve hostname 
for interface: " + iface);
-                unknownHostException.initCause(e);
-                throw unknownHostException;
+            hostAddress = conf.getAdvertisedAddress().trim();
+        } else {
+            String iface = conf.getListeningInterface();
+            if (iface == null) {
+                iface = "default";
             }
-            if (conf.getUseShortHostName()) {
-                /*
-                 * if short hostname is used, then FQDN is not used. Short
-                 * hostname is the hostname cut at the first dot.
-                 */
-                hostAddress = hostAddress.split("\\.", 2)[0];
+            String defaultIP = DNS.getDefaultIP(iface);
+            if (conf.getUseHostNameAsBookieID()) {
+                try {
+                    hostAddress = 
InetAddress.getByName(defaultIP).getCanonicalHostName();
+                } catch (Exception e) {
+                    UnknownHostException unknownHostException =
+                            new UnknownHostException("Unable to resolve 
hostname for interface: " + iface);
+                    unknownHostException.initCause(e);
+                    throw unknownHostException;
+                }
+                if (defaultIP.equals(hostAddress)) {
+                    throw new UnknownHostException("Unable to resolve hostname 
for ip address: " + defaultIP);
+                }
+                if (conf.getUseShortHostName()) {
+                    /*
+                     * if short hostname is used, then FQDN is not used. Short
+                     * hostname is the hostname cut at the first dot.
+                     */
+                    hostAddress = hostAddress.split("\\.", 2)[0];
+                }
+            } else {
+                hostAddress = defaultIP;
             }
         }
 
-        BookieSocketAddress addr =
+        BookieSocketAddress bookieSocketAddress =
                 new BookieSocketAddress(hostAddress, conf.getBookiePort());
-        if (addr.getSocketAddress().getAddress().isLoopbackAddress()
-            && !conf.getAllowLoopback()) {
+        InetAddress inetAddress = 
bookieSocketAddress.getSocketAddress().getAddress();
+        if (inetAddress == null) {
+            throw new UnknownHostException("Failed to resolve InetAddress for 
host address: " + hostAddress);
+        }
+        if (inetAddress.isLoopbackAddress() && !conf.getAllowLoopback()) {
             throw new UnknownHostException("Trying to listen on loopback 
address, "
-                    + addr + " but this is forbidden by default "
+                    + bookieSocketAddress + " but this is forbidden by default 
"
                     + "(see ServerConfiguration#getAllowLoopback()).\n"
                     + "If this happen, you can consider specifying the network 
interface"
                     + " to listen on (e.g. listeningInterface=eth0) or 
specifying the"
                     + " advertised address (e.g. 
advertisedAddress=172.x.y.z)");
         }
-        return addr;
+        return bookieSocketAddress;
     }
 
     public LedgerDirsManager getLedgerDirsManager() {
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
index e970cd6b09..e549197513 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
@@ -1239,6 +1239,16 @@ public class ServerConfiguration extends 
AbstractConfiguration<ServerConfigurati
         return this;
     }
 
+    /**
+     * Remove the configured BookieId for the bookie.
+     *
+     * @return server configuration
+     */
+    public ServerConfiguration removeBookieId() {
+        this.setProperty(BOOKIE_ID, null);
+        return this;
+    }
+
     /**
      * Get the configured advertised address for the bookie.
      *
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieSocketAddressTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieSocketAddressTest.java
new file mode 100644
index 0000000000..8962318486
--- /dev/null
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieSocketAddressTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.bookkeeper.bookie;
+
+import static org.apache.bookkeeper.bookie.BookieImpl.getBookieAddress;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.google.common.net.InetAddresses;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.junit.Test;
+
+public class BookieSocketAddressTest {
+
+    private void testAdvertisedWithLoopbackAddress(String address) throws 
UnknownHostException {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setAdvertisedAddress(address);
+        conf.setAllowLoopback(false);
+        assertThatThrownBy(() -> 
getBookieAddress(conf)).isExactlyInstanceOf(UnknownHostException.class);
+
+        conf.setAllowLoopback(true);
+        BookieSocketAddress bookieAddress = getBookieAddress(conf);
+        assertThat(bookieAddress.getHostName()).isEqualTo(address);
+    }
+
+    @Test
+    public void testAdvertisedWithLoopbackAddress() throws 
UnknownHostException {
+        testAdvertisedWithLoopbackAddress("localhost");
+        testAdvertisedWithLoopbackAddress("127.0.0.1");
+    }
+
+    @Test
+    public void testAdvertisedWithNonLoopbackAddress() throws 
UnknownHostException {
+        String hostAddress = InetAddress.getLocalHost().getHostAddress();
+        if (hostAddress == null) {
+            throw new UnknownHostException("Host address is null");
+        }
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setAllowLoopback(false);
+        conf.setAdvertisedAddress(hostAddress);
+        BookieSocketAddress bookieAddress = getBookieAddress(conf);
+        assertThat(bookieAddress.getHostName()).isEqualTo(hostAddress);
+    }
+
+    @Test
+    public void testBookieAddressIsIPAddressByDefault() throws 
UnknownHostException {
+        ServerConfiguration conf = new ServerConfiguration();
+        BookieSocketAddress bookieAddress = getBookieAddress(conf);
+        
assertThat(InetAddresses.isInetAddress(bookieAddress.getHostName())).isTrue();
+    }
+
+    @Test
+    public void testBookieAddressIsHostname() throws UnknownHostException {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setUseHostNameAsBookieID(true);
+        BookieSocketAddress bookieAddress = getBookieAddress(conf);
+        
assertThat(InetAddresses.isInetAddress(bookieAddress.getHostName())).isFalse();
+    }
+}
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CookieIndexDirTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CookieIndexDirTest.java
index 3caf076082..16fbaced96 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CookieIndexDirTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CookieIndexDirTest.java
@@ -829,9 +829,10 @@ public class CookieIndexDirTest extends 
BookKeeperClusterTestCase {
             .setBookiePort(bookiePort)
             .setMetadataServiceUri(zkUtil.getMetadataServiceUri());
         conf.setUseHostNameAsBookieID(false);
+        conf.setAllowLoopback(true);
         validateConfig(conf);
 
-        conf.setAdvertisedAddress("unknown");
+        conf.setAdvertisedAddress("localhost");
         try {
             validateConfig(conf);
             fail("Should not start a bookie with ip if the bookie has been 
started with an ip");
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/CookieValidationTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/CookieValidationTest.java
index 158bde903f..3fb74cbc2e 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/CookieValidationTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/CookieValidationTest.java
@@ -75,7 +75,8 @@ public class CookieValidationTest {
     private static ServerConfiguration serverConf(boolean stampMissingCookies) 
{
         ServerConfiguration conf = new ServerConfiguration();
         conf.setDataIntegrityStampMissingCookiesEnabled(stampMissingCookies);
-        conf.setAdvertisedAddress("foobar");
+        conf.setAdvertisedAddress("localhost");
+        conf.setAllowLoopback(true);
         return conf;
     }
 
@@ -255,7 +256,7 @@ public class CookieValidationTest {
                 conf, regManager, new MockDataIntegrityCheck());
         v1.checkCookies(dirs); // stamp original cookies
 
-        conf.setAdvertisedAddress("barfoo");
+        conf.setBookieId("barfoo");
         DataIntegrityCookieValidation v2 = new DataIntegrityCookieValidation(
                 conf, regManager, new MockDataIntegrityCheck());
         try {
@@ -265,7 +266,7 @@ public class CookieValidationTest {
             // expected
         }
 
-        conf.setAdvertisedAddress("foobar");
+        conf.removeBookieId();
         DataIntegrityCookieValidation v3 = new DataIntegrityCookieValidation(
                 conf, regManager, new MockDataIntegrityCheck());
         v3.checkCookies(dirs); // should succeed as the cookie is same as 
before
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheckTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheckTest.java
index 6d4c7a122f..4c6d1c9ba9 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheckTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheckTest.java
@@ -105,7 +105,8 @@ public class DataIntegrityCheckTest {
 
     private static ServerConfiguration serverConf() {
         ServerConfiguration conf = new ServerConfiguration();
-        conf.setAdvertisedAddress("foobar");
+        conf.setAdvertisedAddress("localhost");
+        conf.setAllowLoopback(true);
         return conf;
     }
 

Reply via email to