http_client.c revision 2f2f4363
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * http_client.c -- A simple HTTP/QUIC client 4 */ 5 6#ifndef WIN32 7#include <arpa/inet.h> 8#include <netinet/in.h> 9#else 10#include <Windows.h> 11#include <WinSock2.h> 12#include <io.h> 13#include <stdlib.h> 14#include <getopt.h> 15#define STDOUT_FILENO 1 16#define random rand 17#pragma warning(disable:4996) //POSIX name deprecated 18#endif 19#include <assert.h> 20#include <errno.h> 21#include <inttypes.h> 22#include <stddef.h> 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26#include <sys/queue.h> 27#ifndef WIN32 28#include <unistd.h> 29#include <sys/types.h> 30#include <dirent.h> 31#include <limits.h> 32#endif 33#include <sys/stat.h> 34#include <fcntl.h> 35#include <event2/event.h> 36#include <math.h> 37 38#include <openssl/bio.h> 39#include <openssl/pem.h> 40#include <openssl/x509.h> 41 42#include "lsquic.h" 43#include "test_common.h" 44#include "prog.h" 45 46#include "../src/liblsquic/lsquic_logger.h" 47#include "../src/liblsquic/lsquic_int_types.h" 48#include "../src/liblsquic/lsquic_util.h" 49/* include directly for reset_stream testing */ 50#include "../src/liblsquic/lsquic_varint.h" 51#include "../src/liblsquic/lsquic_hq.h" 52#include "../src/liblsquic/lsquic_sfcw.h" 53#include "../src/liblsquic/lsquic_hash.h" 54#include "../src/liblsquic/lsquic_stream.h" 55/* include directly for retire_cid testing */ 56#include "../src/liblsquic/lsquic_conn.h" 57#include "lsxpack_header.h" 58 59#define MIN(a, b) ((a) < (b) ? (a) : (b)) 60 61/* This is used to exercise generating and sending of priority frames */ 62static int randomly_reprioritize_streams; 63 64static int s_display_cert_chain; 65 66/* If this file descriptor is open, the client will accept server push and 67 * dump the contents here. See -u flag. 68 */ 69static int promise_fd = -1; 70 71/* Set to true value to use header bypass. This means that the use code 72 * creates header set via callbacks and then fetches it by calling 73 * lsquic_stream_get_hset() when the first "on_read" event is called. 74 */ 75static int g_header_bypass; 76 77static int s_discard_response; 78 79/* If set to a non-zero value, abandon reading from stream early: read at 80 * most `s_abandon_early' bytes and then close the stream. 81 */ 82static long s_abandon_early; 83 84struct sample_stats 85{ 86 unsigned n; 87 unsigned long min, max; 88 unsigned long sum; /* To calculate mean */ 89 unsigned long sum_X2; /* To calculate stddev */ 90}; 91 92static struct sample_stats s_stat_to_conn, /* Time to connect */ 93 s_stat_ttfb, 94 s_stat_req; /* From TTFB to EOS */ 95static unsigned s_stat_conns_ok, s_stat_conns_failed; 96static unsigned long s_stat_downloaded_bytes; 97 98static void 99update_sample_stats (struct sample_stats *stats, unsigned long val) 100{ 101 LSQ_DEBUG("%s: %p: %lu", __func__, stats, val); 102 if (stats->n) 103 { 104 if (val < stats->min) 105 stats->min = val; 106 else if (val > stats->max) 107 stats->max = val; 108 } 109 else 110 { 111 stats->min = val; 112 stats->max = val; 113 } 114 stats->sum += val; 115 stats->sum_X2 += val * val; 116 ++stats->n; 117} 118 119 120static void 121calc_sample_stats (const struct sample_stats *stats, 122 long double *mean_p, long double *stddev_p) 123{ 124 unsigned long mean, tmp; 125 126 if (stats->n) 127 { 128 mean = stats->sum / stats->n; 129 *mean_p = (long double) mean; 130 if (stats->n > 1) 131 { 132 tmp = stats->sum_X2 - stats->n * mean * mean; 133 tmp /= stats->n - 1; 134 *stddev_p = sqrtl((long double) tmp); 135 } 136 else 137 *stddev_p = 0; 138 } 139 else 140 { 141 *mean_p = 0; 142 *stddev_p = 0; 143 } 144} 145 146 147#ifdef WIN32 148static char * 149strndup(const char *s, size_t n) 150{ 151 char *copy; 152 153 copy = malloc(n + 1); 154 if (copy) 155 { 156 memcpy(copy, s, n); 157 copy[n] = '\0'; 158 } 159 160 return copy; 161} 162#endif 163 164struct lsquic_conn_ctx; 165 166struct path_elem { 167 TAILQ_ENTRY(path_elem) next_pe; 168 const char *path; 169}; 170 171struct http_client_ctx { 172 const char *hostname; 173 const char *method; 174 const char *payload; 175 char payload_size[20]; 176 177 /* hcc_path_elems holds a list of paths which are to be requested from 178 * the server. Each new request gets the next path from the list (the 179 * iterator is stored in hcc_cur_pe); when the end is reached, the 180 * iterator wraps around. 181 */ 182 TAILQ_HEAD(, path_elem) hcc_path_elems; 183 struct path_elem *hcc_cur_pe; 184 185 unsigned hcc_total_n_reqs; 186 unsigned hcc_reqs_per_conn; 187 unsigned hcc_concurrency; 188 unsigned hcc_cc_reqs_per_conn; 189 unsigned hcc_n_open_conns; 190 unsigned hcc_reset_after_nbytes; 191 unsigned hcc_retire_cid_after_nbytes; 192 193 char *hcc_sess_resume_file_name; 194 195 enum { 196 HCC_SKIP_SESS_RESUME = (1 << 0), 197 HCC_SEEN_FIN = (1 << 1), 198 HCC_ABORT_ON_INCOMPLETE = (1 << 2), 199 } hcc_flags; 200 struct prog *prog; 201 const char *qif_file; 202 FILE *qif_fh; 203}; 204 205struct lsquic_conn_ctx { 206 TAILQ_ENTRY(lsquic_conn_ctx) next_ch; 207 lsquic_conn_t *conn; 208 struct http_client_ctx *client_ctx; 209 lsquic_time_t ch_created; 210 unsigned ch_n_reqs; /* This number gets decremented as streams are closed and 211 * incremented as push promises are accepted. 212 */ 213 unsigned ch_n_cc_streams; /* This number is incremented as streams are opened 214 * and decremented as streams are closed. It should 215 * never exceed hcc_cc_reqs_per_conn in client_ctx. 216 */ 217 enum { 218 CH_SESSION_RESUME_SAVED = 1 << 0, 219 } ch_flags; 220}; 221 222 223struct hset_elem 224{ 225 STAILQ_ENTRY(hset_elem) next; 226 size_t nalloc; 227 struct lsxpack_header xhdr; 228}; 229 230 231STAILQ_HEAD(hset, hset_elem); 232 233static void 234hset_dump (const struct hset *, FILE *); 235static void 236hset_destroy (void *hset); 237static void 238display_cert_chain (lsquic_conn_t *); 239 240 241static void 242create_connections (struct http_client_ctx *client_ctx) 243{ 244 size_t len; 245 FILE *file; 246 unsigned char sess_resume[0x2000]; 247 248 if (0 == (client_ctx->hcc_flags & HCC_SKIP_SESS_RESUME) 249 && client_ctx->hcc_sess_resume_file_name) 250 { 251 file = fopen(client_ctx->hcc_sess_resume_file_name, "rb"); 252 if (!file) 253 { 254 LSQ_DEBUG("cannot open %s for reading: %s", 255 client_ctx->hcc_sess_resume_file_name, strerror(errno)); 256 goto no_file; 257 } 258 len = fread(sess_resume, 1, sizeof(sess_resume), file); 259 if (0 == len && !feof(file)) 260 LSQ_WARN("error reading %s: %s", 261 client_ctx->hcc_sess_resume_file_name, strerror(errno)); 262 fclose(file); 263 LSQ_INFO("create connection sess_resume %zu bytes", len); 264 } 265 else no_file: 266 len = 0; 267 268 while (client_ctx->hcc_n_open_conns < client_ctx->hcc_concurrency && 269 client_ctx->hcc_total_n_reqs > 0) 270 if (0 != prog_connect(client_ctx->prog, len ? sess_resume : NULL, len)) 271 { 272 LSQ_ERROR("connection failed"); 273 exit(EXIT_FAILURE); 274 } 275} 276 277 278static void 279create_streams (struct http_client_ctx *client_ctx, lsquic_conn_ctx_t *conn_h) 280{ 281 while (conn_h->ch_n_reqs - conn_h->ch_n_cc_streams && 282 conn_h->ch_n_cc_streams < client_ctx->hcc_cc_reqs_per_conn) 283 { 284 lsquic_conn_make_stream(conn_h->conn); 285 conn_h->ch_n_cc_streams++; 286 } 287} 288 289 290static lsquic_conn_ctx_t * 291http_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 292{ 293 struct http_client_ctx *client_ctx = stream_if_ctx; 294 lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h)); 295 conn_h->conn = conn; 296 conn_h->client_ctx = client_ctx; 297 conn_h->ch_n_reqs = MIN(client_ctx->hcc_total_n_reqs, 298 client_ctx->hcc_reqs_per_conn); 299 client_ctx->hcc_total_n_reqs -= conn_h->ch_n_reqs; 300 ++conn_h->client_ctx->hcc_n_open_conns; 301 if (!TAILQ_EMPTY(&client_ctx->hcc_path_elems)) 302 create_streams(client_ctx, conn_h); 303 conn_h->ch_created = lsquic_time_now(); 304 return conn_h; 305} 306 307 308struct create_another_conn_or_stop_ctx 309{ 310 struct event *event; 311 struct http_client_ctx *client_ctx; 312}; 313 314 315static void 316create_another_conn_or_stop (evutil_socket_t sock, short events, void *ctx) 317{ 318 struct create_another_conn_or_stop_ctx *const cacos = ctx; 319 struct http_client_ctx *const client_ctx = cacos->client_ctx; 320 321 event_del(cacos->event); 322 event_free(cacos->event); 323 free(cacos); 324 325 create_connections(client_ctx); 326 if (0 == client_ctx->hcc_n_open_conns) 327 { 328 LSQ_INFO("All connections are closed: stop engine"); 329 prog_stop(client_ctx->prog); 330 } 331} 332 333 334static void 335http_client_on_conn_closed (lsquic_conn_t *conn) 336{ 337 lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 338 struct create_another_conn_or_stop_ctx *cacos; 339 enum LSQUIC_CONN_STATUS status; 340 struct event_base *eb; 341 char errmsg[80]; 342 343 status = lsquic_conn_status(conn, errmsg, sizeof(errmsg)); 344 LSQ_INFO("Connection closed. Status: %d. Message: %s", status, 345 errmsg[0] ? errmsg : "<not set>"); 346 if (conn_h->client_ctx->hcc_flags & HCC_ABORT_ON_INCOMPLETE) 347 { 348 if (!(conn_h->client_ctx->hcc_flags & HCC_SEEN_FIN)) 349 abort(); 350 } 351 --conn_h->client_ctx->hcc_n_open_conns; 352 353 cacos = calloc(1, sizeof(*cacos)); 354 if (!cacos) 355 { 356 LSQ_ERROR("cannot allocate cacos"); 357 exit(1); 358 } 359 eb = prog_eb(conn_h->client_ctx->prog); 360 cacos->client_ctx = conn_h->client_ctx; 361 cacos->event = event_new(eb, -1, 0, create_another_conn_or_stop, cacos); 362 if (!cacos->event) 363 { 364 LSQ_ERROR("cannot allocate event"); 365 exit(1); 366 } 367 if (0 != event_add(cacos->event, NULL)) 368 { 369 LSQ_ERROR("cannot add cacos event"); 370 exit(1); 371 } 372 event_active(cacos->event, 0, 0); 373 374 free(conn_h); 375} 376 377 378static int 379hsk_status_ok (enum lsquic_hsk_status status) 380{ 381 return status == LSQ_HSK_OK || status == LSQ_HSK_RESUMED_OK; 382} 383 384 385static void 386http_client_on_hsk_done (lsquic_conn_t *conn, enum lsquic_hsk_status status) 387{ 388 lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 389 struct http_client_ctx *client_ctx = conn_h->client_ctx; 390 391 if (hsk_status_ok(status)) 392 LSQ_INFO("handshake success %s", 393 status == LSQ_HSK_RESUMED_OK ? "(session resumed)" : ""); 394 else if (status == LSQ_HSK_FAIL) 395 LSQ_INFO("handshake failed"); 396 else if (status == LSQ_HSK_RESUMED_FAIL) 397 { 398 LSQ_INFO("handshake failed because of session resumption, will retry " 399 "without it"); 400 client_ctx->hcc_flags |= HCC_SKIP_SESS_RESUME; 401 ++client_ctx->hcc_concurrency; 402 ++client_ctx->hcc_total_n_reqs; 403 } 404 else 405 assert(0); 406 407 if (hsk_status_ok(status) && s_display_cert_chain) 408 display_cert_chain(conn); 409 410 if (hsk_status_ok(status)) 411 { 412 conn_h = lsquic_conn_get_ctx(conn); 413 ++s_stat_conns_ok; 414 update_sample_stats(&s_stat_to_conn, 415 lsquic_time_now() - conn_h->ch_created); 416 if (TAILQ_EMPTY(&client_ctx->hcc_path_elems)) 417 { 418 LSQ_INFO("no paths mode: close connection"); 419 lsquic_conn_close(conn_h->conn); 420 } 421 } 422 else 423 ++s_stat_conns_failed; 424} 425 426 427static void 428http_client_on_sess_resume_info (lsquic_conn_t *conn, const unsigned char *buf, 429 size_t bufsz) 430{ 431 lsquic_conn_ctx_t *const conn_h = lsquic_conn_get_ctx(conn); 432 struct http_client_ctx *const client_ctx = conn_h->client_ctx; 433 FILE *file; 434 size_t nw; 435 436 assert(client_ctx->hcc_sess_resume_file_name); 437 438 /* Our client is rather limited: only one file and only one ticket per 439 * connection can be saved. 440 */ 441 if (conn_h->ch_flags & CH_SESSION_RESUME_SAVED) 442 { 443 LSQ_DEBUG("session resumption information already saved for this " 444 "connection"); 445 return; 446 } 447 448 file = fopen(client_ctx->hcc_sess_resume_file_name, "wb"); 449 if (!file) 450 { 451 LSQ_WARN("cannot open %s for writing: %s", 452 client_ctx->hcc_sess_resume_file_name, strerror(errno)); 453 return; 454 } 455 456 nw = fwrite(buf, 1, bufsz, file); 457 if (nw == bufsz) 458 { 459 LSQ_DEBUG("wrote %zd bytes of session resumption information to %s", 460 nw, client_ctx->hcc_sess_resume_file_name); 461 conn_h->ch_flags |= CH_SESSION_RESUME_SAVED; 462 } 463 else 464 LSQ_WARN("error: fwrite(%s) returns %zd instead of %zd: %s", 465 client_ctx->hcc_sess_resume_file_name, nw, bufsz, strerror(errno)); 466 467 fclose(file); 468} 469 470 471struct lsquic_stream_ctx { 472 lsquic_stream_t *stream; 473 struct http_client_ctx *client_ctx; 474 const char *path; 475 enum { 476 HEADERS_SENT = (1 << 0), 477 PROCESSED_HEADERS = 1 << 1, 478 ABANDON = 1 << 2, /* Abandon reading from stream after sh_stop bytes 479 * have been read. 480 */ 481 } sh_flags; 482 lsquic_time_t sh_created; 483 lsquic_time_t sh_ttfb; 484 size_t sh_stop; /* Stop after reading this many bytes if ABANDON is set */ 485 size_t sh_nread; /* Number of bytes read from stream using one of 486 * lsquic_stream_read* functions. 487 */ 488 unsigned count; 489 struct lsquic_reader reader; 490}; 491 492 493static lsquic_stream_ctx_t * 494http_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 495{ 496 const int pushed = lsquic_stream_is_pushed(stream); 497 498 if (pushed) 499 { 500 LSQ_INFO("not accepting server push"); 501 lsquic_stream_refuse_push(stream); 502 return NULL; 503 } 504 505 lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h)); 506 st_h->stream = stream; 507 st_h->client_ctx = stream_if_ctx; 508 st_h->sh_created = lsquic_time_now(); 509 if (st_h->client_ctx->hcc_cur_pe) 510 { 511 st_h->client_ctx->hcc_cur_pe = TAILQ_NEXT( 512 st_h->client_ctx->hcc_cur_pe, next_pe); 513 if (!st_h->client_ctx->hcc_cur_pe) /* Wrap around */ 514 st_h->client_ctx->hcc_cur_pe = 515 TAILQ_FIRST(&st_h->client_ctx->hcc_path_elems); 516 } 517 else 518 st_h->client_ctx->hcc_cur_pe = TAILQ_FIRST( 519 &st_h->client_ctx->hcc_path_elems); 520 st_h->path = st_h->client_ctx->hcc_cur_pe->path; 521 if (st_h->client_ctx->payload) 522 { 523 st_h->reader.lsqr_read = test_reader_read; 524 st_h->reader.lsqr_size = test_reader_size; 525 st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->client_ctx->payload); 526 if (!st_h->reader.lsqr_ctx) 527 exit(1); 528 } 529 else 530 st_h->reader.lsqr_ctx = NULL; 531 LSQ_INFO("created new stream, path: %s", st_h->path); 532 lsquic_stream_wantwrite(stream, 1); 533 if (randomly_reprioritize_streams) 534 lsquic_stream_set_priority(stream, 1 + (random() & 0xFF)); 535 if (s_abandon_early) 536 { 537 st_h->sh_stop = random() % (s_abandon_early + 1); 538 st_h->sh_flags |= ABANDON; 539 } 540 541 return st_h; 542} 543 544 545static void 546send_headers (lsquic_stream_ctx_t *st_h) 547{ 548 const char *hostname = st_h->client_ctx->hostname; 549 struct header_buf hbuf; 550 if (!hostname) 551 hostname = st_h->client_ctx->prog->prog_hostname; 552 hbuf.off = 0; 553 struct lsxpack_header headers_arr[7]; 554#define V(v) (v), strlen(v) 555 header_set_ptr(&headers_arr[0], &hbuf, V(":method"), V(st_h->client_ctx->method)); 556 header_set_ptr(&headers_arr[1], &hbuf, V(":scheme"), V("https")); 557 header_set_ptr(&headers_arr[2], &hbuf, V(":path"), V(st_h->path)); 558 header_set_ptr(&headers_arr[3], &hbuf, V(":authority"), V(hostname)); 559 header_set_ptr(&headers_arr[4], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua)); 560 /* The following headers only gets sent if there is request payload: */ 561 header_set_ptr(&headers_arr[5], &hbuf, V("content-type"), V("application/octet-stream")); 562 header_set_ptr(&headers_arr[6], &hbuf, V("content-length"), V( st_h->client_ctx->payload_size)); 563 lsquic_http_headers_t headers = { 564 .count = sizeof(headers_arr) / sizeof(headers_arr[0]), 565 .headers = headers_arr, 566 }; 567 if (!st_h->client_ctx->payload) 568 headers.count -= 2; 569 if (0 != lsquic_stream_send_headers(st_h->stream, &headers, 570 st_h->client_ctx->payload == NULL)) 571 { 572 LSQ_ERROR("cannot send headers: %s", strerror(errno)); 573 exit(1); 574 } 575} 576 577 578/* This is here to exercise lsquic_conn_get_server_cert_chain() API */ 579static void 580display_cert_chain (lsquic_conn_t *conn) 581{ 582 STACK_OF(X509) *chain; 583 X509_NAME *name; 584 X509 *cert; 585 unsigned i; 586 char buf[100]; 587 588 chain = lsquic_conn_get_server_cert_chain(conn); 589 if (!chain) 590 { 591 LSQ_WARN("could not get server certificate chain"); 592 return; 593 } 594 595 for (i = 0; i < sk_X509_num(chain); ++i) 596 { 597 cert = sk_X509_value(chain, i); 598 name = X509_get_subject_name(cert); 599 LSQ_INFO("cert #%u: name: %s", i, 600 X509_NAME_oneline(name, buf, sizeof(buf))); 601 X509_free(cert); 602 } 603 604 sk_X509_free(chain); 605} 606 607 608static void 609http_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 610{ 611 ssize_t nw; 612 613 if (st_h->sh_flags & HEADERS_SENT) 614 { 615 if (st_h->client_ctx->payload && test_reader_size(st_h->reader.lsqr_ctx) > 0) 616 { 617 nw = lsquic_stream_writef(stream, &st_h->reader); 618 if (nw < 0) 619 { 620 LSQ_ERROR("write error: %s", strerror(errno)); 621 exit(1); 622 } 623 if (test_reader_size(st_h->reader.lsqr_ctx) > 0) 624 { 625 lsquic_stream_wantwrite(stream, 1); 626 } 627 else 628 { 629 lsquic_stream_shutdown(stream, 1); 630 lsquic_stream_wantread(stream, 1); 631 } 632 } 633 else 634 { 635 lsquic_stream_shutdown(stream, 1); 636 lsquic_stream_wantread(stream, 1); 637 } 638 } 639 else 640 { 641 st_h->sh_flags |= HEADERS_SENT; 642 send_headers(st_h); 643 } 644} 645 646 647static size_t 648discard (void *ctx, const unsigned char *buf, size_t sz, int fin) 649{ 650 lsquic_stream_ctx_t *st_h = ctx; 651 652 if (st_h->sh_flags & ABANDON) 653 { 654 if (sz > st_h->sh_stop - st_h->sh_nread) 655 sz = st_h->sh_stop - st_h->sh_nread; 656 } 657 658 return sz; 659} 660 661 662static void 663http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 664{ 665 struct http_client_ctx *const client_ctx = st_h->client_ctx; 666 struct hset *hset; 667 ssize_t nread; 668 unsigned old_prio, new_prio; 669 unsigned char buf[0x200]; 670 unsigned nreads = 0; 671#ifdef WIN32 672 srand(GetTickCount()); 673#endif 674 675 do 676 { 677 if (g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS)) 678 { 679 hset = lsquic_stream_get_hset(stream); 680 if (!hset) 681 { 682 LSQ_ERROR("could not get header set from stream"); 683 exit(2); 684 } 685 st_h->sh_ttfb = lsquic_time_now(); 686 update_sample_stats(&s_stat_ttfb, st_h->sh_ttfb - st_h->sh_created); 687 if (s_discard_response) 688 LSQ_DEBUG("discard response: do not dump headers"); 689 else 690 hset_dump(hset, stdout); 691 hset_destroy(hset); 692 st_h->sh_flags |= PROCESSED_HEADERS; 693 } 694 else if (nread = (s_discard_response 695 ? lsquic_stream_readf(stream, discard, st_h) 696 : lsquic_stream_read(stream, buf, 697 st_h->sh_flags & ABANDON 698 ? MIN(sizeof(buf), st_h->sh_nread - st_h->sh_stop) 699 : sizeof(buf))), 700 nread > 0) 701 { 702 st_h->sh_nread += (size_t) nread; 703 s_stat_downloaded_bytes += nread; 704 /* test stream_reset after some number of read bytes */ 705 if (client_ctx->hcc_reset_after_nbytes && 706 s_stat_downloaded_bytes > client_ctx->hcc_reset_after_nbytes) 707 { 708 lsquic_stream_reset(stream, 0x1); 709 break; 710 } 711 /* test retire_cid after some number of read bytes */ 712 if (client_ctx->hcc_retire_cid_after_nbytes && 713 s_stat_downloaded_bytes > client_ctx->hcc_retire_cid_after_nbytes) 714 { 715 lsquic_conn_retire_cid(lsquic_stream_conn(stream)); 716 client_ctx->hcc_retire_cid_after_nbytes = 0; 717 break; 718 } 719 if (!g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS)) 720 { 721 /* First read is assumed to be the first byte */ 722 st_h->sh_ttfb = lsquic_time_now(); 723 update_sample_stats(&s_stat_ttfb, 724 st_h->sh_ttfb - st_h->sh_created); 725 st_h->sh_flags |= PROCESSED_HEADERS; 726 } 727 if (!s_discard_response) 728 fwrite(buf, 1, nread, stdout); 729 if (randomly_reprioritize_streams && (st_h->count++ & 0x3F) == 0) 730 { 731 old_prio = lsquic_stream_priority(stream); 732 new_prio = 1 + (random() & 0xFF); 733#ifndef NDEBUG 734 const int s = 735#endif 736 lsquic_stream_set_priority(stream, new_prio); 737 assert(s == 0); 738 LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u", 739 lsquic_stream_id(stream), old_prio, new_prio); 740 } 741 if ((st_h->sh_flags & ABANDON) && st_h->sh_nread >= st_h->sh_stop) 742 { 743 LSQ_DEBUG("closing stream early having read %zd bytes", 744 st_h->sh_nread); 745 lsquic_stream_close(stream); 746 break; 747 } 748 } 749 else if (0 == nread) 750 { 751 update_sample_stats(&s_stat_req, lsquic_time_now() - st_h->sh_ttfb); 752 client_ctx->hcc_flags |= HCC_SEEN_FIN; 753 lsquic_stream_shutdown(stream, 0); 754 break; 755 } 756 else if (client_ctx->prog->prog_settings.es_rw_once && EWOULDBLOCK == errno) 757 { 758 LSQ_NOTICE("emptied the buffer in 'once' mode"); 759 break; 760 } 761 else if (lsquic_stream_is_rejected(stream)) 762 { 763 LSQ_NOTICE("stream was rejected"); 764 lsquic_stream_close(stream); 765 break; 766 } 767 else 768 { 769 LSQ_ERROR("could not read: %s", strerror(errno)); 770 exit(2); 771 } 772 } 773 while (client_ctx->prog->prog_settings.es_rw_once 774 && nreads++ < 3 /* Emulate just a few reads */); 775} 776 777 778static void 779http_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 780{ 781 const int pushed = lsquic_stream_is_pushed(stream); 782 if (pushed) 783 { 784 assert(NULL == st_h); 785 return; 786 } 787 788 LSQ_INFO("%s called", __func__); 789 struct http_client_ctx *const client_ctx = st_h->client_ctx; 790 lsquic_conn_t *conn = lsquic_stream_conn(stream); 791 lsquic_conn_ctx_t *const conn_h = lsquic_conn_get_ctx(conn); 792 --conn_h->ch_n_reqs; 793 --conn_h->ch_n_cc_streams; 794 if (0 == conn_h->ch_n_reqs) 795 { 796 LSQ_INFO("all requests completed, closing connection"); 797 lsquic_conn_close(conn_h->conn); 798 } 799 else 800 { 801 LSQ_INFO("%u active stream, %u request remain, creating %u new stream", 802 conn_h->ch_n_cc_streams, 803 conn_h->ch_n_reqs - conn_h->ch_n_cc_streams, 804 MIN((conn_h->ch_n_reqs - conn_h->ch_n_cc_streams), 805 (client_ctx->hcc_cc_reqs_per_conn - conn_h->ch_n_cc_streams))); 806 create_streams(client_ctx, conn_h); 807 } 808 if (st_h->reader.lsqr_ctx) 809 destroy_lsquic_reader_ctx(st_h->reader.lsqr_ctx); 810 free(st_h); 811} 812 813 814static struct lsquic_stream_if http_client_if = { 815 .on_new_conn = http_client_on_new_conn, 816 .on_conn_closed = http_client_on_conn_closed, 817 .on_new_stream = http_client_on_new_stream, 818 .on_read = http_client_on_read, 819 .on_write = http_client_on_write, 820 .on_close = http_client_on_close, 821 .on_hsk_done = http_client_on_hsk_done, 822}; 823 824 825static void 826usage (const char *prog) 827{ 828 const char *const slash = strrchr(prog, '/'); 829 if (slash) 830 prog = slash + 1; 831 printf( 832"Usage: %s [opts]\n" 833"\n" 834"Options:\n" 835" -p PATH Path to request. May be specified more than once. If no\n" 836" path is specified, the connection is closed as soon as\n" 837" handshake succeeds.\n" 838" -n CONNS Number of concurrent connections. Defaults to 1.\n" 839" -r NREQS Total number of requests to send. Defaults to 1.\n" 840" -R MAXREQS Maximum number of requests per single connection. Some\n" 841" connections will have fewer requests than this.\n" 842" -w CONCUR Number of concurrent requests per single connection.\n" 843" Defaults to 1.\n" 844" -M METHOD Method. Defaults to GET.\n" 845" -P PAYLOAD Name of the file that contains payload to be used in the\n" 846" request. This adds two more headers to the request:\n" 847" content-type: application/octet-stream and\n" 848" content-length\n" 849" -K Discard server response\n" 850" -I Abort on incomplete reponse from server\n" 851" -4 Prefer IPv4 when resolving hostname\n" 852" -6 Prefer IPv6 when resolving hostname\n" 853" -0 FILE Provide RTT info file (reading or writing)\n" 854#ifndef WIN32 855" -C DIR Certificate store. If specified, server certificate will\n" 856" be verified.\n" 857#endif 858" -a Display server certificate chain after successful handshake.\n" 859" -b N_BYTES Send RESET_STREAM frame after the client has read n bytes.\n" 860" -t Print stats to stdout.\n" 861" -T FILE Print stats to FILE. If FILE is -, print stats to stdout.\n" 862" -q FILE QIF mode: issue requests from the QIF file and validate\n" 863" server responses.\n" 864" -e TOKEN Hexadecimal string representing resume token.\n" 865" -3 MAX Close stream after reading at most MAX bytes. The actual\n" 866" number of bytes read is randominzed.\n" 867 , prog); 868} 869 870 871#ifndef WIN32 872static X509_STORE *store; 873 874/* Windows does not have regex... */ 875static int 876ends_in_pem (const char *s) 877{ 878 int len; 879 880 len = strlen(s); 881 882 return len >= 4 883 && 0 == strcasecmp(s + len - 4, ".pem"); 884} 885 886 887static X509 * 888file2cert (const char *path) 889{ 890 X509 *cert = NULL; 891 BIO *in; 892 893 in = BIO_new(BIO_s_file()); 894 if (!in) 895 goto end; 896 897 if (BIO_read_filename(in, path) <= 0) 898 goto end; 899 900 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); 901 902 end: 903 BIO_free(in); 904 return cert; 905} 906 907 908static int 909init_x509_cert_store (const char *path) 910{ 911 struct dirent *ent; 912 X509 *cert; 913 DIR *dir; 914 char file_path[NAME_MAX]; 915 int ret; 916 917 dir = opendir(path); 918 if (!dir) 919 { 920 LSQ_WARN("Cannot open directory `%s': %s", path, strerror(errno)); 921 return -1; 922 } 923 924 store = X509_STORE_new(); 925 926 while ((ent = readdir(dir))) 927 { 928 if (ends_in_pem(ent->d_name)) 929 { 930 ret = snprintf(file_path, sizeof(file_path), "%s/%s", 931 path, ent->d_name); 932 if (ret < 0) 933 { 934 LSQ_WARN("file_path formatting error %s", strerror(errno)); 935 continue; 936 } 937 else if ((unsigned)ret >= sizeof(file_path)) 938 { 939 LSQ_WARN("file_path was truncated %s", strerror(errno)); 940 continue; 941 } 942 cert = file2cert(file_path); 943 if (cert) 944 { 945 if (1 != X509_STORE_add_cert(store, cert)) 946 LSQ_WARN("could not add cert from %s", file_path); 947 } 948 else 949 LSQ_WARN("could not read cert from %s", file_path); 950 } 951 } 952 (void) closedir(dir); 953 return 0; 954} 955 956 957static int 958verify_server_cert (void *ctx, STACK_OF(X509) *chain) 959{ 960 X509_STORE_CTX store_ctx; 961 X509 *cert; 962 int ver; 963 964 if (!store) 965 { 966 if (0 != init_x509_cert_store(ctx)) 967 return -1; 968 } 969 970 cert = sk_X509_shift(chain); 971 X509_STORE_CTX_init(&store_ctx, store, cert, chain); 972 973 ver = X509_verify_cert(&store_ctx); 974 975 X509_STORE_CTX_cleanup(&store_ctx); 976 977 if (ver != 1) 978 LSQ_WARN("could not verify server certificate"); 979 980 return ver == 1 ? 0 : -1; 981} 982#endif 983 984 985static void * 986hset_create (void *hsi_ctx, lsquic_stream_t *stream, int is_push_promise) 987{ 988 struct hset *hset; 989 990 if ((hset = malloc(sizeof(*hset)))) 991 { 992 STAILQ_INIT(hset); 993 return hset; 994 } 995 else 996 return NULL; 997} 998 999 1000static struct lsxpack_header * 1001hset_prepare_decode (void *hset_p, struct lsxpack_header *xhdr, 1002 size_t req_space) 1003{ 1004 struct hset *const hset = hset_p; 1005 struct hset_elem *el; 1006 char *buf; 1007 1008 if (0 == req_space) 1009 req_space = 0x100; 1010 1011 if (req_space > LSXPACK_MAX_STRLEN) 1012 { 1013 LSQ_WARN("requested space for header is too large: %zd bytes", 1014 req_space); 1015 return NULL; 1016 } 1017 1018 if (!xhdr) 1019 { 1020 buf = malloc(req_space); 1021 if (!buf) 1022 { 1023 LSQ_WARN("cannot allocate buf of %zd bytes", req_space); 1024 return NULL; 1025 } 1026 el = malloc(sizeof(*el)); 1027 if (!el) 1028 { 1029 LSQ_WARN("cannot allocate hset_elem"); 1030 free(buf); 1031 return NULL; 1032 } 1033 STAILQ_INSERT_TAIL(hset, el, next); 1034 lsxpack_header_prepare_decode(&el->xhdr, buf, 0, req_space); 1035 el->nalloc = req_space; 1036 } 1037 else 1038 { 1039 el = (struct hset_elem *) ((char *) xhdr 1040 - offsetof(struct hset_elem, xhdr)); 1041 if (req_space <= el->nalloc) 1042 { 1043 LSQ_ERROR("requested space is smaller than already allocated"); 1044 return NULL; 1045 } 1046 if (req_space < el->nalloc * 2) 1047 req_space = el->nalloc * 2; 1048 buf = realloc(el->xhdr.buf, req_space); 1049 if (!buf) 1050 { 1051 LSQ_WARN("cannot reallocate hset buf"); 1052 return NULL; 1053 } 1054 el->xhdr.buf = buf; 1055 el->xhdr.val_len = req_space; 1056 el->nalloc = req_space; 1057 } 1058 1059 return &el->xhdr; 1060} 1061 1062 1063static int 1064hset_add_header (void *hset_p, struct lsxpack_header *xhdr) 1065{ 1066 unsigned name_len, value_len; 1067 /* Not much to do: the header value are in xhdr */ 1068 1069 if (xhdr) 1070 { 1071 name_len = xhdr->name_len; 1072 value_len = xhdr->val_len; 1073 s_stat_downloaded_bytes += name_len + value_len + 4; /* ": \r\n" */ 1074 } 1075 else 1076 s_stat_downloaded_bytes += 2; /* \r\n "*/ 1077 1078 return 0; 1079} 1080 1081 1082static void 1083hset_destroy (void *hset_p) 1084{ 1085 struct hset *hset = hset_p; 1086 struct hset_elem *el, *next; 1087 1088 for (el = STAILQ_FIRST(hset); el; el = next) 1089 { 1090 next = STAILQ_NEXT(el, next); 1091 free(el->xhdr.buf); 1092 free(el); 1093 } 1094 free(hset); 1095} 1096 1097 1098static void 1099hset_dump (const struct hset *hset, FILE *out) 1100{ 1101 const struct hset_elem *el; 1102 1103 STAILQ_FOREACH(el, hset, next) 1104 if (el->xhdr.flags & (LSXPACK_HPACK_VAL_MATCHED|LSXPACK_QPACK_IDX)) 1105 fprintf(out, "%.*s (%s static table idx %u): %.*s\n", 1106 (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr), 1107 el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? "hpack" : "qpack", 1108 el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? el->xhdr.hpack_index 1109 : el->xhdr.qpack_index, 1110 (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr)); 1111 else 1112 fprintf(out, "%.*s: %.*s\n", 1113 (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr), 1114 (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr)); 1115 1116 fprintf(out, "\n"); 1117 fflush(out); 1118} 1119 1120 1121/* These are basic and for illustration purposes only. You will want to 1122 * do your own verification by doing something similar to what is done 1123 * in src/liblsquic/lsquic_http1x_if.c 1124 */ 1125static const struct lsquic_hset_if header_bypass_api = 1126{ 1127 .hsi_create_header_set = hset_create, 1128 .hsi_prepare_decode = hset_prepare_decode, 1129 .hsi_process_header = hset_add_header, 1130 .hsi_discard_header_set = hset_destroy, 1131}; 1132 1133 1134static void 1135display_stat (FILE *out, const struct sample_stats *stats, const char *name) 1136{ 1137 long double mean, stddev; 1138 1139 calc_sample_stats(stats, &mean, &stddev); 1140 fprintf(out, "%s: n: %u; min: %.2Lf ms; max: %.2Lf ms; mean: %.2Lf ms; " 1141 "sd: %.2Lf ms\n", name, stats->n, (long double) stats->min / 1000, 1142 (long double) stats->max / 1000, mean / 1000, stddev / 1000); 1143} 1144 1145 1146static lsquic_conn_ctx_t * 1147qif_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 1148{ 1149 lsquic_conn_make_stream(conn); 1150 return stream_if_ctx; 1151} 1152 1153 1154static void 1155qif_client_on_conn_closed (lsquic_conn_t *conn) 1156{ 1157 struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn); 1158 LSQ_INFO("connection is closed: stop engine"); 1159 prog_stop(client_ctx->prog); 1160} 1161 1162 1163struct qif_stream_ctx 1164{ 1165 int reqno; 1166 struct lsquic_http_headers headers; 1167 char *qif_str; 1168 size_t qif_sz; 1169 size_t qif_off; 1170 char *resp_str; /* qif_sz allocated */ 1171 size_t resp_off; /* Read so far */ 1172 enum { 1173 QSC_HEADERS_SENT = 1 << 0, 1174 QSC_GOT_HEADERS = 1 << 1, 1175 } flags; 1176}; 1177 1178#define MAX(a, b) ((a) > (b) ? (a) : (b)) 1179 1180lsquic_stream_ctx_t * 1181qif_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 1182{ 1183 struct http_client_ctx *const client_ctx = stream_if_ctx; 1184 FILE *const fh = client_ctx->qif_fh; 1185 struct qif_stream_ctx *ctx; 1186 struct lsxpack_header *header; 1187 static int reqno; 1188 size_t nalloc; 1189 char *end, *tab, *line; 1190 char line_buf[0x1000]; 1191 1192 ctx = calloc(1, sizeof(*ctx)); 1193 if (!ctx) 1194 { 1195 perror("calloc"); 1196 exit(1); 1197 } 1198 ctx->reqno = reqno++; 1199 1200 nalloc = 0; 1201 while ((line = fgets(line_buf, sizeof(line_buf), fh))) 1202 { 1203 end = strchr(line, '\n'); 1204 if (!end) 1205 { 1206 fprintf(stderr, "no newline\n"); 1207 exit(1); 1208 } 1209 1210 if (end == line) 1211 break; 1212 1213 if (*line == '#') 1214 continue; 1215 1216 tab = strchr(line, '\t'); 1217 if (!tab) 1218 { 1219 fprintf(stderr, "no TAB\n"); 1220 exit(1); 1221 } 1222 1223 if (nalloc + (end + 1 - line) > ctx->qif_sz) 1224 { 1225 if (nalloc) 1226 nalloc = MAX(nalloc * 2, nalloc + (end + 1 - line)); 1227 else 1228 nalloc = end + 1 - line; 1229 ctx->qif_str = realloc(ctx->qif_str, nalloc); 1230 if (!ctx->qif_str) 1231 { 1232 perror("realloc"); 1233 exit(1); 1234 } 1235 } 1236 memcpy(ctx->qif_str + ctx->qif_sz, line, end + 1 - line); 1237 1238 ctx->headers.headers = realloc(ctx->headers.headers, 1239 sizeof(ctx->headers.headers[0]) * (ctx->headers.count + 1)); 1240 if (!ctx->headers.headers) 1241 { 1242 perror("realloc"); 1243 exit(1); 1244 } 1245 header = &ctx->headers.headers[ctx->headers.count++]; 1246 lsxpack_header_set_offset2(header, ctx->qif_str + ctx->qif_sz, 0, 1247 tab - line, tab - line + 1, end - tab - 1); 1248 ctx->qif_sz += end + 1 - line; 1249 } 1250 1251 lsquic_stream_wantwrite(stream, 1); 1252 1253 if (!line) 1254 { 1255 LSQ_DEBUG("Input QIF file ends; close file handle"); 1256 fclose(client_ctx->qif_fh); 1257 client_ctx->qif_fh = NULL; 1258 } 1259 1260 return (void *) ctx; 1261} 1262 1263 1264static void 1265qif_client_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) 1266{ 1267 struct qif_stream_ctx *const ctx = (void *) h; 1268 size_t towrite; 1269 ssize_t nw; 1270 1271 if (ctx->flags & QSC_HEADERS_SENT) 1272 { 1273 towrite = ctx->qif_sz - ctx->qif_off; 1274 nw = lsquic_stream_write(stream, ctx->qif_str + ctx->qif_off, towrite); 1275 if (nw >= 0) 1276 { 1277 LSQ_DEBUG("wrote %zd bytes to stream", nw); 1278 ctx->qif_off += nw; 1279 if (ctx->qif_off == (size_t) nw) 1280 { 1281 lsquic_stream_shutdown(stream, 1); 1282 lsquic_stream_wantread(stream, 1); 1283 LSQ_DEBUG("finished writing request %d", ctx->reqno); 1284 } 1285 } 1286 else 1287 { 1288 LSQ_ERROR("cannot write to stream: %s", strerror(errno)); 1289 lsquic_stream_wantwrite(stream, 0); 1290 lsquic_conn_abort(lsquic_stream_conn(stream)); 1291 } 1292 } 1293 else 1294 { 1295 if (0 == lsquic_stream_send_headers(stream, &ctx->headers, 0)) 1296 { 1297 ctx->flags |= QSC_HEADERS_SENT; 1298 LSQ_DEBUG("sent headers"); 1299 } 1300 else 1301 { 1302 LSQ_ERROR("cannot send headers: %s", strerror(errno)); 1303 lsquic_stream_wantwrite(stream, 0); 1304 lsquic_conn_abort(lsquic_stream_conn(stream)); 1305 } 1306 } 1307} 1308 1309 1310static void 1311qif_client_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) 1312{ 1313 struct qif_stream_ctx *const ctx = (void *) h; 1314 struct hset *hset; 1315 ssize_t nr; 1316 unsigned char buf[1]; 1317 1318 LSQ_DEBUG("reading response to request %d", ctx->reqno); 1319 1320 if (!(ctx->flags & QSC_GOT_HEADERS)) 1321 { 1322 hset = lsquic_stream_get_hset(stream); 1323 if (!hset) 1324 { 1325 LSQ_ERROR("could not get header set from stream"); 1326 exit(2); 1327 } 1328 LSQ_DEBUG("got header set for response %d", ctx->reqno); 1329 hset_dump(hset, stdout); 1330 hset_destroy(hset); 1331 ctx->flags |= QSC_GOT_HEADERS; 1332 } 1333 else 1334 { 1335 if (!ctx->resp_str) 1336 { 1337 ctx->resp_str = malloc(ctx->qif_sz); 1338 if (!ctx->resp_str) 1339 { 1340 perror("malloc"); 1341 exit(1); 1342 } 1343 } 1344 if (ctx->resp_off < ctx->qif_sz) 1345 { 1346 nr = lsquic_stream_read(stream, ctx->resp_str + ctx->resp_off, 1347 ctx->qif_sz - ctx->resp_off); 1348 if (nr > 0) 1349 { 1350 ctx->resp_off += nr; 1351 LSQ_DEBUG("read %zd bytes of reponse %d", nr, ctx->reqno); 1352 } 1353 else if (nr == 0) 1354 { 1355 LSQ_INFO("response %d too short", ctx->reqno); 1356 LSQ_WARN("response %d FAIL", ctx->reqno); 1357 lsquic_stream_shutdown(stream, 0); 1358 } 1359 else 1360 { 1361 LSQ_ERROR("error reading from stream"); 1362 lsquic_stream_wantread(stream, 0); 1363 lsquic_conn_abort(lsquic_stream_conn(stream)); 1364 } 1365 } 1366 else 1367 { 1368 /* Collect EOF */ 1369 nr = lsquic_stream_read(stream, buf, sizeof(buf)); 1370 if (nr == 0) 1371 { 1372 if (0 == memcmp(ctx->qif_str, ctx->resp_str, ctx->qif_sz)) 1373 LSQ_INFO("response %d OK", ctx->reqno); 1374 else 1375 LSQ_WARN("response %d FAIL", ctx->reqno); 1376 lsquic_stream_shutdown(stream, 0); 1377 } 1378 else if (nr > 0) 1379 { 1380 LSQ_INFO("response %d too long", ctx->reqno); 1381 LSQ_WARN("response %d FAIL", ctx->reqno); 1382 lsquic_stream_shutdown(stream, 0); 1383 } 1384 else 1385 { 1386 LSQ_ERROR("error reading from stream"); 1387 lsquic_stream_shutdown(stream, 0); 1388 lsquic_conn_abort(lsquic_stream_conn(stream)); 1389 } 1390 } 1391 } 1392} 1393 1394 1395static void 1396qif_client_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) 1397{ 1398 struct lsquic_conn *conn = lsquic_stream_conn(stream); 1399 struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn); 1400 struct qif_stream_ctx *const ctx = (void *) h; 1401 free(ctx->qif_str); 1402 free(ctx->resp_str); 1403 free(ctx->headers.headers); 1404 free(ctx); 1405 if (client_ctx->qif_fh) 1406 lsquic_conn_make_stream(conn); 1407 else 1408 lsquic_conn_close(conn); 1409} 1410 1411 1412const struct lsquic_stream_if qif_client_if = { 1413 .on_new_conn = qif_client_on_new_conn, 1414 .on_conn_closed = qif_client_on_conn_closed, 1415 .on_new_stream = qif_client_on_new_stream, 1416 .on_read = qif_client_on_read, 1417 .on_write = qif_client_on_write, 1418 .on_close = qif_client_on_close, 1419}; 1420 1421 1422int 1423main (int argc, char **argv) 1424{ 1425 int opt, s, was_empty; 1426 lsquic_time_t start_time; 1427 FILE *stats_fh = NULL; 1428 long double elapsed; 1429 struct http_client_ctx client_ctx; 1430 struct stat st; 1431 struct path_elem *pe; 1432 struct sport_head sports; 1433 struct prog prog; 1434 const char *token = NULL; 1435 1436 TAILQ_INIT(&sports); 1437 memset(&client_ctx, 0, sizeof(client_ctx)); 1438 TAILQ_INIT(&client_ctx.hcc_path_elems); 1439 client_ctx.method = "GET"; 1440 client_ctx.hcc_concurrency = 1; 1441 client_ctx.hcc_cc_reqs_per_conn = 1; 1442 client_ctx.hcc_reqs_per_conn = 1; 1443 client_ctx.hcc_total_n_reqs = 1; 1444 client_ctx.hcc_reset_after_nbytes = 0; 1445 client_ctx.hcc_retire_cid_after_nbytes = 0; 1446 client_ctx.prog = &prog; 1447 1448 prog_init(&prog, LSENG_HTTP, &sports, &http_client_if, &client_ctx); 1449 1450 while (-1 != (opt = getopt(argc, argv, PROG_OPTS 1451 "46Br:R:IKu:EP:M:n:w:H:p:0:q:e:hatT:b:d:" 1452 "3:" /* 3 is 133+ for "e" ("e" for "early") */ 1453#ifndef WIN32 1454 "C:" 1455#endif 1456 ))) 1457 { 1458 switch (opt) { 1459 case 'a': 1460 ++s_display_cert_chain; 1461 break; 1462 case '4': 1463 case '6': 1464 prog.prog_ipver = opt - '0'; 1465 break; 1466 case 'B': 1467 g_header_bypass = 1; 1468 prog.prog_api.ea_hsi_if = &header_bypass_api; 1469 prog.prog_api.ea_hsi_ctx = NULL; 1470 break; 1471 case 'I': 1472 client_ctx.hcc_flags |= HCC_ABORT_ON_INCOMPLETE; 1473 break; 1474 case 'K': 1475 ++s_discard_response; 1476 break; 1477 case 'u': /* Accept p<U>sh promise */ 1478 promise_fd = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, 0644); 1479 if (promise_fd < 0) 1480 { 1481 perror("open"); 1482 exit(1); 1483 } 1484 prog.prog_settings.es_support_push = 1; /* Pokes into prog */ 1485 break; 1486 case 'E': /* E: randomly reprioritize str<E>ams. Now, that's 1487 * pretty random. :) 1488 */ 1489 randomly_reprioritize_streams = 1; 1490 break; 1491 case 'n': 1492 client_ctx.hcc_concurrency = atoi(optarg); 1493 break; 1494 case 'w': 1495 client_ctx.hcc_cc_reqs_per_conn = atoi(optarg); 1496 break; 1497 case 'P': 1498 client_ctx.payload = optarg; 1499 if (0 != stat(optarg, &st)) 1500 { 1501 perror("stat"); 1502 exit(2); 1503 } 1504 sprintf(client_ctx.payload_size, "%jd", (intmax_t) st.st_size); 1505 break; 1506 case 'M': 1507 client_ctx.method = optarg; 1508 break; 1509 case 'r': 1510 client_ctx.hcc_total_n_reqs = atoi(optarg); 1511 break; 1512 case 'R': 1513 client_ctx.hcc_reqs_per_conn = atoi(optarg); 1514 break; 1515 case 'H': 1516 client_ctx.hostname = optarg; 1517 prog.prog_hostname = optarg; /* Pokes into prog */ 1518 break; 1519 case 'p': 1520 pe = calloc(1, sizeof(*pe)); 1521 pe->path = optarg; 1522 TAILQ_INSERT_TAIL(&client_ctx.hcc_path_elems, pe, next_pe); 1523 break; 1524 case 'h': 1525 usage(argv[0]); 1526 prog_print_common_options(&prog, stdout); 1527 exit(0); 1528 case 'q': 1529 client_ctx.qif_file = optarg; 1530 break; 1531 case 'e': 1532 if (TAILQ_EMPTY(&sports)) 1533 token = optarg; 1534 else 1535 sport_set_token(TAILQ_LAST(&sports, sport_head), optarg); 1536 break; 1537#ifndef WIN32 1538 case 'C': 1539 prog.prog_api.ea_verify_cert = verify_server_cert; 1540 prog.prog_api.ea_verify_ctx = optarg; 1541 break; 1542#endif 1543 case 't': 1544 stats_fh = stdout; 1545 break; 1546 case 'T': 1547 if (0 == strcmp(optarg, "-")) 1548 stats_fh = stdout; 1549 else 1550 { 1551 stats_fh = fopen(optarg, "w"); 1552 if (!stats_fh) 1553 { 1554 perror("fopen"); 1555 exit(1); 1556 } 1557 } 1558 break; 1559 case 'b': 1560 client_ctx.hcc_reset_after_nbytes = atoi(optarg); 1561 break; 1562 case 'd': 1563 client_ctx.hcc_retire_cid_after_nbytes = atoi(optarg); 1564 break; 1565 case '0': 1566 http_client_if.on_sess_resume_info = http_client_on_sess_resume_info; 1567 client_ctx.hcc_sess_resume_file_name = optarg; 1568 break; 1569 case '3': 1570 s_abandon_early = strtol(optarg, NULL, 10); 1571 break; 1572 default: 1573 if (0 != prog_set_opt(&prog, opt, optarg)) 1574 exit(1); 1575 } 1576 } 1577 1578#if LSQUIC_CONN_STATS 1579 prog.prog_api.ea_stats_fh = stats_fh; 1580#endif 1581 prog.prog_settings.es_ua = LITESPEED_ID; 1582 1583 if (client_ctx.qif_file) 1584 { 1585 client_ctx.qif_fh = fopen(client_ctx.qif_file, "r"); 1586 if (!client_ctx.qif_fh) 1587 { 1588 fprintf(stderr, "Cannot open %s for reading: %s\n", 1589 client_ctx.qif_file, strerror(errno)); 1590 exit(1); 1591 } 1592 LSQ_NOTICE("opened QIF file %s for reading\n", client_ctx.qif_file); 1593 prog.prog_api.ea_stream_if = &qif_client_if; 1594 g_header_bypass = 1; 1595 prog.prog_api.ea_hsi_if = &header_bypass_api; 1596 prog.prog_api.ea_hsi_ctx = NULL; 1597 } 1598 else if (TAILQ_EMPTY(&client_ctx.hcc_path_elems)) 1599 { 1600 fprintf(stderr, "Specify at least one path using -p option\n"); 1601 exit(1); 1602 } 1603 1604 start_time = lsquic_time_now(); 1605 was_empty = TAILQ_EMPTY(&sports); 1606 if (0 != prog_prep(&prog)) 1607 { 1608 LSQ_ERROR("could not prep"); 1609 exit(EXIT_FAILURE); 1610 } 1611 if (!(client_ctx.hostname || prog.prog_hostname)) 1612 { 1613 fprintf(stderr, "Specify hostname (used for SNI and :authority) via " 1614 "-H option\n"); 1615 exit(EXIT_FAILURE); 1616 } 1617 if (was_empty && token) 1618 sport_set_token(TAILQ_LAST(&sports, sport_head), token); 1619 1620 if (client_ctx.qif_file) 1621 { 1622 if (0 != prog_connect(&prog, NULL, 0)) 1623 { 1624 LSQ_ERROR("connection failed"); 1625 exit(EXIT_FAILURE); 1626 } 1627 } 1628 else 1629 create_connections(&client_ctx); 1630 1631 LSQ_DEBUG("entering event loop"); 1632 1633 s = prog_run(&prog); 1634 1635 if (stats_fh) 1636 { 1637 elapsed = (long double) (lsquic_time_now() - start_time) / 1000000; 1638 fprintf(stats_fh, "overall statistics as calculated by %s:\n", argv[0]); 1639 display_stat(stats_fh, &s_stat_to_conn, "time for connect"); 1640 display_stat(stats_fh, &s_stat_req, "time for request"); 1641 display_stat(stats_fh, &s_stat_ttfb, "time to 1st byte"); 1642 fprintf(stats_fh, "downloaded %lu application bytes in %.3Lf seconds\n", 1643 s_stat_downloaded_bytes, elapsed); 1644 fprintf(stats_fh, "%.2Lf reqs/sec; %.0Lf bytes/sec\n", 1645 (long double) s_stat_req.n / elapsed, 1646 (long double) s_stat_downloaded_bytes / elapsed); 1647 fprintf(stats_fh, "read handler count %lu\n", prog.prog_read_count); 1648 } 1649 1650 prog_cleanup(&prog); 1651 if (promise_fd >= 0) 1652 (void) close(promise_fd); 1653 1654 while ((pe = TAILQ_FIRST(&client_ctx.hcc_path_elems))) 1655 { 1656 TAILQ_REMOVE(&client_ctx.hcc_path_elems, pe, next_pe); 1657 free(pe); 1658 } 1659 1660 if (client_ctx.qif_fh) 1661 (void) fclose(client_ctx.qif_fh); 1662 1663 exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 1664} 1665