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 | |
22 | using namespace lldb_private; |
23 | |
24 | struct SocketTestParams { |
25 | bool is_ipv6; |
26 | std::string localhost_ip; |
27 | }; |
28 | |
29 | class SocketTest : public testing::TestWithParam<SocketTestParams> { |
30 | public: |
31 | SubsystemRAII<Socket> subsystems; |
32 | |
33 | protected: |
34 | bool HostSupportsProtocol() const { |
35 | if (GetParam().is_ipv6) |
36 | return HostSupportsIPv6(); |
37 | return HostSupportsIPv4(); |
38 | } |
39 | }; |
40 | |
41 | TEST_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 |
78 | TEST_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 | |
94 | TEST_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 | |
116 | TEST_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 | |
154 | TEST_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 | |
163 | TEST_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 | |
184 | TEST_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 | |
218 | TEST_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 | |
238 | TEST_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 | |
249 | TEST_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 | |
259 | TEST_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 | |
276 | TEST_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 | |
295 | TEST_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 | |
309 | TEST_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 |
322 | TEST_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 | |
344 | TEST_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__ |
370 | TEST_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 | |
394 | INSTANTIATE_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 | |