xapian-core  2.0.0
tcpserver.cc
Go to the documentation of this file.
1 
4 /* Copyright 2006-2023 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "tcpserver.h"
24 
25 #include <xapian/error.h>
26 
27 #include "resolver.h"
28 #include "socket_utils.h"
29 
30 #include "safefcntl.h"
31 #include "safenetdb.h"
32 #include "safesysexits.h"
33 #include "safesysselect.h"
34 #include "safesyssocket.h"
35 #include "safeunistd.h"
36 
37 #ifdef __WIN32__
38 # include <process.h> /* _beginthreadex, _endthreadex */
39 #else
40 # include <netinet/in.h>
41 # include <netinet/tcp.h>
42 # include <signal.h>
43 # include <sys/wait.h>
44 #endif
45 
46 #include <iostream>
47 #include <limits>
48 
49 #include <cerrno>
50 #include <cstring>
51 #include <cstdlib>
52 #include <sys/types.h>
53 
54 using namespace std;
55 
56 // The parent process/main thread sits in a loop which calls accept() and
57 // then passes the connection off to a new process/threadm so we should accept
58 // connections promptly and shouldn't need to handle a large backlog.
59 //
60 // We've been using 5 for this since 2006 without anyone reporting a problem.
61 #define LISTEN_BACKLOG 5
62 
70 static int
71 create_listener(const std::string& host,
72  int port,
73  bool tcp_nodelay)
74 {
75  int socketfd = -1;
76  int bind_errno = 0;
77  for (auto&& r : Resolver(host, port, AI_PASSIVE)) {
78  int socktype = r.ai_socktype | SOCK_CLOEXEC;
79  int fd = socket(r.ai_family, socktype, r.ai_protocol);
80  if (fd == -1)
81  continue;
82 
83 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
84  // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
85  // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
86  // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
87  if (SOCK_CLOEXEC == 0)
88  (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
89 #endif
90 
91  int on = 1;
92  if (tcp_nodelay) {
93  // 4th argument might need to be void* or char* - cast it to char*
94  // since C++ allows implicit conversion to void* but not from
95  // void*.
96  if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
97  reinterpret_cast<char*>(&on),
98  sizeof(on)) < 0) {
99  int setsockopt_errno = socket_errno();
100  CLOSESOCKET(fd);
101  throw Xapian::NetworkError("setsockopt TCP_NODELAY failed",
102  setsockopt_errno);
103  }
104  }
105 
106 #ifndef __WIN32__
107  // On Unix, we use SO_REUSEADDR so that we can bind to a port which was
108  // previously in use and still has old connections in the TIME_WAIT
109  // state. Without this it may not be possible to restart a server
110  // without downtime.
111  //
112  // It seems that the default Microsoft behaviour is to allow bindings
113  // to a port with old connections in the TIME_WAIT state, so we don't
114  // need to do anything special here.
115  //
116  // We definitely want to avoid SO_REUSEADDR on Microsoft Windows as
117  // it was implemented with stupid and dangerous semantics which allowed
118  // binding to a port which was already bound and listening, and even
119  // worse is that this affected *any* listening process, even if didn't
120  // use SO_REUSEADDR itself. This serious security problem existed for
121  // many years but Microsoft finally addressed it and the situation
122  // with Microsoft Windows versions that are still relevant is that we
123  // don't need to worry about another process hijacking our listening
124  // socket, but we still don't want to use SO_REUSEADDR.
125  //
126  // There's also the Microsoft-specific SO_EXCLUSIVEADDRUSE, but this
127  // doesn't seem to affect the TIME_WAIT situation.
128  //
129  // We used to try to set it in combination with SO_REUSEADDR thinking
130  // this tempered the problems, but you can't set both on the same
131  // socket and the attempt to set SO_EXCLUSIVEADDRUSE was failing with
132  // WSAEINVAL which we ignored (to support OS versions from before it
133  // was added), so we've never actual set SO_EXCLUSIVEADDRUSE in any
134  // released version of Xapian.
135  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
136  reinterpret_cast<char*>(&on),
137  sizeof(on)) < 0) {
138  int setsockopt_errno = socket_errno();
139  CLOSESOCKET(fd);
140  throw Xapian::NetworkError("setsockopt SO_REUSEADDR failed",
141  setsockopt_errno);
142  }
143 #endif
144 
145  if (::bind(fd, r.ai_addr, r.ai_addrlen) == 0) {
146  socketfd = fd;
147  break;
148  }
149 
150  // Note down the error code for the first address we try, which seems
151  // likely to be more helpful than the last in the case where they
152  // differ.
153  if (bind_errno == 0)
154  bind_errno = socket_errno();
155 
156  CLOSESOCKET(fd);
157  }
158 
159  if (socketfd < 0) {
160 #ifdef __WIN32__
161  // On __WIN32__ we can get WSAEACCESS instead of EADDRINUSE in
162  // some cases, but there are no privileged ports so we can just
163  // treat it the same as EADDRINUSE.
164  if (bind_errno == WSAEACCES) bind_errno = EADDRINUSE;
165 #endif
166  if (bind_errno == EADDRINUSE) {
167 address_in_use:
168  cerr << host << ':' << port << " already in use\n";
169  // EX_UNAVAILABLE is 69. Scripts can use this to detect that the
170  // requested port was already in use.
171  exit(EX_UNAVAILABLE);
172  }
173 #ifndef __WIN32__
174  // No privileged ports on __WIN32__.
175  if (bind_errno == EACCES) {
176  cerr << "Can't bind to privileged port " << port << '\n';
177  // EX_NOPERM is 77. Scripts can use this to detect if
178  // xapian-tcpsrv failed to bind to the requested port.
179  exit(EX_NOPERM);
180  }
181 #endif
182  throw Xapian::NetworkError("bind failed", bind_errno);
183  }
184 
185  if (listen(socketfd, LISTEN_BACKLOG) < 0) {
186  int listen_errno = socket_errno();
187  CLOSESOCKET(socketfd);
188  if (listen_errno == EADDRINUSE) {
189  // The Linux listen(2) man page documents:
190  //
191  // EADDRINUSE Another socket is already listening on the same port
192  //
193  // I'm not sure this is actually possible, but it's not hard to
194  // handle it suitably in case it is.
195  goto address_in_use;
196  }
197  throw Xapian::NetworkError("listen failed", listen_errno);
198  }
199 
200  return socketfd;
201 }
202 
203 TcpServer::TcpServer(const std::string& host,
204  int port,
205  bool tcp_nodelay,
206  bool verbose_)
207  : listener(create_listener(host, port, tcp_nodelay)),
208  verbose(verbose_)
209 { }
210 
211 int
212 TcpServer::accept_connection()
213 {
214 #ifndef __WIN32__
215  // We want to be able to report when a client disconnects, but it's better
216  // to do so in the main process to avoid problems with output getting
217  // interleaved. That means we need to be able to take action if either
218  // a new connection is made or an existing connection goes away.
219  //
220  // To achieve this we use select() to wait until there's a connection to
221  // accept(), or we're interrupted by a signal (we'll get SIGCHLD when a
222  // child process servicing a client exits).
223  //
224  // We can rely on socketfd being small and so <= FD_SETSIZE so select()
225  // should work fine here.
226  fd_set fds;
227  FD_ZERO(&fds);
228  while (true) {
229  FD_SET(listener, &fds);
230  if (select(listener + 1, &fds, nullptr, nullptr, nullptr) > 0) {
231  // There's a connection waiting to be accepted.
232  break;
233  }
234 
235  // Reap and report any zombie children.
236  int status;
237  while (waitpid(-1, &status, WNOHANG) > 0) {
238  if (verbose) cout << "Connection closed.\n";
239  }
240  }
241 #endif
242 
243  struct sockaddr_storage client_address;
244  SOCKLEN_T client_address_size = sizeof(client_address);
245  int connection = accept(listener,
246  reinterpret_cast<sockaddr*>(&client_address),
247  &client_address_size);
248 
249  if (connection < 0) {
250  int accept_errno = socket_errno();
251 #ifdef __WIN32__
252  if (accept_errno == WSAEINTR) {
253  // Our CtrlHandler function closed the socket.
254  return -1;
255  }
256 #endif
257  throw Xapian::NetworkError("accept failed", accept_errno);
258  }
259 
260  if (verbose) {
261  char host[PRETTY_IP6_LEN];
262  int port = pretty_ip6(&client_address, host);
263  if (port >= 0) {
264  cout << host << ':' << port << " connected\n";
265  } else {
266  cout << "Unknown host connected\n";
267  }
268  }
269 
270  return connection;
271 }
272 
273 TcpServer::~TcpServer()
274 {
275  CLOSESOCKET(listener);
276 }
277 
278 #ifdef HAVE_FORK
279 // A fork() based implementation.
280 
281 extern "C" {
282 
283 static void
284 sigterm_handler(int)
285 {
286  signal(SIGTERM, SIG_DFL);
287  // Terminate all processes in the same process group.
288  kill(0, SIGTERM);
289 }
290 
291 }
292 
293 void
294 TcpServer::run()
295 {
296  // Handle connections until shutdown.
297 
298  // Put this process into its own process group so that we can easily kill
299  // any child processes on SIGTERM.
300  setpgid(0, 0);
301 
302  // Set up signal handler.
303  signal(SIGTERM, sigterm_handler);
304 
305  while (true) {
306  try {
307  int connected_socket = accept_connection();
308  pid_t pid = fork();
309  if (pid == 0) {
310  // Child process.
311  close(listener);
312 
313  handle_one_connection(connected_socket);
314  close(connected_socket);
315  _exit(0);
316  }
317 
318  // Parent process.
319 
320  if (pid < 0) {
321  // fork() failed.
322  int fork_errno = errno;
323  close(connected_socket);
324  throw Xapian::NetworkError("fork failed", fork_errno);
325  }
326 
327  close(connected_socket);
328  } catch (const Xapian::Error& e) {
329  cerr << "Caught " << e.get_description() << '\n';
330  } catch (...) {
331  cerr << "Caught unknown exception\n";
332  }
333  }
334 }
335 
336 #elif defined __WIN32__
337 
338 // A threaded, Windows specific, implementation.
339 
344 static const int* pShutdownSocket = NULL;
345 
347 static BOOL
348 CtrlHandler(DWORD fdwCtrlType)
349 {
350  switch (fdwCtrlType) {
351  case CTRL_C_EVENT:
352  case CTRL_CLOSE_EVENT:
353  // Console is about to die.
354  // CTRL_CLOSE_EVENT gives us 5 seconds before displaying a
355  // confirmation dialog asking if we really are sure.
356  case CTRL_LOGOFF_EVENT:
357  case CTRL_SHUTDOWN_EVENT:
358  // These 2 will probably need to change when we get service
359  // support - the service will prevent these being seen, so only
360  // apply interactively.
361  cout << "Shutting down...\n";
362  break; // default behaviour
363  case CTRL_BREAK_EVENT:
364  // This (probably) means the developer is struggling to get
365  // things to behave, and really wants to shutdown so let the OS
366  // handle Ctrl+Break in the default way.
367  cout << "Ctrl+Break: aborting process\n";
368  return FALSE;
369  default:
370  cerr << "unexpected CtrlHandler: " << fdwCtrlType << '\n';
371  return FALSE;
372  }
373 
374  // Note: close() does not cause a blocking accept() call to terminate.
375  // However, it appears closesocket() does. This is much easier than trying
376  // to setup a non-blocking accept().
377  if (!pShutdownSocket || closesocket(*pShutdownSocket) == SOCKET_ERROR) {
378  // We failed to close the socket, so just let the OS handle the
379  // event in the default way.
380  return FALSE;
381  }
382 
383  pShutdownSocket = NULL;
384  return TRUE; // Tell the OS that we've handled the event.
385 }
386 
388 struct thread_param
389 {
390  thread_param(TcpServer* s, int c) : server(s), connected_socket(c) {}
391  TcpServer* server;
392  int connected_socket;
393 };
394 
396 static unsigned __stdcall
397 run_thread(void* param_)
398 {
399  thread_param* param(reinterpret_cast<thread_param*>(param_));
400  int socket = param->connected_socket;
401 
402  TcpServer* tcp_server = param->server;
403  tcp_server->handle_one_connection(socket);
404  closesocket(socket);
405 
406  if (tcp_server->get_verbose()) cout << "Connection closed.\n";
407 
408  delete param;
409 
410  _endthreadex(0);
411  return 0;
412 }
413 
414 void
415 TcpServer::run()
416 {
417  // Handle connections until shutdown.
418 
419  // Set up the shutdown handler - this is a bit hacky, and sadly involves
420  // a global variable.
421  pShutdownSocket = &listener;
422  if (!::SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
423  throw Xapian::NetworkError("Failed to install shutdown handler");
424 
425  while (true) {
426  try {
427  int connected_socket = accept_connection();
428  if (connected_socket == -1)
429  return; // Shutdown has happened
430 
431  // Spawn a new thread to handle the connection.
432  // (This seems like lots of hoops just to end up calling
433  // this->handle_one_connection() on a new thread. There might be a
434  // better way...)
435  thread_param* param = new thread_param(this, connected_socket);
436  HANDLE hthread = (HANDLE)_beginthreadex(NULL, 0, ::run_thread,
437  param, 0, NULL);
438  if (hthread == 0) {
439  // errno holds the error code from _beginthreadex, and
440  // closesocket() doesn't set errno.
441  closesocket(connected_socket);
442  throw Xapian::NetworkError("_beginthreadex failed", errno);
443  }
444 
445  // FIXME: keep track of open thread handles so we can gracefully
446  // close each thread down. OTOH, when we want to kill them all its
447  // likely to mean the process is on its way down, so it doesn't
448  // really matter...
449  CloseHandle(hthread);
450  } catch (const Xapian::Error& e) {
451  cerr << "Caught " << e.get_description() << '\n';
452  } catch (...) {
453  cerr << "Caught unknown exception\n";
454  }
455  }
456 }
457 
458 #else
459 # error Neither HAVE_FORK nor __WIN32__ are defined.
460 #endif
461 
462 void
463 TcpServer::run_once()
464 {
465  // Run a single request in the current process/thread.
466  int fd = accept_connection();
467  handle_one_connection(fd);
468  CLOSESOCKET(fd);
469 }
All exceptions thrown by Xapian are subclasses of Xapian::Error.
Definition: error.h:41
std::string get_description() const
Return a string describing this object.
Definition: error.cc:93
Indicates a problem communicating with a remote database.
Definition: error.h:791
#define SOCKLEN_T
Definition: config.h:391
Hierarchy of classes which Xapian can throw as exceptions.
int close(FD &fd)
Definition: fd.h:63
Resolve hostnames and ip addresses.
include <fcntl.h>, but working around broken platforms.
include <netdb.h>, with portability workarounds.
include <sysexits.h> with portability workarounds.
#define EX_UNAVAILABLE
Definition: safesysexits.h:33
#define EX_NOPERM
Definition: safesysexits.h:42
include <sys/select.h> with portability workarounds.
include <sys/socket.h> with portability workarounds.
#define SOCK_CLOEXEC
<unistd.h>, but with compat.
int pretty_ip6(const void *p, char *buf)
Socket handling utilities.
#define CLOSESOCKET(S)
Definition: socket_utils.h:123
constexpr size_t PRETTY_IP6_LEN
Definition: socket_utils.h:137
int socket_errno()
Definition: socket_utils.h:121
static int create_listener(const std::string &host, int port, bool tcp_nodelay)
Create a listening socket ready to accept connections.
Definition: tcpserver.cc:71
#define LISTEN_BACKLOG
Definition: tcpserver.cc:61
Generic TCP/IP socket based server base class.
static int verbose
Definition: xapian-delve.cc:46