Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmcurl/lib/connect.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifdef HAVE_NETINET_IN_H
27
#include <netinet/in.h> /* <netinet/tcp.h> may need it */
28
#endif
29
#ifdef HAVE_SYS_UN_H
30
#include <sys/un.h> /* for sockaddr_un */
31
#endif
32
#ifdef HAVE_LINUX_TCP_H
33
#include <linux/tcp.h>
34
#elif defined(HAVE_NETINET_TCP_H)
35
#include <netinet/tcp.h>
36
#endif
37
#ifdef HAVE_SYS_IOCTL_H
38
#include <sys/ioctl.h>
39
#endif
40
#ifdef HAVE_NETDB_H
41
#include <netdb.h>
42
#endif
43
#ifdef HAVE_ARPA_INET_H
44
#include <arpa/inet.h>
45
#endif
46
47
#ifdef __VMS
48
#include <in.h>
49
#include <inet.h>
50
#endif
51
52
#include "urldata.h"
53
#include "curl_trc.h"
54
#include "strerror.h"
55
#include "cfilters.h"
56
#include "connect.h"
57
#include "cf-haproxy.h"
58
#include "cf-https-connect.h"
59
#include "cf-ip-happy.h"
60
#include "cf-socket.h"
61
#include "multiif.h"
62
#include "curlx/inet_ntop.h"
63
#include "curlx/strparse.h"
64
#include "vtls/vtls.h" /* for vtsl cfilters */
65
#include "progress.h"
66
#include "conncache.h"
67
#include "multihandle.h"
68
#include "http_proxy.h"
69
#include "socks.h"
70
71
#if !defined(CURL_DISABLE_ALTSVC) || defined(USE_HTTPSRR)
72
73
enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len)
74
{
75
  if(len == 2) {
76
    if(!memcmp(name, "h1", 2))
77
      return ALPN_h1;
78
    if(!memcmp(name, "h2", 2))
79
      return ALPN_h2;
80
    if(!memcmp(name, "h3", 2))
81
      return ALPN_h3;
82
  }
83
  else if(len == 8) {
84
    if(!memcmp(name, "http/1.1", 8))
85
      return ALPN_h1;
86
  }
87
  return ALPN_none; /* unknown, probably rubbish input */
88
}
89
90
enum alpnid Curl_str2alpnid(const struct Curl_str *cstr)
91
{
92
  return Curl_alpn2alpnid((const unsigned char *)curlx_str(cstr),
93
                          curlx_strlen(cstr));
94
}
95
96
#endif
97
98
/*
99
 * Curl_timeleft_ms() returns the amount of milliseconds left allowed for the
100
 * transfer/connection. If the value is 0, there is no timeout (ie there is
101
 * infinite time left). If the value is negative, the timeout time has already
102
 * elapsed.
103
 * @param data the transfer to check on
104
 * @param duringconnect TRUE iff connect timeout is also taken into account.
105
 * @unittest: 1303
106
 */
107
timediff_t Curl_timeleft_now_ms(struct Curl_easy *data,
108
                                const struct curltime *pnow,
109
                                bool duringconnect)
110
0
{
111
0
  timediff_t timeleft_ms = 0;
112
0
  timediff_t ctimeleft_ms = 0;
113
0
  timediff_t ctimeout_ms;
114
115
  /* The duration of a connect and the total transfer are calculated from two
116
     different time-stamps. It can end up with the total timeout being reached
117
     before the connect timeout expires and we must acknowledge whichever
118
     timeout that is reached first. The total timeout is set per entire
119
     operation, while the connect timeout is set per connect. */
120
0
  if((!data->set.timeout || data->set.connect_only) && !duringconnect)
121
0
    return 0; /* no timeout in place or checked, return "no limit" */
122
123
0
  if(data->set.timeout) {
124
0
    timeleft_ms = data->set.timeout -
125
0
      curlx_ptimediff_ms(pnow, &data->progress.t_startop);
126
0
    if(!timeleft_ms)
127
0
      timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */
128
0
  }
129
130
0
  if(!duringconnect)
131
0
    return timeleft_ms; /* no connect check, this is it */
132
0
  ctimeout_ms = (data->set.connecttimeout > 0) ?
133
0
    data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT;
134
0
  ctimeleft_ms = ctimeout_ms -
135
0
    curlx_ptimediff_ms(pnow, &data->progress.t_startsingle);
136
0
  if(!ctimeleft_ms)
137
0
    ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */
138
0
  if(!timeleft_ms)
139
0
    return ctimeleft_ms; /* no general timeout, this is it */
140
141
  /* return minimal time left or max amount already expired */
142
0
  return (ctimeleft_ms < timeleft_ms) ? ctimeleft_ms : timeleft_ms;
143
0
}
144
145
timediff_t Curl_timeleft_ms(struct Curl_easy *data,
146
                            bool duringconnect)
