xapian-core  2.0.0
tcpclient.cc
Go to the documentation of this file.
1 
4 /* Copyright 2007-2024 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 "tcpclient.h"
24 
25 #include <xapian/error.h>
26 
27 #include "realtime.h"
28 #include "resolver.h"
29 #include "socket_utils.h"
30 
31 #include "safefcntl.h"
32 #include "safesyssocket.h"
33 
34 #ifdef HAVE_POLL_H
35 # include <poll.h>
36 #else
37 # include "safesysselect.h"
38 #endif
39 
40 #include <cerrno>
41 #ifndef __WIN32__
42 # include <netinet/in.h>
43 # include <netinet/tcp.h>
44 #endif
45 
46 #include <string_view>
47 
48 using namespace std;
49 
50 int
51 TcpClient::open_socket(std::string_view hostname, int port,
52  double timeout_connect, bool tcp_nodelay,
53  const string& context)
54 {
55  int socketfd = -1;
56  int connect_errno = 0;
57  for (auto&& r : Resolver(hostname, port)) {
58  int socktype = r.ai_socktype | SOCK_CLOEXEC;
59 #ifdef SOCK_NONBLOCK
60  // Set the socket as non-blocking so we can implement a timeout on
61  // the connection attempt by using select() or poll(). If
62  // SOCK_NONBLOCK is available we can get socket() to do this, but
63  // if not we call fcntl()/ioctlsocket() below to set it.
64  socktype |= SOCK_NONBLOCK;
65 #endif
66  int fd = socket(r.ai_family, socktype, r.ai_protocol);
67  if (fd < 0)
68  continue;
69 
70 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
71  // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
72  // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
73  // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
74  if (SOCK_CLOEXEC == 0)
75  (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
76 #endif
77 
78 #ifndef SOCK_NONBLOCK
79 #ifdef __WIN32__
80  int rc = [&]() {
81  ULONG on = 1;
82  return ioctlsocket(fd, FIONBIO, &on);
83  }();
84 #define FLAG_NAME "FIONBIO"
85 #elif defined O_NONBLOCK
86  int rc = fcntl(fd, F_SETFL, O_NONBLOCK);
87 #define FLAG_NAME "O_NONBLOCK"
88 #else
89  int rc = fcntl(fd, F_SETFL, O_NDELAY);
90 #define FLAG_NAME "O_NDELAY"
91 #endif
92  if (rc < 0) {
93  int saved_errno = socket_errno();
94  CLOSESOCKET(fd);
95  throw Xapian::NetworkError("Couldn't set " FLAG_NAME,
96  context,
97  saved_errno);
98 #undef FLAG_NAME
99  }
100 #endif
101 
102  if (tcp_nodelay) {
103  int on = 1;
104  // 4th argument might need to be void* or char* - cast it to char*
105  // since C++ allows implicit conversion to void* but not from
106  // void*.
107  if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
108  reinterpret_cast<char*>(&on),
109  sizeof(on)) < 0) {
110  int setsockopt_errno = socket_errno();
111  CLOSESOCKET(fd);
112  throw Xapian::NetworkError("Couldn't set TCP_NODELAY",
113  context,
114  setsockopt_errno);
115  }
116  }
117 
118  int retval = connect(fd, r.ai_addr, r.ai_addrlen);
119  if (retval == 0) {
120  socketfd = fd;
121  break;
122  }
123 
124  int err = socket_errno();
125  if (
126 #ifndef __WIN32__
127  err == EINPROGRESS
128 #else
129  err == WSAEWOULDBLOCK
130 #endif
131  ) {
132  // Wait for the socket to be writable or give an error, with a
133  // timeout. FIXME: Reduce the timeout if we retry.
134 #ifdef HAVE_POLL
135  struct pollfd fds;
136  fds.fd = fd;
137  fds.events = POLLOUT;
138  do {
139  retval = poll(&fds, 1, int(timeout_connect * 1000));
140  } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
141 # define FUNC_NAME "poll()"
142 #else
143  fd_set fdset;
144  FD_ZERO(&fdset);
145  do {
146  FD_SET(fd, &fdset);
147  struct timeval tv;
148  RealTime::to_timeval(timeout_connect, &tv);
149  retval = select(fd + 1, 0, &fdset, 0, &tv);
150  } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
151 # define FUNC_NAME "select()"
152 #endif
153 
154  if (retval <= 0) {
155  int saved_errno = errno;
156  CLOSESOCKET(fd);
157  if (retval < 0) {
158  throw Xapian::NetworkError("Couldn't connect ("
159  FUNC_NAME " on socket failed)",
160  context,
161  saved_errno);
162 #undef FUNC_NAME
163  }
164  throw Xapian::NetworkTimeoutError("Timed out waiting to "
165  "connect",
166  context,
167  ETIMEDOUT);
168  }
169 
170  err = 0;
171  SOCKLEN_T len = sizeof(err);
172 
173  // 4th argument might need to be void* or char* - cast it to char*
174  // since C++ allows implicit conversion to void* but not from void*.
175  retval = getsockopt(fd, SOL_SOCKET, SO_ERROR,
176  reinterpret_cast<char*>(&err), &len);
177 
178  if (retval < 0) {
179  int getsockopt_errno = socket_errno();
180  CLOSESOCKET(fd);
181  throw Xapian::NetworkError("getsockopt failed",
182  context,
183  getsockopt_errno);
184  }
185  if (err == 0) {
186  // Connected successfully.
187  socketfd = fd;
188  break;
189  }
190  }
191 
192  // Note down the error code for the first address we try, which seems
193  // likely to be more helpful than the last in the case where they
194  // differ.
195  if (connect_errno == 0)
196  connect_errno = err;
197 
198  // Failed to connect.
199  CLOSESOCKET(fd);
200  }
201 
202  if (socketfd < 0) {
203  throw Xapian::NetworkError("connect failed",
204  context,
205  connect_errno);
206  }
207 
208  // Set the socket to be blocking.
209 #ifndef __WIN32__
210  fcntl(socketfd, F_SETFL, 0);
211 #else
212  ULONG off = 0;
213  ioctlsocket(socketfd, FIONBIO, &off);
214 #endif
215 
216  return socketfd;
217 }
Indicates a problem communicating with a remote database.
Definition: error.h:791
Indicates a timeout expired while communicating with a remote database.
Definition: error.h:833
#define SOCKLEN_T
Definition: config.h:391
Hierarchy of classes which Xapian can throw as exceptions.
void to_timeval(double t, struct timeval *tv)
Fill in struct timeval from number of seconds in a double.
Definition: realtime.h:110
int open_socket(std::string_view hostname, int port, double timeout_connect, bool tcp_nodelay, const std::string &context)
Attempt to open a TCP/IP socket connection to a server.
Definition: tcpclient.cc:51
Functions for handling a time or time interval in a double.
Resolve hostnames and ip addresses.
include <fcntl.h>, but working around broken platforms.
include <sys/select.h> with portability workarounds.
include <sys/socket.h> with portability workarounds.
#define SOCK_CLOEXEC
Socket handling utilities.
#define CLOSESOCKET(S)
Definition: socket_utils.h:123
int socket_errno()
Definition: socket_utils.h:121
#define FLAG_NAME
#define FUNC_NAME
Open a TCP connection to a server.