2 * @brief Open a TCP connection to a server.
4 /* Copyright 1999,2000,2001 BrightStation PLC
5 * Copyright 2002 Ananova Ltd
6 * Copyright 2004,2005,2006,2007,2008,2010,2012,2013,2015,2017 Olly Betts
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
26 #include "tcpclient.h"
28 #include "remoteconnection.h"
31 #include <xapian/error.h>
34 #include "safefcntl.h"
35 #include "safenetdb.h"
36 #include "safesyssocket.h"
37 #include "socket_utils.h"
42 # include "safesysselect.h"
49 # include <netinet/in.h>
50 # include <netinet/tcp.h>
56 TcpClient::open_socket(const std::string
& hostname
, int port
,
57 double timeout_connect
, bool tcp_nodelay
)
60 int connect_errno
= 0;
61 for (auto&& r
: Resolver(hostname
, port
)) {
62 int socktype
= r
.ai_socktype
| SOCK_CLOEXEC
;
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
);
81 int rc
= ioctlsocket(fd
, FIONBIO
, &enabled
);
82 #define FLAG_NAME "FIONBIO"
83 #elif defined O_NONBLOCK
84 int rc
= fcntl(fd
, F_SETFL
, O_NONBLOCK
);
85 #define FLAG_NAME "O_NONBLOCK"
87 int rc
= fcntl(fd
, F_SETFL
, O_NDELAY
);
88 #define FLAG_NAME "O_NDELAY"
91 int saved_errno
= socket_errno(); // note down in case close hits an error
93 throw Xapian::NetworkError("Couldn't set " FLAG_NAME
, saved_errno
);
100 // 4th argument might need to be void* or char* - cast it to char*
101 // since C++ allows implicit conversion to void* but not from
103 if (setsockopt(fd
, IPPROTO_TCP
, TCP_NODELAY
,
104 reinterpret_cast<char *>(&optval
),
105 sizeof(optval
)) < 0) {
106 int saved_errno
= socket_errno(); // note down in case close hits an error
108 throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno
);
112 int retval
= connect(fd
, r
.ai_addr
, r
.ai_addrlen
);
118 int err
= socket_errno();
121 WSAGetLastError() == WSAEWOULDBLOCK
126 // Wait for the socket to be writable or give an error, with a
131 fds
.events
= POLLOUT
;
133 retval
= poll(&fds
, 1, int(timeout_connect
* 1000));
134 } while (retval
< 0 && (errno
== EINTR
|| errno
== EAGAIN
));
140 // FIXME: Reduce the timeout if we retry on EINTR.
142 RealTime::to_timeval(timeout_connect
, &tv
);
143 retval
= select(fd
+ 1, 0, &fdset
, 0, &tv
);
144 } while (retval
< 0 && (errno
== EINTR
|| errno
== EAGAIN
));
148 int saved_errno
= errno
;
151 throw Xapian::NetworkError("Couldn't connect (poll() or "
152 "select() on socket failed)",
154 throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT
);
158 SOCKLEN_T len
= sizeof(err
);
160 // 4th argument might need to be void* or char* - cast it to char*
161 // since C++ allows implicit conversion to void* but not from void*.
162 retval
= getsockopt(fd
, SOL_SOCKET
, SO_ERROR
,
163 reinterpret_cast<char *>(&err
), &len
);
166 int saved_errno
= socket_errno(); // note down in case close hits an error
168 throw Xapian::NetworkError("Couldn't get socket options", saved_errno
);
171 // Connected successfully.
177 // Note down the error code for the first address we try, which seems
178 // likely to be more helpful than the last in the case where they
180 if (connect_errno
== 0)
183 // Failed to connect.
187 if (socketfd
== -1) {
188 throw Xapian::NetworkError("Couldn't connect", connect_errno
);
193 ioctlsocket(socketfd
, FIONBIO
, &enabled
);
195 fcntl(socketfd
, F_SETFL
, 0);