147
0
{
148
0
  return Curl_timeleft_now_ms(data, Curl_pgrs_now(data), duringconnect);
149
0
}
150
151
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
152
                         int timeout_ms)
153
0
{
154
0
  struct connectdata *conn = data->conn;
155
156
0
  DEBUGASSERT(conn);
157
0
  conn->shutdown.start[sockindex] = *Curl_pgrs_now(data);
158
0
  conn->shutdown.timeout_ms = (timeout_ms > 0) ?
159
0
    (timediff_t)timeout_ms :
160
0
    ((data->set.shutdowntimeout > 0) ?
161
0
     data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS);
162
  /* Set a timer, unless we operate on the admin handle */
163
0
  if(data->mid)
164
0
    Curl_expire_ex(data, conn->shutdown.timeout_ms, EXPIRE_SHUTDOWN);
165
0
}
166
167
timediff_t Curl_shutdown_timeleft(struct Curl_easy *data,
168
                                  struct connectdata *conn,
169
                                  int sockindex)
170
0
{
171
0
  timediff_t left_ms;
172
173
0
  if(!conn->shutdown.start[sockindex].tv_sec ||
174
0
     (conn->shutdown.timeout_ms <= 0))
175
0
    return 0; /* not started or no limits */
176
177
0
  left_ms = conn->shutdown.timeout_ms -
178
0
            curlx_ptimediff_ms(Curl_pgrs_now(data),
179
0
                               &conn->shutdown.start[sockindex]);
180
0
  return left_ms ? left_ms : -1;
181
0
}
182
183
timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data,
184
                                       struct connectdata *conn)
185
0
{
186
0
  timediff_t left_ms = 0, ms;
187
0
  int i;
188
189
0
  for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) {
190
0
    if(!conn->shutdown.start[i].tv_sec)
191
0
      continue;
192
0
    ms = Curl_shutdown_timeleft(data, conn, i);
193
0
    if(ms && (!left_ms || ms < left_ms))
194
0
      left_ms = ms;
195
0
  }
196
0
  return left_ms;
197
0
}
198
199
void Curl_shutdown_clear(struct Curl_easy *data, int sockindex)
200
0
{
201
0
  struct curltime *pt = &data->conn->shutdown.start[sockindex];
202
0
  memset(pt, 0, sizeof(*pt));
203
0
}
204
205
bool Curl_shutdown_started(struct Curl_easy *data, int sockindex)
206
0
{
207
0
  struct curltime *pt = &data->conn->shutdown.start[sockindex];
208
0
  return (pt->tv_sec > 0) || (pt->tv_usec > 0);
209
0
}
210
211
/* retrieves ip address and port from a sockaddr structure. note it calls
212
   curlx_inet_ntop which sets errno on fail, not SOCKERRNO. */
213
bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
214
                      char *addr, uint16_t *port)
215
0
{
216
0
  struct sockaddr_in *si = NULL;
217
0
#ifdef USE_IPV6
218
0
  struct sockaddr_in6 *si6 = NULL;
219
0
#endif
220
#ifdef USE_UNIX_SOCKETS
221
  struct sockaddr_un *su = NULL;
222
#else
223
0
  (void)salen;
224
0
#endif
225
226
0
  switch(sa->sa_family) {
227
0
  case AF_INET:
228
0
    si = (struct sockaddr_in *)(void *)sa;
229
0
    if(curlx_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) {
230
0
      *port = ntohs(si->sin_port);
231
0
      return TRUE;
232
0
    }
233
0
    break;
234
0
#ifdef USE_IPV6
235
0
  case AF_INET6:
236
0
    si6 = (struct sockaddr_in6 *)(void *)sa;
237
0
    if(curlx_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) {
238
0
      *port = ntohs(si6->sin6_port);
239
0
      return TRUE;
240
0
    }
241
0
    break;
242
0
#endif
243
#ifdef USE_UNIX_SOCKETS
244
  case AF_UNIX:
245
    if(salen > (curl_socklen_t)sizeof(CURL_SA_FAMILY_T)) {
246
      su = (struct sockaddr_un *)sa;
247
      curl_msnprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path);
248
    }
249
    else
250
      addr[0] = 0; /* socket with no name */
251
    *port = 0;
252
    return TRUE;
253
#endif
254
0
  default:
255
0
    break;
256
0
  }
