Adding DOCUMENTATION flag to the CMakeLists.txt
[libt2n] / src / socket_client.cpp
CommitLineData
19facd85
TJ
1/*
2Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4The software in this package is distributed under the GNU General
5Public License version 2 (with a special exception described below).
6
7A copy of GNU General Public License (GPL) is included in this distribution,
8in the file COPYING.GPL.
9
10As a special exception, if other files instantiate templates or use macros
11or inline functions from this file, or you compile this file and link it
12with other works to produce a work based on this file, this file
13does not by itself cause the resulting work to be covered
14by the GNU General Public License.
15
16However the source code for this file must still be made available
17in accordance with section (3) of the GNU General Public License.
18
19This exception does not invalidate any other reasons why a work based
20on this file might be covered by the GNU General Public License.
21*/
7087e187
GE
22
23#include <stdio.h>
24#include <errno.h>
25#include <stdlib.h>
26#include <unistd.h>
27#include <sys/types.h>
28#include <sys/socket.h>
29#include <sys/un.h>
30#include <sys/time.h>
31#include <arpa/inet.h>
32#include <netinet/in.h>
33#include <netdb.h>
34#include <fcntl.h>
35#include <time.h>
0a531de6 36
7087e187
GE
37#include <pwd.h>
38#include <grp.h>
39
40#include <sstream>
41
42#include "socket_client.hxx"
43#include "t2n_exception.hxx"
e1614a6d 44#include "log.hxx"
7087e187
GE
45
46using namespace std;
47
48namespace libt2n
49{
50
fb3345ad 51/// returns a closed connection if connection could not be established, call get_last_error_msg() for details
b604df5f 52socket_client_connection::socket_client_connection(int _port, const std::string& _server,
e1614a6d
GE
53 long long _connect_timeout_usec, int _max_retries,
54 std::ostream *_logstream, log_level_values _log_level)
7087e187
GE
55 : client_connection(), socket_handler(0,tcp_s)
56{
57 max_retries=_max_retries;
b604df5f 58 connect_timeout_usec=_connect_timeout_usec;
7087e187
GE
59
60 server=_server;
61 port=_port;
62
e1614a6d
GE
63 set_logging(_logstream,_log_level);
64
fb3345ad
GE
65 try
66 {
67 tcp_connect(max_retries);
68 }
69 catch (t2n_communication_error &e)
70 {
71 lastErrorMsg=e.what();
72 LOGSTREAM(debug,"tcp connect error: " << lastErrorMsg);
56f3994d 73 // FIXME: Don't call virtual function in constructor. Currently not dangerous but bad design.
fb3345ad
GE
74 close();
75 }
e1614a6d 76
fb3345ad
GE
77 if (!connection::is_closed())
78 do_callbacks(new_connection);
7087e187
GE
79}
80
fb3345ad 81/// returns a closed connection if connection could not be established, call get_last_error_msg() for details
b604df5f 82socket_client_connection::socket_client_connection(const std::string& _path,
e1614a6d
GE
83 long long _connect_timeout_usec, int _max_retries,
84 std::ostream *_logstream, log_level_values _log_level)
7087e187
GE
85 : client_connection(), socket_handler(0,unix_s)
86{
87 max_retries=_max_retries;
b604df5f 88 connect_timeout_usec=_connect_timeout_usec;
7087e187
GE
89
90 path=_path;
91
e1614a6d
GE
92 set_logging(_logstream,_log_level);
93
fb3345ad
GE
94 try
95 {
96 unix_connect(max_retries);
97 }
98 catch (t2n_communication_error &e)
99 {
100 lastErrorMsg=e.what();
101 LOGSTREAM(debug,"unix connect error: " << lastErrorMsg);
56f3994d 102 // FIXME: Don't call virtual function in constructor. Currently not dangerous
fb3345ad
GE
103 close();
104 }
e1614a6d 105
fb3345ad
GE
106 if (!connection::is_closed())
107 do_callbacks(new_connection);
7087e187
GE
108}
109
ced847b2
TJ
110/**
111 * Destructor. Closes an open connection.
112 */
113socket_client_connection::~socket_client_connection()
114{
56f3994d 115 // Destructor of socket_handler will close the socket!
ced847b2
TJ
116}
117
118
487afb79 119/// establish a connection via tcp
b604df5f 120void socket_client_connection::tcp_connect(int max_retries)
7087e187 121{
b604df5f
GE
122 struct sockaddr_in sock_addr;
123
124 sock_addr.sin_family = AF_INET;
125 sock_addr.sin_port = htons(port);
126
127 // find the target ip
128 if (inet_aton(server.c_str(),&sock_addr.sin_addr)==0)
7087e187 129 {
b604df5f
GE
130 struct hostent *server_hent;
131 server_hent=gethostbyname(server.c_str());
132 if (server_hent == NULL)
133 throw t2n_connect_error(string("can't find server ")+server);
7087e187 134
b604df5f
GE
135 memcpy(&sock_addr.sin_addr,server_hent->h_addr_list[0],sizeof(sock_addr.sin_addr));
136 }
7087e187 137
b604df5f
GE
138 sock = socket(PF_INET, SOCK_STREAM, 0);
139 if (!sock)
140 throw t2n_connect_error(string("socket() error: ")+strerror(errno));
7087e187 141
b604df5f
GE
142 try
143 {
144 connect_with_timeout((struct sockaddr *) &sock_addr,sizeof(sock_addr));
7087e187 145 }
b604df5f 146 catch (t2n_connect_error &e)
7087e187 147 {
b604df5f
GE
148 // recurse if retries left
149 if (max_retries > 0)
e1614a6d
GE
150 {
151 LOGSTREAM(debug,"retrying connect after connect error");
b604df5f 152 tcp_connect(max_retries-1);
e1614a6d
GE
153 }
154 else
fb3345ad 155 throw t2n_connect_error("no more retries left after connect error");
b604df5f 156 }
b604df5f 157}
7087e187 158
487afb79 159/// establish a connection via unix-socket
b604df5f
GE
160void socket_client_connection::unix_connect(int max_retries)
161{
162 struct sockaddr_un unix_addr;
7d9c3eea 163 size_t path_size = path.size();
7087e187 164
b604df5f 165 unix_addr.sun_family = AF_UNIX;
7d9c3eea
PG
166
167 if (path_size >= sizeof(unix_addr.sun_path))
168 {
169 throw t2n_connect_error((std::string)"path '"
170 + path
171 + "' exceeds permissible UNIX socket path length");
172 }
173
174 memcpy(unix_addr.sun_path, path.c_str(), path_size);
175 unix_addr.sun_path[path_size] = '\0';
7087e187 176
b604df5f
GE
177 sock = socket(PF_UNIX, SOCK_STREAM, 0);
178 if (!sock)
179 throw t2n_connect_error(string("socket() error: ")+strerror(errno));
7087e187 180
b604df5f
GE
181 try
182 {
183 connect_with_timeout((struct sockaddr *) &unix_addr, sizeof(unix_addr));
184 }
185 catch (t2n_connect_error &e)
186 {
187 // recurse if retries left
188 if (max_retries > 0)
e1614a6d
GE
189 {
190 LOGSTREAM(debug,"retrying connect after connect error");
5b71156f 191 unix_connect(max_retries-1);
e1614a6d
GE
192 }
193 else
fb3345ad 194 throw t2n_connect_error("no more retries left after connect error");
7087e187 195 }
b604df5f
GE
196}
197
487afb79 198/// execute a connect on a prepared socket (tcp or unix) respecting timeouts
b604df5f
GE
199void socket_client_connection::connect_with_timeout(struct sockaddr *sock_addr,unsigned int sockaddr_size)
200{
7087e187 201 set_socket_options(sock);
91730468 202
039e52da
GE
203 /* non-blocking mode */
204 int flflags;
205 flflags=fcntl(sock,F_GETFL,0);
206 if (flflags < 0)
207 EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
208
209 flflags &= (O_NONBLOCK ^ 0xFFFF);
210 if (fcntl(sock,F_SETFL,flflags) < 0)
211 EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
212
213
e1614a6d 214 LOGSTREAM(debug,"connect_with_timeout()");
b604df5f
GE
215 int ret=::connect(sock,sock_addr, sockaddr_size);
216
217 if (ret < 0)
218 {
219 if (errno==EINPROGRESS)
220 {
e1614a6d
GE
221 LOGSTREAM(debug,"connect_with_timeout(): EINPROGRESS");
222
b604df5f
GE
223 /* set timeout */
224 struct timeval tval;
225 struct timeval *timeout_ptr;
226
227 if (connect_timeout_usec == -1)
228 timeout_ptr = NULL;
229 else
230 {
231 timeout_ptr = &tval;
232
233 // convert timeout from long long usec to int sec + int usec
234 tval.tv_sec = connect_timeout_usec / 1000000;
235 tval.tv_usec = connect_timeout_usec % 1000000;
236 }
237
238 fd_set connect_socket_set;
239 FD_ZERO(&connect_socket_set);
240 FD_SET(sock,&connect_socket_set);
241
242 int ret;
243 while ((ret=select(FD_SETSIZE, NULL, &connect_socket_set, NULL, timeout_ptr)) &&
244 ret < 0 && errno==EINTR);
245
246 if (ret < 0)
247 throw t2n_connect_error(string("connect() error (select): ")+strerror(errno));
248
249 socklen_t sopt=sizeof(int);
250 int valopt;
251 ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &sopt);
252 if (ret < 0 || valopt)
253 throw t2n_connect_error(string("connect() error (getsockopt): ")+strerror(errno));
254 }
255 else
256 throw t2n_connect_error(string("connect() error: ")+strerror(errno));
257 }
e1614a6d
GE
258
259 LOGSTREAM(debug,"connect_with_timeout(): success");
7087e187
GE
260}
261
262void socket_client_connection::close()
263{
264 if (!client_connection::is_closed())
265 {
266 socket_handler::close();
267 client_connection::close();
268 }
269}
270
af84dfb5 271/** @brief try to reconnect the current connection with the same connection credentials (host and port or path)
fb3345ad
GE
272
273 @note will throw an exeption if reconnecting not possible
af84dfb5
GE
274*/
275void socket_client_connection::reconnect()
276{
e1614a6d
GE
277 LOGSTREAM(debug,"reconnect()");
278
af84dfb5
GE
279 // close the current connection if still open
280 close();
281
282 socket_type_value type=get_type();
283
284 if (type == tcp_s)
285 tcp_connect(max_retries);
286 else if (type == unix_s)
287 unix_connect(max_retries);
288
fb3345ad 289 // connection is open now, otherwise an execption would have been thrown
af84dfb5 290 reopen();
e1614a6d
GE
291
292 LOGSTREAM(debug,"reconnect() done, client_connection::is_closed() now " << client_connection::is_closed());
af84dfb5
GE
293}
294
7087e187 295}