1//===-- SocketTest.cpp ----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "TestingSupport/Host/SocketTestUtilities.h"
10#include "TestingSupport/SubsystemRAII.h"
11#include "lldb/Host/Config.h"
12#include "lldb/Host/MainLoop.h"
13#include "lldb/Utility/UriParser.h"
14#include "llvm/Testing/Support/Error.h"
15#include "gmock/gmock.h"
16#include "gtest/gtest.h"
17#include <chrono>
18#if __linux__
19#include <lldb/Host/linux/AbstractSocket.h>
20#endif
21
22using namespace lldb_private;
23
24struct SocketTestParams {
25 bool is_ipv6;
26 std::string localhost_ip;
27};
28
29class SocketTest : public testing::TestWithParam<SocketTestParams> {
30public:
31 SubsystemRAII<Socket> subsystems;
32
33protected:
34 bool HostSupportsProtocol() const {
35 if (GetParam().is_ipv6)
36 return HostSupportsIPv6();
37 return HostSupportsIPv4();
38 }
39};
40
41TEST_F(SocketTest, DecodeHostAndPort) {
42 EXPECT_THAT_EXPECTED(Socket::DecodeHostAndPort("localhost:1138"),
43 llvm::HasValue(Socket::HostAndPort{"localhost", 1138}));
44
45 EXPECT_THAT_EXPECTED(
46 Socket::DecodeHostAndPort("google.com:65536"),
47 llvm::FailedWithMessage(
48 "invalid host:port specification: 'google.com:65536'"));
49
50 EXPECT_THAT_EXPECTED(
51 Socket::DecodeHostAndPort("google.com:-1138"),
52 llvm::FailedWithMessage(
53 "invalid host:port specification: 'google.com:-1138'"));
54
55 EXPECT_THAT_EXPECTED(
56 Socket::DecodeHostAndPort("google.com:65536"),
57 llvm::FailedWithMessage(
58 "invalid host:port specification: 'google.com:65536'"));
59
60 EXPECT_THAT_EXPECTED(Socket::DecodeHostAndPort("12345"),
61 llvm::HasValue(Socket::HostAndPort{"", 12345}));
62
63 EXPECT_THAT_EXPECTED(Socket::DecodeHostAndPort("*:0"),
64 llvm::HasValue(Socket::HostAndPort{"*", 0}));
65
66 EXPECT_THAT_EXPECTED(Socket::DecodeHostAndPort("*:65535"),
67 llvm::HasValue(Socket::HostAndPort{"*", 65535}));
68
69 EXPECT_THAT_EXPECTED(Socket::DecodeHostAndPort("[::1]:12345"),
70 llvm::HasValue(Socket::HostAndPort{"::1", 12345}));
71
72 EXPECT_THAT_EXPECTED(
73 Socket::DecodeHostAndPort("[abcd:12fg:AF58::1]:12345"),
74 llvm::HasValue(Socket::HostAndPort{"abcd:12fg:AF58::1", 12345}));
75}
76
77#if LLDB_ENABLE_POSIX
78TEST_F(SocketTest, DomainListenConnectAccept) {
79 llvm::SmallString<64> Path;
80 std::error_code EC =
81 llvm::sys::fs::createUniqueDirectory(Prefix: "DomainListenConnectAccept", ResultPath&: Path);
82 ASSERT_FALSE(EC);
83 llvm::sys::path::append(path&: Path, a: "test");
84
85 // Skip the test if the $TMPDIR is too long to hold a domain socket.
86 if (Path.size() > 107u)
87 return;
88
89 std::unique_ptr<DomainSocket> socket_a_up;
90 std::unique_ptr<DomainSocket> socket_b_up;
91 CreateDomainConnectedSockets(path: Path, a_up: &socket_a_up, b_up: &socket_b_up);
92}
93
94TEST_F(SocketTest, DomainListenGetListeningConnectionURI) {
95 llvm::SmallString<64> Path;
96 std::error_code EC =
97 llvm::sys::fs::createUniqueDirectory(Prefix: "DomainListenConnectAccept", ResultPath&: Path);
98 ASSERT_FALSE(EC);
99 llvm::sys::path::append(path&: Path, a: "test");
100
101 // Skip the test if the $TMPDIR is too long to hold a domain socket.
102 if (Path.size() > 107u)
103 return;
104
105 auto listen_socket_up = std::make_unique<DomainSocket>(
106 /*should_close=*/args: true);
107 Status error = listen_socket_up->Listen(name: Path, backlog: 5);
108 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
109 ASSERT_TRUE(listen_socket_up->IsValid());
110
111 ASSERT_THAT(
112 listen_socket_up->GetListeningConnectionURI(),
113 testing::ElementsAre(llvm::formatv("unix-connect://{0}", Path).str()));
114}
115
116TEST_F(SocketTest, DomainMainLoopAccept) {
117 llvm::SmallString<64> Path;
118 std::error_code EC =
119 llvm::sys::fs::createUniqueDirectory(Prefix: "DomainListenConnectAccept", ResultPath&: Path);
120 ASSERT_FALSE(EC);
121 llvm::sys::path::append(path&: Path, a: "test");
122
123 // Skip the test if the $TMPDIR is too long to hold a domain socket.
124 if (Path.size() > 107u)
125 return;
126
127 auto listen_socket_up = std::make_unique<DomainSocket>(
128 /*should_close=*/args: true);
129 Status error = listen_socket_up->Listen(name: Path, backlog: 5);
130 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
131 ASSERT_TRUE(listen_socket_up->IsValid());
132
133 MainLoop loop;
134 std::unique_ptr<Socket> accepted_socket_up;
135 auto expected_handles = listen_socket_up->Accept(
136 loop, sock_cb: [&accepted_socket_up, &loop](std::unique_ptr<Socket> sock_up) {
137 accepted_socket_up = std::move(sock_up);
138 loop.RequestTermination();
139 });
140 ASSERT_THAT_EXPECTED(expected_handles, llvm::Succeeded());
141
142 auto connect_socket_up = std::make_unique<DomainSocket>(
143 /*should_close=*/args: true);
144 ASSERT_THAT_ERROR(connect_socket_up->Connect(Path).ToError(),
145 llvm::Succeeded());
146 ASSERT_TRUE(connect_socket_up->IsValid());
147
148 loop.Run();
149 ASSERT_TRUE(accepted_socket_up);
150 ASSERT_TRUE(accepted_socket_up->IsValid());
151}
152#endif
153
154TEST_P(SocketTest, TCPListen0ConnectAccept) {
155 if (!HostSupportsProtocol())
156 return;
157 std::unique_ptr<TCPSocket> socket_a_up;
158 std::unique_ptr<TCPSocket> socket_b_up;
159 CreateTCPConnectedSockets(listen_remote_ip: GetParam().localhost_ip, a_up: &socket_a_up,
160 b_up: &socket_b_up);
161}
162
163TEST_P(SocketTest, TCPAcceptTimeout) {
164 if (!HostSupportsProtocol())
165 return;
166
167 const bool child_processes_inherit = false;
168 auto listen_socket_up =
169 std::make_unique<TCPSocket>(args: true, args: child_processes_inherit);
170 Status error = listen_socket_up->Listen(
171 name: llvm::formatv(Fmt: "[{0}]:0", Vals: GetParam().localhost_ip).str(), backlog: 5);
172 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
173 ASSERT_TRUE(listen_socket_up->IsValid());
174
175 Socket *socket;
176 ASSERT_THAT_ERROR(
177 listen_socket_up->Accept(std::chrono::milliseconds(10), socket)
178 .takeError(),
179 llvm::Failed<llvm::ErrorInfoBase>(
180 testing::Property(&llvm::ErrorInfoBase::convertToErrorCode,
181 std::make_error_code(std::errc::timed_out))));
182}
183
184TEST_P(SocketTest, TCPMainLoopAccept) {
185 if (!HostSupportsProtocol())
186 return;
187
188 auto listen_socket_up = std::make_unique<TCPSocket>(args: true);
189 Status error = listen_socket_up->Listen(
190 name: llvm::formatv(Fmt: "[{0}]:0", Vals: GetParam().localhost_ip).str(), backlog: 5);
191 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
192 ASSERT_TRUE(listen_socket_up->IsValid());
193
194 MainLoop loop;
195 std::unique_ptr<Socket> accepted_socket_up;
196 auto expected_handles = listen_socket_up->Accept(
197 loop, sock_cb: [&accepted_socket_up, &loop](std::unique_ptr<Socket> sock_up) {
198 accepted_socket_up = std::move(sock_up);
199 loop.RequestTermination();
200 });
201 ASSERT_THAT_EXPECTED(expected_handles, llvm::Succeeded());
202
203 auto connect_socket_up = std::make_unique<TCPSocket>(args: true);
204 ASSERT_THAT_ERROR(
205 connect_socket_up
206 ->Connect(llvm::formatv("[{0}]:{1}", GetParam().localhost_ip,
207 listen_socket_up->GetLocalPortNumber())
208 .str())
209 .ToError(),
210 llvm::Succeeded());
211 ASSERT_TRUE(connect_socket_up->IsValid());
212
213 loop.Run();
214 ASSERT_TRUE(accepted_socket_up);
215 ASSERT_TRUE(accepted_socket_up->IsValid());
216}
217
218TEST_P(SocketTest, TCPGetAddress) {
219 std::unique_ptr<TCPSocket> socket_a_up;
220 std::unique_ptr<TCPSocket> socket_b_up;
221 if (!HostSupportsProtocol())
222 return;
223 CreateTCPConnectedSockets(listen_remote_ip: GetParam().localhost_ip, a_up: &socket_a_up,
224 b_up: &socket_b_up);
225
226 EXPECT_EQ(socket_a_up->GetLocalPortNumber(),
227 socket_b_up->GetRemotePortNumber());
228 EXPECT_EQ(socket_b_up->GetLocalPortNumber(),
229 socket_a_up->GetRemotePortNumber());
230 EXPECT_NE(socket_a_up->GetLocalPortNumber(),
231 socket_b_up->GetLocalPortNumber());
232 EXPECT_STREQ(GetParam().localhost_ip.c_str(),
233 socket_a_up->GetRemoteIPAddress().c_str());
234 EXPECT_STREQ(GetParam().localhost_ip.c_str(),
235 socket_b_up->GetRemoteIPAddress().c_str());
236}
237
238TEST_P(SocketTest, UDPConnect) {
239 // UDPSocket::Connect() creates sockets with AF_INET (IPv4).
240 if (!HostSupportsIPv4())
241 return;
242 llvm::Expected<std::unique_ptr<UDPSocket>> socket =
243 UDPSocket::CreateConnected(name: "127.0.0.1:0");
244
245 ASSERT_THAT_EXPECTED(socket, llvm::Succeeded());
246 EXPECT_TRUE(socket.get()->IsValid());
247}
248
249TEST_P(SocketTest, TCPListen0GetPort) {
250 if (!HostSupportsIPv4())
251 return;
252 llvm::Expected<std::unique_ptr<TCPSocket>> sock =
253 Socket::TcpListen(host_and_port: "10.10.12.3:0", backlog: 5);
254 ASSERT_THAT_EXPECTED(sock, llvm::Succeeded());
255 ASSERT_TRUE(sock.get()->IsValid());
256 EXPECT_NE(sock.get()->GetLocalPortNumber(), 0);
257}
258
259TEST_P(SocketTest, TCPListen0GetListeningConnectionURI) {
260 if (!HostSupportsProtocol())
261 return;
262
263 std::string addr = llvm::formatv(Fmt: "[{0}]:0", Vals: GetParam().localhost_ip).str();
264 llvm::Expected<std::unique_ptr<TCPSocket>> sock = Socket::TcpListen(host_and_port: addr);
265 ASSERT_THAT_EXPECTED(sock, llvm::Succeeded());
266 ASSERT_TRUE(sock.get()->IsValid());
267
268 EXPECT_THAT(
269 sock.get()->GetListeningConnectionURI(),
270 testing::ElementsAre(llvm::formatv("connection://[{0}]:{1}",
271 GetParam().localhost_ip,
272 sock->get()->GetLocalPortNumber())
273 .str()));
274}
275
276TEST_F(SocketTest, TCPListen0MultiListenerGetListeningConnectionURI) {
277 if (!HostSupportsLocalhostToIPv4() || !HostSupportsLocalhostToIPv6())
278 return;
279
280 llvm::Expected<std::unique_ptr<TCPSocket>> sock =
281 Socket::TcpListen(host_and_port: "localhost:0", backlog: 5);
282 ASSERT_THAT_EXPECTED(sock, llvm::Succeeded());
283 ASSERT_TRUE(sock.get()->IsValid());
284
285 EXPECT_THAT(sock.get()->GetListeningConnectionURI(),
286 testing::UnorderedElementsAre(
287 llvm::formatv("connection://[::1]:{0}",
288 sock->get()->GetLocalPortNumber())
289 .str(),
290 llvm::formatv("connection://[127.0.0.1]:{0}",
291 sock->get()->GetLocalPortNumber())
292 .str()));
293}
294
295TEST_P(SocketTest, TCPGetConnectURI) {
296 std::unique_ptr<TCPSocket> socket_a_up;
297 std::unique_ptr<TCPSocket> socket_b_up;
298 if (!HostSupportsProtocol())
299 return;
300 CreateTCPConnectedSockets(listen_remote_ip: GetParam().localhost_ip, a_up: &socket_a_up,
301 b_up: &socket_b_up);
302
303 std::string uri(socket_a_up->GetRemoteConnectionURI());
304 EXPECT_EQ((URI{"connect", GetParam().localhost_ip,
305 socket_a_up->GetRemotePortNumber(), "/"}),
306 URI::Parse(uri));
307}
308
309TEST_P(SocketTest, UDPGetConnectURI) {
310 // UDPSocket::Connect() creates sockets with AF_INET (IPv4).
311 if (!HostSupportsIPv4())
312 return;
313 llvm::Expected<std::unique_ptr<UDPSocket>> socket =
314 UDPSocket::CreateConnected(name: "127.0.0.1:0");
315 ASSERT_THAT_EXPECTED(socket, llvm::Succeeded());
316
317 std::string uri = socket.get()->GetRemoteConnectionURI();
318 EXPECT_EQ((URI{"udp", "127.0.0.1", 0, "/"}), URI::Parse(uri));
319}
320
321#if LLDB_ENABLE_POSIX
322TEST_F(SocketTest, DomainGetConnectURI) {
323 llvm::SmallString<64> domain_path;
324 std::error_code EC = llvm::sys::fs::createUniqueDirectory(
325 Prefix: "DomainListenConnectAccept", ResultPath&: domain_path);
326 ASSERT_FALSE(EC);
327 llvm::sys::path::append(path&: domain_path, a: "test");
328
329 // Skip the test if the $TMPDIR is too long to hold a domain socket.
330 if (domain_path.size() > 107u)
331 return;
332
333 std::unique_ptr<DomainSocket> socket_a_up;
334 std::unique_ptr<DomainSocket> socket_b_up;
335 CreateDomainConnectedSockets(path: domain_path, a_up: &socket_a_up, b_up: &socket_b_up);
336
337 std::string uri(socket_a_up->GetRemoteConnectionURI());
338 EXPECT_EQ((URI{"unix-connect", "", std::nullopt, domain_path}),
339 URI::Parse(uri));
340
341 EXPECT_EQ(socket_b_up->GetRemoteConnectionURI(), "");
342}
343
344TEST_F(SocketTest, DomainSocketFromBoundNativeSocket) {
345 // Generate a name for the domain socket.
346 llvm::SmallString<64> name;
347 std::error_code EC = llvm::sys::fs::createUniqueDirectory(
348 Prefix: "DomainSocketFromBoundNativeSocket", ResultPath&: name);
349 ASSERT_FALSE(EC);
350 llvm::sys::path::append(path&: name, a: "test");
351
352 // Skip the test if the $TMPDIR is too long to hold a domain socket.
353 if (name.size() > 107u)
354 GTEST_SKIP() << "$TMPDIR is too long to hold a domain socket";
355
356 DomainSocket socket(true);
357 Status error = socket.Listen(name, /*backlog=*/10);
358 ASSERT_THAT_ERROR(error.takeError(), llvm::Succeeded());
359 NativeSocket native_socket = socket.GetNativeSocket();
360
361 llvm::Expected<std::unique_ptr<DomainSocket>> sock =
362 DomainSocket::FromBoundNativeSocket(sockfd: native_socket,
363 /*should_close=*/false);
364 ASSERT_THAT_EXPECTED(sock, llvm::Succeeded());
365 ASSERT_EQ(Socket::ProtocolUnixDomain, sock->get()->GetSocketProtocol());
366}
367#endif
368
369#if __linux__
370TEST_F(SocketTest, AbstractSocketFromBoundNativeSocket) {
371 // Generate a name for the abstract socket.
372 llvm::SmallString<100> name;
373 llvm::sys::fs::createUniquePath(Model: "AbstractSocketFromBoundNativeSocket", ResultPath&: name,
374 MakeAbsolute: true);
375 llvm::sys::path::append(path&: name, a: "test");
376
377 // Skip the test if the $TMPDIR is too long to hold a domain socket.
378 if (name.size() > 107u)
379 GTEST_SKIP() << "$TMPDIR is too long to hold a domain socket";
380
381 AbstractSocket socket;
382 Status error = socket.Listen(name, /*backlog=*/10);
383 ASSERT_THAT_ERROR(error.takeError(), llvm::Succeeded());
384 NativeSocket native_socket = socket.GetNativeSocket();
385
386 llvm::Expected<std::unique_ptr<DomainSocket>> sock =
387 DomainSocket::FromBoundNativeSocket(sockfd: native_socket,
388 /*should_close=*/false);
389 ASSERT_THAT_EXPECTED(sock, llvm::Succeeded());
390 ASSERT_EQ(Socket::ProtocolUnixAbstract, sock->get()->GetSocketProtocol());
391}
392#endif
393
394INSTANTIATE_TEST_SUITE_P(
395 SocketTests, SocketTest,
396 testing::Values(SocketTestParams{/*is_ipv6=*/false,
397 /*localhost_ip=*/"127.0.0.1"},
398 SocketTestParams{/*is_ipv6=*/true, /*localhost_ip=*/"::1"}),
399 // Prints "SocketTests/SocketTest.TCPGetAddress/ipv4" etc. in test logs.
400 [](const testing::TestParamInfo<SocketTestParams> &info) {
401 return info.param.is_ipv6 ? "ipv6" : "ipv4";
402 });
403

source code of lldb/unittests/Host/SocketTest.cpp