257
258
0
  addr[0] = '\0';
259
0
  *port = 0;
260
0
  errno = SOCKEAFNOSUPPORT;
261
0
  return FALSE;
262
0
}
263
264
/*
265
 * Used to extract socket and connectdata struct for the most recent
266
 * transfer on the given Curl_easy.
267
 *
268
 * The returned socket will be CURL_SOCKET_BAD in case of failure!
269
 */
270
curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
271
                                  struct connectdata **connp)
272
0
{
273
0
  DEBUGASSERT(data);
274
275
  /* this works for an easy handle:
276
   * - that has been used for curl_easy_perform()
277
   * - that is associated with a multi handle, and whose connection
278
   *   was detached with CURLOPT_CONNECT_ONLY
279
   */
280
0
  if(data->state.lastconnect_id != -1) {
281
0
    struct connectdata *conn;
282
283
0
    conn = Curl_cpool_get_conn(data, data->state.lastconnect_id);
284
0
    if(!conn) {
285
0
      data->state.lastconnect_id = -1;
286
0
      return CURL_SOCKET_BAD;
287
0
    }
288
289
0
    if(connp)
290
      /* only store this if the caller cares for it */
291
0
      *connp = conn;
292
0
    return conn->sock[FIRSTSOCKET];
293
0
  }
294
0
  return CURL_SOCKET_BAD;
295
0
}
296
297
/*
298
 * Curl_conncontrol() marks streams or connection for closure.
299
 */
300
void Curl_conncontrol(struct connectdata *conn,
301
                      int ctrl /* see defines in header */
302
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
303
                      , const char *reason
304
#endif
305
  )
306
0
{
307
  /* close if a connection, or a stream that is not multiplexed. */
308
  /* This function will be called both before and after this connection is
309
     associated with a transfer. */
310
0
  bool closeit, is_multiplex;
311
0
  DEBUGASSERT(conn);
312
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
313
  (void)reason; /* useful for debugging */
314
#endif
315
0
  is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
316
0
  closeit = (ctrl == CONNCTRL_CONNECTION) ||
317
0
            ((ctrl == CONNCTRL_STREAM) && !is_multiplex);
318
0
  if((ctrl == CONNCTRL_STREAM) && is_multiplex)
319
0
    ;  /* stream signal on multiplex conn never affects close state */
320
0
  else if((bit)closeit != conn->bits.close) {
321
0
    conn->bits.close = closeit; /* the only place in the source code that
322
                                   should assign this bit */
323
0
  }
324
0
}
325
326
typedef enum {
327
  CF_SETUP_INIT,
328
  CF_SETUP_CNNCT_EYEBALLS,
329
  CF_SETUP_CNNCT_SOCKS,
330
  CF_SETUP_CNNCT_HTTP_PROXY,
331
  CF_SETUP_CNNCT_HAPROXY,
332
  CF_SETUP_CNNCT_SSL,
333
  CF_SETUP_DONE
334
} cf_setup_state;
335
336
struct cf_setup_ctx {
337
  cf_setup_state state;
338
  int ssl_mode;
339
  uint8_t transport;
340
};
341
342
static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
343
                                 struct Curl_easy *data,
344
                                 bool *done)
345
0
{
346
0
  struct cf_setup_ctx *ctx = cf->ctx;
347
0
  CURLcode result = CURLE_OK;
348
0
  struct Curl_dns_entry *dns = data->state.dns[cf->sockindex];
349
350
0
  if(cf->connected) {
351
0
    *done = TRUE;
352
0
    return CURLE_OK;
353
0
  }
354
355
  /* connect current sub-chain */
356
0
connect_sub_chain:
357
0
  if(!dns)
358
0
    return CURLE_FAILED_INIT;
359
360
0
  if(cf->next && !cf->next->connected) {
361
0
    result = Curl_conn_cf_connect(cf->next, data, done);
362
0
    if(result || !*done)
363
0
      return result;
364
0
  }
365
366
0
  if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
367
0
    result = cf_ip_happy_insert_after(cf, data, ctx->transport);
368
0
    if(result)
369
0
      return result;
370
0
    ctx->state = CF_SETUP_CNNCT_EYEBALLS;
371
0
    if(!cf->next || !cf->next->connected)
372
0
      goto connect_sub_chain;
373
0
  }
