inet.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Internet connection handling
4  *
5  * Copyright (c) 2015-2022 by the developers. See the LICENSE file for details.
6  *
7  * If nothing else is specified, functions return zero to indicate success
8  * and a negative value to indicate an error.
9  */
10 
11 
12 /* ========================================================================== */
13 /* Include headers */
14 
15 #include "posix.h" /* Include this first because of feature test macros */
16 
17 #include <string.h>
18 
19 #include "config.h"
20 #include "inet.h"
21 #include "main.h"
22 
23 
24 /* ========================================================================== */
25 /*! \defgroup INET INET: Internet protocol
26  *
27  * Internet name resolution, socket and connection handling.
28  */
29 /*! @{ */
30 
31 
32 /* ========================================================================== */
33 /* Constants */
34 
35 /*! \brief Message prefix for INET module */
36 #define MAIN_ERR_PREFIX "INET: "
37 
38 /*! \name Socket option actions */
39 /*! @{ */
40 #define INET_OPTS_CLEAR 0 /*!< \brief Remove socket options */
41 #define INET_OPTS_SET 1 /*!< \brief Add socket options */
42 /*! @} */
43 
44 
45 /* ========================================================================== */
46 /* Variables */
47 
48 int inet_force_ipv4 = 0;
49 
50 
51 /* ========================================================================== */
52 /* Host and service name resolver
53  *
54  * \param[in] host Name of host
55  * \param[in] service Name of service
56  * \param[in] af Address family
57  * \param[out] sai Pointer to socket address information
58  *
59  * The parameter \e af should be set to \c POSIX_AF_UNSPEC except if only
60  * results for a specific address family should be requested.
61  *
62  * \note
63  * The data at the location pointed to by sai is undefined after error status
64  * is returned.
65  *
66  * \note
67  * On success the caller is responsible to free the ressources allocated by this
68  * function using \c posix_freeaddrinfo() .
69  *
70  * \return
71  * - Zero if connection was successfully established
72  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
73  */
74 
75 static int inet_name_resolver(const char* host, const char* service,
76  int af, struct_posix_addrinfo** sai)
77 {
78  int res = 0;
79  struct_posix_addrinfo hints;
80  int rv;
81  const char* err_str;
82  char* ebuf = NULL;
83  size_t len;
84 
85  memset((void*) &hints, 0, sizeof(hints));
86  /* Only return results for address families configured on local system */
87  hints.ai_flags = POSIX_AI_ADDRCONFIG;
88  hints.ai_family = af;
89  hints.ai_socktype = POSIX_SOCK_STREAM;
90  hints.ai_protocol = 0;
91  rv = posix_getaddrinfo(host, service, &hints, sai);
92  if(rv)
93  {
94  PRINT_ERROR("getaddrinfo() failed");
95  err_str = posix_gai_strerror(rv);
96  if(NULL != err_str)
97  {
98  len = strlen(MAIN_ERR_PREFIX);
99  len += strlen(err_str);
100  ebuf = (char*) posix_malloc(++len);
101  if(NULL != ebuf)
102  {
103  strcpy(ebuf, MAIN_ERR_PREFIX);
104  strcat(ebuf, err_str);
105  print_error(ebuf);
106  }
107  posix_free((void*) ebuf);
108  }
109  switch(rv)
110  {
111  case POSIX_EAI_FAMILY: { res = INET_ERR_AF; break; }
112  case POSIX_EAI_NONAME: { res = INET_ERR_HNR; break; }
113  case POSIX_EAI_SERVICE: { res = INET_ERR_SNR; break; }
114  default: { res = INET_ERR_UNSPEC; break; }
115  }
116  }
117 
118  return(res);
119 }
120 
121 
122 /* ========================================================================== */
123 /* Create socket
124  *
125  * \param[out] sd Pointer to socket descriptor
126  * \param[in] af Address family
127  *
128  * The value for \e af must be \c POSIX_INET or \c POSIX_INET6 respectively.
129  *
130  * \note
131  * The value -1 is written to the location pointed to by \e sd on error.
132  *
133  * \return
134  * - Zero if socket was successfully created
135  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
136  */
137 
138 static int inet_socket_create(int* sd, int af)
139 {
140  int res = 0;
141  int rv;
142 
143  if(POSIX_AF_INET != af
144 #if CFG_USE_IP6
145  && POSIX_AF_INET6 != af
146 #endif /* CFG_USE_IP6 */
147  )
148  {
149  PRINT_ERROR("socket(): Address family not supported");
150  res = INET_ERR_AF;
151  }
152  else
153  {
154  *sd = posix_socket(af, POSIX_SOCK_STREAM, 0);
155  if(0 > *sd)
156  {
157  PRINT_ERROR("socket() failed");
158  *sd = -1;
159  res = INET_ERR_SOCK;
160  }
161  else
162  {
163  /* Set "Close on exec()" flag */
164  do { rv = posix_fcntl(*sd, POSIX_F_SETFD, POSIX_FD_CLOEXEC); }
165  while(-1 == rv && POSIX_EINTR == posix_errno);
166  if(-1 == rv)
167  {
168  PRINT_ERROR("fcntl() failed");
169  inet_close(sd);
170  res = INET_ERR_SOCK;
171  }
172  }
173  }
174 
175  return(res);
176 }
177 
178 
179 #if CFG_USE_CONNECT_TIMEOUT
180 /* ========================================================================== */
181 /* Configure socket options
182  *
183  * \param[in] sd Socket descriptor
184  * \param[in] action Socket option action (use \c INET_OPTION_xxx constants)
185  * \param[in] opts Socket options
186  *
187  * \return
188  * - Zero if socket was successfully configured
189  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
190  */
191 
192 static int inet_socket_options(int sd, int action, int opts)
193 {
194  int res = 0;
195  int rv;
196  int opts_new;
197 
198  /* Get socket options */
199  do { rv = posix_fcntl(sd, POSIX_F_GETFL); }
200  while(-1 == rv && POSIX_EINTR == posix_errno);
201  if(-1 == rv)
202  {
203  PRINT_ERROR("fcntl() failed");
204  res = INET_ERR_SOCK;
205  }
206 
207  if (!res)
208  {
209  /* Modify socket options */
210  if (INET_OPTS_SET == action) { opts_new = rv | opts; }
211  else { opts_new = rv & ~opts; }
212 
213  /* Set socket options */
214  do { rv = posix_fcntl(sd, POSIX_F_SETFL, opts_new); }
215  while(-1 == rv && POSIX_EINTR == posix_errno);
216  if(-1 == rv)
217  {
218  PRINT_ERROR("fcntl() failed");
219  res = INET_ERR_SOCK;
220  }
221  }
222 
223  return(res);
224 }
225 #endif
226 
227 
228 /* ========================================================================== */
229 /*! \brief Establish stream oriented connection
230  *
231  * \param[out] sd Pointer to socket descriptor
232  * \param[in,out] af Address family
233  * \param[in] host Name of host
234  * \param[in] service Name of service
235  *
236  * On success the socket descriptor of the established connection is written to
237  * the location pointed to by \e sd . If the address family was specified as
238  * \c POSIX_AF_UNSPEC or was enforced by configuration, it is overwritten with
239  * the address family that was used to establish the connection.
240  *
241  * On error -1 is written to the location pointed to by \e sd and the value
242  * pointed to by \e af is unchanged.
243  *
244  * \note
245  * If the name resolver reports multiple addresses for \e host , all entries are
246  * tried in order. If no connection could be established, error status is
247  * returned for the last entry in the list.
248  *
249  * \return
250  * - Zero if connection was successfully established
251  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
252  */
253 
254 int inet_connect(int* sd, int* af, const char* host, const char* service)
255 {
256  int res;
257  int rv;
258  struct_posix_addrinfo* sai;
259  struct_posix_addrinfo* saie;
260  int family;
261  int socket;
262  struct_posix_pollfd fds[1];
263  posix_socklen_t opt_len;
264 #if CFG_USE_CONNECT_TIMEOUT
265  posix_time_t ts1, ts2, ts_diff;
266 #endif
267  int timeout;
268  int timed_out = 0;
269  int refused = 0;
270 
271  *sd = -1;
272 
273  /* Override address family on request */
274  if(inet_force_ipv4) { *af = POSIX_AF_INET; }
275 
276  /* Resolve host and service names */
277  res = inet_name_resolver(host, service, *af, &sai);
278  if(!res)
279  {
280  for(saie = sai; saie != NULL; saie = saie->ai_next)
281  {
282  /* Create socket */
283  family = saie->ai_family;
284  res = inet_socket_create(&socket, family);
285 #if CFG_USE_CONNECT_TIMEOUT
286  if(!res)
287  {
288  /* Configure socket to nonblocking mode */
289  res = inet_socket_options(socket, INET_OPTS_SET, POSIX_O_NONBLOCK);
290  /* Store timestamp */
291  ts1 = posix_time(NULL);
292  if((posix_time_t) 0 > ts1) { ts1 = 0; }
293  }
294 #endif
295  if(!res)
296  {
297  /* Try to establish connection */
298  refused = 0;
299  timed_out = 0;
300  rv = posix_connect(socket, saie->ai_addr, saie->ai_addrlen);
301  if(rv)
302  {
303  if(POSIX_ECONNREFUSED == posix_errno) { refused = 1; }
304  res = INET_ERR_CONN;
305  }
306  if(-1 == rv &&
307  (POSIX_EINPROGRESS == posix_errno || POSIX_EINTR == posix_errno))
308  {
309  /*
310  * Interrupted by signal (or nonblocking mode)
311  * => Connection is established asynchronously
312  */
313  do
314  {
315  timeout = -1;
316 #if CFG_USE_CONNECT_TIMEOUT
317  timeout = 500; /* Milliseconds */
318  /* Timeout will be aborted/restartet by signals */
319  ts2 = posix_time(NULL);
320  if((posix_time_t) 0 > ts2) { ts2 = 0; }
321  ts_diff = ts2 - ts1;
322  if ((float) CFG_USE_CONNECT_TIMEOUT < (float) ts_diff)
323  {
324  rv = 0;
325  break;
326  }
327 #endif
328  fds[0].fd = socket;
329  fds[0].events = POSIX_POLLOUT;
330  fds[0].revents = 0;
331  rv = posix_poll(fds, 1, timeout);
332  }
333  while(0 == rv || (-1 == rv && POSIX_EINTR == posix_errno));
334  if(0 == rv) { timed_out = 1; }
335  else if(1 == rv)
336  {
337  /* Get return value of 'connect()' */
338  opt_len = sizeof(int);
339  rv = posix_getsockopt(socket, POSIX_SOL_SOCKET,
340  POSIX_SO_ERROR, &res, &opt_len);
341  if(-1 == rv || res) { res = INET_ERR_CONN; }
342  }
343  }
344  }
345 #if CFG_USE_CONNECT_TIMEOUT
346  if (!res)
347  {
348  /* Configure socket to blocking mode */
349  res = inet_socket_options(socket, INET_OPTS_CLEAR,
350  POSIX_O_NONBLOCK);
351  }
352 #endif
353 
354  /* Check for error */
355  if(res)
356  {
357  if(refused) { PRINT_ERROR("Connection refused"); }
358  else if(timed_out)
359  {
360  PRINT_ERROR("User defined TCP connection timeout");
361  }
362  else { PRINT_ERROR("connect() failed"); }
363  inet_close(&socket);
364  /* Try next entry in linked list */
365  }
366  else
367  {
368  /* Connection established */
369  if(POSIX_AF_INET == family)
370  {
371  printf("%s: %s%s\n", CFG_NAME, MAIN_ERR_PREFIX,
372  "Using IPv4 protocol");
373  }
374 #if CFG_USE_IP6
375  else if(POSIX_AF_INET6 == family)
376  {
377  printf("%s: %s%s\n", CFG_NAME, MAIN_ERR_PREFIX,
378  "Using IPv6 protocol");
379  }
380 #endif /* CFG_USE_IP6 */
381  *af = family;
382  *sd = socket;
383  break;
384  }
385  }
386  /* Release ressources allocated by name resolver */
387  posix_freeaddrinfo(sai);
388  }
389 
390  return(res);
391 }
392 
393 
394 /* ========================================================================== */
395 /*! \brief Try to set RX timeout for socket
396  *
397  * \param[in] sd Socket descriptor
398  * \param[in] rx_to RX timeout in seconds (Upper limit: 3600)
399  *
400  * \attention
401  * On POSIX systems it is implementation defined whether timeouts on sockets
402  * can be set or not. It is no misbehaviour if the OS rejects the requests and
403  * this function returns an error.
404  *
405  * If \e rx_to is set to zero this means "no timeout" (an existing timeout
406  * setting will be removed).
407  *
408  * \note
409  * POSIX offers no way to get the type (integer and floating point allowed) and
410  * range of "time_t". We limit the range to [0, 3600], expecting that every sane
411  * platform can handle 1 hour (even if this strictly isn't required by C90 and
412  * POSIX).
413  *
414  * \return
415  * - Zero on success
416  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
417  */
418 
419 int inet_set_rx_timeout(int sd, unsigned int rx_to)
420 {
421  int res = INET_ERR_BADF;
422  int rv;
423  struct_posix_timeval rx;
424 
425  if(-1 != sd)
426  {
427  if(3600U >= rx_to)
428  {
429  rx.tv_sec = (posix_time_t) rx_to;
430  rx.tv_usec = 0;
431  rv = posix_setsockopt(sd, POSIX_SOL_SOCKET, POSIX_SO_RCVTIMEO,
432  (void*) &rx,
433  (posix_socklen_t) sizeof(struct_posix_timeval));
434  if(-1 == rv)
435  {
436  PRINT_ERROR("setsockopt() failed for RX timeout");
437  if(POSIX_EINVAL == posix_errno) { res = INET_ERR_RX_TO; }
438  if(POSIX_ENOPROTOOPT == posix_errno) { res = INET_ERR_RX_TO; }
439  if(POSIX_EBADF != posix_errno && POSIX_ENOTSOCK != errno)
440  {
441  res = INET_ERR_UNSPEC;
442  }
443  }
444  else { res = 0; }
445  }
446  }
447 
448  return(res);
449 }
450 
451 
452 /* ========================================================================== */
453 /*! \brief Try to set TX timeout for socket
454  *
455  * \param[in] sd Socket descriptor
456  * \param[in] tx_to TX timeout in seconds (Upper limit: 3600)
457  *
458  * \attention
459  * On POSIX systems it is implementation defined whether timeouts on sockets
460  * can be set or not. It is no misbehaviour if the OS rejects the requests and
461  * this function returns an error.
462  *
463  * If \e tx_to is set to zero this means "no timeout" (an existing timeout
464  * setting will be removed).
465  *
466  * \note
467  * POSIX offers no way to get the type (integer and floating point allowed) and
468  * range of "time_t". We limit the range to [0, 3600], expecting that every sane
469  * platform can handle 1 hour (even if this strictly isn't required by C90 and
470  * POSIX).
471  *
472  * \return
473  * - Zero on success
474  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
475  */
476 
477 int inet_set_tx_timeout(int sd, unsigned int tx_to)
478 {
479  int res = INET_ERR_BADF;
480  int rv;
481  struct_posix_timeval tx;
482 
483  if(-1 != sd)
484  {
485  if(3600U >= tx_to)
486  {
487  tx.tv_sec = (posix_time_t) tx_to;
488  tx.tv_usec = 0;
489  rv = posix_setsockopt(sd, POSIX_SOL_SOCKET, POSIX_SO_SNDTIMEO,
490  (void*) &tx,
491  (posix_socklen_t) sizeof(struct_posix_timeval));
492  if(-1 == rv)
493  {
494  PRINT_ERROR("setsockopt() failed for TX timeout");
495  if(POSIX_EINVAL == posix_errno) { res = INET_ERR_TX_TO; }
496  if(POSIX_ENOPROTOOPT == posix_errno) { res = INET_ERR_TX_TO; }
497  if(POSIX_EBADF != posix_errno && POSIX_ENOTSOCK != errno)
498  {
499  res = INET_ERR_UNSPEC;
500  }
501  }
502  else { res = 0; }
503  }
504  }
505 
506  return(res);
507 }
508 
509 
510 /* ========================================================================== */
511 /*! \brief Close connection and destroy socket
512  *
513  * \param[in,out] sd Pointer to socket descriptor
514  *
515  * \note
516  * It is save to call this function with \e sd pointing to \c -1 . This will
517  * execute as NOP.
518  *
519  * \note
520  * The value -1 is present at the location pointed to by \e sd in any case after
521  * return.
522  */
523 
524 void inet_close(int* sd)
525 {
526  if(-1 != *sd)
527  {
528  posix_close(*sd);
529  *sd = -1;
530  }
531 }
532 
533 
534 /*! @} */
535 
536 /* EOF */
INET_ERR_AF
#define INET_ERR_AF
Address family not supported.
Definition: inet.h:17
INET_ERR_SOCK
#define INET_ERR_SOCK
Socket creation/configuration failed.
Definition: inet.h:18
INET_ERR_UNSPEC
#define INET_ERR_UNSPEC
Unspecified error.
Definition: inet.h:14
INET_OPTS_SET
#define INET_OPTS_SET
Add socket options.
Definition: inet.c:41
INET_ERR_TX_TO
#define INET_ERR_TX_TO
Setting TX timeout failed.
Definition: inet.h:22
INET_ERR_RX_TO
#define INET_ERR_RX_TO
Setting RX timeout failed.
Definition: inet.h:21
inet_set_tx_timeout
int inet_set_tx_timeout(int sd, unsigned int tx_to)
Try to set TX timeout for socket.
Definition: inet.c:477
inet_close
void inet_close(int *sd)
Close connection and destroy socket.
Definition: inet.c:524
INET_ERR_CONN
#define INET_ERR_CONN
Connection failed.
Definition: inet.h:19
INET_OPTS_CLEAR
#define INET_OPTS_CLEAR
Remove socket options.
Definition: inet.c:40
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
INET_ERR_HNR
#define INET_ERR_HNR
Host name resolution failed.
Definition: inet.h:15
INET_ERR_BADF
#define INET_ERR_BADF
Invalid socket descriptor.
Definition: inet.h:20
INET_ERR_SNR
#define INET_ERR_SNR
Service name resolution failed.
Definition: inet.h:16
MAIN_ERR_PREFIX
#define MAIN_ERR_PREFIX
Message prefix for INET module.
Definition: inet.c:36
inet_set_rx_timeout
int inet_set_rx_timeout(int sd, unsigned int rx_to)
Try to set RX timeout for socket.
Definition: inet.c:419
inet_connect
int inet_connect(int *sd, int *af, const char *host, const char *service)
Establish stream oriented connection.
Definition: inet.c:254
print_error
void print_error(const char *)
Print error message.
Definition: main.cxx:276

Generated at 2024-04-27 using  doxygen