1 /* tcpclient.cc: Open a TCP connection to a server.
2 *
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2004,2005,2006,2007,2008,2010,2013 Olly Betts
6 *
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.
11 *
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.
16 *
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
20 * USA
21 */
22
23 #include <config.h>
24
25 #include "remoteconnection.h"
26 #include "tcpclient.h"
27 #include <xapian/error.h>
28
29 #include "safeerrno.h"
30 #include "safefcntl.h"
31 #include "safesysselect.h"
32 #include "socket_utils.h"
33
34 #include <cmath>
35 #include <cstring>
36 #ifndef __WIN32__
37 # include <arpa/inet.h>
38 # include "safenetdb.h"
39 # include <netinet/in.h>
40 # include <netinet/tcp.h>
41 # include <sys/socket.h>
42 #else
43 # include "safewinsock2.h"
44 #endif
45
46 using namespace std;
47
48 int
open_socket(const std::string & hostname,int port,double timeout_connect,bool tcp_nodelay)49 TcpClient::open_socket(const std::string & hostname, int port,
50 double timeout_connect, bool tcp_nodelay)
51 {
52 // FIXME: timeout on gethostbyname() ?
53 struct hostent *host = gethostbyname(hostname.c_str());
54
55 if (host == 0) {
56 throw Xapian::NetworkError(std::string("Couldn't resolve host ") + hostname,
57 #ifdef __WIN32__
58 socket_errno()
59 #else
60 // "socket_errno()" is just errno on UNIX which is
61 // inappropriate here - if gethostbyname() returns NULL an
62 // error code is available in h_errno (with values
63 // incompatible with errno). On Linux at least, if h_errno
64 // is < 0, then the error code *IS* in errno!
65 (h_errno < 0 ? errno : -h_errno)
66 #endif
67 );
68 }
69
70 int socketfd = socket(PF_INET, SOCK_STREAM, 0);
71 if (socketfd < 0) {
72 throw Xapian::NetworkError("Couldn't create socket", socket_errno());
73 }
74
75 struct sockaddr_in remaddr;
76 memset(&remaddr, 0, sizeof(remaddr));
77 remaddr.sin_family = AF_INET;
78 remaddr.sin_port = htons(port);
79 memcpy(&remaddr.sin_addr, host->h_addr, host->h_length);
80
81 #ifdef __WIN32__
82 ULONG enabled = 1;
83 int rc = ioctlsocket(socketfd, FIONBIO, &enabled);
84 #define FLAG_NAME "FIONBIO"
85 #elif defined O_NONBLOCK
86 int rc = fcntl(socketfd, F_SETFL, O_NONBLOCK);
87 #define FLAG_NAME "O_NONBLOCK"
88 #else
89 int rc = fcntl(socketfd, F_SETFL, O_NDELAY);
90 #define FLAG_NAME "O_NDELAY"
91 #endif
92 if (rc < 0) {
93 int saved_errno = socket_errno(); // note down in case close hits an error
94 close_fd_or_socket(socketfd);
95 throw Xapian::NetworkError("Couldn't set " FLAG_NAME, saved_errno);
96 #undef FLAG_NAME
97 }
98
99 if (tcp_nodelay) {
100 int optval = 1;
101 // 4th argument might need to be void* or char* - cast it to char*
102 // since C++ allows implicit conversion to void* but not from void*.
103 if (setsockopt(socketfd, 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
107 close_fd_or_socket(socketfd);
108 throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno);
109 }
110 }
111
112 int retval = connect(socketfd, reinterpret_cast<sockaddr *>(&remaddr),
113 sizeof(remaddr));
114
115 if (retval < 0) {
116 #ifdef __WIN32__
117 if (WSAGetLastError() != WSAEWOULDBLOCK) {
118 #else
119 if (socket_errno() != EINPROGRESS) {
120 #endif
121 int saved_errno = socket_errno(); // note down in case close hits an error
122 close_fd_or_socket(socketfd);
123 throw Xapian::NetworkError("Couldn't connect (1)", saved_errno);
124 }
125
126 // wait for input to be available.
127 fd_set fdset;
128 FD_ZERO(&fdset);
129 FD_SET(socketfd, &fdset);
130
131 do {
132 // FIXME: Reduce the timeout if we retry on EINTR.
133 struct timeval tv;
134 tv.tv_sec = long(timeout_connect);
135 tv.tv_usec = long(std::fmod(timeout_connect, 1.0) * 1e6);
136
137 retval = select(socketfd + 1, 0, &fdset, &fdset, &tv);
138 } while (retval < 0 && errno == EINTR);
139
140 if (retval < 0) {
141 int saved_errno = errno;
142 close_fd_or_socket(socketfd);
143 throw Xapian::NetworkError("Couldn't connect (2)", saved_errno);
144 }
145
146 if (retval <= 0) {
147 close_fd_or_socket(socketfd);
148 throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT);
149 }
150
151 int err = 0;
152 SOCKLEN_T len = sizeof(err);
153
154 // 4th argument might need to be void* or char* - cast it to char*
155 // since C++ allows implicit conversion to void* but not from void*.
156 retval = getsockopt(socketfd, SOL_SOCKET, SO_ERROR,
157 reinterpret_cast<char *>(&err), &len);
158
159 if (retval < 0) {
160 int saved_errno = socket_errno(); // note down in case close hits an error
161 close_fd_or_socket(socketfd);
162 throw Xapian::NetworkError("Couldn't get socket options", saved_errno);
163 }
164 if (err) {
165 close_fd_or_socket(socketfd);
166 throw Xapian::NetworkError("Couldn't connect (3)", err);
167 }
168 }
169
170 #ifdef __WIN32__
171 enabled = 0;
172 ioctlsocket(socketfd, FIONBIO, &enabled);
173 #else
174 fcntl(socketfd, F_SETFL, 0);
175 #endif
176 return socketfd;
177 }
178