dol: initial dol commit
[jump.git] / dol / src / dol / visitor / hdsd / scd / scd_socket.cpp
1 #include "scd_socket.h"
2
3 #include <sys/socket.h>
4 #include <netinet/tcp.h> // for TCP_NODELAY
5 #include <cerrno>
6 #include <fcntl.h>
7 #include <cassert>
8
9 #include "scd_logging.h"
10 #include "scd_exception.h"
11
12 scd_socket::scd_socket()
13 {
14     _socket = -1;
15     _is_connecting = false;
16     _is_connected = false;
17 }
18
19 scd_socket::~scd_socket() {}
20
21 bool scd_socket::is_valid() const
22 {
23     return (_socket != -1);
24 }
25
26 bool scd_socket::create()
27 {
28     // check if socket already created before
29     if ( is_valid() )
30     {
31         return false;
32     }
33
34     // create TCP socket
35     _socket = socket(PF_INET, SOCK_STREAM, 0);
36
37     assert(is_valid());
38     
39     _set_blocking(false);
40
41     _is_connecting = false;
42     _is_connected = false;
43
44     return true;
45
46 } // create()
47
48 void scd_socket::close()
49 {
50     if ( is_valid() )
51     {
52         ::close(_socket);
53     }
54
55     _socket = -1;
56     _is_connecting = false;
57     _is_connected = false;
58 }
59
60 bool scd_socket::bind(const std::string &loc_name, const uint16_t loc_port)
61 {
62     int ret;
63
64     if ( ! is_valid() )
65     {
66         return false;
67     }
68     
69     // prepare the local address
70     sockaddr_in loc_addr;
71     if ( !_get_sockaddr(loc_addr, loc_name, loc_port) )
72     {
73         return false;
74     }
75
76     // set socket reusable (to recreate listener without timeout)
77     int on = 1;
78     ret = setsockopt( _socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
79     if (ret == -1)
80     {
81         throw scd_exception("setsockopt()", errno);
82     }
83
84     ret = ::bind( _socket, reinterpret_cast<struct sockaddr*>(&loc_addr),
85                  sizeof( loc_addr ) );
86
87     if ( ret == -1 )
88     {
89         switch(errno)
90         {
91         case EACCES:
92             scd_error("must be root to bind to desired port");
93             break;
94         case EADDRINUSE:
95             scd_error("unable to bind as the address is already in use");
96             break;
97         default:
98             throw scd_exception("bind()", errno);
99         }
100         return false;
101     }
102
103     return true;
104
105 } // bind()
106
107
108 bool scd_socket::bind(const uint16_t loc_port)
109 {
110     return bind("", loc_port);
111 } // bind()
112
113
114 bool scd_socket::listen()
115 {
116     int ret;
117
118     if ( !is_valid() )
119     {
120         return false;
121     }
122
123     ret = ::listen(_socket, SCD_MAXCONN);
124
125     if (ret == -1)
126     {
127         throw scd_exception("listen()", errno);
128     }
129
130     return true;
131 } // listen()
132
133 bool scd_socket::accept(scd_socket &new_sock)
134 {
135     int ret;
136
137     if ( !is_valid() )
138     {
139         return false;
140     }
141
142     ret = ::accept(_socket, NULL, NULL);
143
144     if (ret < 0)
145     {
146         switch (errno)
147         {
148         case EAGAIN:
149         case ECONNABORTED:
150         case EINTR:
151         case EPROTO:
152             return false;
153             break;
154         default:
155             throw scd_exception("accept()", errno);
156             break;
157         }
158     }
159     else
160     {
161         new_sock._is_connected = true;
162         new_sock._socket = ret;
163         new_sock._set_blocking(false);
164         #ifdef TCP_NODELAY
165         new_sock._set_nodelay();
166         #endif
167     }
168
169     return true;
170
171 } // accept()
172
173
174 bool scd_socket::connect(const std::string &rem_name, const uint16_t rem_port)
175 {
176     int ret;
177
178     if ( !is_valid() || _is_connecting || _is_connected )
179         return false;
180
181     sockaddr_in rem_addr;
182     if ( !_get_sockaddr(rem_addr, rem_name, rem_port) )
183         return false;
184
185     ret = ::connect(_socket, reinterpret_cast<const sockaddr*>(&rem_addr),
186             sizeof(rem_addr));
187
188     if (ret == -1)
189     {
190         switch(errno)
191         {
192         case EINPROGRESS:
193             _is_connecting = true;
194             break;
195         case ECONNABORTED:
196         case ECONNREFUSED:
197         case EINTR:
198         case ENETUNREACH:
199         case ETIMEDOUT:
200             break;
201         case EALREADY: // should be prevented by _is_connecting
202         default:
203             throw scd_exception("connect()", errno);
204             break;
205         }
206     }
207     else // ret == 0
208     {
209         _is_connected = true;
210         _is_connecting = false;
211     }
212
213     return true;
214
215 } // connect()
216
217 bool scd_socket::is_connecting()
218 {
219     if ( !is_valid() || !_is_connecting || _is_connected )
220         return false;
221
222     return true;
223
224 } // is_connecting()
225
226
227 bool scd_socket::is_connected()
228 {
229     if ( !is_valid() || _is_connecting || !_is_connected)
230         return false;
231
232     return true;
233
234 } // is_connecting()
235
236
237 bool scd_socket::connected_event()
238 {
239     if (_is_connected)
240         return true;
241
242     if ( !is_valid() )
243     {
244         scd_warn("write event occured on an invalid socket");
245         return false;
246     }
247     else if ( !_is_connecting )
248     {
249         scd_warn("checking connected event while not connecting");
250         return false;
251     }
252
253     // get possible errors from connection attempt
254     int err, ret;
255     socklen_t err_size = sizeof(err);
256     ret = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &err, &err_size);
257
258     if (ret == -1)
259     {
260         throw scd_exception("getsockopt()", errno);
261     }
262
263     // handle errors
264     switch(err)
265     {
266     case 0:
267         // connection established
268         _is_connecting = false;
269         _is_connected = true;
270         #ifdef TCP_NODELAY
271         _set_nodelay();
272         #endif
273         return true;
274         break;
275     case ECONNABORTED:
276     case ECONNREFUSED:
277     case EINTR:
278     case ENETUNREACH:
279     case ETIMEDOUT:
280         // connection attempt failed
281         _is_connecting = false;
282         return false;
283         break;
284     case EINPROGRESS:
285         // should not be possible as we received a completion event
286         throw scd_exception("checking connected event while still connecting");
287         break;
288     case EALREADY: // should be prevented by _is_connecting
289     default:
290         throw scd_exception("connect()", errno);
291         break;
292     }
293     
294     // not reached
295 } //connected_event()
296
297
298 size_t scd_socket::send(const void* buf, size_t len)
299 {
300     ssize_t ret;
301
302     if ( !is_valid() || len == 0 || !_is_connected )
303         return 0;
304
305     ret = ::send(_socket, buf, len, MSG_NOSIGNAL);
306
307     if (ret < 0)
308     {
309         switch(errno)
310         {
311         case ECONNRESET:
312         case EPIPE:
313         case ETIMEDOUT:
314             close();
315             break;
316         case EWOULDBLOCK:
317             break;
318         default:
319             throw scd_exception("send()", errno);
320             break;
321         }
322         return 0;
323     }
324     else
325         return ret;
326
327 } // send()
328
329 size_t scd_socket::recv(void* buf, size_t len)
330 {
331     ssize_t ret;
332
333     if ( !is_valid() || len == 0 || !_is_connected)
334         return 0;
335
336     ret = ::recv(_socket, buf, len, MSG_NOSIGNAL);
337
338     if (ret < 0)
339     {
340         switch(errno)
341         {
342         case ETIMEDOUT:
343             close();
344             break;
345         case EAGAIN:
346         case EINTR:
347             break;
348         case ENOTCONN:
349         default:
350             throw scd_exception("recv()", errno);
351             break;
352         }
353         return 0;
354     }
355     else if (ret == 0)
356     {
357         close();
358         return 0;
359     }
360     else
361         return ret;
362
363 } // recv()
364
365 bool scd_socket::_get_sockaddr(sockaddr_in &addr, const std::string &name,
366             const uint16_t port) const
367 {
368     // flush addr
369     memset( &addr, 0, sizeof(addr) );
370
371     /* set content */
372     addr.sin_family = AF_INET;
373     addr.sin_port = htons( port );
374     if ( name.empty() )
375     {
376         // bind to all local addresses
377         addr.sin_addr.s_addr = INADDR_ANY;
378     }
379     else
380     {
381         // bint to specified address only
382         struct hostent* he;
383
384         // resolve name
385         he = gethostbyname2(&name[0], AF_INET);
386         if (he == NULL)
387             return false;
388
389         // copy address (network order)
390         addr.sin_addr.s_addr =
391                reinterpret_cast<struct in_addr*>(he->h_addr_list[0])->s_addr;
392     }
393
394     return true;
395
396 } // _get_sockaddr()
397
398
399 bool scd_socket::_set_blocking(const bool mode)
400 {
401     if ( !is_valid() )
402     {
403         return false;
404     }
405
406     int opts = fcntl( _socket, F_GETFL );
407
408     if ( opts < 0 )
409     {
410         throw scd_exception("fcntl()", errno);
411     }
412
413     if ( !mode )
414         opts = ( opts | O_NONBLOCK );
415     else
416         opts = ( opts & ~O_NONBLOCK );
417
418     fcntl( _socket, F_SETFL,opts );
419
420     return true;
421
422 } //_set_blocking()
423
424
425 bool scd_socket::_set_nodelay()
426 {
427     if ( !is_valid())
428         return false;
429
430     // disable Nagle algorithm
431     int on = 1;
432     int ret = setsockopt( _socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) );
433     if (ret == -1)
434     {
435         throw scd_exception("setsockopt()", errno);
436     }
437
438     return true;
439 }