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; }