xref: /third_party/curl/tests/unit/unit2600.c (revision 13498266)
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 "curlcheck.h"
25
26#ifdef HAVE_NETINET_IN_H
27#include <netinet/in.h>
28#endif
29#ifdef HAVE_NETINET_IN6_H
30#include <netinet/in6.h>
31#endif
32#ifdef HAVE_NETDB_H
33#include <netdb.h>
34#endif
35#ifdef HAVE_ARPA_INET_H
36#include <arpa/inet.h>
37#endif
38#ifdef __VMS
39#include <in.h>
40#include <inet.h>
41#endif
42
43#include <setjmp.h>
44#include <signal.h>
45
46#include "urldata.h"
47#include "connect.h"
48#include "cfilters.h"
49#include "multiif.h"
50#include "curl_trc.h"
51
52
53static CURL *easy;
54
55static CURLcode unit_setup(void)
56{
57  CURLcode res = CURLE_OK;
58
59  global_init(CURL_GLOBAL_ALL);
60  easy = curl_easy_init();
61  if(!easy) {
62    curl_global_cleanup();
63    return CURLE_OUT_OF_MEMORY;
64  }
65  curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
66  return res;
67}
68
69static void unit_stop(void)
70{
71  curl_easy_cleanup(easy);
72  curl_global_cleanup();
73}
74
75#ifdef DEBUGBUILD
76
77struct test_case {
78  int id;
79  const char *url;
80  const char *resolve_info;
81  unsigned char ip_version;
82  timediff_t connect_timeout_ms;
83  timediff_t he_timeout_ms;
84  timediff_t cf4_fail_delay_ms;
85  timediff_t cf6_fail_delay_ms;
86
87  int exp_cf4_creations;
88  int exp_cf6_creations;
89  timediff_t min_duration_ms;
90  timediff_t max_duration_ms;
91  CURLcode exp_result;
92  const char *pref_family;
93};
94
95struct ai_family_stats {
96  const char *family;
97  int creations;
98  timediff_t first_created;
99  timediff_t last_created;
100};
101
102struct test_result {
103  CURLcode result;
104  struct curltime started;
105  struct curltime ended;
106  struct ai_family_stats cf4;
107  struct ai_family_stats cf6;
108};
109
110static struct test_case *current_tc;
111static struct test_result *current_tr;
112
113struct cf_test_ctx {
114  int ai_family;
115  int transport;
116  char id[16];
117  struct curltime started;
118  timediff_t fail_delay_ms;
119  struct ai_family_stats *stats;
120};
121
122static void cf_test_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
123{
124  struct cf_test_ctx *ctx = cf->ctx;
125#ifndef CURL_DISABLE_VERBOSE_STRINGS
126  infof(data, "%04dms: cf[%s] destroyed",
127        (int)Curl_timediff(Curl_now(), current_tr->started), ctx->id);
128#else
129  (void)data;
130#endif
131  free(ctx);
132  cf->ctx = NULL;
133}
134
135static CURLcode cf_test_connect(struct Curl_cfilter *cf,
136                                struct Curl_easy *data,
137                                bool blocking, bool *done)
138{
139  struct cf_test_ctx *ctx = cf->ctx;
140  timediff_t duration_ms;
141
142  (void)data;
143  (void)blocking;
144  *done = FALSE;
145  duration_ms = Curl_timediff(Curl_now(), ctx->started);
146  if(duration_ms >= ctx->fail_delay_ms) {
147    infof(data, "%04dms: cf[%s] fail delay reached",
148          (int)duration_ms, ctx->id);
149    return CURLE_COULDNT_CONNECT;
150  }
151  if(duration_ms)
152    infof(data, "%04dms: cf[%s] continuing", (int)duration_ms, ctx->id);
153  Curl_expire(data, ctx->fail_delay_ms - duration_ms, EXPIRE_RUN_NOW);
154  return CURLE_OK;
155}
156
157static struct Curl_cftype cft_test = {
158  "TEST",
159  CF_TYPE_IP_CONNECT,
160  CURL_LOG_LVL_NONE,
161  cf_test_destroy,
162  cf_test_connect,
163  Curl_cf_def_close,
164  Curl_cf_def_get_host,
165  Curl_cf_def_adjust_pollset,
166  Curl_cf_def_data_pending,
167  Curl_cf_def_send,
168  Curl_cf_def_recv,
169  Curl_cf_def_cntrl,
170  Curl_cf_def_conn_is_alive,
171  Curl_cf_def_conn_keep_alive,
172  Curl_cf_def_query,
173};
174
175static CURLcode cf_test_create(struct Curl_cfilter **pcf,
176                               struct Curl_easy *data,
177                               struct connectdata *conn,
178                               const struct Curl_addrinfo *ai,
179                               int transport)
180{
181  struct cf_test_ctx *ctx = NULL;
182  struct Curl_cfilter *cf = NULL;
183  timediff_t created_at;
184  CURLcode result;
185
186  (void)data;
187  (void)conn;
188  ctx = calloc(1, sizeof(*ctx));
189  if(!ctx) {
190    result = CURLE_OUT_OF_MEMORY;
191    goto out;
192  }
193  ctx->ai_family = ai->ai_family;
194  ctx->transport = transport;
195  ctx->started = Curl_now();
196#ifdef ENABLE_IPV6
197  if(ctx->ai_family == AF_INET6) {
198    ctx->stats = &current_tr->cf6;
199    ctx->fail_delay_ms = current_tc->cf6_fail_delay_ms;
200    curl_msprintf(ctx->id, "v6-%d", ctx->stats->creations);
201    ctx->stats->creations++;
202  }
203  else
204#endif
205  {
206    ctx->stats = &current_tr->cf4;
207    ctx->fail_delay_ms = current_tc->cf4_fail_delay_ms;
208    curl_msprintf(ctx->id, "v4-%d", ctx->stats->creations);
209    ctx->stats->creations++;
210  }
211
212  created_at = Curl_timediff(ctx->started, current_tr->started);
213  if(ctx->stats->creations == 1)
214    ctx->stats->first_created = created_at;
215  ctx->stats->last_created = created_at;
216  infof(data, "%04dms: cf[%s] created", (int)created_at, ctx->id);
217
218  result = Curl_cf_create(&cf, &cft_test, ctx);
219  if(result)
220    goto out;
221
222  Curl_expire(data, ctx->fail_delay_ms, EXPIRE_RUN_NOW);
223
224out:
225  *pcf = (!result)? cf : NULL;
226  if(result) {
227    free(cf);
228    free(ctx);
229  }
230  return result;
231}
232
233static void check_result(struct test_case *tc,
234                         struct test_result *tr)
235{
236  char msg[256];
237  timediff_t duration_ms;
238
239  duration_ms = Curl_timediff(tr->ended, tr->started);
240  fprintf(stderr, "%d: test case took %dms\n", tc->id, (int)duration_ms);
241
242  if(tr->result != tc->exp_result
243    && CURLE_OPERATION_TIMEDOUT != tr->result) {
244    /* on CI we encounter the TIMEOUT result, since images get less CPU
245     * and events are not as sharply timed. */
246    curl_msprintf(msg, "%d: expected result %d but got %d",
247                  tc->id, tc->exp_result, tr->result);
248    fail(msg);
249  }
250  if(tr->cf4.creations != tc->exp_cf4_creations) {
251    curl_msprintf(msg, "%d: expected %d ipv4 creations, but got %d",
252                  tc->id, tc->exp_cf4_creations, tr->cf4.creations);
253    fail(msg);
254  }
255  if(tr->cf6.creations != tc->exp_cf6_creations) {
256    curl_msprintf(msg, "%d: expected %d ipv6 creations, but got %d",
257                  tc->id, tc->exp_cf6_creations, tr->cf6.creations);
258    fail(msg);
259  }
260
261  duration_ms = Curl_timediff(tr->ended, tr->started);
262  if(duration_ms < tc->min_duration_ms) {
263    curl_msprintf(msg, "%d: expected min duration of %dms, but took %dms",
264                  tc->id, (int)tc->min_duration_ms, (int)duration_ms);
265    fail(msg);
266  }
267  if(duration_ms > tc->max_duration_ms) {
268    curl_msprintf(msg, "%d: expected max duration of %dms, but took %dms",
269                  tc->id, (int)tc->max_duration_ms, (int)duration_ms);
270    fail(msg);
271  }
272  if(tr->cf6.creations && tr->cf4.creations && tc->pref_family) {
273    /* did ipv4 and ipv6 both, expect the preferred family to start right arway
274     * with the other being delayed by the happy_eyeball_timeout */
275    struct ai_family_stats *stats1 = !strcmp(tc->pref_family, "v6")?
276                                     &tr->cf6 : &tr->cf4;
277    struct ai_family_stats *stats2 = !strcmp(tc->pref_family, "v6")?
278                                     &tr->cf4 : &tr->cf6;
279
280    if(stats1->first_created > 100) {
281      curl_msprintf(msg, "%d: expected ip%s to start right away, instead "
282                    "first attempt made after %dms",
283                    tc->id, stats1->family, (int)stats1->first_created);
284      fail(msg);
285    }
286    if(stats2->first_created < tc->he_timeout_ms) {
287      curl_msprintf(msg, "%d: expected ip%s to start delayed after %dms, "
288                    "instead first attempt made after %dms",
289                    tc->id, stats2->family, (int)tc->he_timeout_ms,
290                    (int)stats2->first_created);
291      fail(msg);
292    }
293  }
294}
295
296static void test_connect(struct test_case *tc)
297{
298  struct test_result tr;
299  struct curl_slist *list = NULL;
300
301  Curl_debug_set_transport_provider(TRNSPRT_TCP, cf_test_create);
302  current_tc = tc;
303  current_tr = &tr;
304
305  list = curl_slist_append(NULL, tc->resolve_info);
306  fail_unless(list, "error allocating resolve list entry");
307  curl_easy_setopt(easy, CURLOPT_RESOLVE, list);
308  curl_easy_setopt(easy, CURLOPT_IPRESOLVE, (long)tc->ip_version);
309  curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS,
310                   (long)tc->connect_timeout_ms);
311  curl_easy_setopt(easy, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS,
312                   (long)tc->he_timeout_ms);
313
314  curl_easy_setopt(easy, CURLOPT_URL, tc->url);
315  memset(&tr, 0, sizeof(tr));
316  tr.cf6.family = "v6";
317  tr.cf4.family = "v4";
318
319  tr.started = Curl_now();
320  tr.result = curl_easy_perform(easy);
321  tr.ended = Curl_now();
322
323  curl_easy_setopt(easy, CURLOPT_RESOLVE, NULL);
324  curl_slist_free_all(list);
325  list = NULL;
326  current_tc = NULL;
327  current_tr = NULL;
328
329  check_result(tc, &tr);
330}
331
332#endif /* DEBUGBUILD */
333
334/*
335 * How these test cases work:
336 * - replace the creation of the TCP socket filter with our test filter
337 * - test filter does nothing and reports failure after configured delay
338 * - we feed addresses into the resolve cache to simulate different cases
339 * - we monitor how many instances of ipv4/v6 attempts are made and when
340 * - for mixed families, we expect HAPPY_EYEBALLS_TIMEOUT to trigger
341 *
342 * Max Duration checks needs to be conservative since CI jobs are not
343 * as sharp.
344 */
345#define TURL "http://test.com:123"
346
347#define R_FAIL      CURLE_COULDNT_CONNECT
348/* timeout values accounting for low cpu resources in CI */
349#define TC_TMOT     90000  /* 90 sec max test duration */
350#define CNCT_TMOT   60000  /* 60sec connect timeout */
351
352static struct test_case TEST_CASES[] = {
353  /* TIMEOUT_MS,    FAIL_MS      CREATED    DURATION     Result, HE_PREF */
354  /* CNCT   HE      v4    v6     v4 v6      MIN   MAX */
355  { 1, TURL, "test.com:123:192.0.2.1", CURL_IPRESOLVE_WHATEVER,
356    CNCT_TMOT, 150, 200,  200,    1,  0,      200,  TC_TMOT,  R_FAIL, NULL },
357  /* 1 ipv4, fails after ~200ms, reports COULDNT_CONNECT   */
358  { 2, TURL, "test.com:123:192.0.2.1,192.0.2.2", CURL_IPRESOLVE_WHATEVER,
359    CNCT_TMOT, 150, 200,  200,    2,  0,      400,  TC_TMOT,  R_FAIL, NULL },
360  /* 2 ipv4, fails after ~400ms, reports COULDNT_CONNECT   */
361#ifdef ENABLE_IPV6
362  { 3, TURL, "test.com:123:::1", CURL_IPRESOLVE_WHATEVER,
363    CNCT_TMOT, 150, 200,  200,    0,  1,      200,  TC_TMOT,  R_FAIL, NULL },
364  /* 1 ipv6, fails after ~200ms, reports COULDNT_CONNECT   */
365  { 4, TURL, "test.com:123:::1,::2", CURL_IPRESOLVE_WHATEVER,
366    CNCT_TMOT, 150, 200,  200,    0,  2,      400,  TC_TMOT,  R_FAIL, NULL },
367  /* 2 ipv6, fails after ~400ms, reports COULDNT_CONNECT   */
368
369  { 5, TURL, "test.com:123:192.0.2.1,::1", CURL_IPRESOLVE_WHATEVER,
370    CNCT_TMOT, 150, 200, 200,     1,  1,      350,  TC_TMOT,  R_FAIL, "v4" },
371  /* mixed ip4+6, v4 starts, v6 kicks in on HE, fails after ~350ms */
372  { 6, TURL, "test.com:123:::1,192.0.2.1", CURL_IPRESOLVE_WHATEVER,
373    CNCT_TMOT, 150, 200, 200,     1,  1,      350,  TC_TMOT,  R_FAIL, "v6" },
374  /* mixed ip6+4, v6 starts, v4 never starts due to high HE, TIMEOUT */
375  { 7, TURL, "test.com:123:192.0.2.1,::1", CURL_IPRESOLVE_V4,
376    CNCT_TMOT, 150, 500, 500,     1,  0,      400,  TC_TMOT,  R_FAIL, NULL },
377  /* mixed ip4+6, but only use v4, check it uses full connect timeout,
378     although another address of the 'wrong' family is available */
379  { 8, TURL, "test.com:123:::1,192.0.2.1", CURL_IPRESOLVE_V6,
380    CNCT_TMOT, 150, 500, 500,     0,  1,      400,  TC_TMOT,  R_FAIL, NULL },
381  /* mixed ip4+6, but only use v6, check it uses full connect timeout,
382     although another address of the 'wrong' family is available */
383#endif
384};
385
386UNITTEST_START
387
388#if defined(DEBUGBUILD)
389  size_t i;
390
391  for(i = 0; i < sizeof(TEST_CASES)/sizeof(TEST_CASES[0]); ++i) {
392    test_connect(&TEST_CASES[i]);
393  }
394#else
395  (void)TEST_CASES;
396  (void)test_connect;
397#endif
398
399UNITTEST_STOP
400