2 * @brief Open a TCP connection to a server.
4 /* Copyright 2007-2024 Olly Betts
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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "tcpclient.h"
25 #include <xapian/error.h>
29 #include "socket_utils.h"
31 #include "safefcntl.h"
32 #include "safesyssocket.h"
37 # include "safesysselect.h"
42 # include <netinet/in.h>
43 # include <netinet/tcp.h>
46 #include <string_view>
51 TcpClient::open_socket(std::string_view hostname
, int port
,
52 double timeout_connect
, bool tcp_nodelay
,
53 const string
& context
)
56 int connect_errno
= 0;
57 for (auto&& r
: Resolver(hostname
, port
)) {
58 int socktype
= r
.ai_socktype
| SOCK_CLOEXEC
;
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
;
66 int fd
= socket(r
.ai_family
, socktype
, r
.ai_protocol
);
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
);
82 return ioctlsocket(fd
, FIONBIO
, &on
);
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"
89 int rc
= fcntl(fd
, F_SETFL
, O_NDELAY
);
90 #define FLAG_NAME "O_NDELAY"
93 int saved_errno
= socket_errno();
95 throw Xapian::NetworkError("Couldn't set " FLAG_NAME
,
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
107 if (setsockopt(fd
, IPPROTO_TCP
, TCP_NODELAY
,
108 reinterpret_cast<char*>(&on
),
110 int setsockopt_errno
= socket_errno();
112 throw Xapian::NetworkError("Couldn't set TCP_NODELAY",
118 int retval
= connect(fd
, r
.ai_addr
, r
.ai_addrlen
);
124 int err
= socket_errno();
129 err
== WSAEWOULDBLOCK
132 // Wait for the socket to be writable or give an error, with a
133 // timeout. FIXME: Reduce the timeout if we retry.
137 fds
.events
= POLLOUT
;
139 retval
= poll(&fds
, 1, int(timeout_connect
* 1000));
140 } while (retval
< 0 && (errno
== EINTR
|| errno
== EAGAIN
));
141 # define FUNC_NAME "poll()"
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()"
155 int saved_errno
= errno
;
158 throw Xapian::NetworkError("Couldn't connect ("
159 FUNC_NAME
" on socket failed)",
164 throw Xapian::NetworkTimeoutError("Timed out waiting to "
171 SOCKLEN_T len
= sizeof(err
);
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
);
179 int getsockopt_errno
= socket_errno();
181 throw Xapian::NetworkError("getsockopt failed",
186 // Connected successfully.
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
195 if (connect_errno
== 0)
198 // Failed to connect.
203 throw Xapian::NetworkError("connect failed",
208 // Set the socket to be blocking.
210 fcntl(socketfd
, F_SETFL
, 0);
213 ioctlsocket(socketfd
, FIONBIO
, &off
);