fca01d144ec3de540fc3d02e50713e8d8a0febbd
[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         // FIXME: Don't call virtual function in constructor. Currently not dangerous but bad design.
70         close();
71     }
72
73     if (!connection::is_closed())
74         do_callbacks(new_connection);
75 }
76
77 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
78 socket_client_connection::socket_client_connection(const std::string& _path,
79             long long _connect_timeout_usec, int _max_retries,
80             std::ostream *_logstream, log_level_values _log_level)
81     : client_connection(), socket_handler(0,unix_s)
82 {
83     max_retries=_max_retries;
84     connect_timeout_usec=_connect_timeout_usec;
85
86     path=_path;
87
88     set_logging(_logstream,_log_level);
89
90     try
91     {
92         unix_connect(max_retries);
93     }
94     catch (t2n_communication_error &e)
95     {
96         lastErrorMsg=e.what();
97         LOGSTREAM(debug,"unix connect error: " << lastErrorMsg);
98         // FIXME: Don't call virtual function in constructor. Currently not dangerous
99         close();
100     }
101
102     if (!connection::is_closed())
103         do_callbacks(new_connection);
104 }
105
106 /**
107  * Destructor. Closes an open connection.
108  */
109 socket_client_connection::~socket_client_connection()
110 {
111     // Destructor of socket_handler will close the socket!
112 }
113
114
115 /// establish a connection via tcp
116 void socket_client_connection::tcp_connect(int max_retries)
117 {
118     struct sockaddr_in sock_addr;
119
120     sock_addr.sin_family = AF_INET;
121     sock_addr.sin_port = htons(port);
122
123     // find the target ip
124     if (inet_aton(server.c_str(),&sock_addr.sin_addr)==0)
125     {
126         struct hostent *server_hent;
127         server_hent=gethostbyname(server.c_str());
128         if (server_hent == NULL)
129             throw t2n_connect_error(string("can't find server ")+server);
130
131         memcpy(&sock_addr.sin_addr,server_hent->h_addr_list[0],sizeof(sock_addr.sin_addr));
132     }
133
134     sock = socket(PF_INET, SOCK_STREAM, 0);
135     if (!sock)
136         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
137
138     try
139     {
140         connect_with_timeout((struct sockaddr *) &sock_addr,sizeof(sock_addr));
141     }
142     catch (t2n_connect_error &e)
143     {
144         // recurse if retries left
145         if (max_retries > 0)
146         {
147             LOGSTREAM(debug,"retrying connect after connect error");
148             tcp_connect(max_retries-1);
149         }
150         else
151             throw t2n_connect_error("no more retries left after connect error");
152     }
153 }
154
155 /// establish a connection via unix-socket
156 void socket_client_connection::unix_connect(int max_retries)
157 {
158     struct sockaddr_un unix_addr;
159
160     unix_addr.sun_family = AF_UNIX;
161     strcpy (unix_addr.sun_path, path.c_str());
162
163     sock = socket(PF_UNIX, SOCK_STREAM, 0);
164     if (!sock)
165         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
166
167     try
168     {
169         connect_with_timeout((struct sockaddr *) &unix_addr, sizeof(unix_addr));
170     }
171     catch (t2n_connect_error &e)
172     {
173         // recurse if retries left
174         if (max_retries > 0)
175         {
176             LOGSTREAM(debug,"retrying connect after connect error");
177             unix_connect(max_retries-1);
178         }
179         else
180             throw t2n_connect_error("no more retries left after connect error");
181     }
182 }
183
184 /// execute a connect on a prepared socket (tcp or unix) respecting timeouts
185 void socket_client_connection::connect_with_timeout(struct sockaddr *sock_addr,unsigned int sockaddr_size)
186 {
187     set_socket_options(sock);
188
189    /* non-blocking mode */
190     int flflags;
191     flflags=fcntl(sock,F_GETFL,0);
192     if (flflags < 0)
193         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
194
195     flflags &= (O_NONBLOCK ^ 0xFFFF);
196     if (fcntl(sock,F_SETFL,flflags) < 0)
197         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
198
199
200     LOGSTREAM(debug,"connect_with_timeout()");
201     int ret=::connect(sock,sock_addr, sockaddr_size);
202
203     if (ret < 0)
204     {
205         if (errno==EINPROGRESS)
206         {
207             LOGSTREAM(debug,"connect_with_timeout(): EINPROGRESS");
208
209             /* set timeout */
210             struct timeval tval;
211             struct timeval *timeout_ptr;
212
213             if (connect_timeout_usec == -1)
214                 timeout_ptr = NULL;
215             else
216             {
217                 timeout_ptr = &tval;
218
219                 // convert timeout from long long usec to int sec + int usec
220                 tval.tv_sec = connect_timeout_usec / 1000000;
221                 tval.tv_usec = connect_timeout_usec % 1000000;
222             }
223
224             fd_set connect_socket_set;
225             FD_ZERO(&connect_socket_set);
226             FD_SET(sock,&connect_socket_set);
227
228             int ret;
229             while ((ret=select(FD_SETSIZE, NULL, &connect_socket_set, NULL, timeout_ptr)) &&
230                     ret < 0 && errno==EINTR);
231
232             if (ret < 0)
233                 throw t2n_connect_error(string("connect() error (select): ")+strerror(errno));
234
235             socklen_t sopt=sizeof(int);
236             int valopt;
237             ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &sopt);
238             if (ret < 0 || valopt)
239                 throw t2n_connect_error(string("connect() error (getsockopt): ")+strerror(errno));
240         }
241         else
242             throw t2n_connect_error(string("connect() error: ")+strerror(errno));
243     }
244
245     LOGSTREAM(debug,"connect_with_timeout(): success");
246 }
247
248 void socket_client_connection::close()
249 {
250     if (!client_connection::is_closed())
251     {
252         socket_handler::close();
253         client_connection::close();
254     }
255 }
256
257 /** @brief try to reconnect the current connection with the same connection credentials (host and port or path)
258
259     @note will throw an exeption if reconnecting not possible
260 */
261 void socket_client_connection::reconnect()
262 {
263     LOGSTREAM(debug,"reconnect()");
264
265     // close the current connection if still open
266     close();
267
268     socket_type_value type=get_type();
269
270     if (type == tcp_s)
271         tcp_connect(max_retries);
272     else if (type == unix_s)
273         unix_connect(max_retries);
274
275     // connection is open now, otherwise an execption would have been thrown
276     reopen();
277
278     LOGSTREAM(debug,"reconnect() done, client_connection::is_closed() now " << client_connection::is_closed());
279 }
280
281 }