374
375
  /* sub-chain connected, do we need to add more? */
376
0
#ifndef CURL_DISABLE_PROXY
377
0
  if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
378
0
    result = Curl_cf_socks_proxy_insert_after(cf, data);
379
0
    if(result)
380
0
      return result;
381
0
    ctx->state = CF_SETUP_CNNCT_SOCKS;
382
0
    if(!cf->next || !cf->next->connected)
383
0
      goto connect_sub_chain;
384
0
  }
385
386
0
  if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
387
#ifdef USE_SSL
388
    if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) &&
389
       !Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
390
      result = Curl_cf_ssl_proxy_insert_after(cf, data);
391
      if(result)
392
        return result;
393
    }
394
#endif /* USE_SSL */
395
396
0
#ifndef CURL_DISABLE_HTTP
397
0
    if(cf->conn->bits.tunnel_proxy) {
398
0
      result = Curl_cf_http_proxy_insert_after(cf, data);
399
0
      if(result)
400
0
        return result;
401
0
    }
402
0
#endif /* !CURL_DISABLE_HTTP */
403
0
    ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
404
0
    if(!cf->next || !cf->next->connected)
405
0
      goto connect_sub_chain;
406
0
  }
407
0
#endif /* !CURL_DISABLE_PROXY */
408
409
0
  if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
410
0
#ifndef CURL_DISABLE_PROXY
411
0
    if(data->set.haproxyprotocol) {
412
0
      if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
413
0
        failf(data, "haproxy protocol not support with SSL "
414
0
              "encryption in place (QUIC?)");
415
0
        return CURLE_UNSUPPORTED_PROTOCOL;
416
0
      }
417
0
      result = Curl_cf_haproxy_insert_after(cf, data);
418
0
      if(result)
419
0
        return result;
420
0
    }
421
0
#endif /* !CURL_DISABLE_PROXY */
422
0
    ctx->state = CF_SETUP_CNNCT_HAPROXY;
423
0
    if(!cf->next || !cf->next->connected)
424
0
      goto connect_sub_chain;
425
0
  }
426
427
0
  if(ctx->state < CF_SETUP_CNNCT_SSL) {
428
#ifdef USE_SSL
429
    if((ctx->ssl_mode == CURL_CF_SSL_ENABLE ||
430
        (ctx->ssl_mode != CURL_CF_SSL_DISABLE &&
431
         cf->conn->handler->flags & PROTOPT_SSL))       /* we want SSL */
432
       && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
433
      result = Curl_cf_ssl_insert_after(cf, data);
434
      if(result)
435
        return result;
436
    }
437
#endif /* USE_SSL */
438
0
    ctx->state = CF_SETUP_CNNCT_SSL;
439
0
    if(!cf->next || !cf->next->connected)
440
0
      goto connect_sub_chain;
441
0
  }
442
443
0
  ctx->state = CF_SETUP_DONE;
444
0
  cf->connected = TRUE;
445
0
  *done = TRUE;
446
0
  return CURLE_OK;
447
0
}
448
449
static void cf_setup_close(struct Curl_cfilter *cf,
450
                           struct Curl_easy *data)
451
0
{
452
0
  struct cf_setup_ctx *ctx = cf->ctx;
453
454
0
  CURL_TRC_CF(data, cf, "close");
455
0
  cf->connected = FALSE;
456
0
  ctx->state = CF_SETUP_INIT;
457
458
0
  if(cf->next) {
459
0
    cf->next->cft->do_close(cf->next, data);
460
0
    Curl_conn_cf_discard_chain(&cf->next, data);
461
0
  }
462
0
}
463
464
static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
465
0
{
466
0
  struct cf_setup_ctx *ctx = cf->ctx;
467
468
0
  (void)data;
469
0
  CURL_TRC_CF(data, cf, "destroy");
470
0
  Curl_safefree(ctx);
471
0
}
472
473
struct Curl_cftype Curl_cft_setup = {
474
  "SETUP",
475
  0,
476
  CURL_LOG_LVL_NONE,
477
  cf_setup_destroy,
478
  cf_setup_connect,
479
  cf_setup_close,
480
  Curl_cf_def_shutdown,
481
  Curl_cf_def_adjust_pollset,
482
  Curl_cf_def_data_pending,
483
  Curl_cf_def_send,
484
  Curl_cf_def_recv,
485
  Curl_cf_def_cntrl,
486
  Curl_cf_def_conn_is_alive,
487
  Curl_cf_def_conn_keep_alive,
488
  Curl_cf_def_query,
489
};
490
491
static CURLcode cf_setup_create(struct Curl_cfilter **pcf,
492
                                struct Curl_easy *data,
493
                                uint8_t transport,
494
                                int ssl_mode)
