This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new 1daeefb93 KUDU-1457 [4/n] enable webserver for IPv6
1daeefb93 is described below
commit 1daeefb93e6c47928fabf9c1b5db0cf5206f1061
Author: Ashwani Raina <[email protected]>
AuthorDate: Wed Sep 10 16:52:01 2025 +0530
KUDU-1457 [4/n] enable webserver for IPv6
This patch mainly does two things - enable webserver to make use of
IPv6 and introduce a ip config flag that can be used to choose IP
protocol for various communication channels. More details as follows:
- Add build flag (USE_IPV6) to squeasel for IPv6 support for users.
- Set appropriate bind interface for http based on webserver ip mode.
- Augment GetBoundAddresses() to enable fetching of IPv6 address.
- Introduce ip_config_mode flag that can be configured by a user if need
arises to override the default mode i.e. ipv4. One of the purposes of
this flag is to serve a hint inside various server modules like rpc,
webserver, etc to regulate the protocol used for communication.
- Set default value of --webserver_interface flag to 0.0.0.0. The value
is used as bind interface during webserver object initialisation.
- Reduce frequency of 'master address resolution to multiple addresses'
warning once a minute to reduce flooding of logs with such message
when hostname resolves to multiple addresses.
- Add parallel suite of tests to webserver-test and mini_master-test.
- Additional manual testing is also performed to ensure webserver is
correctly hosted on IPv6 interfaces and the endpoint is capable of
performing http methods.
- Fix a bug in java client net utils to ensure that input address is
correctly parsed into host and port for different IPv6 address
combinations.
- Manual testing for different combinations with servers running on
IPv4,IPv6 and Dual stack address endpoints and Kudu CLI able to
connect with servers and perform basic operations like ksck,
'perf loadgen', table list, etc. Kudu CLI also makes use of the ip
config mode flag to enforce the IP communication protocol.
Change-Id: I7095561539f427b5c58ada7b480ca549b0ab795e
Reviewed-on: http://gerrit.cloudera.org:8080/23411
Reviewed-by: Marton Greber <[email protected]>
Tested-by: Marton Greber <[email protected]>
Reviewed-by: Alexey Serbin <[email protected]>
---
.../main/java/org/apache/kudu/util/NetUtil.java | 5 +-
.../java/org/apache/kudu/util/TestNetUtil.java | 27 ++
src/kudu/master/mini_master-test.cc | 33 ++-
src/kudu/master/mini_master.cc | 5 +-
src/kudu/scripts/start_kudu.sh | 9 +
src/kudu/server/webserver-test.cc | 304 +++++++++++++++------
src/kudu/server/webserver.cc | 31 ++-
src/kudu/server/webserver_options.cc | 5 +-
src/kudu/tserver/heartbeater.cc | 2 +-
src/kudu/util/net/net_util-test.cc | 28 +-
src/kudu/util/net/net_util.cc | 67 ++++-
src/kudu/util/net/net_util.h | 21 +-
thirdparty/build-definitions.sh | 3 +-
13 files changed, 433 insertions(+), 107 deletions(-)
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/util/NetUtil.java
b/java/kudu-client/src/main/java/org/apache/kudu/util/NetUtil.java
index 5f40e49ba..7dd41d2f6 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/util/NetUtil.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/util/NetUtil.java
@@ -67,10 +67,9 @@ public class NetUtil {
*/
public static HostAndPort parseString(String addrString, int defaultPort) {
// Use Guava's HostAndPort so we don't need to handle parsing ourselves.
- com.google.common.net.HostAndPort hostAndPort = addrString.indexOf(':') ==
-1 ?
- com.google.common.net.HostAndPort.fromParts(addrString, defaultPort) :
+ com.google.common.net.HostAndPort hostAndPort =
com.google.common.net.HostAndPort.fromString(addrString);
- return new HostAndPort(hostAndPort.getHost(), hostAndPort.getPort());
+ return new HostAndPort(hostAndPort.getHost(),
hostAndPort.getPortOrDefault(defaultPort));
}
/**
diff --git
a/java/kudu-client/src/test/java/org/apache/kudu/util/TestNetUtil.java
b/java/kudu-client/src/test/java/org/apache/kudu/util/TestNetUtil.java
index f92023d3b..34d67c49b 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/util/TestNetUtil.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/util/TestNetUtil.java
@@ -55,6 +55,18 @@ public class TestNetUtil {
HostAndPort hostAndPortForStringWithoutPort =
NetUtil.parseString(stringWithoutPort, 12345);
assertEquals(hostAndPortForStringWithoutPort.getHost(), stringWithoutPort);
assertEquals(hostAndPortForStringWithoutPort.getPort(), 12345);
+
+ String stringWithPortIpv6 = "[1:2:3::4]:1234";
+ HostAndPort hostAndPortForStringWithPortIpv6 = NetUtil.parseString(
+ stringWithPortIpv6, 0);
+ assertEquals(hostAndPortForStringWithPortIpv6.getHost(), "1:2:3::4");
+ assertEquals(hostAndPortForStringWithPortIpv6.getPort(), 1234);
+
+ String stringWithoutPortIpv6 = "[1:2:3::4]";
+ HostAndPort hostAndPortForStringWithoutPortIpv6 = NetUtil.parseString(
+ stringWithoutPortIpv6, 12345);
+ assertEquals(hostAndPortForStringWithoutPortIpv6.getHost(), "1:2:3::4");
+ assertEquals(hostAndPortForStringWithoutPortIpv6.getPort(), 12345);
}
/**
@@ -71,6 +83,14 @@ public class TestNetUtil {
new HostAndPort("10.0.0.1", 5555),
new HostAndPort("127.0.0.1", 7777)}
);
+ String testAddrsIpv6 = "1234:5678::1,[10:10::10]:5555,[::1]:7777";
+ List<HostAndPort> hostsAndPortsIpv6 = NetUtil.parseStrings(testAddrsIpv6,
3333);
+ assertArrayEquals(hostsAndPortsIpv6.toArray(),
+ new HostAndPort[] {
+ new HostAndPort("1234:5678::1", 3333),
+ new HostAndPort("10:10::10", 5555),
+ new HostAndPort("::1", 7777)}
+ );
}
@Test
@@ -80,12 +100,19 @@ public class TestNetUtil {
new HostAndPort("1.2.3.4.5", 0)
);
assertEquals("127.0.0.1:1111,1.2.3.4.5:0",
NetUtil.hostsAndPortsToString(hostsAndPorts));
+
+ List<HostAndPort> hostsAndPortsIpv6 = Arrays.asList(
+ new HostAndPort("::1", 1111),
+ new HostAndPort("1:2:3:4::5", 0)
+ );
+ assertEquals("::1:1111,1:2:3:4::5:0",
NetUtil.hostsAndPortsToString(hostsAndPortsIpv6));
}
@Test
public void testLocal() throws Exception {
assertTrue(NetUtil.isLocalAddress(NetUtil.getInetAddress("localhost")));
assertTrue(NetUtil.isLocalAddress(NetUtil.getInetAddress("127.0.0.1")));
+ assertTrue(NetUtil.isLocalAddress(NetUtil.getInetAddress("::1")));
assertTrue(NetUtil.isLocalAddress(InetAddress.getLocalHost()));
assertFalse(NetUtil.isLocalAddress(NetUtil.getInetAddress("kudu.apache.org")));
}
diff --git a/src/kudu/master/mini_master-test.cc
b/src/kudu/master/mini_master-test.cc
index f2072021f..064b45b91 100644
--- a/src/kudu/master/mini_master-test.cc
+++ b/src/kudu/master/mini_master-test.cc
@@ -16,8 +16,11 @@
// under the License.
#include <memory>
+#include <string>
#include <vector>
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
#include <gtest/gtest.h>
#include "kudu/fs/fs_manager.h"
@@ -25,28 +28,50 @@
#include "kudu/master/mini_master.h"
#include "kudu/util/net/net_util.h"
#include "kudu/util/path_util.h"
+#include "kudu/util/status.h"
#include "kudu/util/test_macros.h"
#include "kudu/util/test_util.h"
+DECLARE_string(ip_config_mode);
+
namespace kudu {
namespace master {
using std::unique_ptr;
-class MiniMasterTest : public KuduTest {};
+class MiniMasterTest : public KuduTest,
+ public ::testing::WithParamInterface<std::string> {
+ protected:
+ static std::string get_host() {
+ IPMode mode;
+ FLAGS_ip_config_mode = GetParam();
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ switch (mode) {
+ case IPMode::IPV6:
+ return "::1";
+ case IPMode::DUAL:
+ return "::";
+ default:
+ return "127.0.0.1";
+ }
+ }
+};
+
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, MiniMasterTest,
+ testing::Values("ipv4", "ipv6", "dual"));
-TEST_F(MiniMasterTest, TestMultiDirMaster) {
+TEST_P(MiniMasterTest, TestMultiDirMaster) {
// Specifying the number of data directories will create subdirectories
under the test root.
unique_ptr<MiniMaster> mini_master;
FsManager* fs_manager;
int kNumDataDirs = 3;
- mini_master.reset(new MiniMaster(GetTestPath("Master"),
HostPort("127.0.0.1", 0), kNumDataDirs));
+ mini_master.reset(new MiniMaster(GetTestPath("Master"), HostPort(get_host(),
0), kNumDataDirs));
ASSERT_OK(mini_master->Start());
fs_manager = mini_master->master()->fs_manager();
ASSERT_STR_CONTAINS(DirName(fs_manager->GetWalsRootDir()), "wal");
ASSERT_EQ(kNumDataDirs, fs_manager->GetDataRootDirs().size());
}
-
} // namespace master
} // namespace kudu
diff --git a/src/kudu/master/mini_master.cc b/src/kudu/master/mini_master.cc
index 480c7073e..c234c6e46 100644
--- a/src/kudu/master/mini_master.cc
+++ b/src/kudu/master/mini_master.cc
@@ -47,6 +47,7 @@ using strings::Substitute;
DECLARE_bool(enable_minidumps);
DECLARE_bool(rpc_listen_on_unix_domain_socket);
DECLARE_bool(rpc_server_allow_ephemeral_ports);
+DECLARE_string(ip_config_mode);
namespace kudu {
namespace master {
@@ -62,7 +63,7 @@ MiniMaster::MiniMaster(string fs_root, HostPort
rpc_bind_addr, int num_data_dirs
// of multi-master mini-clusters, but the SO_REUSEPORT option isn't
applicable
// for Unix domain sockets.
opts_.rpc_opts.rpc_reuseport = !FLAGS_rpc_listen_on_unix_domain_socket;
- opts_.webserver_opts.bind_interface = web_bind_addr.host();
+ CHECK_OK(HostPort::ToIpInterface(web_bind_addr.host(),
&opts_.webserver_opts.bind_interface));
opts_.webserver_opts.port = web_bind_addr.port();
opts_.set_block_cache_metrics_policy(Cache::ExistingMetricsPolicy::kKeep);
if (num_data_dirs == 1) {
@@ -110,7 +111,7 @@ Status MiniMaster::Start() {
Status MiniMaster::Restart() {
CHECK(!master_);
opts_.rpc_opts.rpc_bind_addresses = bound_rpc_.ToString();
- opts_.webserver_opts.bind_interface = bound_http_.host();
+ CHECK_OK(HostPort::ToIpInterface(bound_http_.host(),
&opts_.webserver_opts.bind_interface));
opts_.webserver_opts.port = bound_http_.port();
Shutdown();
return Start();
diff --git a/src/kudu/scripts/start_kudu.sh b/src/kudu/scripts/start_kudu.sh
index c2ded63b3..e67ef35e1 100755
--- a/src/kudu/scripts/start_kudu.sh
+++ b/src/kudu/scripts/start_kudu.sh
@@ -38,6 +38,7 @@ EXTRA_MASTER_FLAGS=""
ENABLE_TDE=""
RPC_IP="127.0.0.1"
HTTP_IP="127.0.0.1"
+IP_CONFIG_MODE=""
function usage() {
cat << EOF
@@ -241,6 +242,10 @@ function start_master() {
ARGS="$ARGS --unlock_unsafe_flags"
ARGS="$ARGS --webserver_interface=$HTTP_IP"
ARGS="$ARGS --webserver_port=$HTTP_PORT"
+ if [ -n "$IP_CONFIG_MODE" ]; then
+ ARGS="$ARGS --ip_config_mode=$IP_CONFIG_MODE"
+ ARGS="$ARGS --unlock_experimental_flags=true"
+ fi
if [ -d "$WEBSERVER_DOC_ROOT" ]; then
ARGS="$ARGS --webserver_doc_root=$WEBSERVER_DOC_ROOT"
fi
@@ -272,6 +277,10 @@ function start_tserver() {
ARGS="$ARGS --webserver_interface=$HTTP_IP"
ARGS="$ARGS --webserver_port=$HTTP_PORT"
ARGS="$ARGS --tserver_master_addrs=$4"
+ if [ -n "$IP_CONFIG_MODE" ]; then
+ ARGS="$ARGS --ip_config_mode=$IP_CONFIG_MODE"
+ ARGS="$ARGS --unlock_experimental_flags=true"
+ fi
if [ -d "$WEBSERVER_DOC_ROOT" ]; then
ARGS="$ARGS --webserver_doc_root=$WEBSERVER_DOC_ROOT"
fi
diff --git a/src/kudu/server/webserver-test.cc
b/src/kudu/server/webserver-test.cc
index 76f8b7b08..d95c7372c 100644
--- a/src/kudu/server/webserver-test.cc
+++ b/src/kudu/server/webserver-test.cc
@@ -20,7 +20,6 @@
#include <cstdlib>
#include <functional>
#include <initializer_list>
-#include <iosfwd>
#include <memory>
#include <ostream>
#include <string>
@@ -49,6 +48,7 @@
#include "kudu/util/faststring.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/logging.h"
+#include "kudu/util/net/net_util.h"
#include "kudu/util/net/sockaddr.h"
#include "kudu/util/openssl_util.h"
#include "kudu/util/slice.h"
@@ -63,24 +63,24 @@ using std::vector;
using std::unique_ptr;
using strings::Substitute;
+DECLARE_bool(webserver_enable_csp);
DECLARE_bool(webserver_hsts_include_sub_domains);
DECLARE_int32(webserver_hsts_max_age_seconds);
DECLARE_int32(webserver_max_post_length_bytes);
+DECLARE_string(ip_config_mode);
+DECLARE_string(spnego_keytab_file);
DECLARE_string(trusted_certificate_file);
DECLARE_string(webserver_cache_control_options);
+DECLARE_string(webserver_interface);
DECLARE_string(webserver_x_content_type_options);
DECLARE_string(webserver_x_frame_options);
DEFINE_bool(test_sensitive_flag, false, "a sensitive flag");
TAG_FLAG(test_sensitive_flag, sensitive);
-DECLARE_bool(webserver_enable_csp);
-
-DECLARE_string(spnego_keytab_file);
-
namespace kudu {
-const bool g_is_fips = security::IsFIPSEnabled();
+const bool kIsFips = security::IsFIPSEnabled();
namespace {
void SetSslOptions(WebserverOptions* opts) {
@@ -99,16 +99,24 @@ void SetHTPasswdOptions(WebserverOptions* opts) {
} // anonymous namespace
-class WebserverTest : public KuduTest {
+class WebserverTest : public KuduTest,
+ public ::testing::WithParamInterface<std::string> {
public:
WebserverTest() {
+ IPMode mode;
static_dir_ = GetTestPath("webserver-docroot");
CHECK_OK(env_->CreateDir(static_dir_));
+ FLAGS_ip_config_mode = GetParam();
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ if (mode == IPMode::DUAL) {
+ FLAGS_webserver_interface = "[::]";
+ }
}
void SetUp() override {
KuduTest::SetUp();
+ FLAGS_ip_config_mode = GetParam();
WebserverOptions opts;
opts.port = 0;
opts.doc_root = static_dir_;
@@ -126,7 +134,7 @@ class WebserverTest : public KuduTest {
AddPreInitializedDefaultPathHandlers(server_.get());
AddPostInitializedDefaultPathHandlers(server_.get());
- if (!use_htpasswd() || !g_is_fips) {
+ if (!use_htpasswd() || !kIsFips) {
ASSERT_OK(server_->Start());
vector<Sockaddr> addrs;
@@ -182,18 +190,22 @@ class PasswdWebserverTest : public WebserverTest {
bool use_htpasswd() const override { return true; }
};
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, PasswdWebserverTest,
+ testing::Values("ipv4", "dual"));
+
// Send a HTTP request with no username and password. It should reject
// the request as the .htpasswd is presented to webserver.
-TEST_F(PasswdWebserverTest, TestPasswdMissing) {
- if (g_is_fips) {
+TEST_P(PasswdWebserverTest, TestPasswdMissing) {
+ if (kIsFips) {
GTEST_SKIP();
}
Status status = curl_.FetchURL(url_, &buf_);
ASSERT_EQ("Remote error: HTTP 401", status.ToString());
}
-TEST_F(PasswdWebserverTest, TestPasswdPresent) {
- if (g_is_fips) {
+TEST_P(PasswdWebserverTest, TestPasswdPresent) {
+ if (kIsFips) {
GTEST_SKIP();
}
curl_.set_auth(CurlAuthType::DIGEST,
@@ -202,8 +214,8 @@ TEST_F(PasswdWebserverTest, TestPasswdPresent) {
ASSERT_OK(curl_.FetchURL(addr_.ToString(), &buf_));
}
-TEST_F(PasswdWebserverTest, TestCrashInFIPSMode) {
- if (!g_is_fips) {
+TEST_P(PasswdWebserverTest, TestCrashInFIPSMode) {
+ if (!kIsFips) {
GTEST_SKIP();
}
@@ -278,27 +290,35 @@ class SpnegoDedicatedKeytabWebserverTest : public
SpnegoWebserverTest {
};
-TEST_F(SpnegoDedicatedKeytabWebserverTest, TestAuthenticated) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, SpnegoDedicatedKeytabWebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(SpnegoDedicatedKeytabWebserverTest, TestAuthenticated) {
ASSERT_OK(kdc_->Kinit("alice"));
ASSERT_OK(DoSpnegoCurl());
EXPECT_EQ("[email protected]", last_authenticated_spn_);
EXPECT_STR_CONTAINS(buf_.ToString(), "Kudu");
}
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, SpnegoWebserverTest,
+ testing::Values("ipv4", "dual"));
+
// Tests that execute DoSpnegoCurl() are ignored in MacOS (except the first
test case)
// MacOS heimdal kerberos caches kdc port number somewhere so that all the
test cases
// executing DoSpnegoCurl() use same kdc port number and it is test defect.
// Please refer to KUDU-3533(https://issues.apache.org/jira/browse/KUDU-3533)
#ifndef __APPLE__
-TEST_F(SpnegoWebserverTest, TestAuthenticated) {
+TEST_P(SpnegoWebserverTest, TestAuthenticated) {
ASSERT_OK(kdc_->Kinit("alice"));
ASSERT_OK(DoSpnegoCurl());
EXPECT_EQ("[email protected]", last_authenticated_spn_);
EXPECT_STR_CONTAINS(buf_.ToString(), "Kudu");
}
-TEST_F(SpnegoWebserverTest, TestUnauthenticatedBadKeytab) {
+TEST_P(SpnegoWebserverTest, TestUnauthenticatedBadKeytab) {
ASSERT_OK(kdc_->Kinit("alice"));
// Randomize the server's key in the KDC so that the key in the keytab
doesn't match the
// one for which the client will get a ticket. This is just an easy way to
provoke an
@@ -325,7 +345,7 @@ TEST_F(SpnegoWebserverTest, TestUnauthenticatedBadKeytab) {
"Must authenticate with SPNEGO)");
}
-TEST_F(SpnegoWebserverTest, TestUnauthenticatedNoClientAuth) {
+TEST_P(SpnegoWebserverTest, TestUnauthenticatedNoClientAuth) {
Status curl_status = DoSpnegoCurl();
EXPECT_EQ("Remote error: HTTP 401", curl_status.ToString());
EXPECT_EQ("Must authenticate with SPNEGO.", buf_.ToString());
@@ -335,7 +355,7 @@ TEST_F(SpnegoWebserverTest,
TestUnauthenticatedNoClientAuth) {
#endif
// Test some malformed authorization headers.
-TEST_F(SpnegoWebserverTest, TestInvalidHeaders) {
+TEST_P(SpnegoWebserverTest, TestInvalidHeaders) {
EXPECT_EQ(curl_.FetchURL(url_, &buf_, { "Authorization: blahblah"
}).ToString(),
"Remote error: HTTP 500");
EXPECT_STR_CONTAINS(buf_.ToString(), "bad Negotiate header");
@@ -350,7 +370,7 @@ TEST_F(SpnegoWebserverTest, TestInvalidHeaders) {
// Test that if no authorization header at all is provided, the response
// contains an empty "WWW-Authenticate: Negotiate" header.
-TEST_F(SpnegoWebserverTest, TestNoAuthHeader) {
+TEST_P(SpnegoWebserverTest, TestNoAuthHeader) {
curl_.set_return_headers(true);
ASSERT_EQ(curl_.FetchURL(url_, &buf_).ToString(), "Remote error: HTTP 401");
ASSERT_STR_CONTAINS(buf_.ToString(), "WWW-Authenticate: Negotiate\r\n");
@@ -363,7 +383,7 @@ TEST_F(SpnegoWebserverTest, TestNoAuthHeader) {
// it would not produce a successful authentication result, since it is a
saved constant
// from some previous run of SPNEGO on a different KDC. This test is primarily
concerned
// with defending against remote buffer overflows during token parsing, etc.
-TEST_F(SpnegoWebserverTest, TestBitFlippedTokens) {
+TEST_P(SpnegoWebserverTest, TestBitFlippedTokens) {
string token;
CHECK(strings::Base64Unescape(kWellFormedTokenBase64, &token));
@@ -386,7 +406,7 @@ TEST_F(SpnegoWebserverTest, TestBitFlippedTokens) {
// crash.
//
// NOTE: see above regarding "well-formed" vs "valid".
-TEST_F(SpnegoWebserverTest, TestTruncatedTokens) {
+TEST_P(SpnegoWebserverTest, TestTruncatedTokens) {
string token;
CHECK(strings::Base64Unescape(kWellFormedTokenBase64, &token));
@@ -403,11 +423,15 @@ TEST_F(SpnegoWebserverTest, TestTruncatedTokens) {
// Tests that even if we don't provide adequate authentication information in
// an OPTIONS request, the server still honors it.
-TEST_F(SpnegoWebserverTest, TestAuthNotRequiredForOptions) {
+TEST_P(SpnegoWebserverTest, TestAuthNotRequiredForOptions) {
NO_FATALS(RunTestOptions());
}
-TEST_F(WebserverTest, TestIndexPage) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, WebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(WebserverTest, TestIndexPage) {
curl_.set_return_headers(true);
ASSERT_OK(curl_.FetchURL(url_, &buf_));
@@ -448,7 +472,7 @@ TEST_F(WebserverTest, TestIndexPage) {
ASSERT_STR_NOT_CONTAINS(buf_.ToString(), "X-Content-Type-Options");
}
-TEST_F(WebserverTest, TestHttpCompression) {
+TEST_P(WebserverTest, TestHttpCompression) {
std::ostringstream oss;
string decoded_str;
@@ -504,7 +528,11 @@ TEST_F(WebserverTest, TestHttpCompression) {
ASSERT_STR_CONTAINS(buf_.ToString(), "memz");
}
-TEST_F(SslWebserverTest, TestSSL) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, SslWebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(SslWebserverTest, TestSSL) {
// We use a self-signed cert, so we have to trust it manually.
FLAGS_trusted_certificate_file = cert_path_;
@@ -513,7 +541,11 @@ TEST_F(SslWebserverTest, TestSSL) {
ASSERT_STR_CONTAINS(buf_.ToString(), "Kudu");
}
-TEST_F(Tls13WebserverTest, TestTlsMinVersion) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, Tls13WebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(Tls13WebserverTest, TestTlsMinVersion) {
FLAGS_trusted_certificate_file = cert_path_;
curl_.set_tls_max_version(TlsVersion::TLSv1_2);
@@ -527,7 +559,7 @@ TEST_F(Tls13WebserverTest, TestTlsMinVersion) {
ASSERT_OK(curl_.FetchURL(url_, &buf_));
}
-TEST_F(SslWebserverTest, StrictTransportSecurtyPolicyHeaders) {
+TEST_P(SslWebserverTest, StrictTransportSecurtyPolicyHeaders) {
constexpr const char* const kHstsHeader = "Strict-Transport-Security";
// Since the server uses a self-signed TLS certificate, disable the cert
// validation in curl.
@@ -567,7 +599,7 @@ TEST_F(SslWebserverTest,
StrictTransportSecurtyPolicyHeaders) {
"$0: max-age=$1; includeSubDomains", kHstsHeader, kMaxAge));
}
-TEST_F(WebserverTest, TestDefaultPaths) {
+TEST_P(WebserverTest, TestDefaultPaths) {
// Test memz
ASSERT_OK(curl_.FetchURL(Substitute("$0/memz?raw=1", url_), &buf_));
#ifdef TCMALLOC_ENABLED
@@ -589,7 +621,7 @@ TEST_F(WebserverTest, TestDefaultPaths) {
ASSERT_STR_CONTAINS(buf_.ToString(), "OK");
}
-TEST_F(WebserverTest, TestRedactFlagsDump) {
+TEST_P(WebserverTest, TestRedactFlagsDump) {
kudu::g_should_redact = kudu::RedactContext::ALL;
// Test varz -- check for the sensitive flag is redacted and HTML-escaped.
ASSERT_OK(curl_.FetchURL(Substitute("$0/varz", url_), &buf_));
@@ -601,7 +633,7 @@ TEST_F(WebserverTest, TestRedactFlagsDump) {
kRedactionMessage));
}
-TEST_F(WebserverTest, TestCSPHeader) {
+TEST_P(WebserverTest, TestCSPHeader) {
constexpr const char* kCspHeader = "Content-Security-Policy";
curl_.set_return_headers(true);
@@ -624,7 +656,7 @@ void SomeMethodForSymbolTest1() {}
// Used in symbolization test below.
void SomeMethodForSymbolTest2() {}
-TEST_F(WebserverTest, TestPprofPaths) {
+TEST_P(WebserverTest, TestPprofPaths) {
// Test /pprof/cmdline GET
ASSERT_OK(curl_.FetchURL(Substitute("$0/pprof/cmdline", url_), &buf_));
ASSERT_STR_CONTAINS(buf_.ToString(), "webserver-test");
@@ -653,7 +685,7 @@ TEST_F(WebserverTest, TestPprofPaths) {
// Send a POST request with too much data. It should reject
// the request with the correct HTTP error code.
-TEST_F(WebserverTest, TestPostTooBig) {
+TEST_P(WebserverTest, TestPostTooBig) {
FLAGS_webserver_max_post_length_bytes = 10;
string req(10000, 'c');
Status s = curl_.PostToURL(Substitute("$0/pprof/symbol", url_), req, &buf_);
@@ -662,7 +694,7 @@ TEST_F(WebserverTest, TestPostTooBig) {
// Test that static files are served and that directory listings are
// disabled.
-TEST_F(WebserverTest, TestStaticFiles) {
+TEST_P(WebserverTest, TestStaticFiles) {
// Fetch a non-existent static file.
Status s = curl_.FetchURL(Substitute("$0/foo.txt", url_), &buf_);
ASSERT_EQ("Remote error: HTTP 404", s.ToString());
@@ -679,13 +711,13 @@ TEST_F(WebserverTest, TestStaticFiles) {
ASSERT_EQ("Remote error: HTTP 403", s.ToString());
}
-TEST_F(WebserverTest, TestDeleteMethodNotAllowed) {
+TEST_P(WebserverTest, TestDeleteMethodNotAllowed) {
curl_.set_custom_method("DELETE");
Status s = curl_.FetchURL(Substitute("$0/index.html", url_), &buf_);
ASSERT_EQ("Remote error: HTTP 401", s.ToString());
}
-TEST_F(WebserverTest, TestPutMethodNotAllowed) {
+TEST_P(WebserverTest, TestPutMethodNotAllowed) {
curl_.set_custom_method("PUT");
Status s = curl_.FetchURL(Substitute("$0/index.html", url_), &buf_);
ASSERT_EQ("Remote error: HTTP 401", s.ToString());
@@ -714,24 +746,32 @@ class NoAuthnWebserverTest : public WebserverTest {
}
};
-TEST_F(NoAuthnWebserverTest, TestUnauthenticatedUser) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, NoAuthnWebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(NoAuthnWebserverTest, TestUnauthenticatedUser) {
ASSERT_OK(curl_.FetchURL(Substitute("$0/authn", url_), &buf_));
ASSERT_EQ(buf_.ToString(), "");
}
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, AuthnWebserverTest,
+ testing::Values("ipv4", "dual"));
+
// The following tests are skipped on macOS due to inconsistent behavior of
SPNEGO.
// macOS heimdal kerberos caches the KDC port number, which can cause
subsequent tests to fail.
// For more details, refer to KUDU-3533
(https://issues.apache.org/jira/browse/KUDU-3533)
#ifndef __APPLE__
-TEST_F(AuthnWebserverTest, TestAuthenticatedUserPassedToHandler) {
+TEST_P(AuthnWebserverTest, TestAuthenticatedUserPassedToHandler) {
ASSERT_OK(kdc_->Kinit("alice"));
curl_.set_auth(CurlAuthType::SPNEGO);
ASSERT_OK(curl_.FetchURL(Substitute("$0/authn", url_), &buf_));
ASSERT_STR_CONTAINS(buf_.ToString(), "alice");
}
-TEST_F(AuthnWebserverTest, TestUnauthenticatedBadKeytab) {
+TEST_P(AuthnWebserverTest, TestUnauthenticatedBadKeytab) {
// Test based on the SpnegoWebserverTest::TestUnauthenticatedBadKeytab test.
ASSERT_OK(kdc_->Kinit("alice"));
ASSERT_OK(kdc_->RandomizePrincipalKey("HTTP/127.0.0.1"));
@@ -744,7 +784,7 @@ TEST_F(AuthnWebserverTest, TestUnauthenticatedBadKeytab) {
"Must authenticate with SPNEGO)");
}
-TEST_F(AuthnWebserverTest, TestUnauthenticatedRequestWithoutClientAuth) {
+TEST_P(AuthnWebserverTest, TestUnauthenticatedRequestWithoutClientAuth) {
// Test based on the SpnegoWebserverTest::TestUnauthenticatedNoClientAuth
test.
curl_.set_auth(CurlAuthType::SPNEGO);
Status curl_status = curl_.FetchURL(Substitute("$0/authn", url_), &buf_);
@@ -792,27 +832,31 @@ class PathParamWebserverTest : public WebserverTest {
};
} // anonymous namespace
-TEST_F(PathParamWebserverTest, TestPathParameterAtEnd) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, PathParamWebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(PathParamWebserverTest, TestPathParameterAtEnd) {
ASSERT_OK(
curl_.FetchURL(Substitute("$0/api/tables/45dc8d192549427b8dca871dbbb20bb3",
url_), &buf_));
ASSERT_STR_CONTAINS(buf_.ToString(),
"\"table_id\":\"45dc8d192549427b8dca871dbbb20bb3\"");
}
-TEST_F(PathParamWebserverTest, TestPathParameterInMiddleAndEnd) {
+TEST_P(PathParamWebserverTest, TestPathParameterInMiddleAndEnd) {
ASSERT_OK(curl_.FetchURL(
Substitute("$0/api/tables/45dc8d192549427b8dca871dbbb20bb3/tablets/1",
url_), &buf_));
ASSERT_STR_CONTAINS(buf_.ToString(),
"\"table_id\":\"45dc8d192549427b8dca871dbbb20bb3\"");
ASSERT_STR_CONTAINS(buf_.ToString(), "\"tablet_id\":\"1\"");
}
-TEST_F(PathParamWebserverTest, TestInvalidPathParameter) {
+TEST_P(PathParamWebserverTest, TestInvalidPathParameter) {
Status s = curl_.FetchURL(Substitute("$0/api/tables//tablets/1", url_),
&buf_);
ASSERT_EQ("Remote error: HTTP 404", s.ToString());
}
// Test that the query string is correctly parsed and returned in the response,
// even when the path contains a path parameter.
-TEST_F(PathParamWebserverTest, TestPathWithQueryString) {
+TEST_P(PathParamWebserverTest, TestPathWithQueryString) {
ASSERT_OK(curl_.FetchURL(
Substitute("$0/api/tables/45dc8d192549427b8dca871dbbb20bb3?foo=bar",
url_), &buf_));
ASSERT_STR_CONTAINS(buf_.ToString(),
@@ -820,7 +864,7 @@ TEST_F(PathParamWebserverTest, TestPathWithQueryString) {
ASSERT_STR_CONTAINS(buf_.ToString(), "\"query_params\":{\"foo\":\"bar\"}");
}
-TEST_F(PathParamWebserverTest, TestInvalidPathWithSpace) {
+TEST_P(PathParamWebserverTest, TestInvalidPathWithSpace) {
Status s = curl_.FetchURL(
Substitute("$0/api/tables/45dc8d192549427b8dca871dbbb20bb3 /tablets/1",
url_), &buf_);
ASSERT_EQ(
@@ -829,7 +873,7 @@ TEST_F(PathParamWebserverTest, TestInvalidPathWithSpace) {
s.ToString());
}
-TEST_F(PathParamWebserverTest, TestRegisteredPathWithSpace) {
+TEST_P(PathParamWebserverTest, TestRegisteredPathWithSpace) {
Status s =
curl_.FetchURL(Substitute("$0/columns/45dc8d192549427b8dca871dbbb20bb3", url_),
&buf_);
ASSERT_EQ("Remote error: HTTP 404", s.ToString());
Status s2 =
@@ -840,7 +884,7 @@ TEST_F(PathParamWebserverTest, TestRegisteredPathWithSpace)
{
s2.ToString());
}
-TEST_F(PathParamWebserverTest, TestPathParamWithNonAsciiCharacter) {
+TEST_P(PathParamWebserverTest, TestPathParamWithNonAsciiCharacter) {
Status s = curl_.FetchURL(
Substitute("$0/api/tables/45dc8d192549427b8dca871dbbb20bb3ΓΌ20/tablets/1",
url_), &buf_);
ASSERT_EQ("Remote error: HTTP 400", s.ToString());
@@ -851,19 +895,23 @@ class DisabledDocRootWebserverTest : public WebserverTest
{
bool enable_doc_root() const override { return false; }
};
-TEST_F(DisabledDocRootWebserverTest, TestHandlerNotFound) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, DisabledDocRootWebserverTest,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(DisabledDocRootWebserverTest, TestHandlerNotFound) {
Status s = curl_.FetchURL(Substitute("$0/foo", url_), &buf_);
ASSERT_EQ("Remote error: HTTP 404", s.ToString());
ASSERT_STR_CONTAINS(buf_.ToString(), "No handler for URI /foo");
}
// Test that HTTP OPTIONS requests are permitted.
-TEST_F(WebserverTest, TestHttpOptions) {
+TEST_P(WebserverTest, TestHttpOptions) {
NO_FATALS(RunTestOptions());
}
// Test that we're able to reuse connections for subsequent fetches.
-TEST_F(WebserverTest, TestConnectionReuse) {
+TEST_P(WebserverTest, TestConnectionReuse) {
ASSERT_OK(curl_.FetchURL(url_, &buf_));
ASSERT_EQ(1, curl_.num_connects());
ASSERT_OK(curl_.FetchURL(url_, &buf_));
@@ -896,9 +944,16 @@ class WebserverAdvertisedAddressesTest : public KuduTest {
protected:
// Overridden by subclasses.
- virtual string use_webserver_interface() const { return ""; }
+ virtual string use_webserver_interface() {
+ IPMode mode;
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ if (mode == IPMode::IPV6) {
+ FLAGS_webserver_interface = "[::]";
+ }
+ return FLAGS_webserver_interface;
+ }
virtual int32 use_webserver_port() const { return 0; }
- virtual string use_advertised_addresses() const { return ""; }
+ virtual string use_advertised_addresses() { return ""; }
void GetAddresses(vector<Sockaddr>* bound_addrs,
vector<Sockaddr>* advertised_addrs) {
@@ -907,69 +962,158 @@ class WebserverAdvertisedAddressesTest : public KuduTest
{
}
unique_ptr<Webserver> server_;
+ std::string expected_webserver_host_;
+ std::string expected_advertised_host_;
};
-class AdvertisedOnlyWebserverTest : public WebserverAdvertisedAddressesTest {
+class AdvertisedOnlyWebserverTest : public WebserverAdvertisedAddressesTest,
+ public
::testing::WithParamInterface<std::string> {
+ public:
+ AdvertisedOnlyWebserverTest() {
+ FLAGS_ip_config_mode = GetParam();
+ }
protected:
- string use_advertised_addresses() const override { return "1.2.3.4:1234"; }
+ string use_advertised_addresses() override {
+ IPMode mode;
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ switch (mode) {
+ case IPMode::IPV4:
+ expected_advertised_host_ = "1.2.3.4";
+ return "1.2.3.4:1234";
+ case IPMode::IPV6:
+ expected_advertised_host_ = "1234:4567::1";
+ return "[1234:4567::1]:1234";
+ default:
+ CHECK(false) << "Unexpected IP configuration: " <<
FLAGS_ip_config_mode;
+ }
+ }
};
-class BoundOnlyWebserverTest : public WebserverAdvertisedAddressesTest {
+class BoundOnlyWebserverTest : public WebserverAdvertisedAddressesTest,
+ public
::testing::WithParamInterface<std::string> {
+ public:
+ BoundOnlyWebserverTest() {
+ FLAGS_ip_config_mode = GetParam();
+ }
protected:
- string use_webserver_interface() const override { return "127.0.0.1"; }
+ string use_webserver_interface() override {
+ IPMode mode;
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ switch (mode) {
+ case IPMode::IPV4:
+ expected_webserver_host_ = expected_advertised_host_ = "127.0.0.1";
+ return expected_webserver_host_;
+ case IPMode::IPV6:
+ expected_webserver_host_ = expected_advertised_host_ = "::1";
+ return "[::1]";
+ default:
+ CHECK(false) << "Unexpected IP configuration: " <<
FLAGS_ip_config_mode;
+ }
+ }
int32 use_webserver_port() const override { return 9999; }
};
-class BothBoundAndAdvertisedWebserverTest : public
WebserverAdvertisedAddressesTest {
+class BothBoundAndAdvertisedWebserverTest : public
WebserverAdvertisedAddressesTest,
+ public
::testing::WithParamInterface<std::string> {
+ public:
+ BothBoundAndAdvertisedWebserverTest() {
+ FLAGS_ip_config_mode = GetParam();
+ }
protected:
- string use_advertised_addresses() const override { return "1.2.3.4:1234"; }
- string use_webserver_interface() const override { return "127.0.0.1"; }
+ string use_advertised_addresses() override {
+ IPMode mode;
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ switch (mode) {
+ case IPMode::IPV4:
+ expected_advertised_host_ = "1.2.3.4";
+ return "1.2.3.4:1234";
+ case IPMode::IPV6:
+ expected_advertised_host_ = "1234:4567::1";
+ return "[1234:4567::1]:1234";
+ default:
+ CHECK(false) << "Unexpected IP configuration: " <<
FLAGS_ip_config_mode;
+ }
+ }
+ string use_webserver_interface() override {
+ IPMode mode;
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ switch (mode) {
+ case IPMode::IPV4:
+ expected_webserver_host_ = "127.0.0.1";
+ return expected_webserver_host_;
+ case IPMode::IPV6:
+ expected_webserver_host_ = "::1";
+ return "[::1]";
+ default:
+ CHECK(false) << "Unexpected IP configuration: " <<
FLAGS_ip_config_mode;
+ }
+ }
+
int32 use_webserver_port() const override { return 9999; }
};
-TEST_F(AdvertisedOnlyWebserverTest, OnlyAdvertisedAddresses) {
- vector<Sockaddr> bound_addrs, advertised_addrs;
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, AdvertisedOnlyWebserverTest,
+ testing::Values("ipv4", "ipv6"));
+
+TEST_P(AdvertisedOnlyWebserverTest, OnlyAdvertisedAddresses) {
+ vector<Sockaddr> bound_addrs;
+ vector<Sockaddr> advertised_addrs;
NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
ASSERT_EQ(1, advertised_addrs.size());
ASSERT_EQ(1, bound_addrs.size());
- ASSERT_EQ("1.2.3.4", advertised_addrs[0].host());
+ ASSERT_EQ(expected_advertised_host_, advertised_addrs[0].host());
ASSERT_EQ(1234, advertised_addrs[0].port());
ASSERT_NE(9999, bound_addrs[0].port());
}
-TEST_F(BoundOnlyWebserverTest, OnlyBoundAddresses) {
- vector<Sockaddr> bound_addrs, advertised_addrs;
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, BoundOnlyWebserverTest,
+ testing::Values("ipv4", "ipv6"));
+
+TEST_P(BoundOnlyWebserverTest, OnlyBoundAddresses) {
+ vector<Sockaddr> bound_addrs;
+ vector<Sockaddr> advertised_addrs;
NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
ASSERT_EQ(1, advertised_addrs.size());
ASSERT_EQ(1, bound_addrs.size());
- ASSERT_EQ("127.0.0.1", advertised_addrs[0].host());
+ ASSERT_EQ(expected_advertised_host_, advertised_addrs[0].host());
ASSERT_EQ(9999, advertised_addrs[0].port());
- ASSERT_EQ("127.0.0.1", bound_addrs[0].host());
+ ASSERT_EQ(expected_webserver_host_, bound_addrs[0].host());
ASSERT_EQ(9999, bound_addrs[0].port());
}
-TEST_F(BothBoundAndAdvertisedWebserverTest, BothBoundAndAdvertisedAddresses) {
- vector<Sockaddr> bound_addrs, advertised_addrs;
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, BothBoundAndAdvertisedWebserverTest,
+ testing::Values("ipv4", "ipv6"));
+
+TEST_P(BothBoundAndAdvertisedWebserverTest, BothBoundAndAdvertisedAddresses) {
+ vector<Sockaddr> bound_addrs;
+ vector<Sockaddr> advertised_addrs;
NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
ASSERT_EQ(1, advertised_addrs.size());
ASSERT_EQ(1, bound_addrs.size());
- ASSERT_EQ("1.2.3.4", advertised_addrs[0].host());
+
+ ASSERT_EQ(expected_advertised_host_, advertised_addrs[0].host());
ASSERT_EQ(1234, advertised_addrs[0].port());
- ASSERT_EQ("127.0.0.1", bound_addrs[0].host());
+
+ ASSERT_EQ(expected_webserver_host_, bound_addrs[0].host());
ASSERT_EQ(9999, bound_addrs[0].port());
}
// Various tests for failed webserver startup cases.
-class WebserverNegativeTests : public KuduTest {
+class WebserverNegativeTests : public KuduTest,
+ public
::testing::WithParamInterface<std::string> {
protected:
// Tries to start the webserver, expecting it to fail.
// 'func' is used to set webserver options before starting it.
template<class OptsFunc>
void ExpectFailedStartup(const OptsFunc& func) {
+ FLAGS_ip_config_mode = GetParam();
WebserverOptions opts;
opts.port = 0;
func(&opts);
@@ -979,47 +1123,51 @@ class WebserverNegativeTests : public KuduTest {
}
};
-TEST_F(WebserverNegativeTests, BadCertFile) {
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, WebserverNegativeTests,
+ testing::Values("ipv4", "dual"));
+
+TEST_P(WebserverNegativeTests, BadCertFile) {
ExpectFailedStartup([](WebserverOptions* opts) {
SetSslOptions(opts);
opts->certificate_file = "/dev/null";
});
}
-TEST_F(WebserverNegativeTests, BadKeyFile) {
+TEST_P(WebserverNegativeTests, BadKeyFile) {
ExpectFailedStartup([](WebserverOptions* opts) {
SetSslOptions(opts);
opts->private_key_file = "/dev/null";
});
}
-TEST_F(WebserverNegativeTests, WrongPassword) {
+TEST_P(WebserverNegativeTests, WrongPassword) {
ExpectFailedStartup([](WebserverOptions* opts) {
SetSslOptions(opts);
opts->private_key_password_cmd = "echo wrong_pass";
});
}
-TEST_F(WebserverNegativeTests, BadPasswordCommand) {
+TEST_P(WebserverNegativeTests, BadPasswordCommand) {
ExpectFailedStartup([](WebserverOptions* opts) {
SetSslOptions(opts);
opts->private_key_password_cmd = "/bin/false";
});
}
-TEST_F(WebserverNegativeTests, BadAdvertisedAddresses) {
+TEST_P(WebserverNegativeTests, BadAdvertisedAddresses) {
ExpectFailedStartup([](WebserverOptions* opts) {
opts->webserver_advertised_addresses = ";;;;;";
});
}
-TEST_F(WebserverNegativeTests, BadAdvertisedAddressesZeroPort) {
+TEST_P(WebserverNegativeTests, BadAdvertisedAddressesZeroPort) {
ExpectFailedStartup([](WebserverOptions* opts) {
opts->webserver_advertised_addresses = "localhost:0";
});
}
-TEST_F(WebserverNegativeTests, SpnegoWithoutKeytab) {
+TEST_P(WebserverNegativeTests, SpnegoWithoutKeytab) {
ExpectFailedStartup([](WebserverOptions* opts) {
opts->require_spnego = true;
});
diff --git a/src/kudu/server/webserver.cc b/src/kudu/server/webserver.cc
index 85eff8fc7..c39d3283a 100644
--- a/src/kudu/server/webserver.cc
+++ b/src/kudu/server/webserver.cc
@@ -135,7 +135,9 @@ DEFINE_string(webserver_x_content_type_options, "nosniff",
TAG_FLAG(webserver_x_content_type_options, advanced);
TAG_FLAG(webserver_x_content_type_options, runtime);
+DECLARE_string(ip_config_mode);
DECLARE_string(spnego_keytab_file);
+DECLARE_string(webserver_interface);
namespace kudu {
@@ -253,7 +255,7 @@ Webserver::Webserver(const WebserverOptions& opts)
: opts_(opts),
context_(nullptr),
is_started_(false) {
- string host = opts.bind_interface.empty() ? "0.0.0.0" : opts.bind_interface;
+ string host = opts.bind_interface.empty() ? FLAGS_webserver_interface :
opts.bind_interface;
http_address_ = host + ":" + std::to_string(opts.port);
}
@@ -449,7 +451,17 @@ Status Webserver::Start() {
signal(SIGCHLD, sig_chld);
if (context_ == nullptr) {
- Sockaddr addr = Sockaddr::Wildcard();
+ IPMode mode;
+ RETURN_NOT_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ Sockaddr addr;
+ switch (mode) {
+ case IPMode::IPV4:
+ addr = Sockaddr::Wildcard();
+ break;
+ default:
+ addr = Sockaddr::Wildcard(AF_INET6);
+ break;
+ }
addr.set_port(opts_.port);
TryRunLsof(addr);
string err_msg = Substitute("Webserver: could not start on address $0",
http_address_);
@@ -507,7 +519,20 @@ Status Webserver::GetBoundAddresses(std::vector<Sockaddr>*
addrs) const {
addrs->reserve(num_addrs);
for (int i = 0; i < num_addrs; i++) {
- addrs->emplace_back(*reinterpret_cast<struct sockaddr_in*>(sockaddrs[i]));
+ switch (sockaddrs[i]->ss_family) {
+ case AF_INET:
+ {
+ addrs->emplace_back(*reinterpret_cast<struct
sockaddr_in*>(sockaddrs[i]));
+ break;
+ }
+ case AF_INET6:
+ {
+ addrs->emplace_back(*reinterpret_cast<struct
sockaddr_in6*>(sockaddrs[i]));
+ break;
+ }
+ default:
+ DCHECK(false) << "Unexpected address family: " <<
sockaddrs[i]->ss_family;
+ }
free(sockaddrs[i]);
}
free(sockaddrs);
diff --git a/src/kudu/server/webserver_options.cc
b/src/kudu/server/webserver_options.cc
index 16dfc8d50..826735f7a 100644
--- a/src/kudu/server/webserver_options.cc
+++ b/src/kudu/server/webserver_options.cc
@@ -44,9 +44,8 @@ static std::string GetDefaultDocumentRoot();
// not use these directly, but rather access them via WebserverOptions.
// This makes it easier to instantiate web servers with different options
// within a single unit test.
-DEFINE_string(webserver_interface, "",
- "Interface to start the embedded webserver on. If blank, the webserver "
- "binds to 0.0.0.0");
+DEFINE_string(webserver_interface, "0.0.0.0",
+ "Interface to start the embedded webserver on.");
TAG_FLAG(webserver_interface, advanced);
DEFINE_string(webserver_advertised_addresses, "",
diff --git a/src/kudu/tserver/heartbeater.cc b/src/kudu/tserver/heartbeater.cc
index d9bd2e9e6..b90be84a0 100644
--- a/src/kudu/tserver/heartbeater.cc
+++ b/src/kudu/tserver/heartbeater.cc
@@ -803,7 +803,7 @@ Status Heartbeater::Thread::MasterServiceProxyForHostPort(
&addrs));
CHECK(!addrs.empty());
if (addrs.size() > 1) {
- LOG(WARNING) << Substitute(
+ KLOG_EVERY_N_SECS(WARNING, 60) << Substitute(
"Master address '$0' resolves to $1 different addresses. Using $2",
master_address_.ToString(), addrs.size(), addrs[0].ToString());
}
diff --git a/src/kudu/util/net/net_util-test.cc
b/src/kudu/util/net/net_util-test.cc
index 177a4137f..f4d8f48ca 100644
--- a/src/kudu/util/net/net_util-test.cc
+++ b/src/kudu/util/net/net_util-test.cc
@@ -40,6 +40,7 @@
DECLARE_bool(fail_dns_resolution);
DECLARE_string(fail_dns_resolution_hostports);
+DECLARE_string(ip_config_mode);
using std::string;
using std::vector;
@@ -203,6 +204,7 @@ TEST(SockaddrTest, Test) {
TEST_F(NetUtilTest, TestParseAddresses) {
string ret;
+ FLAGS_ip_config_mode = "dual";
ASSERT_OK(DoParseBindAddresses("0.0.0.0:12345", &ret));
ASSERT_EQ("0.0.0.0:12345", ret);
@@ -278,9 +280,25 @@ TEST_F(NetUtilTest, TestParseAddressesWithScheme) {
ASSERT_STR_CONTAINS(s.ToString(), "invalid scheme format");
}
-TEST_F(NetUtilTest, TestInjectFailureToResolveAddresses) {
- HostPort hp1("localhost", 12345);
- HostPort hp2("localhost", 12346);
+class ResolveAddressTest : public NetUtilTest,
+ public ::testing::WithParamInterface<std::string> {
+public:
+ ResolveAddressTest() : hostname("localhost") {
+ FLAGS_ip_config_mode = GetParam();
+ }
+protected:
+ string hostname;
+};
+
+
+// This is used to run all parameterized tests with different IP modes.
+INSTANTIATE_TEST_SUITE_P(Parameters, ResolveAddressTest,
+ testing::Values("ipv4", "ipv6", "dual"));
+
+// Paramterize these tests for ipv4, ipv6 and dual as localhost can resolve to
any of these.
+TEST_P(ResolveAddressTest, TestInjectFailureToResolveAddresses) {
+ HostPort hp1(hostname, 12345);
+ HostPort hp2(hostname, 12346);
FLAGS_fail_dns_resolution_hostports = hp1.ToString();
FLAGS_fail_dns_resolution = true;
@@ -311,8 +329,8 @@ TEST_F(NetUtilTest, TestInjectFailureToResolveAddresses) {
ASSERT_TRUE(s.IsNetworkError()) << s.ToString();
}
-TEST_F(NetUtilTest, TestResolveAddresses) {
- HostPort hp("localhost", 12345);
+TEST_P(ResolveAddressTest, TestResolveAddresses) {
+ HostPort hp(hostname, 12345);
vector<Sockaddr> addrs;
ASSERT_OK(hp.ResolveAddresses(&addrs));
ASSERT_TRUE(!addrs.empty());
diff --git a/src/kudu/util/net/net_util.cc b/src/kudu/util/net/net_util.cc
index 7b88e1492..b862b8480 100644
--- a/src/kudu/util/net/net_util.cc
+++ b/src/kudu/util/net/net_util.cc
@@ -21,6 +21,7 @@
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
+#include <sys/socket.h>
#include <unistd.h>
// IWYU pragma: no_include <bits/local_lim.h>
@@ -82,6 +83,13 @@ TAG_FLAG(dns_addr_resolution_override, hidden);
DEFINE_string(host_for_tests, "", "Host to use when resolving a given server's
locally bound or "
"advertised addresses.");
+DEFINE_string(ip_config_mode, "ipv4",
+ "Internet protocol config mode for server bind interface. "
+ "Can be any one of these - 'ipv4' (default), 'ipv6' or 'dual'. "
+ "Value is case insensitive.");
+TAG_FLAG(ip_config_mode, advanced);
+TAG_FLAG(ip_config_mode, experimental);
+
using std::function;
using std::string;
using std::string_view;
@@ -103,6 +111,17 @@ const int kServersMaxNum = (1 << kServerIdxBits) - 2;
namespace {
+bool ValidateIPConfigMode(const char* /* flagname */, const string& value) {
+ IPMode mode;
+ const auto s = ParseIPModeFlag(value, &mode);
+ if (s.ok()) {
+ return true;
+ }
+ LOG(ERROR) << s.ToString();
+ return false;
+}
+DEFINE_validator(ip_config_mode, &ValidateIPConfigMode);
+
using AddrInfo = unique_ptr<addrinfo, function<void(addrinfo*)>>;
// A utility wrapper around getaddrinfo() call to convert the return code
@@ -148,6 +167,35 @@ Status HostPortFromSockaddrReplaceWildcard(const Sockaddr&
addr, HostPort* hp) {
} // anonymous namespace
+Status ParseIPModeFlag(const string& flag_value, IPMode* mode) {
+ if (iequals(flag_value, "ipv4")) {
+ *mode = IPMode::IPV4;
+ } else if (iequals(flag_value, "ipv6")) {
+ *mode = IPMode::IPV6;
+ } else if (iequals(flag_value, "dual")) {
+ *mode = IPMode::DUAL;
+ } else {
+ return Status::InvalidArgument(
+ Substitute("$0: invalid value for flag --ip_config_mode, can be one of
"
+ "'ipv4', 'ipv6', 'dual'; case insensitive",
+ flag_value));
+ }
+ return Status::OK();
+}
+
+sa_family_t GetIPFamily() {
+ IPMode mode;
+ CHECK_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ switch (mode) {
+ case IPMode::IPV4:
+ return AF_INET;
+ case IPMode::IPV6:
+ return AF_INET6;
+ default:
+ return AF_UNSPEC;
+ }
+}
+
HostPort::HostPort()
: host_(""),
port_(0) {
@@ -304,7 +352,7 @@ Status HostPort::ParseStringWithScheme(const string& str,
uint16_t default_port)
return ParseString(str_copy, default_port);
}
-Status HostPort::ResolveAddresses(vector<Sockaddr>* addresses, sa_family_t
family) const {
+Status HostPort::ResolveAddresses(vector<Sockaddr>* addresses) const {
TRACE_EVENT1("net", "HostPort::ResolveAddresses",
"host", host_);
TRACE_COUNTER_SCOPE_LATENCY_US("dns_us");
@@ -330,7 +378,7 @@ Status HostPort::ResolveAddresses(vector<Sockaddr>*
addresses, sa_family_t famil
}
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
+ hints.ai_family = GetIPFamily();
hints.ai_socktype = SOCK_STREAM;
AddrInfo result;
const string op_description = Substitute("resolve address for $0", host_);
@@ -460,6 +508,19 @@ string HostPort::AddrToString(const void* addr,
sa_family_t family) {
return str;
}
+Status HostPort::ToIpInterface(const string& host, string* out) {
+ IPMode mode = IPMode::IPV4;
+ DCHECK(out);
+
+ RETURN_NOT_OK(ParseIPModeFlag(FLAGS_ip_config_mode, &mode));
+ if (mode == IPMode::IPV6 || mode == IPMode::DUAL) {
+ *out = "[" + host + "]";
+ } else {
+ *out = host;
+ }
+ return Status::OK();
+}
+
Network::Network()
: addr_(0),
netmask_(0),
@@ -645,8 +706,8 @@ Status GetFQDN(string* hostname) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
+ hints.ai_family = GetIPFamily();
hints.ai_socktype = SOCK_DGRAM;
- hints.ai_family = AF_INET;
hints.ai_flags = AI_CANONNAME;
AddrInfo result;
const string op_description =
diff --git a/src/kudu/util/net/net_util.h b/src/kudu/util/net/net_util.h
index fae6b8cb1..c6499a214 100644
--- a/src/kudu/util/net/net_util.h
+++ b/src/kudu/util/net/net_util.h
@@ -16,7 +16,6 @@
// under the License.
#pragma once
-#include <sys/socket.h>
#include <sys/un.h>
#include <cstddef>
@@ -74,10 +73,8 @@ class HostPort {
//
// 'addresses' may be NULL, in which case this function simply checks that
// the host/port pair can be resolved, without returning anything.
- // 'family' is to provide hint to translation function to return address
- // for a specific family. Default is AF_UNSPEC i.e. any address family.
Status ResolveAddresses(
- std::vector<Sockaddr>* addresses, sa_family_t family = AF_UNSPEC) const;
+ std::vector<Sockaddr>* addresses) const;
std::string ToString() const;
@@ -124,6 +121,9 @@ class HostPort {
// REQUIRES: addr should be in big-endian byte order.
static std::string AddrToString(const void* addr, sa_family_t family);
+ // Encloses hostname in square brackets if required, returns in out
parameter.
+ static Status ToIpInterface(const std::string& host, std::string* out);
+
private:
std::string host_;
uint16_t port_;
@@ -272,6 +272,19 @@ enum class BindMode {
LOOPBACK
};
+enum class IPMode {
+ IPV4,
+ IPV6,
+ DUAL,
+};
+
+// This is a helper function to parse a flag that has three possible values:
+// "ipv4", "ipv6", "dual"
+Status ParseIPModeFlag(const std::string& flag_value, IPMode* mode);
+
+// Return appropriate sa_family_t based on ip_config_mode flag value.
+sa_family_t GetIPFamily();
+
// Gets a random port from the ephemeral range by binding to port 0 on address
// 'address' and letting the kernel choose an unused one from the ephemeral
port
// range. The socket is then immediately closed and it remains in TIME_WAIT for
diff --git a/thirdparty/build-definitions.sh b/thirdparty/build-definitions.sh
index 057a612af..96d1808de 100644
--- a/thirdparty/build-definitions.sh
+++ b/thirdparty/build-definitions.sh
@@ -746,7 +746,8 @@ build_squeasel() {
mkdir -p $SQUEASEL_BDIR
pushd $SQUEASEL_BDIR
CFLAGS="$EXTRA_CFLAGS \
- -DALLOW_UNSAFE_HTTP_METHODS"
+ -DALLOW_UNSAFE_HTTP_METHODS \
+ -DUSE_IPV6"
${CC:-gcc} $CFLAGS $OPENSSL_CFLAGS $OPENSSL_LDFLAGS -std=c99 -O3 -DNDEBUG
-fPIC -c "$SQUEASEL_SOURCE/squeasel.c"
ar rs libsqueasel.a squeasel.o
cp libsqueasel.a $PREFIX/lib/