tsnetwork_connect.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <stdlib.h>
  6. #include "warnp.h"
  7. #include "tsnetwork.h"
  8. struct network_connect_cookie {
  9. int s;
  10. int failed;
  11. int errnum;
  12. network_callback * callback;
  13. void * cookie;
  14. };
  15. static network_callback callback_connect;
  16. /**
  17. * callback_connect(cookie, status):
  18. * Callback helper for tsnetwork_connect.
  19. */
  20. static int
  21. callback_connect(void * cookie, int status)
  22. {
  23. struct network_connect_cookie * C = cookie;
  24. int sockerr;
  25. socklen_t sockerrlen = sizeof(int);
  26. int rc;
  27. /*
  28. * A timeout here is either a connection timeout or a connection
  29. * error, depending upon whether we got here as a result of a zero
  30. * second timeout used to postpone handling of a connect() failure.
  31. */
  32. if (status == NETWORK_STATUS_TIMEOUT) {
  33. if (C->failed) {
  34. /*
  35. * The connect() call returned an error; restore the
  36. * errno value from that point so that when we invoke
  37. * our callback we have the right value there.
  38. */
  39. status = NETWORK_STATUS_CONNERR;
  40. errno = C->errnum;
  41. } else {
  42. /*
  43. * There was a connection timeout; set errno to zero
  44. * here since any value in errno at this point is
  45. * just left over from a previous failed syscall.
  46. */
  47. status = NETWORK_STATUS_CTIMEOUT;
  48. errno = 0;
  49. }
  50. }
  51. if (status != NETWORK_STATUS_OK)
  52. goto docallback;
  53. /* Even if the status is ok, we need to check for pending error. */
  54. if (getsockopt(C->s, SOL_SOCKET, SO_ERROR, &sockerr, &sockerrlen)) {
  55. status = NETWORK_STATUS_CONNERR;
  56. goto docallback;
  57. }
  58. if (sockerr != 0) {
  59. errno = sockerr;
  60. status = NETWORK_STATUS_CONNERR;
  61. }
  62. docallback:
  63. /* Call the upstream callback. */
  64. rc = (C->callback)(C->cookie, status);
  65. /* Free the cookie. */
  66. free(C);
  67. /* Return value from user callback. */
  68. return (rc);
  69. }
  70. /**
  71. * tsnetwork_connect(s, addr, addrlen, timeout, callback, cookie):
  72. * Connect the specified socket to the specified address, and call the
  73. * specified callback when connected or the connection attempt has failed.
  74. */
  75. int
  76. tsnetwork_connect(int s, const struct sockaddr * addr, socklen_t addrlen,
  77. struct timeval * timeout, network_callback * callback, void * cookie)
  78. {
  79. struct network_connect_cookie * C;
  80. struct timeval timeo;
  81. int rc = 0; /* Success unless specified otherwise. */
  82. /* Create a cookie to be passed to callback_connect. */
  83. if ((C = malloc(sizeof(struct network_connect_cookie))) == NULL)
  84. goto err0;
  85. C->s = s;
  86. C->failed = 0;
  87. C->callback = callback;
  88. C->cookie = cookie;
  89. /* Mark socket as non-blocking. */
  90. if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
  91. warnp("fcntl(O_NONBLOCK)");
  92. goto err1;
  93. }
  94. /* Try to connect to the server. */
  95. if ((connect(s, addr, addrlen) == 0) ||
  96. (errno == EINPROGRESS) ||
  97. (errno == EINTR)) {
  98. /* Connection is being established. */
  99. if (network_register(s, NETWORK_OP_WRITE, timeout,
  100. callback_connect, C))
  101. goto err1;
  102. } else if ((errno == ECONNREFUSED) ||
  103. (errno == ECONNRESET) ||
  104. (errno == ENETDOWN) ||
  105. (errno == ENETUNREACH) ||
  106. #ifdef FREEBSD_PORTRANGE_BUG
  107. /*-
  108. * If FreeBSD's net.inet.ip.portrange.randomized sysctl is set to 1
  109. * (the default value) FreeBSD sometimes reuses a source port faster
  110. * than might naively be expected. This doesn't cause any problems
  111. * except if the pf firewall is running on the source system; said
  112. * firewall detects the packet as belonging to an expired connection
  113. * and drops it. This would be fine, except that the FreeBSD kernel
  114. * doesn't merely drop the packet when a firewall blocks an outgoing
  115. * packet; instead, it reports EPERM back to the userland process
  116. * which was responsible for the packet being sent.
  117. * In short, things interact in wacky ways which make it possible to
  118. * get EPERM back in response to a connect(2) syscall. Work around
  119. * this by handling EPERM the same way as transient network glitches;
  120. * the upstream code will handle this appropriately by retrying the
  121. * connection, at which point a new source port number will be chosen
  122. * and everything will (probably) work fine.
  123. */
  124. (errno == EPERM) ||
  125. #endif
  126. (errno == EHOSTUNREACH)) {
  127. /*
  128. * The connection attempt has failed. Schedule a callback
  129. * to be performed after we return, since we're not allowed
  130. * to perform the callback right now.
  131. */
  132. C->failed = 1;
  133. C->errnum = errno;
  134. timeo.tv_sec = timeo.tv_usec = 0;
  135. if (network_sleep(&timeo, callback_connect, C))
  136. goto err1;
  137. } else {
  138. /* Something went seriously wrong. */
  139. warnp("Network connection failure");
  140. goto err1;
  141. }
  142. /* Return success or the status from the callback. */
  143. return (rc);
  144. err1:
  145. free(C);
  146. err0:
  147. /* Failure! */
  148. return (-1);
  149. }