Switch time() calls to monotonic clock calls (#7597)
[libt2n] / src / socket_client.cpp
1 /*
2 Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4 The software in this package is distributed under the GNU General
5 Public License version 2 (with a special exception described below).
6
7 A copy of GNU General Public License (GPL) is included in this distribution,
8 in the file COPYING.GPL.
9
10 As a special exception, if other files instantiate templates or use macros
11 or inline functions from this file, or you compile this file and link it
12 with other works to produce a work based on this file, this file
13 does not by itself cause the resulting work to be covered
14 by the GNU General Public License.
15
16 However the source code for this file must still be made available
17 in accordance with section (3) of the GNU General Public License.
18
19 This exception does not invalidate any other reasons why a work based
20 on 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
46 using namespace std;
47
48 namespace libt2n
49 {
50
51 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
52 socket_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
82 socket_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  */
113 socket_client_connection::~socket_client_connection()
114 {
115     // Destructor of socket_handler will close the socket!
116 }
117
118
119 /// establish a connection via tcp
120 void 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
160 void 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
199 void 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
262 void 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 */
275 void 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 }