Coverage Report

Created: 2026-03-11 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/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 vtls 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
0
{
75
0
  if(len == 2) {
76
0
    if(!memcmp(name, "h1", 2))
77
0
      return ALPN_h1;
78
0
    if(!memcmp(name, "h2", 2))
79
0
      return ALPN_h2;
80
0
    if(!memcmp(name, "h3", 2))
81
0
      return ALPN_h3;
82
0
  }
83
0
  else if(len == 8) {
84
0
    if(!memcmp(name, "http/1.1", 8))
85
0
      return ALPN_h1;
86
0
  }
87
0
  return ALPN_none; /* unknown, probably rubbish input */
88
0
}
89
90
enum alpnid Curl_str2alpnid(const struct Curl_str *cstr)
91
0
{
92
0
  return Curl_alpn2alpnid((const unsigned char *)curlx_str(cstr),
93
0
                          curlx_strlen(cstr));
94
0
}
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
 * @unittest: 1303
104
 */
105
timediff_t Curl_timeleft_now_ms(struct Curl_easy *data,
106
                                const struct curltime *pnow)
107
0
{
108
0
  timediff_t timeleft_ms = 0;
109
0
  timediff_t ctimeleft_ms = 0;
110
111
0
  if(Curl_shutdown_started(data, FIRSTSOCKET))
112
0
    return Curl_shutdown_timeleft(data, data->conn, FIRSTSOCKET);
113
0
  else if(Curl_is_connecting(data)) {
114
0
    timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ?
115
0
      data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT;
116
0
    ctimeleft_ms = ctimeout_ms -
117
0
      curlx_ptimediff_ms(pnow, &data->progress.t_startsingle);
118
0
    if(!ctimeleft_ms)
119
0
      ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */
120
0
  }
121
0
  else if(!data->set.timeout || data->set.connect_only) {
122
0
    return 0; /* no timeout in place or checked, return "no limit" */
123
0
  }
124
125
0
  if(data->set.timeout) {
126
0
    timeleft_ms = data->set.timeout -
127
0
      curlx_ptimediff_ms(pnow, &data->progress.t_startop);
128
0
    if(!timeleft_ms)
129
0
      timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */
130
0
  }
131
132
0
  if(!ctimeleft_ms)
133
0
    return timeleft_ms;
134
0
  else if(!timeleft_ms)
135
0
    return ctimeleft_ms;
136
0
  return CURLMIN(ctimeleft_ms, timeleft_ms);
137
0
}
138
139
timediff_t Curl_timeleft_ms(struct Curl_easy *data)
140
0
{
141
0
  return Curl_timeleft_now_ms(data, Curl_pgrs_now(data));
142
0
}
143
144
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
145
                         int timeout_ms)
146
0
{
147
0
  struct connectdata *conn = data->conn;
148
149
0
  DEBUGASSERT(conn);
150
0
  conn->shutdown.start[sockindex] = *Curl_pgrs_now(data);
151
0
  conn->shutdown.timeout_ms = (timeout_ms > 0) ?
152
0
    (timediff_t)timeout_ms :
153
0
    ((data->set.shutdowntimeout > 0) ?
154
0
     data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS);
155
  /* Set a timer, unless we operate on the admin handle */
156
0
  if(data->mid)
157
0
    Curl_expire_ex(data, conn->shutdown.timeout_ms, EXPIRE_SHUTDOWN);
158
0
  CURL_TRC_M(data, "shutdown start on%s connection",
159
0
             sockindex ? " secondary" : "");
160
0
}
161
162
timediff_t Curl_shutdown_timeleft(struct Curl_easy *data,
163
                                  struct connectdata *conn,
164
                                  int sockindex)
165
0
{
166
0
  timediff_t left_ms;
167
168
0
  if(!conn->shutdown.start[sockindex].tv_sec ||
169
0
     (conn->shutdown.timeout_ms <= 0))
170
0
    return 0; /* not started or no limits */
171
172
0
  left_ms = conn->shutdown.timeout_ms -
173
0
            curlx_ptimediff_ms(Curl_pgrs_now(data),
174
0
                               &conn->shutdown.start[sockindex]);
175
0
  return left_ms ? left_ms : -1;
176
0
}
177
178
timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data,
179
                                       struct connectdata *conn)
