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