1 /* Copyright (c) 2018 MariaDB Corporation.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 of the License.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
15
16 /* Accepting connections on Windows */
17
18 #include <my_global.h>
19 #include <sql_class.h>
20 #include <sql_connect.h>
21 #include <mysqld.h>
22 #include <mswsock.h>
23 #include <mysql/psi/mysql_socket.h>
24 #include <sddl.h>
25
26 #include <handle_connections_win.h>
27
28 /* From mysqld.cc */
29 extern HANDLE hEventShutdown;
30 extern MYSQL_SOCKET base_ip_sock, extra_ip_sock;
31 #ifdef HAVE_POOL_OF_THREADS
32 extern PTP_CALLBACK_ENVIRON get_threadpool_win_callback_environ();
33 extern void tp_win_callback_prolog();
34 #else
35 #define get_threadpool_win_callback_environ() 0
36 #define tp_win_callback_prolog() do{}while(0)
37 #endif
38 static SECURITY_ATTRIBUTES pipe_security;
39
40 /**
41 Abstract base class for accepting new connection,
42 asynchronously (i.e the accept() operation can be posted,
43 and result is retrieved later) , and creating a new connection.
44 */
45
46 struct Listener
47 {
48 /** Windows handle of the Listener.
49 Subclasses would use SOCKET or named pipe handle
50 */
51 HANDLE m_handle;
52 /** Required for all async IO*/
53 OVERLAPPED m_overlapped;
54
55 /** Create new listener
56 @param handle - @see m_handle
57 @param wait_handle - usually, event handle or INVALID_HANDLE_VALUE
58 @see wait_handle
59 */
ListenerListener60 Listener(HANDLE handle, HANDLE wait_handle):
61 m_handle(handle), m_overlapped()
62 {
63 m_overlapped.hEvent= wait_handle;
64 }
65
66 /**
67 if not NULL, this handle can be be used in WaitForSingle/MultipleObject(s).
68 This handle will be closed when object is destroyed.
69
70 If NULL, the completion notification happens in threadpool.
71 */
wait_handleListener72 HANDLE wait_handle()
73 {
74 return m_overlapped.hEvent;
75 }
76
77 /* Start waiting for new client connection. */
78 virtual void begin_accept()= 0;
79
80 /**
81 Completion callback,called whenever IO posted by begin_accept is finisjed
82 Listener needs to create a new THD then (or, call scheduler so it creates one)
83
84 @param success - whether IO completed successfull
85 */
86 virtual void completion_callback(bool success)= 0;
87
88 /**
89 Completion callback for Listener, that uses events for waiting
90 to IO. Not suitable for threadpool etc. Retrieves the status of
91 completed IO from the OVERLAPPED structure
92 */
completion_callbackListener93 void completion_callback()
94 {
95 DBUG_ASSERT(wait_handle() && (wait_handle() != INVALID_HANDLE_VALUE));
96 DWORD bytes;
97 return completion_callback(
98 GetOverlappedResult(wait_handle(), &m_overlapped, &bytes, FALSE));
99 }
100
101 /** Cancel an in-progress IO. Useful for threadpool-bound IO */
cancelListener102 void cancel()
103 {
104 CancelIoEx(m_handle, &m_overlapped);
105 }
106
107 /* Destructor. Closes wait handle, if it was passed in constructor */
~ListenerListener108 virtual ~Listener()
109 {
110 if (m_overlapped.hEvent)
111 CloseHandle(m_overlapped.hEvent);
112 };
113 };
114
115 /* Winsock extension finctions. */
116 static LPFN_ACCEPTEX my_AcceptEx;
117 static LPFN_GETACCEPTEXSOCKADDRS my_GetAcceptExSockaddrs;
118
119 /**
120 Listener that handles socket connections.
121 Can be threadpool-bound (i.e the completion is executed in threadpool thread),
122 or use events for waits.
123
124 Threadpool-bound listener should be used with theradpool scheduler, for better
125 performance.
126 */
127 struct Socket_Listener: public Listener
128 {
129 /** Client socket passed to AcceptEx() call.*/
130 SOCKET m_client_socket;
131
132 /** Buffer for sockaddrs passed to AcceptEx()/GetAcceptExSockaddrs() */
133 char m_buffer[2 * sizeof(sockaddr_storage) + 32];
134
135 /* Threadpool IO struct.*/
136 PTP_IO m_tp_io;
137
138 /**
139 Callback for Windows threadpool's StartThreadpoolIo() function.
140 */
tp_accept_completion_callbackSocket_Listener141 static void CALLBACK tp_accept_completion_callback(
142 PTP_CALLBACK_INSTANCE, PVOID context, PVOID , ULONG io_result,
143 ULONG_PTR, PTP_IO io)
144 {
145 tp_win_callback_prolog();
146 Listener *listener= (Listener *)context;
147
148 if (io_result == ERROR_OPERATION_ABORTED)
149 {
150 /* ERROR_OPERATION_ABORTED caused by CancelIoEx()*/
151 CloseThreadpoolIo(io);
152 delete listener;
153 return;
154 }
155 listener->completion_callback(io_result == 0);
156 }
157
158 /**
159 Constructor
160 @param listen_socket - listening socket
161 @PTP_CALLBACK_ENVIRON callback_environ - threadpool environment, or NULL
162 if threadpool is not used for completion callbacks.
163 */
Socket_ListenerSocket_Listener164 Socket_Listener(MYSQL_SOCKET listen_socket, PTP_CALLBACK_ENVIRON callback_environ) :
165 Listener((HANDLE)listen_socket.fd,0),
166 m_client_socket(INVALID_SOCKET)
167 {
168 if (callback_environ)
169 {
170 /* Accept executed in threadpool. */
171 m_tp_io= CreateThreadpoolIo(m_handle,
172 tp_accept_completion_callback, this, callback_environ);
173 }
174 else
175 {
176 /* Completion signaled via event. */
177 m_tp_io= 0;
178 m_overlapped.hEvent= CreateEvent(0, FALSE , FALSE, 0);
179 }
180 }
181
182 /*
183 Use AcceptEx to asynchronously wait for new connection;
184 */
begin_acceptSocket_Listener185 void begin_accept()
186 {
187 retry :
188 m_client_socket= socket(server_socket_ai_family, SOCK_STREAM, IPPROTO_TCP);
189 if (m_client_socket == INVALID_SOCKET)
190 {
191 sql_perror("socket() call failed.");
192 unireg_abort(1);
193 }
194
195 DWORD bytes_received;
196 if (m_tp_io)
197 StartThreadpoolIo(m_tp_io);
198
199 BOOL ret= my_AcceptEx(
200 (SOCKET)m_handle,
201 m_client_socket,
202 m_buffer,
203 0,
204 sizeof(sockaddr_storage) + 16,
205 sizeof(sockaddr_storage) + 16,
206 &bytes_received,
207 &m_overlapped);
208
209 DWORD last_error= ret? 0: WSAGetLastError();
210 if (last_error == WSAECONNRESET || last_error == ERROR_NETNAME_DELETED)
211 {
212 if (m_tp_io)
213 CancelThreadpoolIo(m_tp_io);
214 closesocket(m_client_socket);
215 goto retry;
216 }
217
218 if (ret || last_error == ERROR_IO_PENDING || abort_loop)
219 return;
220
221 sql_print_error("my_AcceptEx failed, last error %u", last_error);
222 abort();
223 }
224
225 /* Create new socket connection.*/
completion_callbackSocket_Listener226 void completion_callback(bool success)
227 {
228 if (!success)
229 {
230 /* my_AcceptEx() returned error */
231 closesocket(m_client_socket);
232 begin_accept();
233 return;
234 }
235
236 MYSQL_SOCKET s_client{m_client_socket};
237 MYSQL_SOCKET s_listen{(SOCKET)m_handle};
238
239 #ifdef HAVE_PSI_SOCKET_INTERFACE
240 /* Parse socket addresses buffer filled by AcceptEx(),
241 only needed for PSI instrumentation. */
242 sockaddr *local_addr, *remote_addr;
243 int local_addr_len, remote_addr_len;
244
245 my_GetAcceptExSockaddrs(m_buffer,
246 0, sizeof(sockaddr_storage) + 16, sizeof(sockaddr_storage) + 16,
247 &local_addr, &local_addr_len, &remote_addr, &remote_addr_len);
248
249 s_client.m_psi= PSI_SOCKET_CALL(init_socket)
250 (key_socket_client_connection, (const my_socket*)&s_listen.fd, remote_addr, remote_addr_len);
251 #endif
252
253 /* Start accepting new connection. After this point, do not use
254 any member data, they could be used by a different (threadpool) thread. */
255 begin_accept();
256
257 /* Some chores post-AcceptEx() that we need to create a normal socket.*/
258 if (setsockopt(s_client.fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
259 (char *)&s_listen.fd, sizeof(s_listen.fd)))
260 {
261 if (!abort_loop)
262 {
263 sql_perror("setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed.");
264 abort();
265 }
266 }
267
268 /* Create a new connection.*/
269 handle_accepted_socket(s_client, s_listen);
270 }
271
~Socket_ListenerSocket_Listener272 ~Socket_Listener()
273 {
274 if (m_client_socket != INVALID_SOCKET)
275 closesocket(m_client_socket);
276 }
277
278 /*
279 Retrieve the pointer to the Winsock extension functions
280 AcceptEx and GetAcceptExSockaddrs.
281 */
init_winsock_extensionsSocket_Listener282 static void init_winsock_extensions()
283 {
284 SOCKET s= mysql_socket_getfd(base_ip_sock);
285 if (s == INVALID_SOCKET)
286 s= mysql_socket_getfd(extra_ip_sock);
287 if (s == INVALID_SOCKET)
288 {
289 /* --skip-networking was used*/
290 return;
291 }
292 GUID guid_AcceptEx= WSAID_ACCEPTEX;
293 GUID guid_GetAcceptExSockaddrs= WSAID_GETACCEPTEXSOCKADDRS;
294
295 GUID *guids[]= { &guid_AcceptEx, &guid_GetAcceptExSockaddrs };
296 void *funcs[]= { &my_AcceptEx, &my_GetAcceptExSockaddrs };
297 DWORD bytes;
298 for (int i= 0; i < array_elements(guids); i++)
299 {
300 if (WSAIoctl(s,
301 SIO_GET_EXTENSION_FUNCTION_POINTER,
302 guids[i], sizeof(GUID),
303 funcs[i], sizeof(void *),
304 &bytes, 0, 0) == -1)
305 {
306 sql_print_error("WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER) failed");
307 unireg_abort(1);
308 }
309 }
310 }
311 };
312
313 /*
314 Create a security descriptor for pipe.
315 - Use low integrity level, so that it is possible to connect
316 from any process.
317 - Give current user read/write access to pipe.
318 - Give Everyone read/write access to pipe minus FILE_CREATE_PIPE_INSTANCE
319 */
init_pipe_security_descriptor()320 static void init_pipe_security_descriptor()
321 {
322 #define SDDL_FMT "S:(ML;; NW;;; LW) D:(A;; 0x%08x;;; WD)(A;; FRFW;;; %s)"
323 #define EVERYONE_PIPE_ACCESS_MASK \
324 (FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | \
325 SYNCHRONIZE | FILE_WRITE_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES)
326
327 #ifndef SECURITY_MAX_SID_STRING_CHARACTERS
328 /* Old SDK does not have this constant */
329 #define SECURITY_MAX_SID_STRING_CHARACTERS 187
330 #endif
331
332 /*
333 Figure out SID of the user that runs the server, then create SDDL string
334 for pipe permissions, and convert it to the security descriptor.
335 */
336 char sddl_string[sizeof(SDDL_FMT) + 8 + SECURITY_MAX_SID_STRING_CHARACTERS];
337 struct
338 {
339 TOKEN_USER token_user;
340 BYTE buffer[SECURITY_MAX_SID_SIZE];
341 } token_buffer;
342 HANDLE token;
343 DWORD tmp;
344
345 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
346 goto fail;
347
348 if (!GetTokenInformation(token, TokenUser, &token_buffer,
349 (DWORD) sizeof(token_buffer), &tmp))
350 goto fail;
351
352 CloseHandle(token);
353
354 char *current_user_string_sid;
355 if (!ConvertSidToStringSid(token_buffer.token_user.User.Sid,
356 ¤t_user_string_sid))
357 goto fail;
358
359 snprintf(sddl_string, sizeof(sddl_string), SDDL_FMT,
360 EVERYONE_PIPE_ACCESS_MASK, current_user_string_sid);
361 LocalFree(current_user_string_sid);
362
363 if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl_string,
364 SDDL_REVISION_1, &pipe_security.lpSecurityDescriptor, 0))
365 return;
366
367 fail:
368 sql_perror("Can't start server : Initialize security descriptor");
369 unireg_abort(1);
370 }
371
372 /**
373 Pipe Listener.
374 Only event notification mode is implemented, no threadpool
375 */
376 struct Pipe_Listener : public Listener
377 {
378 PTP_CALLBACK_ENVIRON m_tp_env;
Pipe_ListenerPipe_Listener379 Pipe_Listener():
380 Listener(INVALID_HANDLE_VALUE, CreateEvent(0, FALSE, FALSE, 0)),
381 m_tp_env(get_threadpool_win_callback_environ())
382 {
383 }
384
385 /*
386 Creates local named pipe instance \\.\pipe\$socket for named pipe connection.
387 */
create_named_pipePipe_Listener388 static HANDLE create_named_pipe()
389 {
390 static bool first_instance= true;
391 static char pipe_name[512];
392 DWORD open_mode= PIPE_ACCESS_DUPLEX |
393 FILE_FLAG_OVERLAPPED;
394
395 if (first_instance)
396 {
397 snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\%s", mysqld_unix_port);
398 open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
399 init_pipe_security_descriptor();
400 pipe_security.nLength= sizeof(SECURITY_ATTRIBUTES);
401 pipe_security.bInheritHandle= FALSE;
402 }
403 HANDLE pipe_handle= CreateNamedPipe(pipe_name,
404 open_mode,
405 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
406 PIPE_UNLIMITED_INSTANCES,
407 (int)global_system_variables.net_buffer_length,
408 (int)global_system_variables.net_buffer_length,
409 NMPWAIT_USE_DEFAULT_WAIT,
410 &pipe_security);
411 if (pipe_handle == INVALID_HANDLE_VALUE)
412 {
413 sql_perror("Create named pipe failed");
414 sql_print_error("Aborting");
415 exit(1);
416 }
417 first_instance= false;
418 return pipe_handle;
419 }
420
create_pipe_connectionPipe_Listener421 static void create_pipe_connection(HANDLE pipe)
422 {
423 CONNECT *connect;
424 if (!(connect= new CONNECT) || !(connect->vio= vio_new_win32pipe(pipe)))
425 {
426 CloseHandle(pipe);
427 delete connect;
428 statistic_increment(aborted_connects, &LOCK_status);
429 statistic_increment(connection_errors_internal, &LOCK_status);
430 return;
431 }
432 connect->host= my_localhost;
433 create_new_thread(connect);
434 }
435
436 /* Threadpool callback.*/
tp_create_pipe_connectionPipe_Listener437 static void CALLBACK tp_create_pipe_connection(
438 PTP_CALLBACK_INSTANCE,void *Context)
439 {
440 tp_win_callback_prolog();
441 create_pipe_connection(Context);
442 }
443
begin_acceptPipe_Listener444 void begin_accept()
445 {
446 m_handle= create_named_pipe();
447 BOOL connected= ConnectNamedPipe(m_handle, &m_overlapped);
448 if (connected)
449 {
450 /* Overlapped ConnectNamedPipe should return zero. */
451 sql_perror("Overlapped ConnectNamedPipe() already connected.");
452 abort();
453 }
454 DWORD last_error= GetLastError();
455 switch (last_error)
456 {
457 case ERROR_PIPE_CONNECTED:
458 /* Client is already connected, so signal an event.*/
459 {
460 /*
461 Cleanup overlapped (so that subsequent GetOverlappedResult()
462 does not show results of previous IO
463 */
464 HANDLE e= m_overlapped.hEvent;
465 memset(&m_overlapped, 0, sizeof(m_overlapped));
466 m_overlapped.hEvent = e;
467 }
468 if (!SetEvent(m_overlapped.hEvent))
469 {
470 sql_perror("SetEvent() failed for connected pipe.");
471 abort();
472 }
473 break;
474 case ERROR_IO_PENDING:
475 break;
476 default:
477 sql_perror("ConnectNamedPipe() failed.");
478 abort();
479 break;
480 }
481 }
482
completion_callbackPipe_Listener483 void completion_callback(bool success)
484 {
485 if (!success)
486 {
487 #ifdef DBUG_OFF
488 sql_print_warning("ConnectNamedPipe completed with %u", GetLastError());
489 #endif
490 CloseHandle(m_handle);
491 m_handle= INVALID_HANDLE_VALUE;
492 begin_accept();
493 return;
494 }
495 HANDLE pipe= m_handle;
496 begin_accept();
497 // If threadpool is on, create connection in threadpool thread
498 if (!m_tp_env || !TrySubmitThreadpoolCallback(tp_create_pipe_connection, pipe, m_tp_env))
499 create_pipe_connection(pipe);
500 }
501
~Pipe_ListenerPipe_Listener502 ~Pipe_Listener()
503 {
504 if (m_handle != INVALID_HANDLE_VALUE)
505 {
506 CloseHandle(m_handle);
507 }
508 }
509
cleanupPipe_Listener510 static void cleanup()
511 {
512 LocalFree(pipe_security.lpSecurityDescriptor);
513 }
514 };
515
516 /**
517 Accept new client connections on Windows.
518
519 Since we deal with pipe and sockets, they cannot be put into a select/loop.
520 But we can use asynchronous IO, and WaitForMultipleObject() loop.
521
522 In addition, for slightly better performance, if we're using threadpool,
523 socket connections are accepted directly in the threadpool.
524
525 The mode of operation is therefore
526
527 1. There is WaitForMultipleObject() loop that waits for shutdown notification
528 (hEventShutdown),and possibly pipes and sockets(e.g if threadpool is not used)
529 This loop ends when shutdown notification is detected.
530
531 2. If threadpool is used, new socket connections are accepted there.
532 */
533
534
535 #define MAX_WAIT_HANDLES 32
536 #define NUM_PIPE_LISTENERS 24
537 #define SHUTDOWN_IDX 0
538 #define LISTENER_START_IDX 1
539
handle_connections_win()540 void handle_connections_win()
541 {
542 Listener* all_listeners[MAX_WAIT_HANDLES]= {};
543 HANDLE wait_events[MAX_WAIT_HANDLES]= {};
544 int n_listeners= 0;
545 int n_waits= 0;
546
547 Socket_Listener::init_winsock_extensions();
548
549 /* Listen for TCP connections on "extra-port" (no threadpool).*/
550 if (extra_ip_sock.fd != INVALID_SOCKET)
551 all_listeners[n_listeners++]= new Socket_Listener(extra_ip_sock, 0);
552
553 /* Listen for named pipe connections */
554 if (mysqld_unix_port[0] && !opt_bootstrap && opt_enable_named_pipe)
555 {
556 /*
557 Use several listeners for pipe, to reduce ERROR_PIPE_BUSY on client side.
558 */
559 for (int i= 0; i < NUM_PIPE_LISTENERS; i++)
560 all_listeners[n_listeners++]= new Pipe_Listener();
561 }
562
563 if (base_ip_sock.fd != INVALID_SOCKET)
564 {
565 /* Wait for TCP connections.*/
566 SetFileCompletionNotificationModes((HANDLE)base_ip_sock.fd, FILE_SKIP_SET_EVENT_ON_HANDLE);
567 all_listeners[n_listeners++]= new Socket_Listener(base_ip_sock, get_threadpool_win_callback_environ());
568 }
569
570 if (!n_listeners && !opt_bootstrap)
571 {
572 sql_print_error("Either TCP connections or named pipe connections must be enabled.");
573 unireg_abort(1);
574 }
575
576 wait_events[SHUTDOWN_IDX]= hEventShutdown;
577 n_waits = 1;
578
579 for (int i= 0; i < n_listeners; i++)
580 {
581 HANDLE wait_handle= all_listeners[i]->wait_handle();
582 if(wait_handle)
583 {
584 DBUG_ASSERT((i == 0) || (all_listeners[i-1]->wait_handle() != 0));
585 wait_events[n_waits++]= wait_handle;
586 }
587 all_listeners[i]->begin_accept();
588 }
589
590 for (;;)
591 {
592 DWORD idx = WaitForMultipleObjects(n_waits ,wait_events, FALSE, INFINITE);
593 DBUG_ASSERT((int)idx >= 0 && (int)idx < n_waits);
594
595 if (idx == SHUTDOWN_IDX)
596 break;
597
598 all_listeners[idx - LISTENER_START_IDX]->completion_callback();
599 }
600
601 /* Cleanup */
602 for (int i= 0; i < n_listeners; i++)
603 {
604 Listener *listener= all_listeners[i];
605 if (listener->wait_handle())
606 delete listener;
607 else
608 // Threadpool-bound listener will be deleted in threadpool
609 // Do not call destructor, because callback maybe running.
610 listener->cancel();
611 }
612 Pipe_Listener::cleanup();
613 }
614