d8dc413aeae469529ab80a35e43b2288adfb9875
[libt2n] / src / socket_client.cpp
1 /***************************************************************************
2  *   Copyright (C) 2006 by Gerd v. Egidy                                   *
3  *   gve@intra2net.com                                                     *
4  *                                                                         *
5  *   This library is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU Lesser General Public License version   *
7  *   2.1 as published by the Free Software Foundation.                     *
8  *                                                                         *
9  *   This library is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU Lesser General Public License for more details.                   *
13  *                                                                         *
14  *   You should have received a copy of the GNU Lesser General Public      *
15  *   License along with this program; if not, write to the                 *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19
20 #include <stdio.h>
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <sys/time.h>
28 #include <arpa/inet.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <fcntl.h>
32 #include <time.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #include <sstream>
37
38 #include "socket_client.hxx"
39 #include "t2n_exception.hxx"
40 #include "log.hxx"
41
42 using namespace std;
43
44 namespace libt2n
45 {
46
47 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
48 socket_client_connection::socket_client_connection(int _port, const std::string& _server, 
49             long long _connect_timeout_usec, int _max_retries,
50             std::ostream *_logstream, log_level_values _log_level)
51     : client_connection(), socket_handler(0,tcp_s)
52 {
53     max_retries=_max_retries;
54     connect_timeout_usec=_connect_timeout_usec;
55
56     server=_server;
57     port=_port;
58
59     set_logging(_logstream,_log_level);
60
61     try
62     {
63         tcp_connect(max_retries);
64     }
65     catch (t2n_communication_error &e)
66     {
67         lastErrorMsg=e.what();
68         LOGSTREAM(debug,"tcp connect error: " << lastErrorMsg);
69         close();
70     }
71
72     if (!connection::is_closed())
73         do_callbacks(new_connection);
74 }
75
76 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
77 socket_client_connection::socket_client_connection(const std::string& _path,
78             long long _connect_timeout_usec, int _max_retries,
79             std::ostream *_logstream, log_level_values _log_level)
80     : client_connection(), socket_handler(0,unix_s)
81 {
82     max_retries=_max_retries;
83     connect_timeout_usec=_connect_timeout_usec;
84
85     path=_path;
86
87     set_logging(_logstream,_log_level);
88
89     try
90     {
91         unix_connect(max_retries);
92     }
93     catch (t2n_communication_error &e)
94     {
95         lastErrorMsg=e.what();
96         LOGSTREAM(debug,"unix connect error: " << lastErrorMsg);
97         // FIXME: Calls virtual function close in constructor
98         close();
99     }
100
101     if (!connection::is_closed())
102         do_callbacks(new_connection);
103 }
104
105 /**
106  * Destructor. Closes an open connection.
107  */
108 socket_client_connection::~socket_client_connection()
109 {
110     close();
111 }
112
113
114 /// establish a connection via tcp
115 void socket_client_connection::tcp_connect(int max_retries)
116 {
117     struct sockaddr_in sock_addr;
118
119     sock_addr.sin_family = AF_INET;
120     sock_addr.sin_port = htons(port);
121
122     // find the target ip
123     if (inet_aton(server.c_str(),&sock_addr.sin_addr)==0)
124     {
125         struct hostent *server_hent;
126         server_hent=gethostbyname(server.c_str());
127         if (server_hent == NULL)
128             throw t2n_connect_error(string("can't find server ")+server);
129
130         memcpy(&sock_addr.sin_addr,server_hent->h_addr_list[0],sizeof(sock_addr.sin_addr));
131     }
132
133     sock = socket(PF_INET, SOCK_STREAM, 0);
134     if (!sock)
135         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
136
137     try
138     {
139         connect_with_timeout((struct sockaddr *) &sock_addr,sizeof(sock_addr));
140     }
141     catch (t2n_connect_error &e)
142     {
143         // recurse if retries left
144         if (max_retries > 0)
145         {
146             LOGSTREAM(debug,"retrying connect after connect error");
147             tcp_connect(max_retries-1);
148         }
149         else
150             throw t2n_connect_error("no more retries left after connect error");
151     }
152 }
153
154 /// establish a connection via unix-socket
155 void socket_client_connection::unix_connect(int max_retries)
156 {
157     struct sockaddr_un unix_addr;
158
159     unix_addr.sun_family = AF_UNIX;
160     strcpy (unix_addr.sun_path, path.c_str());
161
162     sock = socket(PF_UNIX, SOCK_STREAM, 0);
163     if (!sock)
164         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
165
166     try
167     {
168         connect_with_timeout((struct sockaddr *) &unix_addr, sizeof(unix_addr));
169     }
170     catch (t2n_connect_error &e)
171     {
172         // recurse if retries left
173         if (max_retries > 0)
174         {
175             LOGSTREAM(debug,"retrying connect after connect error");
176             unix_connect(max_retries-1);
177         }
178         else
179             throw t2n_connect_error("no more retries left after connect error");
180     }
181 }
182
183 /// execute a connect on a prepared socket (tcp or unix) respecting timeouts
184 void socket_client_connection::connect_with_timeout(struct sockaddr *sock_addr,unsigned int sockaddr_size)
185 {
186     set_socket_options(sock);
187
188    /* non-blocking mode */
189     int flflags;
190     flflags=fcntl(sock,F_GETFL,0);
191     if (flflags < 0)
192         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
193
194     flflags &= (O_NONBLOCK ^ 0xFFFF);
195     if (fcntl(sock,F_SETFL,flflags) < 0)
196         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
197
198
199     LOGSTREAM(debug,"connect_with_timeout()");
200     int ret=::connect(sock,sock_addr, sockaddr_size);
201
202     if (ret < 0)
203     {
204         if (errno==EINPROGRESS)
205         {
206             LOGSTREAM(debug,"connect_with_timeout(): EINPROGRESS");
207
208             /* set timeout */
209             struct timeval tval;
210             struct timeval *timeout_ptr;
211
212             if (connect_timeout_usec == -1)
213                 timeout_ptr = NULL;
214             else
215             {
216                 timeout_ptr = &tval;
217
218                 // convert timeout from long long usec to int sec + int usec
219                 tval.tv_sec = connect_timeout_usec / 1000000;
220                 tval.tv_usec = connect_timeout_usec % 1000000;
221             }
222
223             fd_set connect_socket_set;
224             FD_ZERO(&connect_socket_set);
225             FD_SET(sock,&connect_socket_set);
226
227             int ret;
228             while ((ret=select(FD_SETSIZE, NULL, &connect_socket_set, NULL, timeout_ptr)) &&
229                     ret < 0 && errno==EINTR);
230
231             if (ret < 0)
232                 throw t2n_connect_error(string("connect() error (select): ")+strerror(errno));
233
234             socklen_t sopt=sizeof(int);
235             int valopt;
236             ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &sopt);
237             if (ret < 0 || valopt)
238                 throw t2n_connect_error(string("connect() error (getsockopt): ")+strerror(errno));
239         }
240         else
241             throw t2n_connect_error(string("connect() error: ")+strerror(errno));
242     }
243
244     LOGSTREAM(debug,"connect_with_timeout(): success");
245 }
246
247 void socket_client_connection::close()
248 {
249     if (!client_connection::is_closed())
250     {
251         socket_handler::close();
252         client_connection::close();
253     }
254 }
255
256 /** @brief try to reconnect the current connection with the same connection credentials (host and port or path)
257
258     @note will throw an exeption if reconnecting not possible
259 */
260 void socket_client_connection::reconnect()
261 {
262     LOGSTREAM(debug,"reconnect()");
263
264     // close the current connection if still open
265     close();
266
267     socket_type_value type=get_type();
268
269     if (type == tcp_s)
270         tcp_connect(max_retries);
271     else if (type == unix_s)
272         unix_connect(max_retries);
273
274     // connection is open now, otherwise an execption would have been thrown
275     reopen();
276
277     LOGSTREAM(debug,"reconnect() done, client_connection::is_closed() now " << client_connection::is_closed());
278 }
279
280 }