180
0
{
181
0
  timediff_t left_ms = 0, ms;
182
0
  int i;
183
184
0
  for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) {
185
0
    if(!conn->shutdown.start[i].tv_sec)
186
0
      continue;
187
0
    ms = Curl_shutdown_timeleft(data, conn, i);
188
0
    if(ms && (!left_ms || ms < left_ms))
189
0
      left_ms = ms;
190
0
  }
191
0
  return left_ms;
192
0
}
193
194
void Curl_shutdown_clear(struct Curl_easy *data, int sockindex)
195
0
{
196
0
  struct curltime *pt = &data->conn->shutdown.start[sockindex];
197
0
  memset(pt, 0, sizeof(*pt));
198
0
}
199
200
bool Curl_shutdown_started(struct Curl_easy *data, int sockindex)
201
0
{
202
0
  if(data->conn) {
203
0
    struct curltime *pt = &data->conn->shutdown.start[sockindex];
204
0
    return (pt->tv_sec > 0) || (pt->tv_usec > 0);
205
0
  }
206
0
  return FALSE;
207
0
}
208
209
/* retrieves ip address and port from a sockaddr structure. note it calls
210
   curlx_inet_ntop which sets errno on fail, not SOCKERRNO. */
211
bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
212
                      char *addr, uint16_t *port)
213
0
{
214
0
  struct sockaddr_in *si = NULL;
215
0
#ifdef USE_IPV6
216
0
  struct sockaddr_in6 *si6 = NULL;
217
0
#endif
218
0
#ifdef USE_UNIX_SOCKETS
219
0
  struct sockaddr_un *su = NULL;
220
#else
221
  (void)salen;
222
#endif
223
224
0
  switch(sa->sa_family) {
225
0
  case AF_INET:
226
0
    si = (struct sockaddr_in *)(void *)sa;
227
0
    if(curlx_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) {
228
0
      *port = ntohs(si->sin_port);
229
0
      return TRUE;
230
0
    }
231
0
    break;
232
0
#ifdef USE_IPV6
233
0
  case AF_INET6:
234
0
    si6 = (struct sockaddr_in6 *)(void *)sa;
235
0
    if(curlx_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) {
236
0
      *port = ntohs(si6->sin6_port);
237
0
      return TRUE;
238
0
    }
239
0
    break;
240
0
#endif
241
0
#ifdef USE_UNIX_SOCKETS
242
0
  case AF_UNIX:
243
0
    if(salen > (curl_socklen_t)sizeof(CURL_SA_FAMILY_T)) {
244
0
      su = (struct sockaddr_un *)sa;
245
0
      curl_msnprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path);
246
0
    }
247
0
    else
248
0
      addr[0] = 0; /* socket with no name */
249
0
    *port = 0;
250
0
    return TRUE;
251
0
#endif
252
0
  default:
253
0
    break;
254
0
  }
255
256
0
  addr[0] = '\0';
257
0
  *port = 0;
258
0
  errno = SOCKEAFNOSUPPORT;
259
0
  return FALSE;
260
0
}
261
262
/*
263
 * Used to extract socket and connectdata struct for the most recent
264
 * transfer on the given Curl_easy.
265
 *
266
 * The returned socket will be CURL_SOCKET_BAD in case of failure!
267
 */
268
curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
269
                                  struct connectdata **connp)
270
0
{
271
0
  DEBUGASSERT(data);
272
273
  /* this works for an easy handle:
274
   * - that has been used for curl_easy_perform()
275
   * - that is associated with a multi handle, and whose connection
276
   *   was detached with CURLOPT_CONNECT_ONLY
277
   */
278
0
  if(data->state.lastconnect_id != -1) {
279
0
    struct connectdata *conn;
280
281
0
    conn = Curl_cpool_get_conn(data, data->state.lastconnect_id);
282
0
    if(!conn) {
283
0
      data->state.lastconnect_id = -1;
284
0
      return CURL_SOCKET_BAD;
285
0
    }
286
287
0
    if(connp)
288
      /* only store this if the caller cares for it */
289
0
      *connp = conn;
290
0
    return conn->sock[FIRSTSOCKET];
291
0
  }
292
0
  return CURL_SOCKET_BAD;
293
0
}
294
295
/*
296
 * Curl_conncontrol() marks streams or connection for closure.
297
 */
