inet.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Internet connection handling
4  *
5  * Copyright (c) 2015-2024 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 API_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 api_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, api_posix_struct_addrinfo** sai)
77 {
78  int res = 0;
79  api_posix_struct_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 = API_POSIX_AI_ADDRCONFIG;
88  hints.ai_family = af;
89  hints.ai_socktype = API_POSIX_SOCK_STREAM;
90  hints.ai_protocol = 0;
91  rv = api_posix_getaddrinfo(host, service, &hints, sai);
92  if(rv)
93  {
94  PRINT_ERROR("getaddrinfo() failed");
95 #if CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI
96  /* Don't use NLS for error messages on stderr */
97  api_posix_setlocale(API_POSIX_LC_MESSAGES, "POSIX");
98 #endif /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI */
99  err_str = api_posix_gai_strerror(rv);
100 #if CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI
101  api_posix_setlocale(API_POSIX_LC_MESSAGES, "");
102 #endif /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI */
103  if(NULL != err_str)
104  {
105  len = strlen(MAIN_ERR_PREFIX);
106  len += strlen(err_str);
107  ebuf = (char*) api_posix_malloc(++len);
108  if(NULL != ebuf)
109  {
110  strcpy(ebuf, MAIN_ERR_PREFIX);
111  strcat(ebuf, err_str);
112  print_error(ebuf);
113  }
114  api_posix_free((void*) ebuf);
115  }
116  switch(rv)
117  {
118  case API_POSIX_EAI_FAMILY: { res = INET_ERR_AF; break; }
119  case API_POSIX_EAI_NONAME: { res = INET_ERR_HNR; break; }
120  case API_POSIX_EAI_SERVICE: { res = INET_ERR_SNR; break; }
121  default: { res = INET_ERR_UNSPEC; break; }
122  }
123  }
124 
125  return(res);
126 }
127 
128 
129 /* ========================================================================== */
130 /* Create socket
131  *
132  * \param[out] sd Pointer to socket descriptor
133  * \param[in] af Address family
134  *
135  * The value for \e af must be \c API_POSIX_INET or \c API_POSIX_INET6
136  * respectively.
137  *
138  * \note
139  * The value -1 is written to the location pointed to by \e sd on error.
140  *
141  * \return
142  * - Zero if socket was successfully created
143  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
144  */
145 
146 static int inet_socket_create(int* sd, int af)
147 {
148  int res = 0;
149  int rv;
150 
151  if(API_POSIX_AF_INET != af
152 #if CFG_USE_IP6
153  && API_POSIX_AF_INET6 != af
154 #endif /* CFG_USE_IP6 */
155  )
156  {
157  PRINT_ERROR("socket(): Address family not supported");
158  res = INET_ERR_AF;
159  }
160  else
161  {
162  *sd = api_posix_socket(af, API_POSIX_SOCK_STREAM, 0);
163  if(0 > *sd)
164  {
165  PRINT_ERROR("socket() failed");
166  *sd = -1;
167  res = INET_ERR_SOCK;
168  }
169  else
170  {
171  /* Set "Close on exec()" flag */
172  do
173  {
174  rv = api_posix_fcntl(*sd, API_POSIX_F_SETFD, API_POSIX_FD_CLOEXEC);
175  }
176  while(-1 == rv && API_POSIX_EINTR == api_posix_errno);
177  if(-1 == rv)
178  {
179  PRINT_ERROR("fcntl() failed");
180  inet_close(sd);
181  res = INET_ERR_SOCK;
182  }
183  }
184  }
185 
186  return(res);
187 }
188 
189 
190 #if CFG_USE_CONNECT_TIMEOUT
191 /* ========================================================================== */
192 /* Configure socket options
193  *
194  * \param[in] sd Socket descriptor
195  * \param[in] action Socket option action (use \c INET_OPTION_xxx constants)
196  * \param[in] opts Socket options
197  *
198  * \return
199  * - Zero if socket was successfully configured
200  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
201  */
202 
203 static int inet_socket_options(int sd, int action, int opts)
204 {
205  int res = 0;
206  int rv;
207  int opts_new;
208 
209  /* Get socket options */
210  do { rv = api_posix_fcntl(sd, API_POSIX_F_GETFL); }
211  while(-1 == rv && API_POSIX_EINTR == api_posix_errno);
212  if(-1 == rv)
213  {
214  PRINT_ERROR("fcntl() failed");
215  res = INET_ERR_SOCK;
216  }
217 
218  if (!res)
219  {
220  /* Modify socket options */
221  if (INET_OPTS_SET == action) { opts_new = rv | opts; }
222  else { opts_new = rv & ~opts; }
223 
224  /* Set socket options */
225  do { rv = api_posix_fcntl(sd, API_POSIX_F_SETFL, opts_new); }
226  while(-1 == rv && API_POSIX_EINTR == api_posix_errno);
227  if(-1 == rv)
228  {
229  PRINT_ERROR("fcntl() failed");
230  res = INET_ERR_SOCK;
231  }
232  }
233 
234  return(res);
235 }
236 #endif
237 
238 
239 /* ========================================================================== */
240 /*! \brief Establish stream oriented connection
241  *
242  * \param[out] sd Pointer to socket descriptor
243  * \param[in,out] af Address family
244  * \param[in] host Name of host
245  * \param[in] service Name of service
246  *
247  * On success the socket descriptor of the established connection is written to
248  * the location pointed to by \e sd . If the address family was specified as
249  * \c API_POSIX_AF_UNSPEC or was enforced by configuration, it is overwritten
250  * with the address family that was used to establish the connection.
251  *
252  * On error -1 is written to the location pointed to by \e sd and the value
253  * pointed to by \e af is unchanged.
254  *
255  * \note
256  * If the name resolver reports multiple addresses for \e host , all entries are
257  * tried in order. If no connection could be established, error status is
258  * returned for the last entry in the list.
259  *
260  * \return
261  * - Zero if connection was successfully established
262  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
263  */
264 
265 int inet_connect(int* sd, int* af, const char* host, const char* service)
266 {
267  int res;
268  int rv;
269  api_posix_struct_addrinfo* sai;
270  api_posix_struct_addrinfo* saie;
271  int family;
272  int socket;
273  api_posix_struct_pollfd fds[1];
274  api_posix_socklen_t opt_len;
275 #if CFG_USE_CONNECT_TIMEOUT
276  api_posix_time_t ts1 = 0, ts2, ts_diff;
277 #endif
278  int timeout;
279  int timed_out = 0;
280  int refused = 0;
281 
282  *sd = -1;
283 
284  /* Override address family on request */
285  if(inet_force_ipv4) { *af = API_POSIX_AF_INET; }
286 
287  /* Resolve host and service names */
288  res = inet_name_resolver(host, service, *af, &sai);
289  if(!res)
290  {
291  for(saie = sai; saie != NULL; saie = saie->ai_next)
292  {
293  /* Create socket */
294  family = saie->ai_family;
295  res = inet_socket_create(&socket, family);
296 #if CFG_USE_CONNECT_TIMEOUT
297  if(!res)
298  {
299  /* Configure socket to nonblocking mode */
300  res = inet_socket_options(socket, INET_OPTS_SET,
301  API_POSIX_O_NONBLOCK);
302  /* Store timestamp */
303  ts1 = api_posix_time(NULL);
304  if((api_posix_time_t) 0 > ts1) { ts1 = 0; }
305  }
306 #endif
307  if(!res)
308  {
309  /* Try to establish connection */
310  refused = 0;
311  timed_out = 0;
312  rv = api_posix_connect(socket, saie->ai_addr, saie->ai_addrlen);
313  if(rv)
314  {
315  if(API_POSIX_ECONNREFUSED == api_posix_errno) { refused = 1; }
316  res = INET_ERR_CONN;
317  }
318  if(-1 == rv &&
319  (API_POSIX_EINPROGRESS == api_posix_errno
320  || API_POSIX_EINTR == api_posix_errno))
321  {
322  /*
323  * Interrupted by signal (or nonblocking mode)
324  * => Connection is established asynchronously
325  */
326  do
327  {
328  timeout = -1;
329 #if CFG_USE_CONNECT_TIMEOUT
330  timeout = 500; /* Milliseconds */
331  /* Timeout will be aborted/restartet by signals */
332  ts2 = api_posix_time(NULL);
333  if((api_posix_time_t) 0 > ts2) { ts2 = 0; }
334  ts_diff = ts2 - ts1;
335  if ((float) CFG_USE_CONNECT_TIMEOUT < (float) ts_diff)
336  {
337  rv = 0;
338  break;
339  }
340 #endif
341  fds[0].fd = socket;
342  fds[0].events = API_POSIX_POLLOUT;
343  fds[0].revents = 0;
344  rv = api_posix_poll(fds, 1, timeout);
345  }
346  while(0 == rv ||
347  (-1 == rv && API_POSIX_EINTR == api_posix_errno));
348  if(0 == rv) { timed_out = 1; }
349  else if(1 == rv)
350  {
351  /* Get return value of 'connect()' */
352  opt_len = sizeof(int);
353  rv = api_posix_getsockopt(socket, API_POSIX_SOL_SOCKET,
354  API_POSIX_SO_ERROR, &res, &opt_len);
355  if(-1 == rv || res) { res = INET_ERR_CONN; }
356  }
357  }
358  }
359 #if CFG_USE_CONNECT_TIMEOUT
360  if (!res)
361  {
362  /* Configure socket to blocking mode */
363  res = inet_socket_options(socket, INET_OPTS_CLEAR,
364  API_POSIX_O_NONBLOCK);
365  }
366 #endif
367 
368  /* Check for error */
369  if(res)
370  {
371  if(refused) { PRINT_ERROR("Connection refused"); }
372  else if(timed_out)
373  {
374  PRINT_ERROR("User defined TCP connection timeout");
375  }
376  else { PRINT_ERROR("connect() failed"); }
377  inet_close(&socket);
378  /* Try next entry in linked list */
379  }
380  else
381  {
382  /* Connection established */
383  if(API_POSIX_AF_INET == family)
384  {
385  printf("%s: %s%s\n", CFG_NAME, MAIN_ERR_PREFIX,
386  "Using IPv4 protocol");
387  }
388 #if CFG_USE_IP6
389  else if(API_POSIX_AF_INET6 == family)
390  {
391  printf("%s: %s%s\n", CFG_NAME, MAIN_ERR_PREFIX,
392  "Using IPv6 protocol");
393  }
394 #endif /* CFG_USE_IP6 */
395  *af = family;
396  *sd = socket;
397  break;
398  }
399  }
400  /* Release ressources allocated by name resolver */
401  api_posix_freeaddrinfo(sai);
402  }
403 
404  return(res);
405 }
406 
407 
408 /* ========================================================================== */
409 /*! \brief Try to set RX timeout for socket
410  *
411  * \param[in] sd Socket descriptor
412  * \param[in] rx_to RX timeout in seconds (Upper limit: 3600)
413  *
414  * \attention
415  * On POSIX systems it is implementation defined whether timeouts on sockets
416  * can be set or not. It is no misbehaviour if the OS rejects the requests and
417  * this function returns an error.
418  *
419  * If \e rx_to is set to zero this means "no timeout" (an existing timeout
420  * setting will be removed).
421  *
422  * \note
423  * POSIX offers no way to get the type (integer and floating point allowed) and
424  * range of "time_t". We limit the range to [0, 3600], expecting that every sane
425  * platform can handle 1 hour (even if this strictly isn't required by C90 and
426  * POSIX).
427  *
428  * \return
429  * - Zero on success
430  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
431  */
432 
433 int inet_set_rx_timeout(int sd, unsigned int rx_to)
434 {
435  int res = INET_ERR_BADF;
436  int rv;
437  api_posix_struct_timeval rx;
438 
439  if(-1 != sd)
440  {
441  if(3600U >= rx_to)
442  {
443  rx.tv_sec = (api_posix_time_t) rx_to;
444  rx.tv_usec = 0;
445  rv = api_posix_setsockopt(sd, API_POSIX_SOL_SOCKET,
446  API_POSIX_SO_RCVTIMEO, (void*) &rx,
447  (api_posix_socklen_t)
448  sizeof(api_posix_struct_timeval));
449  if(-1 == rv)
450  {
451  PRINT_ERROR("setsockopt() failed for RX timeout");
452  if(API_POSIX_EINVAL == api_posix_errno) { res = INET_ERR_RX_TO; }
453  if(API_POSIX_ENOPROTOOPT == api_posix_errno)
454  {
455  res = INET_ERR_RX_TO;
456  }
457  if(API_POSIX_EBADF != api_posix_errno
458  && API_POSIX_ENOTSOCK != errno)
459  {
460  res = INET_ERR_UNSPEC;
461  }
462  }
463  else { res = 0; }
464  }
465  }
466 
467  return(res);
468 }
469 
470 
471 /* ========================================================================== */
472 /*! \brief Try to set TX timeout for socket
473  *
474  * \param[in] sd Socket descriptor
475  * \param[in] tx_to TX timeout in seconds (Upper limit: 3600)
476  *
477  * \attention
478  * On POSIX systems it is implementation defined whether timeouts on sockets
479  * can be set or not. It is no misbehaviour if the OS rejects the requests and
480  * this function returns an error.
481  *
482  * If \e tx_to is set to zero this means "no timeout" (an existing timeout
483  * setting will be removed).
484  *
485  * \note
486  * POSIX offers no way to get the type (integer and floating point allowed) and
487  * range of "time_t". We limit the range to [0, 3600], expecting that every sane
488  * platform can handle 1 hour (even if this strictly isn't required by C90 and
489  * POSIX).
490  *
491  * \return
492  * - Zero on success
493  * - Negative value on error (use \c INET_ERR_xxx constants for checks)
494  */
495 
496 int inet_set_tx_timeout(int sd, unsigned int tx_to)
497 {
498  int res = INET_ERR_BADF;
499  int rv;
500  api_posix_struct_timeval tx;
501 
502  if(-1 != sd)
503  {
504  if(3600U >= tx_to)
505  {
506  tx.tv_sec = (api_posix_time_t) tx_to;
507  tx.tv_usec = 0;
508  rv = api_posix_setsockopt(sd, API_POSIX_SOL_SOCKET,
509  API_POSIX_SO_SNDTIMEO, (void*) &tx,
510  (api_posix_socklen_t)
511  sizeof(api_posix_struct_timeval));
512  if(-1 == rv)
513  {
514  PRINT_ERROR("setsockopt() failed for TX timeout");
515  if(API_POSIX_EINVAL == api_posix_errno) { res = INET_ERR_TX_TO; }
516  if(API_POSIX_ENOPROTOOPT == api_posix_errno)
517  {
518  res = INET_ERR_TX_TO;
519  }
520  if(API_POSIX_EBADF != api_posix_errno
521  && API_POSIX_ENOTSOCK != errno)
522  {
523  res = INET_ERR_UNSPEC;
524  }
525  }
526  else { res = 0; }
527  }
528  }
529 
530  return(res);
531 }
532 
533 
534 /* ========================================================================== */
535 /*! \brief Close connection and destroy socket
536  *
537  * \param[in,out] sd Pointer to socket descriptor
538  *
539  * \note
540  * It is save to call this function with \e sd pointing to \c -1 . This will
541  * execute as NOP.
542  *
543  * \note
544  * The value -1 is present at the location pointed to by \e sd in any case after
545  * return.
546  */
547 
548 void inet_close(int* sd)
549 {
550  if(-1 != *sd)
551  {
552  api_posix_close(*sd);
553  *sd = -1;
554  }
555 }
556 
557 
558 /*! @} */
559 
560 /* 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:496
inet_close
void inet_close(int *sd)
Close connection and destroy socket.
Definition: inet.c:548
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:433
inet_connect
int inet_connect(int *sd, int *af, const char *host, const char *service)
Establish stream oriented connection.
Definition: inet.c:265
print_error
void print_error(const char *)
Print error message.
Definition: main.cxx:276

Generated at 2026-01-27 using  doxygen