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 = ¤t_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 = ¤t_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 }