298
void Curl_conncontrol(struct connectdata *conn,
299
                      int ctrl /* see defines in header */
300
#if defined(DEBUGBUILD) && defined(CURLVERBOSE)
301
                      , const char *reason
302
#endif
303
  )
304
0
{
305
  /* close if a connection, or a stream that is not multiplexed. */
306
  /* This function will be called both before and after this connection is
307
     associated with a transfer. */
308
0
  bool closeit, is_multiplex;
309
0
  DEBUGASSERT(conn);
310
0
#if defined(DEBUGBUILD) && defined(CURLVERBOSE)
311
0
  (void)reason; /* useful for debugging */
312
0
#endif
313
0
  is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
314
0
  closeit = (ctrl == CONNCTRL_CONNECTION) ||
315
0
            ((ctrl == CONNCTRL_STREAM) && !is_multiplex);
316
0
  if((ctrl == CONNCTRL_STREAM) && is_multiplex)
317
0
    ;  /* stream signal on multiplex conn never affects close state */
318
0
  else if((curl_bit)closeit != conn->bits.close) {
319
0
    conn->bits.close = closeit; /* the only place in the source code that
320
                                   should assign this bit */
321
0
  }
322
0
}
323
324
typedef enum {
325
  CF_SETUP_INIT,
326
  CF_SETUP_CNNCT_EYEBALLS,
327
  CF_SETUP_CNNCT_SOCKS,
328
  CF_SETUP_CNNCT_HTTP_PROXY,
329
  CF_SETUP_CNNCT_HAPROXY,
330
  CF_SETUP_CNNCT_SSL,
331
  CF_SETUP_DONE
332
} cf_setup_state;
333
334
struct cf_setup_ctx {
335
  cf_setup_state state;
336
  int ssl_mode;
337
  uint8_t transport;
338
};
339
340
static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
341
                                 struct Curl_easy *data,
342
                                 bool *done)
343
0
{
344
0
  struct cf_setup_ctx *ctx = cf->ctx;
345
0
  CURLcode result = CURLE_OK;
346
0
  struct Curl_dns_entry *dns = data->state.dns[cf->sockindex];
347
348
0
  if(cf->connected) {
349
0
    *done = TRUE;
350
0
    return CURLE_OK;
351
0
  }
352
353
  /* connect current sub-chain */
354
0
connect_sub_chain:
355
0
  if(!dns)
356
0
    return CURLE_FAILED_INIT;
357
358
0
  if(cf->next && !cf->next->connected) {
359
0
    result = Curl_conn_cf_connect(cf->next, data, done);
360
0
    if(result || !*done)
361
0
      return result;
362
0
  }
363
364
0
  if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
365
0
    result = cf_ip_happy_insert_after(cf, data, ctx->transport);
366
0
    if(result)
367
0
      return result;
368
0
    ctx->state = CF_SETUP_CNNCT_EYEBALLS;
369
0
    if(!cf->next || !cf->next->connected)
370
0
      goto connect_sub_chain;
371
0
  }
372
373
  /* sub-chain connected, do we need to add more? */
374
0
#ifndef CURL_DISABLE_PROXY
375
0
  if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
376
0
    result = Curl_cf_socks_proxy_insert_after(cf, data);
377
0
    if(result)
378
0
      return result;
379
0
    ctx->state = CF_SETUP_CNNCT_SOCKS;
380
0
    if(!cf->next || !cf->next->connected)
381
0
      goto connect_sub_chain;
382
0
  }
383
384
0
  if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
385
0
#ifdef USE_SSL
386
0
    if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) &&
387
0
       !Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
388
0
      result = Curl_cf_ssl_proxy_insert_after(cf, data);
389
0
      if(result)
390
0
        return result;
