]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - unix/uxnet.c
Rework handling of asynchronous connect(2) errors on Unix.
[PuTTY.git] / unix / uxnet.c
index 760d0a5719b036596feb896a1b760683dc7563a1..903faeb096744e20e943fec174a2f0063166ad9a 100644 (file)
  * Access to sockaddr types without breaking C strict aliasing rules.
  */
 union sockaddr_union {
-#ifdef NO_IPV6
-    struct sockaddr_in storage;
-#else
     struct sockaddr_storage storage;
-    struct sockaddr_in6 sin6;
-#endif
     struct sockaddr sa;
     struct sockaddr_in sin;
+#ifndef NO_IPV6
+    struct sockaddr_in6 sin6;
+#endif
     struct sockaddr_un su;
 };
 
@@ -1344,21 +1342,7 @@ static int net_select_result(int fd, int event)
            }
        }
        if (ret < 0) {
-            /*
-             * An error at this point _might_ be an error reported
-             * by a non-blocking connect(). So before we return a
-             * panic status to the user, let's just see whether
-             * that's the case.
-             */
-            int err = errno;
-           if (s->addr) {
-               plug_log(s->plug, 1, s->addr, s->port, strerror(err), err);
-               while (s->addr && sk_nextaddr(s->addr, &s->step)) {
-                   err = try_connect(s);
-               }
-           }
-            if (err != 0)
-                return plug_closing(s->plug, strerror(err), err, 0);
+            return plug_closing(s->plug, strerror(errno), errno, 0);
        } else if (0 == ret) {
             s->incomingeof = TRUE;     /* stop trying to read now */
             uxsel_tell(s);
@@ -1380,11 +1364,48 @@ static int net_select_result(int fd, int event)
        if (!s->connected) {
            /*
             * select() reports a socket as _writable_ when an
-            * asynchronous connection is completed.
+            * asynchronous connect() attempt either completes or
+            * fails. So first we must find out which.
             */
+            {
+                int err;
+                socklen_t errlen = sizeof(err);
+                char *errmsg = NULL;
+                if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) {
+                    errmsg = dupprintf("getsockopt(SO_ERROR): %s",
+                                       strerror(errno));
+                    err = errno;       /* got to put something in here */
+                } else if (err != 0) {
+                    errmsg = dupstr(strerror(err));
+                }
+                if (errmsg) {
+                    /*
+                     * The asynchronous connection attempt failed.
+                     * Report the problem via plug_log, and try again
+                     * with the next candidate address, if we have
+                     * more than one.
+                     */
+                    assert(s->addr);
+                    plug_log(s->plug, 1, s->addr, s->port, errmsg, err);
+                    while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
+                        err = try_connect(s);
+                    }
+                    if (err)
+                        return plug_closing(s->plug, strerror(err), err, 0);
+                    if (!s->connected)
+                        return 0;      /* another async attempt in progress */
+                }
+            }
+
+            /*
+             * If we get here, we've managed to make a connection.
+             */
+            if (s->addr) {
+                sk_addr_free(s->addr);
+                s->addr = NULL;
+            }
            s->connected = s->writable = 1;
            uxsel_tell(s);
-           break;
        } else {
            int bufsize_before, bufsize_after;
            s->writable = 1;
@@ -1427,26 +1448,27 @@ static void sk_tcp_set_frozen(Socket sock, int is_frozen)
 static char *sk_tcp_peer_info(Socket sock)
 {
     Actual_Socket s = (Actual_Socket) sock;
-    struct sockaddr_storage addr;
+    union sockaddr_union addr;
     socklen_t addrlen = sizeof(addr);
+#ifndef NO_IPV6
     char buf[INET6_ADDRSTRLEN];
+#endif
 
-    if (getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
+    if (getpeername(s->s, &addr.sa, &addrlen) < 0)
         return NULL;
-    if (addr.ss_family == AF_INET) {
+    if (addr.storage.ss_family == AF_INET) {
         return dupprintf
             ("%s:%d",
-             inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr),
-             (int)ntohs(((struct sockaddr_in *)&addr)->sin_port));
+             inet_ntoa(addr.sin.sin_addr),
+             (int)ntohs(addr.sin.sin_port));
 #ifndef NO_IPV6
-    } else if (addr.ss_family == AF_INET6) {
+    } else if (addr.storage.ss_family == AF_INET6) {
         return dupprintf
             ("[%s]:%d",
-             inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr,
-                       buf, sizeof(buf)),
-             (int)ntohs(((struct sockaddr_in6 *)&addr)->sin6_port));
+             inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf)),
+             (int)ntohs(addr.sin6.sin6_port));
 #endif
-    } else if (addr.ss_family == AF_UNIX) {
+    } else if (addr.storage.ss_family == AF_UNIX) {
         /*
          * For Unix sockets, the source address is unlikely to be
          * helpful. Instead, we try SO_PEERCRED and try to get the