connnect: fix timeout handling to use full duration

- connect timeout was used at half the configured value, if the
  destination had 1 ip version 4 and other version 6 addresses
  (or the other way around)
- extended test2600 to reproduce these cases

Reported-by: Michael Kaufmann
Fixes #10514
Closes #10517
diff --git a/lib/connect.c b/lib/connect.c
index 78aa47b..993a7f9 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -186,7 +186,7 @@
 static const struct Curl_addrinfo *
 addr_next_match(const struct Curl_addrinfo *addr, int family)
 {
-  while(addr->ai_next) {
+  while(addr && addr->ai_next) {
     addr = addr->ai_next;
     if(addr->ai_family == family)
       return addr;
@@ -406,7 +406,8 @@
   baller->ai_family = ai_family;
   baller->primary = primary;
   baller->delay_ms = delay_ms;
-  baller->timeoutms = (addr && addr->ai_next)? timeout_ms / 2 : timeout_ms;
+  baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)?
+                        timeout_ms / 2 : timeout_ms;
   baller->timeout_id = timeout_id;
   baller->result = CURLE_COULDNT_CONNECT;
 
@@ -467,7 +468,7 @@
     wcf->sockindex = cf->sockindex;
   }
 
-  if(baller->addr && baller->addr->ai_next) {
+  if(addr_next_match(baller->addr, baller->ai_family)) {
     Curl_expire(data, baller->timeoutms, baller->timeout_id);
   }
 
@@ -498,8 +499,8 @@
 
   while(baller->addr) {
     baller->started = Curl_now();
-    baller->timeoutms = (baller->addr->ai_next == NULL) ?
-                         timeoutms : timeoutms / 2;
+    baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ?
+                         timeoutms / 2 : timeoutms;
     baller_initiate(cf, data, baller);
     if(!baller->result)
       break;
@@ -662,7 +663,8 @@
           DEBUGF(LOG_CF(data, cf, "%s done", baller->name));
         }
         else {
-          DEBUGF(LOG_CF(data, cf, "%s starting", baller->name));
+          DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%ldms)",
+                        baller->name, baller->timeoutms));
           ++ongoing;
           ++added;
         }
@@ -799,6 +801,8 @@
                           timeout_ms,  EXPIRE_DNS_PER_NAME);
   if(result)
     return result;
+  DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
+                ctx->baller[0]->name, ctx->baller[0]->timeoutms));
   if(addr1) {
     /* second one gets a delayed start */
     result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1,
@@ -808,6 +812,8 @@
                             timeout_ms,  EXPIRE_DNS_PER_NAME2);
     if(result)
       return result;
+    DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
+                  ctx->baller[1]->name, ctx->baller[1]->timeoutms));
   }
 
   Curl_expire(data, data->set.happy_eyeballs_timeout,
diff --git a/tests/unit/unit2600.c b/tests/unit/unit2600.c
index ad5dbb9..1d5cf01 100644
--- a/tests/unit/unit2600.c
+++ b/tests/unit/unit2600.c
@@ -56,6 +56,7 @@
   int id;
   const char *url;
   const char *resolve_info;
+  unsigned char ip_version;
   timediff_t connect_timeout_ms;
   timediff_t he_timeout_ms;
   timediff_t cf4_fail_delay_ms;
@@ -270,6 +271,7 @@
   list = curl_slist_append(NULL, tc->resolve_info);
   fail_unless(list, "error allocating resolve list entry");
   curl_easy_setopt(easy, CURLOPT_RESOLVE, list);
+  curl_easy_setopt(easy, CURLOPT_IPRESOLVE, (long)tc->ip_version);
   curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS,
                    (long)tc->connect_timeout_ms);
   curl_easy_setopt(easy, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS,
@@ -314,29 +316,37 @@
 static struct test_case TEST_CASES[] = {
   /* TIMEOUT_MS,        FAIL_MS      CREATED    DURATION     Result, HE_PREF */
   /* CNCT   HE          v4    v6     v4 v6      MIN   MAX */
-  { 1, TURL, "test.com:123:192.0.2.1",
+  { 1, TURL, "test.com:123:192.0.2.1", CURL_IPRESOLVE_WHATEVER,
      250,  150,        200,  200,    1,  0,      200,  500,  R_FAIL, NULL },
   /* 1 ipv4, fails after ~200ms, reports COULDNT_CONNECT   */
-  { 2, TURL, "test.com:123:192.0.2.1,192.0.2.2",
+  { 2, TURL, "test.com:123:192.0.2.1,192.0.2.2", CURL_IPRESOLVE_WHATEVER,
      500,  150,        200,  200,    2,  0,      400,  800,  R_FAIL, NULL },
   /* 2 ipv4, fails after ~400ms, reports COULDNT_CONNECT   */
 #ifdef ENABLE_IPV6
-  { 3, TURL, "test.com:123:::1",
+  { 3, TURL, "test.com:123:::1", CURL_IPRESOLVE_WHATEVER,
      250,  150,        200,  200,    0,  1,      200,  500,  R_FAIL, NULL },
   /* 1 ipv6, fails after ~200ms, reports COULDNT_CONNECT   */
-  { 4, TURL, "test.com:123:::1,::2",
+  { 4, TURL, "test.com:123:::1,::2", CURL_IPRESOLVE_WHATEVER,
      500,  150,        200,  200,    0,  2,      400,  800,  R_FAIL, NULL },
   /* 2 ipv6, fails after ~400ms, reports COULDNT_CONNECT   */
 
-  { 5, TURL, "test.com:123:192.0.2.1,::1",
+  { 5, TURL, "test.com:123:192.0.2.1,::1", CURL_IPRESOLVE_WHATEVER,
      500,  150,        200, 200,     1,  1,      350,  800,  R_FAIL, "v4" },
   /* mixed ip4+6, v4 starts, v6 kicks in on HE, fails after ~350ms */
-  { 6, TURL, "test.com:123:::1,192.0.2.1",
+  { 6, TURL, "test.com:123:::1,192.0.2.1", CURL_IPRESOLVE_WHATEVER,
      500,  150,        200, 200,     1,  1,      350,  800,  R_FAIL, "v6" },
   /* mixed ip6+4, v6 starts, v4 kicks in on HE, fails after ~350ms */
-  { 7, TURL, "test.com:123:::1,192.0.2.1,::2,::3",
+  { 7, TURL, "test.com:123:::1,192.0.2.1,::2,::3", CURL_IPRESOLVE_WHATEVER,
      500,  600,        200, 200,     0,  3,      350,  800,  R_TIME, "v6" },
   /* mixed ip6+4, v6 starts, v4 never starts due to high HE, TIMEOUT */
+  { 8, TURL, "test.com:123:192.0.2.1,::1", CURL_IPRESOLVE_V4,
+     400,  150,        500, 500,     1,  0,      400,  600,  R_FAIL, NULL },
+  /* mixed ip4+6, but only use v4, check it uses full connect timeout,
+     although another address of the 'wrong' family is availbale */
+  { 9, TURL, "test.com:123:::1,192.0.2.1", CURL_IPRESOLVE_V6,
+     400,  150,        500, 500,     0,  1,      400,  600,  R_FAIL, NULL },
+  /* mixed ip4+6, but only use v6, check it uses full connect timeout,
+     although another address of the 'wrong' family is availbale */
 #endif
 };