391
0
    }
392
0
#endif /* USE_SSL */
393
394
0
#ifndef CURL_DISABLE_HTTP
395
0
    if(cf->conn->bits.tunnel_proxy) {
396
0
      result = Curl_cf_http_proxy_insert_after(cf, data);
397
0
      if(result)
398
0
        return result;
399
0
    }
400
0
#endif /* !CURL_DISABLE_HTTP */
401
0
    ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
402
0
    if(!cf->next || !cf->next->connected)
403
0
      goto connect_sub_chain;
404
0
  }
405
0
#endif /* !CURL_DISABLE_PROXY */
406
407
0
  if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
408
0
#ifndef CURL_DISABLE_PROXY
409
0
    if(data->set.haproxyprotocol) {
410
0
      if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
411
0
        failf(data, "haproxy protocol not support with SSL "
412
0
              "encryption in place (QUIC?)");
413
0
        return CURLE_UNSUPPORTED_PROTOCOL;
414
0
      }
415
0
      result = Curl_cf_haproxy_insert_after(cf, data);
416
0
      if(result)
417
0
        return result;
418
0
    }
419
0
#endif /* !CURL_DISABLE_PROXY */
420
0
    ctx->state = CF_SETUP_CNNCT_HAPROXY;
421
0
    if(!cf->next || !cf->next->connected)
422
0
      goto connect_sub_chain;
423
0
  }
424
425
0
  if(ctx->state < CF_SETUP_CNNCT_SSL) {
426
0
#ifdef USE_SSL
427
0
    if((ctx->ssl_mode == CURL_CF_SSL_ENABLE ||
428
0
        (ctx->ssl_mode != CURL_CF_SSL_DISABLE &&
429
0
         cf->conn->scheme->flags & PROTOPT_SSL)) &&  /* we want SSL */
430
0
       !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
431
0
      result = Curl_cf_ssl_insert_after(cf, data);
432
0
      if(result)
433
0
        return result;
434
0
    }
435
0
#endif /* USE_SSL */
436
0
    ctx->state = CF_SETUP_CNNCT_SSL;
437
0
    if(!cf->next || !cf->next->connected)
438
0
      goto connect_sub_chain;
439
0
  }
440
441
0
  ctx->state = CF_SETUP_DONE;
442
0
  cf->connected = TRUE;
443
0
  *done = TRUE;
444
0
  return CURLE_OK;
445
0
}
446
447
static void cf_setup_close(struct Curl_cfilter *cf,
448
                           struct Curl_easy *data)
449
0
{
450
0
  struct cf_setup_ctx *ctx = cf->ctx;
451
452
0
  CURL_TRC_CF(data, cf, "close");
453
0
  cf->connected = FALSE;
454
0
  ctx->state = CF_SETUP_INIT;
455
456
0
  if(cf->next) {
457
0
    cf->next->cft->do_close(cf->next, data);
458
0
    Curl_conn_cf_discard_chain(&cf->next, data);
459
0
  }
460
0
}
461
462
static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
463
0
{
464
0
  struct cf_setup_ctx *ctx = cf->ctx;
465
466
0
  CURL_TRC_CF(data, cf, "destroy");
467
0
  Curl_safefree(ctx);
468
0
}
469
470
struct Curl_cftype Curl_cft_setup = {
471
  "SETUP",
472
  0,
473
  CURL_LOG_LVL_NONE,
474
  cf_setup_destroy,
475
  cf_setup_connect,
476
  cf_setup_close,
477
  Curl_cf_def_shutdown,
478
  Curl_cf_def_adjust_pollset,
479
  Curl_cf_def_data_pending,
480
  Curl_cf_def_send,
481
  Curl_cf_def_recv,
482
  Curl_cf_def_cntrl,
483
  Curl_cf_def_conn_is_alive,
484
  Curl_cf_def_conn_keep_alive,
485
  Curl_cf_def_query,
486
};
487
488
static CURLcode cf_setup_create(struct Curl_cfilter **pcf,
489
                                struct Curl_easy *data,
490
                                uint8_t transport,
491
                                int ssl_mode)