495
0
{
496
0
  struct Curl_cfilter *cf = NULL;
497
0
  struct cf_setup_ctx *ctx;
498
0
  CURLcode result = CURLE_OK;
499
500
0
  (void)data;
501
0
  ctx = curlx_calloc(1, sizeof(*ctx));
502
0
  if(!ctx) {
503
0
    result = CURLE_OUT_OF_MEMORY;
504
0
    goto out;
505
0
  }
506
0
  ctx->state = CF_SETUP_INIT;
507
0
  ctx->ssl_mode = ssl_mode;
508
0
  ctx->transport = transport;
509
510
0
  result = Curl_cf_create(&cf, &Curl_cft_setup, ctx);
511
0
  if(result)
512
0
    goto out;
513
0
  ctx = NULL;
514
515
0
out:
516
0
  *pcf = result ? NULL : cf;
517
0
  if(ctx) {
518
0
    curlx_free(ctx);
519
0
  }
520
0
  return result;
521
0
}
522
523
static CURLcode cf_setup_add(struct Curl_easy *data,
524
                             struct connectdata *conn,
525
                             int sockindex,
526
                             uint8_t transport,
527
                             int ssl_mode)
528
0
{
529
0
  struct Curl_cfilter *cf;
530
0
  CURLcode result = CURLE_OK;
531
532
0
  DEBUGASSERT(data);
533
0
  result = cf_setup_create(&cf, data, transport, ssl_mode);
534
0
  if(result)
535
0
    goto out;
536
0
  Curl_conn_cf_add(data, conn, sockindex, cf);
537
0
out:
538
0
  return result;
539
0
}
540
541
CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
542
                                    struct Curl_easy *data,
543
                                    uint8_t transport,
544
                                    int ssl_mode)
545
0
{
546
0
  struct Curl_cfilter *cf;
547
0
  CURLcode result;
548
549
0
  DEBUGASSERT(data);
550
0
  result = cf_setup_create(&cf, data, transport, ssl_mode);
551
0
  if(result)
552
0
    goto out;
553
0
  Curl_conn_cf_insert_after(cf_at, cf);
554
0
out:
555
0
  return result;
556
0
}
557
558
CURLcode Curl_conn_setup(struct Curl_easy *data,
559
                         struct connectdata *conn,
560
                         int sockindex,
561
                         struct Curl_dns_entry *dns,
562
                         int ssl_mode)
563
0
{
564
0
  CURLcode result = CURLE_OK;
565
566
0
  DEBUGASSERT(data);
567
0
  DEBUGASSERT(conn->handler);
568
0
  DEBUGASSERT(dns);
569
570
0
  Curl_resolv_unlink(data, &data->state.dns[sockindex]);
571
0
  data->state.dns[sockindex] = dns;
572
573
0
#ifndef CURL_DISABLE_HTTP
574
0
  if(!conn->cfilter[sockindex] &&
575
0
     conn->handler->protocol == CURLPROTO_HTTPS) {
576
0
    DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE);
577
0
    result = Curl_cf_https_setup(data, conn, sockindex);
578
0
    if(result)
579
0
      goto out;
580
0
  }
581
0
#endif /* !CURL_DISABLE_HTTP */
582
583
  /* Still no cfilter set, apply default. */
584
0
  if(!conn->cfilter[sockindex]) {
585
0
    result = cf_setup_add(data, conn, sockindex,
586
0
                          conn->transport_wanted, ssl_mode);
587
0
    if(result)
588
0
      goto out;
589
0
  }
590
591
0
  DEBUGASSERT(conn->cfilter[sockindex]);
592
0
out:
593
0
  if(result)
594
0
    Curl_resolv_unlink(data, &data->state.dns[sockindex]);
595
0
  return result;
596
0
}
597
598
void Curl_conn_set_multiplex(struct connectdata *conn)
599
0
{
600
0
  if(!conn->bits.multiplex) {
601
0
    conn->bits.multiplex = TRUE;
602
0
    if(conn->attached_multi) {
603
0
      Curl_multi_connchanged(conn->attached_multi);
604
0
    }
605
0
  }
606
0
}