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