1 /* tcpclient.cc: Open a TCP connection to a server.
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2004,2005,2006,2007,2008,2010,2012,2013,2015,2017 Olly Betts
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
25 #include "tcpclient.h"
27 #include "remoteconnection.h"
30 #include <xapian/error.h>
33 #include "safeerrno.h"
34 #include "safefcntl.h"
35 #include "safenetdb.h"
36 #include "safesysselect.h"
37 #include "safesyssocket.h"
38 #include "socket_utils.h"
43 # include <netinet/in.h>
44 # include <netinet/tcp.h>
50 TcpClient::open_socket(const std::string
& hostname
, int port
,
51 double timeout_connect
, bool tcp_nodelay
)
54 int connect_errno
= 0;
55 for (auto&& r
: Resolver(hostname
, port
)) {
56 int socktype
= r
.ai_socktype
| SOCK_CLOEXEC
;
57 int fd
= socket(r
.ai_family
, socktype
, r
.ai_protocol
);
61 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
62 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
63 // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
64 // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
65 if (SOCK_CLOEXEC
== 0)
66 (void)fcntl(fd
, F_SETFD
, FD_CLOEXEC
);
71 int rc
= ioctlsocket(fd
, FIONBIO
, &enabled
);
72 #define FLAG_NAME "FIONBIO"
73 #elif defined O_NONBLOCK
74 int rc
= fcntl(fd
, F_SETFL
, O_NONBLOCK
);
75 #define FLAG_NAME "O_NONBLOCK"
77 int rc
= fcntl(fd
, F_SETFL
, O_NDELAY
);
78 #define FLAG_NAME "O_NDELAY"
81 int saved_errno
= socket_errno(); // note down in case close hits an error
82 close_fd_or_socket(fd
);
83 throw Xapian::NetworkError("Couldn't set " FLAG_NAME
, saved_errno
);
89 // 4th argument might need to be void* or char* - cast it to char*
90 // since C++ allows implicit conversion to void* but not from
92 if (setsockopt(fd
, IPPROTO_TCP
, TCP_NODELAY
,
93 reinterpret_cast<char *>(&optval
),
94 sizeof(optval
)) < 0) {
95 int saved_errno
= socket_errno(); // note down in case close hits an error
96 close_fd_or_socket(fd
);
97 throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno
);
101 int retval
= connect(fd
, r
.ai_addr
, r
.ai_addrlen
);
107 int err
= socket_errno();
110 WSAGetLastError() == WSAEWOULDBLOCK
115 // Wait for the socket to be writable, with a timeout.
121 // FIXME: Reduce the timeout if we retry on EINTR.
123 RealTime::to_timeval(timeout_connect
, &tv
);
124 retval
= select(fd
+ 1, 0, &fdset
, &fdset
, &tv
);
125 } while (retval
< 0 && (errno
== EINTR
|| errno
== EAGAIN
));
128 int saved_errno
= errno
;
129 close_fd_or_socket(fd
);
131 throw Xapian::NetworkError("Couldn't connect (select() on socket failed)",
133 throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT
);
137 SOCKLEN_T len
= sizeof(err
);
139 // 4th argument might need to be void* or char* - cast it to char*
140 // since C++ allows implicit conversion to void* but not from void*.
141 retval
= getsockopt(fd
, SOL_SOCKET
, SO_ERROR
,
142 reinterpret_cast<char *>(&err
), &len
);
145 int saved_errno
= socket_errno(); // note down in case close hits an error
146 close_fd_or_socket(fd
);
147 throw Xapian::NetworkError("Couldn't get socket options", saved_errno
);
150 // Connected successfully.
156 // Note down the error code for the first address we try, which seems
157 // likely to be more helpful than the last in the case where they
159 if (connect_errno
== 0)
162 // Failed to connect.
166 if (socketfd
== -1) {
167 throw Xapian::NetworkError("Couldn't connect", connect_errno
);
172 ioctlsocket(socketfd
, FIONBIO
, &enabled
);
174 fcntl(socketfd
, F_SETFL
, 0);
179 #ifdef DISABLE_GPL_LIBXAPIAN
180 # error GPL source we cannot relicense included in libxapian