Adding DOCUMENTATION flag to the CMakeLists.txt
[libt2n] / src / socket_client.cpp
... / ...
CommitLineData
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*/
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>
36
37#include <pwd.h>
38#include <grp.h>
39
40#include <sstream>
41
42#include "socket_client.hxx"
43#include "t2n_exception.hxx"
44#include "log.hxx"
45
46using namespace std;
47
48namespace libt2n
49{
50
51/// returns a closed connection if connection could not be established, call get_last_error_msg() for details
52socket_client_connection::socket_client_connection(int _port, const std::string& _server,
53 long long _connect_timeout_usec, int _max_retries,
54 std::ostream *_logstream, log_level_values _log_level)
55 : client_connection(), socket_handler(0,tcp_s)
56{
57 max_retries=_max_retries;
58 connect_timeout_usec=_connect_timeout_usec;
59
60 server=_server;
61 port=_port;
62
63 set_logging(_logstream,_log_level);
64
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);
73 // FIXME: Don't call virtual function in constructor. Currently not dangerous but bad design.
74 close();
75 }
76
77 if (!connection::is_closed())
78 do_callbacks(new_connection);
79}
80
81/// returns a closed connection if connection could not be established, call get_last_error_msg() for details
82socket_client_connection::socket_client_connection(const std::string& _path,
83 long long _connect_timeout_usec, int _max_retries,
84 std::ostream *_logstream, log_level_values _log_level)
85 : client_connection(), socket_handler(0,unix_s)
86{
87 max_retries=_max_retries;
88 connect_timeout_usec=_connect_timeout_usec;
89
90 path=_path;
91
92 set_logging(_logstream,_log_level);
93
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);
102 // FIXME: Don't call virtual function in constructor. Currently not dangerous
103 close();
104 }
105
106 if (!connection::is_closed())
107 do_callbacks(new_connection);
108}
109
110/**
111 * Destructor. Closes an open connection.
112 */
113socket_client_connection::~socket_client_connection()
114{
115 // Destructor of socket_handler will close the socket!
116}
117
118
119/// establish a connection via tcp
120void socket_client_connection::tcp_connect(int max_retries)
121{
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)
129 {
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);
134
135 memcpy(&sock_addr.sin_addr,server_hent->h_addr_list[0],sizeof(sock_addr.sin_addr));
136 }
137
138 sock = socket(PF_INET, SOCK_STREAM, 0);
139 if (!sock)
140 throw t2n_connect_error(string("socket() error: ")+strerror(errno));
141
142 try
143 {
144 connect_with_timeout((struct sockaddr *) &sock_addr,sizeof(sock_addr));
145 }
146 catch (t2n_connect_error &e)
147 {
148 // recurse if retries left
149 if (max_retries > 0)
150 {
151 LOGSTREAM(debug,"retrying connect after connect error");
152 tcp_connect(max_retries-1);
153 }
154 else
155 throw t2n_connect_error("no more retries left after connect error");
156 }
157}
158
159/// establish a connection via unix-socket
160void socket_client_connection::unix_connect(int max_retries)
161{
162 struct sockaddr_un unix_addr;
163 size_t path_size = path.size();
164
165 unix_addr.sun_family = AF_UNIX;
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';
176
177 sock = socket(PF_UNIX, SOCK_STREAM, 0);
178 if (!sock)
179 throw t2n_connect_error(string("socket() error: ")+strerror(errno));
180
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)
189 {
190 LOGSTREAM(debug,"retrying connect after connect error");
191 unix_connect(max_retries-1);
192 }
193 else
194 throw t2n_connect_error("no more retries left after connect error");
195 }
196}
197
198/// execute a connect on a prepared socket (tcp or unix) respecting timeouts
199void socket_client_connection::connect_with_timeout(struct sockaddr *sock_addr,unsigned int sockaddr_size)
200{
201 set_socket_options(sock);
202
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
214 LOGSTREAM(debug,"connect_with_timeout()");
215 int ret=::connect(sock,sock_addr, sockaddr_size);
216
217 if (ret < 0)
218 {
219 if (errno==EINPROGRESS)
220 {
221 LOGSTREAM(debug,"connect_with_timeout(): EINPROGRESS");
222
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 }
258
259 LOGSTREAM(debug,"connect_with_timeout(): success");
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
271/** @brief try to reconnect the current connection with the same connection credentials (host and port or path)
272
273 @note will throw an exeption if reconnecting not possible
274*/
275void socket_client_connection::reconnect()
276{
277 LOGSTREAM(debug,"reconnect()");
278
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
289 // connection is open now, otherwise an execption would have been thrown
290 reopen();
291
292 LOGSTREAM(debug,"reconnect() done, client_connection::is_closed() now " << client_connection::is_closed());
293}
294
295}