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> |
33 | namespace { |
34 | std::atomic<int> wsaInitCount = {0}; |
35 | } // anonymous namespace |
36 | #else |
37 | #include <fcntl.h> |
38 | #include <unistd.h> |
39 | namespace { |
40 | using SOCKET = int; |
41 | } // anonymous namespace |
42 | #endif |
43 | |
44 | namespace { |
45 | constexpr SOCKET InvalidSocket = static_cast<SOCKET>(-1); |
46 | void init() { |
47 | #if defined(_WIN32) |
48 | if (wsaInitCount++ == 0) { |
49 | WSADATA winsockData; |
50 | (void)WSAStartup(MAKEWORD(2, 2), &winsockData); |
51 | } |
52 | #endif |
53 | } |
54 | |
55 | void term() { |
56 | #if defined(_WIN32) |
57 | if (--wsaInitCount == 0) { |
58 | WSACleanup(); |
59 | } |
60 | #endif |
61 | } |
62 | |
63 | bool 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 | |
77 | bool 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 | |
89 | class 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 | |
247 | namespace dap { |
248 | |
249 | Socket::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 | |
266 | std::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 | |
283 | bool Socket::isOpen() const { |
284 | if (shared) { |
285 | return shared->isOpen(); |
286 | } |
287 | return false; |
288 | } |
289 | |
290 | void Socket::close() const { |
291 | if (shared) { |
292 | shared->close(); |
293 | } |
294 | } |
295 | |
296 | std::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 | |