1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "socket.h"
16
17#include "rwmutex.h"
18
19#if defined(_WIN32)
20#include <winsock2.h>
21#include <ws2tcpip.h>
22#else
23#include <netdb.h>
24#include <netinet/in.h>
25#include <netinet/tcp.h>
26#include <sys/select.h>
27#include <sys/socket.h>
28#include <unistd.h>
29#endif
30
31#if defined(_WIN32)
32#include <atomic>
33namespace {
34std::atomic<int> wsaInitCount = {0};
35} // anonymous namespace
36#else
37#include <fcntl.h>
38#include <unistd.h>
39namespace {
40using SOCKET = int;
41} // anonymous namespace
42#endif
43
44namespace {
45constexpr SOCKET InvalidSocket = static_cast<SOCKET>(-1);
46void init() {
47#if defined(_WIN32)
48 if (wsaInitCount++ == 0) {
49 WSADATA winsockData;
50 (void)WSAStartup(MAKEWORD(2, 2), &winsockData);
51 }
52#endif
53}
54
55void term() {
56#if defined(_WIN32)
57 if (--wsaInitCount == 0) {
58 WSACleanup();
59 }
60#endif
61}
62
63bool setBlocking(SOCKET s, bool blocking) {
64#if defined(_WIN32)
65 u_long mode = blocking ? 0 : 1;
66 return ioctlsocket(s, FIONBIO, &mode) == NO_ERROR;
67#else
68 auto arg = fcntl(s, F_GETFL, nullptr);
69 if (arg < 0) {
70 return false;
71 }
72 arg = blocking ? (arg & ~O_NONBLOCK) : (arg | O_NONBLOCK);
73 return fcntl(s, F_SETFL, arg) >= 0;
74#endif
75}
76
77bool errored(SOCKET s) {
78 if (s == InvalidSocket) {
79 return true;
80 }
81 char error = 0;
82 socklen_t len = sizeof(error);
83 getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len);
84 return error != 0;
85}
86
87} // anonymous namespace
88
89class dap::Socket::Shared : public dap::ReaderWriter {
90 public:
91 static std::shared_ptr<Shared> create(const char* address, const char* port) {
92 init();
93
94 addrinfo hints = {};
95 hints.ai_family = AF_INET;
96 hints.ai_socktype = SOCK_STREAM;
97 hints.ai_protocol = IPPROTO_TCP;
98 hints.ai_flags = AI_PASSIVE;
99
100 addrinfo* info = nullptr;
101 getaddrinfo(address, port, &hints, &info);
102
103 if (info) {
104 auto socket =
105 ::socket(info->ai_family, info->ai_socktype, info->ai_protocol);
106 auto out = std::make_shared<Shared>(info, socket);
107 out->setOptions();
108 return out;
109 }
110
111 freeaddrinfo(info);
112 term();
113 return nullptr;
114 }
115
116 Shared(SOCKET socket) : info(nullptr), s(socket) {}
117 Shared(addrinfo* info, SOCKET socket) : info(info), s(socket) {}
118
119 ~Shared() {
120 freeaddrinfo(info);
121 close();
122 term();
123 }
124
125 template <typename FUNCTION>
126 void lock(FUNCTION&& f) {
127 RLock l(mutex);
128 f(s, info);
129 }
130
131 void setOptions() {
132 RLock l(mutex);
133 if (s == InvalidSocket) {
134 return;
135 }
136
137 int enable = 1;
138
139#if !defined(_WIN32)
140 // Prevent sockets lingering after process termination, causing
141 // reconnection issues on the same port.
142 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable));
143
144 struct {
145 int l_onoff; /* linger active */
146 int l_linger; /* how many seconds to linger for */
147 } linger = {false, 0};
148 setsockopt(s, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));
149#endif // !defined(_WIN32)
150
151 // Enable TCP_NODELAY.
152 // DAP usually consists of small packet requests, with small packet
153 // responses. When there are many frequent, blocking requests made,
154 // Nagle's algorithm can dramatically limit the request->response rates.
155 setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(enable));
156 }
157
158 // dap::ReaderWriter compliance
159 bool isOpen() {
160 {
161 RLock l(mutex);
162 if ((s != InvalidSocket) && !errored(s)) {
163 return true;
164 }
165 }
166 WLock lock(mutex);
167 s = InvalidSocket;
168 return false;
169 }
170
171 bool isConnected() {
172 RLock l(mutex);
173 if (InvalidSocket == s)
174 return false;
175 struct tcp_info info;
176 socklen_t len = sizeof(info);
177 getsockopt(s, IPPROTO_TCP, TCP_INFO, &info, &len);
178 return info.tcpi_state == TCP_ESTABLISHED;
179 }
180
181 void close() {
182 {
183 RLock l(mutex);
184 if (s != InvalidSocket) {
185#if defined(_WIN32)
186 closesocket(s);
187#elif __APPLE__
188 // ::shutdown() *should* be sufficient to unblock ::accept(), but
189 // apparently on macos it can return ENOTCONN and ::accept() continues
190 // to block indefinitely.
191 // Note: There is a race here. Calling ::close() frees the socket ID,
192 // which may be reused before `s` is assigned InvalidSocket.
193 ::shutdown(s, SHUT_RDWR);
194 ::close(s);
195#else
196 // ::shutdown() to unblock ::accept(). We'll actually close the socket
197 // under lock below.
198 ::shutdown(s, SHUT_RDWR);
199#endif
200 }
201 }
202
203 WLock l(mutex);
204 if (s != InvalidSocket) {
205#if !defined(_WIN32) && !defined(__APPLE__)
206 ::close(s);
207#endif
208 s = InvalidSocket;
209 }
210 }
211
212 size_t read(void* buffer, size_t bytes) {
213 RLock lock(mutex);
214 if (s == InvalidSocket) {
215 return 0;
216 }
217
218 if (!isConnected())
219 return 0;
220 auto len =
221 recv(s, reinterpret_cast<char*>(buffer), static_cast<int>(bytes), 0);
222 return (len < 0) ? 0 : len;
223 }
224
225 bool write(const void* buffer, size_t bytes) {
226 RLock lock(mutex);
227 if (s == InvalidSocket) {
228 return false;
229 }
230 if (bytes == 0) {
231 return false;
232 }
233
234 if (!isConnected())
235 return false;
236
237 return ::send(s, reinterpret_cast<const char*>(buffer),
238 static_cast<int>(bytes), 0) > 0;
239 }
240
241 private:
242 addrinfo* const info;
243 SOCKET s = InvalidSocket;
244 RWMutex mutex;
245};
246
247namespace dap {
248
249Socket::Socket(const char* address, const char* port)
250 : shared(Shared::create(address, port)) {
251 if (shared) {
252 shared->lock([&](SOCKET socket, const addrinfo* info) {
253 if (bind(socket, info->ai_addr, (int)info->ai_addrlen) != 0) {
254 shared.reset();
255 return;
256 }
257
258 if (listen(socket, 0) != 0) {
259 shared.reset();
260 return;
261 }
262 });
263 }
264}
265
266std::shared_ptr<ReaderWriter> Socket::accept() const {
267 std::shared_ptr<Shared> out;
268 if (shared) {
269 shared->lock([&](SOCKET socket, const addrinfo*) {
270 if (socket != InvalidSocket && !errored(socket)) {
271 init();
272 auto accepted = ::accept(socket, 0, 0);
273 if (accepted != InvalidSocket) {
274 out = std::make_shared<Shared>(accepted);
275 out->setOptions();
276 }
277 }
278 });
279 }
280 return out;
281}
282
283bool Socket::isOpen() const {
284 if (shared) {
285 return shared->isOpen();
286 }
287 return false;
288}
289
290void Socket::close() const {
291 if (shared) {
292 shared->close();
293 }
294}
295
296std::shared_ptr<ReaderWriter> Socket::connect(const char* address,
297 const char* port,
298 uint32_t timeoutMillis) {
299 auto shared = Shared::create(address, port);
300 if (!shared) {
301 return nullptr;
302 }
303
304 std::shared_ptr<ReaderWriter> out;
305 shared->lock([&](SOCKET socket, const addrinfo* info) {
306 if (socket == InvalidSocket) {
307 return;
308 }
309
310 if (timeoutMillis == 0) {
311 if (::connect(socket, info->ai_addr, (int)info->ai_addrlen) == 0) {
312 out = shared;
313 }
314 return;
315 }
316
317 if (!setBlocking(socket, false)) {
318 return;
319 }
320
321 auto res = ::connect(socket, info->ai_addr, (int)info->ai_addrlen);
322 if (res == 0) {
323 if (setBlocking(socket, true)) {
324 out = shared;
325 }
326 } else {
327 const auto microseconds = timeoutMillis * 1000;
328
329 fd_set fdset;
330 FD_ZERO(&fdset);
331 FD_SET(socket, &fdset);
332
333 timeval tv;
334 tv.tv_sec = microseconds / 1000000;
335 tv.tv_usec = microseconds - static_cast<uint32_t>(tv.tv_sec * 1000000);
336 res = select(static_cast<int>(socket + 1), nullptr, &fdset, nullptr, &tv);
337 if (res > 0 && !errored(socket) && setBlocking(socket, true)) {
338 out = shared;
339 }
340 }
341 });
342
343 if (!out) {
344 return nullptr;
345 }
346
347 return out->isOpen() ? out : nullptr;
348}
349
350} // namespace dap
351