quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

unit2600.c (12759B)


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