492
0
{
493
0
  struct Curl_cfilter *cf = NULL;
494
0
  struct cf_setup_ctx *ctx;
495
0
  CURLcode result = CURLE_OK;
496
497
0
  (void)data;
498
0
  ctx = curlx_calloc(1, sizeof(*ctx));
499
0
  if(!ctx) {
500
0
    result = CURLE_OUT_OF_MEMORY;
501
0
    goto out;
502
0
  }
503
0
  ctx->state = CF_SETUP_INIT;
504
0
  ctx->ssl_mode = ssl_mode;
505
0
  ctx->transport = transport;
506
507
0
  result = Curl_cf_create(&cf, &Curl_cft_setup, ctx);
508
0
  if(result)
509
0
    goto out;
510
0
  ctx = NULL;
511
512
0
out:
513
0
  *pcf = result ? NULL : cf;
514
0
  if(ctx) {
515
0
    curlx_free(ctx);
516
0
  }
517
0
  return result;
518
0
}
519
520
static CURLcode cf_setup_add(struct Curl_easy *data,
521
                             struct connectdata *conn,
522
                             int sockindex,
523
                             uint8_t transport,
524
                             int ssl_mode)
525
0
{
526
0
  struct Curl_cfilter *cf;
527
0
  CURLcode result = CURLE_OK;
528
529
0
  DEBUGASSERT(data);
530
0
  result = cf_setup_create(&cf, data, transport, ssl_mode);
531
0
  if(result)
532
0
    goto out;
533
0
  Curl_conn_cf_add(data, conn, sockindex, cf);
534
0
out:
535
0
  return result;
536
0
}
537
538
CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
539
                                    struct Curl_easy *data,
540
                                    uint8_t transport,
541
                                    int ssl_mode)
542
0
{
543
0
  struct Curl_cfilter *cf;
544
0
  CURLcode result;
545
546
0
  DEBUGASSERT(data);
547
0
  result = cf_setup_create(&cf, data, transport, ssl_mode);
548
0
  if(result)
549
0
    goto out;
550
0
  Curl_conn_cf_insert_after(cf_at, cf);
551
0
out:
552
0
  return result;
553
0
}
554
555
CURLcode Curl_conn_setup(struct Curl_easy *data,
556
                         struct connectdata *conn,
557
                         int sockindex,
558
                         struct Curl_dns_entry *dns,
559
                         int ssl_mode)
560
0
{
561
0
  CURLcode result = CURLE_OK;
562
563
0
  DEBUGASSERT(data);
564
0
  DEBUGASSERT(conn->scheme);
565
0
  DEBUGASSERT(dns);
566
567
0
  Curl_resolv_unlink(data, &data->state.dns[sockindex]);
568
0
  data->state.dns[sockindex] = dns;
569
570
0
#ifndef CURL_DISABLE_HTTP
571
0
  if(!conn->cfilter[sockindex] &&
572
0
     conn->scheme->protocol == CURLPROTO_HTTPS) {
573
0
    DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE);
574
0
    result = Curl_cf_https_setup(data, conn, sockindex);
575
0
    if(result)
576
0
      goto out;
577
0
  }
578
0
#endif /* !CURL_DISABLE_HTTP */
579
580
  /* Still no cfilter set, apply default. */
581
0
  if(!conn->cfilter[sockindex]) {
582
0
    result = cf_setup_add(data, conn, sockindex,
583
0
                          conn->transport_wanted, ssl_mode);
584
0
    if(result)
585
0
      goto out;
586
0
  }
587
588
0
  DEBUGASSERT(conn->cfilter[sockindex]);
589
0
out:
590
0
  if(result)
591
0
    Curl_resolv_unlink(data, &data->state.dns[sockindex]);
592
0
  return result;
593
0
}
594
595
void Curl_conn_set_multiplex(struct connectdata *conn)
596
0
{
597
0
  if(!conn->bits.multiplex) {
598
0
    conn->bits.multiplex = TRUE;
599
0
    if(conn->attached_multi) {
600
0
      Curl_multi_connchanged(conn->attached_multi);
601
0
    }
602
0
  }
603
0
}