From c3193e464cbd5e8b7cade103032c222bf8bc0e27 Mon Sep 17 00:00:00 2001 From: Jordan Rupprecht Date: Wed, 30 Sep 2020 10:47:48 -0700 Subject: [PATCH] [lldb/ipv6] Support running lldb tests in an ipv6-only environment. When running in an ipv6-only environment where `AF_INET` sockets are not available, many lldb tests (mostly gdb remote tests) fail because things like `127.0.0.1` don't work there. Use `localhost` instead of `127.0.0.1` whenever possible, or include a fallback of creating `AF_INET6` sockets when `AF_INET` fails. Reviewed By: labath Differential Revision: https://reviews.llvm.org/D87333 --- .../test/tools/lldb-server/gdbremote_testcase.py | 10 +++- .../Process/gdb-remote/GDBRemoteCommunication.cpp | 4 +- .../gdb_remote_client/gdbclientutils.py | 13 +++- .../commandline/TestStubReverseConnect.py | 14 ++++- lldb/tools/lldb-server/lldb-gdbserver.cpp | 3 +- lldb/unittests/Host/SocketTest.cpp | 69 ++++++++++++++++------ lldb/unittests/Host/SocketTestUtilities.cpp | 11 ++-- 7 files changed, 93 insertions(+), 31 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py index 253fd35..7d7b61c 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -318,7 +318,13 @@ class GdbRemoteTestCaseBase(TestBase): raise _ConnectionRefused() # Got EOF, connection dropped. def create_socket(self): - sock = socket.socket() + try: + sock = socket.socket(family=socket.AF_INET) + except OSError as e: + if e.errno != errno.EAFNOSUPPORT: + raise + sock = socket.socket(family=socket.AF_INET6) + logger = self.logger triple = self.dbg.GetSelectedPlatform().GetTriple() @@ -379,7 +385,7 @@ class GdbRemoteTestCaseBase(TestBase): ["*:{}".format(self.port)] else: commandline_args = self.debug_monitor_extra_args + \ - ["127.0.0.1:{}".format(self.port)] + ["localhost:{}".format(self.port)] if attach_pid: commandline_args += ["--attach=%d" % attach_pid] diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp index 832760f..4981345 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -1234,7 +1234,7 @@ GDBRemoteCommunication::ConnectLocally(GDBRemoteCommunication &client, const int backlog = 5; TCPSocket listen_socket(true, child_processes_inherit); if (llvm::Error error = - listen_socket.Listen("127.0.0.1:0", backlog).ToError()) + listen_socket.Listen("localhost:0", backlog).ToError()) return error; Socket *accept_socket; @@ -1243,7 +1243,7 @@ GDBRemoteCommunication::ConnectLocally(GDBRemoteCommunication &client, llvm::SmallString<32> remote_addr; llvm::raw_svector_ostream(remote_addr) - << "connect://127.0.0.1:" << listen_socket.GetLocalPortNumber(); + << "connect://localhost:" << listen_socket.GetLocalPortNumber(); std::unique_ptr conn_up( new ConnectionFileDescriptor()); diff --git a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py index eb789e8..dabe942 100644 --- a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py +++ b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py @@ -1,3 +1,4 @@ +import errno import os import os.path import threading @@ -317,12 +318,20 @@ class MockGDBServer: def __init__(self, port = 0): self.responder = MockGDBServerResponder() self.port = port - self._socket = socket.socket() + try: + self._socket = socket.socket(family=socket.AF_INET) + except OSError as e: + if e.errno != errno.EAFNOSUPPORT: + raise + self._socket = socket.socket(family=socket.AF_INET6) def start(self): # Block until the socket is up, so self.port is available immediately. # Then start a thread that waits for a client connection. - addr = ("127.0.0.1", self.port) + if self._socket.family == socket.AF_INET: + addr = ("127.0.0.1", self.port) + elif self._socket.family == socket.AF_INET6: + addr = ("::1", self.port) self._socket.bind(addr) self.port = self._socket.getsockname()[1] self._socket.listen(1) diff --git a/lldb/test/API/tools/lldb-server/commandline/TestStubReverseConnect.py b/lldb/test/API/tools/lldb-server/commandline/TestStubReverseConnect.py index a3250ab..4306f8d 100644 --- a/lldb/test/API/tools/lldb-server/commandline/TestStubReverseConnect.py +++ b/lldb/test/API/tools/lldb-server/commandline/TestStubReverseConnect.py @@ -1,5 +1,6 @@ from __future__ import print_function +import errno import gdbremote_testcase import lldbgdbserverutils import re @@ -24,11 +25,20 @@ class TestStubReverseConnect(gdbremote_testcase.GdbRemoteTestCaseBase): self.listener_port = self.listener_socket.getsockname()[1] def create_listener_socket(self): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except OSError as e: + if e.errno != errno.EAFNOSUPPORT: + raise + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.assertIsNotNone(sock) sock.settimeout(self.DEFAULT_TIMEOUT) - sock.bind(("127.0.0.1", 0)) + if sock.family == socket.AF_INET: + bind_addr = ("127.0.0.1", 0) + elif sock.family == socket.AF_INET6: + bind_addr = ("::1", 0) + sock.bind(bind_addr) sock.listen(1) def tear_down_listener(): diff --git a/lldb/tools/lldb-server/lldb-gdbserver.cpp b/lldb/tools/lldb-server/lldb-gdbserver.cpp index 5f06503..7f53756 100644 --- a/lldb/tools/lldb-server/lldb-gdbserver.cpp +++ b/lldb/tools/lldb-server/lldb-gdbserver.cpp @@ -267,7 +267,8 @@ void ConnectToRemote(MainLoop &mainloop, final_host_and_port.append("localhost"); final_host_and_port.append(host_and_port); - const std::string::size_type colon_pos = final_host_and_port.find(':'); + // Note: use rfind, because the host/port may look like "[::1]:12345". + const std::string::size_type colon_pos = final_host_and_port.rfind(':'); if (colon_pos != std::string::npos) { connection_host = final_host_and_port.substr(0, colon_pos); connection_port = final_host_and_port.substr(colon_pos + 1); diff --git a/lldb/unittests/Host/SocketTest.cpp b/lldb/unittests/Host/SocketTest.cpp index c53d266..901f878 100644 --- a/lldb/unittests/Host/SocketTest.cpp +++ b/lldb/unittests/Host/SocketTest.cpp @@ -14,12 +14,24 @@ using namespace lldb_private; -class SocketTest : public testing::Test { +struct SocketTestParams { + bool is_ipv6; + std::string localhost_ip; +}; + +class SocketTest : public testing::TestWithParam { public: SubsystemRAII subsystems; + +protected: + bool HostSupportsProtocol() const { + if (GetParam().is_ipv6) + return HostSupportsIPv6(); + return HostSupportsIPv4(); + } }; -TEST_F(SocketTest, DecodeHostAndPort) { +TEST_P(SocketTest, DecodeHostAndPort) { std::string host_str; std::string port_str; int32_t port; @@ -86,7 +98,7 @@ TEST_F(SocketTest, DecodeHostAndPort) { } #if LLDB_ENABLE_POSIX -TEST_F(SocketTest, DomainListenConnectAccept) { +TEST_P(SocketTest, DomainListenConnectAccept) { llvm::SmallString<64> Path; std::error_code EC = llvm::sys::fs::createUniqueDirectory("DomainListenConnectAccept", Path); ASSERT_FALSE(EC); @@ -102,18 +114,22 @@ TEST_F(SocketTest, DomainListenConnectAccept) { } #endif -TEST_F(SocketTest, TCPListen0ConnectAccept) { +TEST_P(SocketTest, TCPListen0ConnectAccept) { + if (!HostSupportsProtocol()) + return; std::unique_ptr socket_a_up; std::unique_ptr socket_b_up; - CreateTCPConnectedSockets("127.0.0.1", &socket_a_up, &socket_b_up); + CreateTCPConnectedSockets(GetParam().localhost_ip, &socket_a_up, + &socket_b_up); } -TEST_F(SocketTest, TCPGetAddress) { +TEST_P(SocketTest, TCPGetAddress) { std::unique_ptr socket_a_up; std::unique_ptr socket_b_up; - if (!HostSupportsIPv4()) + if (!HostSupportsProtocol()) return; - CreateTCPConnectedSockets("127.0.0.1", &socket_a_up, &socket_b_up); + CreateTCPConnectedSockets(GetParam().localhost_ip, &socket_a_up, + &socket_b_up); EXPECT_EQ(socket_a_up->GetLocalPortNumber(), socket_b_up->GetRemotePortNumber()); @@ -121,11 +137,16 @@ TEST_F(SocketTest, TCPGetAddress) { socket_a_up->GetRemotePortNumber()); EXPECT_NE(socket_a_up->GetLocalPortNumber(), socket_b_up->GetLocalPortNumber()); - EXPECT_STREQ("127.0.0.1", socket_a_up->GetRemoteIPAddress().c_str()); - EXPECT_STREQ("127.0.0.1", socket_b_up->GetRemoteIPAddress().c_str()); + EXPECT_STREQ(GetParam().localhost_ip.c_str(), + socket_a_up->GetRemoteIPAddress().c_str()); + EXPECT_STREQ(GetParam().localhost_ip.c_str(), + socket_b_up->GetRemoteIPAddress().c_str()); } -TEST_F(SocketTest, UDPConnect) { +TEST_P(SocketTest, UDPConnect) { + // UDPSocket::Connect() creates sockets with AF_INET (IPv4). + if (!HostSupportsIPv4()) + return; llvm::Expected> socket = UDPSocket::Connect("127.0.0.1:0", /*child_processes_inherit=*/false); @@ -133,7 +154,9 @@ TEST_F(SocketTest, UDPConnect) { EXPECT_TRUE(socket.get()->IsValid()); } -TEST_F(SocketTest, TCPListen0GetPort) { +TEST_P(SocketTest, TCPListen0GetPort) { + if (!HostSupportsIPv4()) + return; Predicate port_predicate; port_predicate.SetValue(0, eBroadcastNever); llvm::Expected> sock = @@ -143,12 +166,13 @@ TEST_F(SocketTest, TCPListen0GetPort) { EXPECT_NE(sock.get()->GetLocalPortNumber(), 0); } -TEST_F(SocketTest, TCPGetConnectURI) { +TEST_P(SocketTest, TCPGetConnectURI) { std::unique_ptr socket_a_up; std::unique_ptr socket_b_up; - if (!HostSupportsIPv4()) + if (!HostSupportsProtocol()) return; - CreateTCPConnectedSockets("127.0.0.1", &socket_a_up, &socket_b_up); + CreateTCPConnectedSockets(GetParam().localhost_ip, &socket_a_up, + &socket_b_up); llvm::StringRef scheme; llvm::StringRef hostname; @@ -160,7 +184,8 @@ TEST_F(SocketTest, TCPGetConnectURI) { EXPECT_EQ(port, socket_a_up->GetRemotePortNumber()); } -TEST_F(SocketTest, UDPGetConnectURI) { +TEST_P(SocketTest, UDPGetConnectURI) { + // UDPSocket::Connect() creates sockets with AF_INET (IPv4). if (!HostSupportsIPv4()) return; llvm::Expected> socket = @@ -177,7 +202,7 @@ TEST_F(SocketTest, UDPGetConnectURI) { } #if LLDB_ENABLE_POSIX -TEST_F(SocketTest, DomainGetConnectURI) { +TEST_P(SocketTest, DomainGetConnectURI) { llvm::SmallString<64> domain_path; std::error_code EC = llvm::sys::fs::createUniqueDirectory("DomainListenConnectAccept", domain_path); @@ -202,3 +227,13 @@ TEST_F(SocketTest, DomainGetConnectURI) { EXPECT_EQ(path, domain_path); } #endif + +INSTANTIATE_TEST_CASE_P( + SocketTests, SocketTest, + testing::Values(SocketTestParams{/*is_ipv6=*/false, + /*localhost_ip=*/"127.0.0.1"}, + SocketTestParams{/*is_ipv6=*/true, /*localhost_ip=*/"::1"}), + // Prints "SocketTests/SocketTest.DecodeHostAndPort/ipv4" etc. in test logs. + [](const testing::TestParamInfo &info) { + return info.param.is_ipv6 ? "ipv6" : "ipv4"; + }); diff --git a/lldb/unittests/Host/SocketTestUtilities.cpp b/lldb/unittests/Host/SocketTestUtilities.cpp index e2006b8..3b52a66 100644 --- a/lldb/unittests/Host/SocketTestUtilities.cpp +++ b/lldb/unittests/Host/SocketTestUtilities.cpp @@ -101,13 +101,14 @@ static bool CheckIPSupport(llvm::StringRef Proto, llvm::StringRef Addr) { "Creating a canary {0} TCP socket failed: {1}.", Proto, Err) .str(); - bool HasAddrNotAvail = false; + bool HasProtocolError = false; handleAllErrors(std::move(Err), [&](std::unique_ptr ECErr) { - if (ECErr->convertToErrorCode() == - std::make_error_code(std::errc::address_not_available)) - HasAddrNotAvail = true; + std::error_code ec = ECErr->convertToErrorCode(); + if (ec == std::make_error_code(std::errc::address_family_not_supported) || + ec == std::make_error_code(std::errc::address_not_available)) + HasProtocolError = true; }); - if (HasAddrNotAvail) { + if (HasProtocolError) { GTEST_LOG_(WARNING) << llvm::formatv( "Assuming the host does not support {0}. Skipping test.", Proto) -- 2.7.4