http_client.c revision fb3e20e0
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 struct header_buf hbuf; 535 if (!hostname) 536 hostname = st_h->client_ctx->prog->prog_hostname; 537 hbuf.off = 0; 538 struct lsxpack_header headers_arr[7]; 539#define V(v) (v), strlen(v) 540 header_set_ptr(&headers_arr[0], &hbuf, V(":method"), V(st_h->client_ctx->method)); 541 header_set_ptr(&headers_arr[1], &hbuf, V(":scheme"), V("https")); 542 header_set_ptr(&headers_arr[2], &hbuf, V(":path"), V(st_h->path)); 543 header_set_ptr(&headers_arr[3], &hbuf, V(":authority"), V(hostname)); 544 header_set_ptr(&headers_arr[4], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua)); 545 /* The following headers only gets sent if there is request payload: */ 546 header_set_ptr(&headers_arr[5], &hbuf, V("content-type"), V("application/octet-stream")); 547 header_set_ptr(&headers_arr[6], &hbuf, V("content-length"), V( st_h->client_ctx->payload_size)); 548 lsquic_http_headers_t headers = { 549 .count = sizeof(headers_arr) / sizeof(headers_arr[0]), 550 .headers = headers_arr, 551 }; 552 if (!st_h->client_ctx->payload) 553 headers.count -= 2; 554 if (0 != lsquic_stream_send_headers(st_h->stream, &headers, 555 st_h->client_ctx->payload == NULL)) 556 { 557 LSQ_ERROR("cannot send headers: %s", strerror(errno)); 558 exit(1); 559 } 560} 561 562 563/* This is here to exercise lsquic_conn_get_server_cert_chain() API */ 564static void 565display_cert_chain (lsquic_conn_t *conn) 566{ 567 STACK_OF(X509) *chain; 568 X509_NAME *name; 569 X509 *cert; 570 unsigned i; 571 char buf[100]; 572 573 chain = lsquic_conn_get_server_cert_chain(conn); 574 if (!chain) 575 { 576 LSQ_WARN("could not get server certificate chain"); 577 return; 578 } 579 580 for (i = 0; i < sk_X509_num(chain); ++i) 581 { 582 cert = sk_X509_value(chain, i); 583 name = X509_get_subject_name(cert); 584 LSQ_INFO("cert #%u: name: %s", i, 585 X509_NAME_oneline(name, buf, sizeof(buf))); 586 X509_free(cert); 587 } 588 589 sk_X509_free(chain); 590} 591 592 593static void 594http_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 595{ 596 ssize_t nw; 597 598 if (st_h->sh_flags & HEADERS_SENT) 599 { 600 if (st_h->client_ctx->payload && test_reader_size(st_h->reader.lsqr_ctx) > 0) 601 { 602 nw = lsquic_stream_writef(stream, &st_h->reader); 603 if (nw < 0) 604 { 605 LSQ_ERROR("write error: %s", strerror(errno)); 606 exit(1); 607 } 608 if (test_reader_size(st_h->reader.lsqr_ctx) > 0) 609 { 610 lsquic_stream_wantwrite(stream, 1); 611 } 612 else 613 { 614 lsquic_stream_shutdown(stream, 1); 615 lsquic_stream_wantread(stream, 1); 616 } 617 } 618 else 619 { 620 lsquic_stream_shutdown(stream, 1); 621 lsquic_stream_wantread(stream, 1); 622 } 623 } 624 else 625 { 626 st_h->sh_flags |= HEADERS_SENT; 627 send_headers(st_h); 628 } 629} 630 631 632static size_t 633discard (void *ctx, const unsigned char *buf, size_t sz, int fin) 634{ 635 return sz; 636} 637 638 639static void 640http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 641{ 642 struct http_client_ctx *const client_ctx = st_h->client_ctx; 643 struct hset *hset; 644 ssize_t nread; 645 unsigned old_prio, new_prio; 646 unsigned char buf[0x200]; 647 unsigned nreads = 0; 648#ifdef WIN32 649 srand(GetTickCount()); 650#endif 651 652 do 653 { 654 if (g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS)) 655 { 656 hset = lsquic_stream_get_hset(stream); 657 if (!hset) 658 { 659 LSQ_ERROR("could not get header set from stream"); 660 exit(2); 661 } 662 st_h->sh_ttfb = lsquic_time_now(); 663 update_sample_stats(&s_stat_ttfb, st_h->sh_ttfb - st_h->sh_created); 664 if (s_discard_response) 665 LSQ_DEBUG("discard response: do not dump headers"); 666 else 667 hset_dump(hset, stdout); 668 hset_destroy(hset); 669 st_h->sh_flags |= PROCESSED_HEADERS; 670 } 671 else if (nread = (s_discard_response 672 ? lsquic_stream_readf(stream, discard, NULL) 673 : lsquic_stream_read(stream, buf, sizeof(buf))), 674 nread > 0) 675 { 676 s_stat_downloaded_bytes += nread; 677 /* test stream_reset after some number of read bytes */ 678 if (client_ctx->hcc_reset_after_nbytes && 679 s_stat_downloaded_bytes > client_ctx->hcc_reset_after_nbytes) 680 { 681 lsquic_stream_reset(stream, 0x1); 682 break; 683 } 684 /* test retire_cid after some number of read bytes */ 685 if (client_ctx->hcc_retire_cid_after_nbytes && 686 s_stat_downloaded_bytes > client_ctx->hcc_retire_cid_after_nbytes) 687 { 688 lsquic_conn_retire_cid(lsquic_stream_conn(stream)); 689 client_ctx->hcc_retire_cid_after_nbytes = 0; 690 break; 691 } 692 if (!g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS)) 693 { 694 /* First read is assumed to be the first byte */ 695 st_h->sh_ttfb = lsquic_time_now(); 696 update_sample_stats(&s_stat_ttfb, 697 st_h->sh_ttfb - st_h->sh_created); 698 st_h->sh_flags |= PROCESSED_HEADERS; 699 } 700 if (!s_discard_response) 701 fwrite(buf, 1, nread, stdout); 702 if (randomly_reprioritize_streams && (st_h->count++ & 0x3F) == 0) 703 { 704 old_prio = lsquic_stream_priority(stream); 705 new_prio = 1 + (random() & 0xFF); 706#ifndef NDEBUG 707 const int s = 708#endif 709 lsquic_stream_set_priority(stream, new_prio); 710 assert(s == 0); 711 LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u", 712 lsquic_stream_id(stream), old_prio, new_prio); 713 } 714 } 715 else if (0 == nread) 716 { 717 update_sample_stats(&s_stat_req, lsquic_time_now() - st_h->sh_ttfb); 718 client_ctx->hcc_flags |= HCC_SEEN_FIN; 719 lsquic_stream_shutdown(stream, 0); 720 break; 721 } 722 else if (client_ctx->prog->prog_settings.es_rw_once && EWOULDBLOCK == errno) 723 { 724 LSQ_NOTICE("emptied the buffer in 'once' mode"); 725 break; 726 } 727 else if (lsquic_stream_is_rejected(stream)) 728 { 729 LSQ_NOTICE("stream was rejected"); 730 lsquic_stream_close(stream); 731 break; 732 } 733 else 734 { 735 LSQ_ERROR("could not read: %s", strerror(errno)); 736 exit(2); 737 } 738 } 739 while (client_ctx->prog->prog_settings.es_rw_once 740 && nreads++ < 3 /* Emulate just a few reads */); 741} 742 743 744static void 745http_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 746{ 747 const int pushed = lsquic_stream_is_pushed(stream); 748 if (pushed) 749 { 750 assert(NULL == st_h); 751 return; 752 } 753 754 LSQ_INFO("%s called", __func__); 755 struct http_client_ctx *const client_ctx = st_h->client_ctx; 756 lsquic_conn_t *conn = lsquic_stream_conn(stream); 757 lsquic_conn_ctx_t *conn_h; 758 TAILQ_FOREACH(conn_h, &client_ctx->conn_ctxs, next_ch) 759 if (conn_h->conn == conn) 760 break; 761 assert(conn_h); 762 --conn_h->ch_n_reqs; 763 --conn_h->ch_n_cc_streams; 764 if (0 == conn_h->ch_n_reqs) 765 { 766 LSQ_INFO("all requests completed, closing connection"); 767 lsquic_conn_close(conn_h->conn); 768 } 769 else 770 { 771 LSQ_INFO("%u active stream, %u request remain, creating %u new stream", 772 conn_h->ch_n_cc_streams, 773 conn_h->ch_n_reqs - conn_h->ch_n_cc_streams, 774 MIN((conn_h->ch_n_reqs - conn_h->ch_n_cc_streams), 775 (client_ctx->hcc_cc_reqs_per_conn - conn_h->ch_n_cc_streams))); 776 create_streams(client_ctx, conn_h); 777 } 778 if (st_h->reader.lsqr_ctx) 779 destroy_lsquic_reader_ctx(st_h->reader.lsqr_ctx); 780 free(st_h); 781} 782 783 784static struct lsquic_stream_if http_client_if = { 785 .on_new_conn = http_client_on_new_conn, 786 .on_conn_closed = http_client_on_conn_closed, 787 .on_new_stream = http_client_on_new_stream, 788 .on_read = http_client_on_read, 789 .on_write = http_client_on_write, 790 .on_close = http_client_on_close, 791 .on_hsk_done = http_client_on_hsk_done, 792}; 793 794 795static void 796usage (const char *prog) 797{ 798 const char *const slash = strrchr(prog, '/'); 799 if (slash) 800 prog = slash + 1; 801 printf( 802"Usage: %s [opts]\n" 803"\n" 804"Options:\n" 805" -p PATH Path to request. May be specified more than once. If no\n" 806" path is specified, the connection is closed as soon as\n" 807" handshake succeeds.\n" 808" -n CONNS Number of concurrent connections. Defaults to 1.\n" 809" -r NREQS Total number of requests to send. Defaults to 1.\n" 810" -R MAXREQS Maximum number of requests per single connection. Some\n" 811" connections will have fewer requests than this.\n" 812" -w CONCUR Number of concurrent requests per single connection.\n" 813" Defaults to 1.\n" 814" -M METHOD Method. Defaults to GET.\n" 815" -P PAYLOAD Name of the file that contains payload to be used in the\n" 816" request. This adds two more headers to the request:\n" 817" content-type: application/octet-stream and\n" 818" content-length\n" 819" -K Discard server response\n" 820" -I Abort on incomplete reponse from server\n" 821" -4 Prefer IPv4 when resolving hostname\n" 822" -6 Prefer IPv6 when resolving hostname\n" 823" -0 FILE Provide RTT info file (reading or writing)\n" 824#ifndef WIN32 825" -C DIR Certificate store. If specified, server certificate will\n" 826" be verified.\n" 827#endif 828" -a Display server certificate chain after successful handshake.\n" 829" -b N_BYTES Send RESET_STREAM frame after the client has read n bytes.\n" 830" -t Print stats to stdout.\n" 831" -T FILE Print stats to FILE. If FILE is -, print stats to stdout.\n" 832" -q FILE QIF mode: issue requests from the QIF file and validate\n" 833" server responses.\n" 834" -e TOKEN Hexadecimal string representing resume token.\n" 835 , prog); 836} 837 838 839#ifndef WIN32 840static X509_STORE *store; 841 842/* Windows does not have regex... */ 843static int 844ends_in_pem (const char *s) 845{ 846 int len; 847 848 len = strlen(s); 849 850 return len >= 4 851 && 0 == strcasecmp(s + len - 4, ".pem"); 852} 853 854 855static X509 * 856file2cert (const char *path) 857{ 858 X509 *cert = NULL; 859 BIO *in; 860 861 in = BIO_new(BIO_s_file()); 862 if (!in) 863 goto end; 864 865 if (BIO_read_filename(in, path) <= 0) 866 goto end; 867 868 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); 869 870 end: 871 BIO_free(in); 872 return cert; 873} 874 875 876static int 877init_x509_cert_store (const char *path) 878{ 879 struct dirent *ent; 880 X509 *cert; 881 DIR *dir; 882 char file_path[NAME_MAX]; 883 int ret; 884 885 dir = opendir(path); 886 if (!dir) 887 { 888 LSQ_WARN("Cannot open directory `%s': %s", path, strerror(errno)); 889 return -1; 890 } 891 892 store = X509_STORE_new(); 893 894 while ((ent = readdir(dir))) 895 { 896 if (ends_in_pem(ent->d_name)) 897 { 898 ret = snprintf(file_path, sizeof(file_path), "%s/%s", 899 path, ent->d_name); 900 if (ret < 0) 901 { 902 LSQ_WARN("file_path formatting error %s", strerror(errno)); 903 continue; 904 } 905 else if ((unsigned)ret >= sizeof(file_path)) 906 { 907 LSQ_WARN("file_path was truncated %s", strerror(errno)); 908 continue; 909 } 910 cert = file2cert(file_path); 911 if (cert) 912 { 913 if (1 != X509_STORE_add_cert(store, cert)) 914 LSQ_WARN("could not add cert from %s", file_path); 915 } 916 else 917 LSQ_WARN("could not read cert from %s", file_path); 918 } 919 } 920 (void) closedir(dir); 921 return 0; 922} 923 924 925static int 926verify_server_cert (void *ctx, STACK_OF(X509) *chain) 927{ 928 X509_STORE_CTX store_ctx; 929 X509 *cert; 930 int ver; 931 932 if (!store) 933 { 934 if (0 != init_x509_cert_store(ctx)) 935 return -1; 936 } 937 938 cert = sk_X509_shift(chain); 939 X509_STORE_CTX_init(&store_ctx, store, cert, chain); 940 941 ver = X509_verify_cert(&store_ctx); 942 943 X509_STORE_CTX_cleanup(&store_ctx); 944 945 if (ver != 1) 946 LSQ_WARN("could not verify server certificate"); 947 948 return ver == 1 ? 0 : -1; 949} 950#endif 951 952 953static void * 954hset_create (void *hsi_ctx, lsquic_stream_t *stream, int is_push_promise) 955{ 956 struct hset *hset; 957 958 if (s_discard_response) 959 return (void *) 1; 960 else if ((hset = malloc(sizeof(*hset)))) 961 { 962 STAILQ_INIT(hset); 963 return hset; 964 } 965 else 966 return NULL; 967} 968 969 970static struct lsxpack_header * 971hset_prepare_decode (void *hset_p, struct lsxpack_header *xhdr, 972 size_t req_space) 973{ 974 struct hset *const hset = hset_p; 975 struct hset_elem *el; 976 char *buf; 977 978 if (0 == req_space) 979 req_space = 0x100; 980 981 if (req_space > LSXPACK_MAX_STRLEN) 982 { 983 LSQ_WARN("requested space for header is too large: %zd bytes", 984 req_space); 985 return NULL; 986 } 987 988 if (!xhdr) 989 { 990 buf = malloc(req_space); 991 if (!buf) 992 { 993 LSQ_WARN("cannot allocate buf of %zd bytes", req_space); 994 return NULL; 995 } 996 el = malloc(sizeof(*el)); 997 if (!el) 998 { 999 LSQ_WARN("cannot allocate hset_elem"); 1000 free(buf); 1001 return NULL; 1002 } 1003 STAILQ_INSERT_TAIL(hset, el, next); 1004 lsxpack_header_prepare_decode(&el->xhdr, buf, 0, req_space); 1005 el->nalloc = req_space; 1006 } 1007 else 1008 { 1009 el = (struct hset_elem *) ((char *) xhdr 1010 - offsetof(struct hset_elem, xhdr)); 1011 if (req_space <= el->nalloc) 1012 { 1013 LSQ_ERROR("requested space is smaller than already allocated"); 1014 return NULL; 1015 } 1016 if (req_space < el->nalloc * 2) 1017 req_space = el->nalloc * 2; 1018 buf = realloc(el->xhdr.buf, req_space); 1019 if (!buf) 1020 { 1021 LSQ_WARN("cannot reallocate hset buf"); 1022 return NULL; 1023 } 1024 el->xhdr.buf = buf; 1025 el->xhdr.val_len = req_space; 1026 el->nalloc = req_space; 1027 } 1028 1029 return &el->xhdr; 1030} 1031 1032 1033static int 1034hset_add_header (void *hset_p, struct lsxpack_header *xhdr) 1035{ 1036 unsigned name_len, value_len; 1037 /* Not much to do: the header value are in xhdr */ 1038 1039 if (xhdr) 1040 { 1041 name_len = xhdr->name_len; 1042 value_len = xhdr->val_len; 1043 s_stat_downloaded_bytes += name_len + value_len + 4; /* ": \r\n" */ 1044 } 1045 else 1046 s_stat_downloaded_bytes += 2; /* \r\n "*/ 1047 1048 return 0; 1049} 1050 1051 1052static void 1053hset_destroy (void *hset_p) 1054{ 1055 struct hset *hset = hset_p; 1056 struct hset_elem *el, *next; 1057 1058 if (!s_discard_response) 1059 { 1060 for (el = STAILQ_FIRST(hset); el; el = next) 1061 { 1062 next = STAILQ_NEXT(el, next); 1063 free(el->xhdr.buf); 1064 free(el); 1065 } 1066 free(hset); 1067 } 1068} 1069 1070 1071static void 1072hset_dump (const struct hset *hset, FILE *out) 1073{ 1074 const struct hset_elem *el; 1075 1076 STAILQ_FOREACH(el, hset, next) 1077 if (el->xhdr.flags & (LSXPACK_HPACK_VAL_MATCHED|LSXPACK_QPACK_IDX)) 1078 fprintf(out, "%.*s (%s static table idx %u): %.*s\n", 1079 (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr), 1080 el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? "hpack" : "qpack", 1081 el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? el->xhdr.hpack_index 1082 : el->xhdr.qpack_index, 1083 (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr)); 1084 else 1085 fprintf(out, "%.*s: %.*s\n", 1086 (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr), 1087 (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr)); 1088 1089 fprintf(out, "\n"); 1090 fflush(out); 1091} 1092 1093 1094/* These are basic and for illustration purposes only. You will want to 1095 * do your own verification by doing something similar to what is done 1096 * in src/liblsquic/lsquic_http1x_if.c 1097 */ 1098static const struct lsquic_hset_if header_bypass_api = 1099{ 1100 .hsi_create_header_set = hset_create, 1101 .hsi_prepare_decode = hset_prepare_decode, 1102 .hsi_process_header = hset_add_header, 1103 .hsi_discard_header_set = hset_destroy, 1104}; 1105 1106 1107static void 1108display_stat (FILE *out, const struct sample_stats *stats, const char *name) 1109{ 1110 long double mean, stddev; 1111 1112 calc_sample_stats(stats, &mean, &stddev); 1113 fprintf(out, "%s: n: %u; min: %.2Lf ms; max: %.2Lf ms; mean: %.2Lf ms; " 1114 "sd: %.2Lf ms\n", name, stats->n, (long double) stats->min / 1000, 1115 (long double) stats->max / 1000, mean / 1000, stddev / 1000); 1116} 1117 1118 1119static lsquic_conn_ctx_t * 1120qif_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 1121{ 1122 lsquic_conn_make_stream(conn); 1123 return stream_if_ctx; 1124} 1125 1126 1127static void 1128qif_client_on_conn_closed (lsquic_conn_t *conn) 1129{ 1130 struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn); 1131 LSQ_INFO("connection is closed: stop engine"); 1132 prog_stop(client_ctx->prog); 1133} 1134 1135 1136struct qif_stream_ctx 1137{ 1138 int reqno; 1139 struct lsquic_http_headers headers; 1140 char *qif_str; 1141 size_t qif_sz; 1142 size_t qif_off; 1143 char *resp_str; /* qif_sz allocated */ 1144 size_t resp_off; /* Read so far */ 1145 enum { 1146 QSC_HEADERS_SENT = 1 << 0, 1147 QSC_GOT_HEADERS = 1 << 1, 1148 } flags; 1149}; 1150 1151#define MAX(a, b) ((a) > (b) ? (a) : (b)) 1152 1153lsquic_stream_ctx_t * 1154qif_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 1155{ 1156 struct http_client_ctx *const client_ctx = stream_if_ctx; 1157 FILE *const fh = client_ctx->qif_fh; 1158 struct qif_stream_ctx *ctx; 1159 struct lsxpack_header *header; 1160 static int reqno; 1161 size_t nalloc; 1162 char *end, *tab, *line; 1163 char line_buf[0x1000]; 1164 1165 ctx = calloc(1, sizeof(*ctx)); 1166 if (!ctx) 1167 { 1168 perror("calloc"); 1169 exit(1); 1170 } 1171 ctx->reqno = reqno++; 1172 1173 nalloc = 0; 1174 while ((line = fgets(line_buf, sizeof(line_buf), fh))) 1175 { 1176 end = strchr(line, '\n'); 1177 if (!end) 1178 { 1179 fprintf(stderr, "no newline\n"); 1180 exit(1); 1181 } 1182 1183 if (end == line) 1184 break; 1185 1186 if (*line == '#') 1187 continue; 1188 1189 tab = strchr(line, '\t'); 1190 if (!tab) 1191 { 1192 fprintf(stderr, "no TAB\n"); 1193 exit(1); 1194 } 1195 1196 if (nalloc + (end + 1 - line) > ctx->qif_sz) 1197 { 1198 if (nalloc) 1199 nalloc = MAX(nalloc * 2, nalloc + (end + 1 - line)); 1200 else 1201 nalloc = end + 1 - line; 1202 ctx->qif_str = realloc(ctx->qif_str, nalloc); 1203 if (!ctx->qif_str) 1204 { 1205 perror("realloc"); 1206 exit(1); 1207 } 1208 } 1209 memcpy(ctx->qif_str + ctx->qif_sz, line, end + 1 - line); 1210 1211 ctx->headers.headers = realloc(ctx->headers.headers, 1212 sizeof(ctx->headers.headers[0]) * (ctx->headers.count + 1)); 1213 if (!ctx->headers.headers) 1214 { 1215 perror("realloc"); 1216 exit(1); 1217 } 1218 header = &ctx->headers.headers[ctx->headers.count++]; 1219 lsxpack_header_set_offset2(header, ctx->qif_str + ctx->qif_sz, 0, 1220 tab - line, tab - line + 1, end - tab - 1); 1221 ctx->qif_sz += end + 1 - line; 1222 } 1223 1224 lsquic_stream_wantwrite(stream, 1); 1225 1226 if (!line) 1227 { 1228 LSQ_DEBUG("Input QIF file ends; close file handle"); 1229 fclose(client_ctx->qif_fh); 1230 client_ctx->qif_fh = NULL; 1231 } 1232 1233 return (void *) ctx; 1234} 1235 1236 1237static void 1238qif_client_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) 1239{ 1240 struct qif_stream_ctx *const ctx = (void *) h; 1241 size_t towrite; 1242 ssize_t nw; 1243 1244 if (ctx->flags & QSC_HEADERS_SENT) 1245 { 1246 towrite = ctx->qif_sz - ctx->qif_off; 1247 nw = lsquic_stream_write(stream, ctx->qif_str + ctx->qif_off, towrite); 1248 if (nw >= 0) 1249 { 1250 LSQ_DEBUG("wrote %zd bytes to stream", nw); 1251 ctx->qif_off += nw; 1252 if (ctx->qif_off == (size_t) nw) 1253 { 1254 lsquic_stream_shutdown(stream, 1); 1255 lsquic_stream_wantread(stream, 1); 1256 LSQ_DEBUG("finished writing request %d", ctx->reqno); 1257 } 1258 } 1259 else 1260 { 1261 LSQ_ERROR("cannot write to stream: %s", strerror(errno)); 1262 lsquic_stream_wantwrite(stream, 0); 1263 lsquic_conn_abort(lsquic_stream_conn(stream)); 1264 } 1265 } 1266 else 1267 { 1268 if (0 == lsquic_stream_send_headers(stream, &ctx->headers, 0)) 1269 { 1270 ctx->flags |= QSC_HEADERS_SENT; 1271 LSQ_DEBUG("sent headers"); 1272 } 1273 else 1274 { 1275 LSQ_ERROR("cannot send headers: %s", strerror(errno)); 1276 lsquic_stream_wantwrite(stream, 0); 1277 lsquic_conn_abort(lsquic_stream_conn(stream)); 1278 } 1279 } 1280} 1281 1282 1283static void 1284qif_client_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) 1285{ 1286 struct qif_stream_ctx *const ctx = (void *) h; 1287 struct hset *hset; 1288 ssize_t nr; 1289 unsigned char buf[1]; 1290 1291 LSQ_DEBUG("reading response to request %d", ctx->reqno); 1292 1293 if (!(ctx->flags & QSC_GOT_HEADERS)) 1294 { 1295 hset = lsquic_stream_get_hset(stream); 1296 if (!hset) 1297 { 1298 LSQ_ERROR("could not get header set from stream"); 1299 exit(2); 1300 } 1301 LSQ_DEBUG("got header set for response %d", ctx->reqno); 1302 hset_dump(hset, stdout); 1303 hset_destroy(hset); 1304 ctx->flags |= QSC_GOT_HEADERS; 1305 } 1306 else 1307 { 1308 if (!ctx->resp_str) 1309 { 1310 ctx->resp_str = malloc(ctx->qif_sz); 1311 if (!ctx->resp_str) 1312 { 1313 perror("malloc"); 1314 exit(1); 1315 } 1316 } 1317 if (ctx->resp_off < ctx->qif_sz) 1318 { 1319 nr = lsquic_stream_read(stream, ctx->resp_str + ctx->resp_off, 1320 ctx->qif_sz - ctx->resp_off); 1321 if (nr > 0) 1322 { 1323 ctx->resp_off += nr; 1324 LSQ_DEBUG("read %zd bytes of reponse %d", nr, ctx->reqno); 1325 } 1326 else if (nr == 0) 1327 { 1328 LSQ_INFO("response %d too short", ctx->reqno); 1329 LSQ_WARN("response %d FAIL", ctx->reqno); 1330 lsquic_stream_shutdown(stream, 0); 1331 } 1332 else 1333 { 1334 LSQ_ERROR("error reading from stream"); 1335 lsquic_stream_wantread(stream, 0); 1336 lsquic_conn_abort(lsquic_stream_conn(stream)); 1337 } 1338 } 1339 else 1340 { 1341 /* Collect EOF */ 1342 nr = lsquic_stream_read(stream, buf, sizeof(buf)); 1343 if (nr == 0) 1344 { 1345 if (0 == memcmp(ctx->qif_str, ctx->resp_str, ctx->qif_sz)) 1346 LSQ_INFO("response %d OK", ctx->reqno); 1347 else 1348 LSQ_WARN("response %d FAIL", ctx->reqno); 1349 lsquic_stream_shutdown(stream, 0); 1350 } 1351 else if (nr > 0) 1352 { 1353 LSQ_INFO("response %d too long", ctx->reqno); 1354 LSQ_WARN("response %d FAIL", ctx->reqno); 1355 lsquic_stream_shutdown(stream, 0); 1356 } 1357 else 1358 { 1359 LSQ_ERROR("error reading from stream"); 1360 lsquic_stream_shutdown(stream, 0); 1361 lsquic_conn_abort(lsquic_stream_conn(stream)); 1362 } 1363 } 1364 } 1365} 1366 1367 1368static void 1369qif_client_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) 1370{ 1371 struct lsquic_conn *conn = lsquic_stream_conn(stream); 1372 struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn); 1373 struct qif_stream_ctx *const ctx = (void *) h; 1374 free(ctx->qif_str); 1375 free(ctx->resp_str); 1376 free(ctx->headers.headers); 1377 free(ctx); 1378 if (client_ctx->qif_fh) 1379 lsquic_conn_make_stream(conn); 1380 else 1381 lsquic_conn_close(conn); 1382} 1383 1384 1385const struct lsquic_stream_if qif_client_if = { 1386 .on_new_conn = qif_client_on_new_conn, 1387 .on_conn_closed = qif_client_on_conn_closed, 1388 .on_new_stream = qif_client_on_new_stream, 1389 .on_read = qif_client_on_read, 1390 .on_write = qif_client_on_write, 1391 .on_close = qif_client_on_close, 1392}; 1393 1394 1395int 1396main (int argc, char **argv) 1397{ 1398 int opt, s, was_empty; 1399 lsquic_time_t start_time; 1400 FILE *stats_fh = NULL; 1401 long double elapsed; 1402 struct http_client_ctx client_ctx; 1403 struct stat st; 1404 struct path_elem *pe; 1405 struct sport_head sports; 1406 struct prog prog; 1407 const char *token = NULL; 1408 1409 TAILQ_INIT(&sports); 1410 memset(&client_ctx, 0, sizeof(client_ctx)); 1411 TAILQ_INIT(&client_ctx.hcc_path_elems); 1412 TAILQ_INIT(&client_ctx.conn_ctxs); 1413 client_ctx.method = "GET"; 1414 client_ctx.hcc_concurrency = 1; 1415 client_ctx.hcc_cc_reqs_per_conn = 1; 1416 client_ctx.hcc_reqs_per_conn = 1; 1417 client_ctx.hcc_total_n_reqs = 1; 1418 client_ctx.hcc_reset_after_nbytes = 0; 1419 client_ctx.hcc_retire_cid_after_nbytes = 0; 1420 client_ctx.prog = &prog; 1421 1422 prog_init(&prog, LSENG_HTTP, &sports, &http_client_if, &client_ctx); 1423 1424 while (-1 != (opt = getopt(argc, argv, PROG_OPTS 1425 "46Br:R:IKu:EP:M:n:w:H:p:0:q:e:hatT:b:d:" 1426#ifndef WIN32 1427 "C:" 1428#endif 1429 ))) 1430 { 1431 switch (opt) { 1432 case 'a': 1433 ++s_display_cert_chain; 1434 break; 1435 case '4': 1436 case '6': 1437 prog.prog_ipver = opt - '0'; 1438 break; 1439 case 'B': 1440 g_header_bypass = 1; 1441 prog.prog_api.ea_hsi_if = &header_bypass_api; 1442 prog.prog_api.ea_hsi_ctx = NULL; 1443 break; 1444 case 'I': 1445 client_ctx.hcc_flags |= HCC_ABORT_ON_INCOMPLETE; 1446 break; 1447 case 'K': 1448 ++s_discard_response; 1449 break; 1450 case 'u': /* Accept p<U>sh promise */ 1451 promise_fd = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, 0644); 1452 if (promise_fd < 0) 1453 { 1454 perror("open"); 1455 exit(1); 1456 } 1457 prog.prog_settings.es_support_push = 1; /* Pokes into prog */ 1458 break; 1459 case 'E': /* E: randomly reprioritize str<E>ams. Now, that's 1460 * pretty random. :) 1461 */ 1462 randomly_reprioritize_streams = 1; 1463 break; 1464 case 'n': 1465 client_ctx.hcc_concurrency = atoi(optarg); 1466 break; 1467 case 'w': 1468 client_ctx.hcc_cc_reqs_per_conn = atoi(optarg); 1469 break; 1470 case 'P': 1471 client_ctx.payload = optarg; 1472 if (0 != stat(optarg, &st)) 1473 { 1474 perror("stat"); 1475 exit(2); 1476 } 1477 sprintf(client_ctx.payload_size, "%jd", (intmax_t) st.st_size); 1478 break; 1479 case 'M': 1480 client_ctx.method = optarg; 1481 break; 1482 case 'r': 1483 client_ctx.hcc_total_n_reqs = atoi(optarg); 1484 break; 1485 case 'R': 1486 client_ctx.hcc_reqs_per_conn = atoi(optarg); 1487 break; 1488 case 'H': 1489 client_ctx.hostname = optarg; 1490 prog.prog_hostname = optarg; /* Pokes into prog */ 1491 break; 1492 case 'p': 1493 pe = calloc(1, sizeof(*pe)); 1494 pe->path = optarg; 1495 TAILQ_INSERT_TAIL(&client_ctx.hcc_path_elems, pe, next_pe); 1496 break; 1497 case 'h': 1498 usage(argv[0]); 1499 prog_print_common_options(&prog, stdout); 1500 exit(0); 1501 case 'q': 1502 client_ctx.qif_file = optarg; 1503 break; 1504 case 'e': 1505 if (TAILQ_EMPTY(&sports)) 1506 token = optarg; 1507 else 1508 sport_set_token(TAILQ_LAST(&sports, sport_head), optarg); 1509 break; 1510#ifndef WIN32 1511 case 'C': 1512 prog.prog_api.ea_verify_cert = verify_server_cert; 1513 prog.prog_api.ea_verify_ctx = optarg; 1514 break; 1515#endif 1516 case 't': 1517 stats_fh = stdout; 1518 break; 1519 case 'T': 1520 if (0 == strcmp(optarg, "-")) 1521 stats_fh = stdout; 1522 else 1523 { 1524 stats_fh = fopen(optarg, "w"); 1525 if (!stats_fh) 1526 { 1527 perror("fopen"); 1528 exit(1); 1529 } 1530 } 1531 break; 1532 case 'b': 1533 client_ctx.hcc_reset_after_nbytes = atoi(optarg); 1534 break; 1535 case 'd': 1536 client_ctx.hcc_retire_cid_after_nbytes = atoi(optarg); 1537 break; 1538 case '0': 1539 http_client_if.on_zero_rtt_info = http_client_on_zero_rtt_info; 1540 client_ctx.hcc_zero_rtt_file_name = optarg; 1541 break; 1542 default: 1543 if (0 != prog_set_opt(&prog, opt, optarg)) 1544 exit(1); 1545 } 1546 } 1547 1548#if LSQUIC_CONN_STATS 1549 prog.prog_api.ea_stats_fh = stats_fh; 1550#endif 1551 prog.prog_settings.es_ua = LITESPEED_ID; 1552 1553 if (client_ctx.qif_file) 1554 { 1555 client_ctx.qif_fh = fopen(client_ctx.qif_file, "r"); 1556 if (!client_ctx.qif_fh) 1557 { 1558 fprintf(stderr, "Cannot open %s for reading: %s\n", 1559 client_ctx.qif_file, strerror(errno)); 1560 exit(1); 1561 } 1562 LSQ_NOTICE("opened QIF file %s for reading\n", client_ctx.qif_file); 1563 prog.prog_api.ea_stream_if = &qif_client_if; 1564 g_header_bypass = 1; 1565 prog.prog_api.ea_hsi_if = &header_bypass_api; 1566 prog.prog_api.ea_hsi_ctx = NULL; 1567 } 1568 else if (TAILQ_EMPTY(&client_ctx.hcc_path_elems)) 1569 { 1570 fprintf(stderr, "Specify at least one path using -p option\n"); 1571 exit(1); 1572 } 1573 1574 start_time = lsquic_time_now(); 1575 was_empty = TAILQ_EMPTY(&sports); 1576 if (0 != prog_prep(&prog)) 1577 { 1578 LSQ_ERROR("could not prep"); 1579 exit(EXIT_FAILURE); 1580 } 1581 if (!(client_ctx.hostname || prog.prog_hostname)) 1582 { 1583 fprintf(stderr, "Specify hostname (used for SNI and :authority) via " 1584 "-H option\n"); 1585 exit(EXIT_FAILURE); 1586 } 1587 if (was_empty && token) 1588 sport_set_token(TAILQ_LAST(&sports, sport_head), token); 1589 1590 if (client_ctx.qif_file) 1591 { 1592 if (0 != prog_connect(&prog, NULL, 0)) 1593 { 1594 LSQ_ERROR("connection failed"); 1595 exit(EXIT_FAILURE); 1596 } 1597 } 1598 else 1599 create_connections(&client_ctx); 1600 1601 LSQ_DEBUG("entering event loop"); 1602 1603 s = prog_run(&prog); 1604 1605 if (stats_fh) 1606 { 1607 elapsed = (long double) (lsquic_time_now() - start_time) / 1000000; 1608 fprintf(stats_fh, "overall statistics as calculated by %s:\n", argv[0]); 1609 display_stat(stats_fh, &s_stat_to_conn, "time for connect"); 1610 display_stat(stats_fh, &s_stat_req, "time for request"); 1611 display_stat(stats_fh, &s_stat_ttfb, "time to 1st byte"); 1612 fprintf(stats_fh, "downloaded %lu application bytes in %.3Lf seconds\n", 1613 s_stat_downloaded_bytes, elapsed); 1614 fprintf(stats_fh, "%.2Lf reqs/sec; %.0Lf bytes/sec\n", 1615 (long double) s_stat_req.n / elapsed, 1616 (long double) s_stat_downloaded_bytes / elapsed); 1617 fprintf(stats_fh, "read handler count %lu\n", prog.prog_read_count); 1618 } 1619 1620 prog_cleanup(&prog); 1621 if (promise_fd >= 0) 1622 (void) close(promise_fd); 1623 1624 while ((pe = TAILQ_FIRST(&client_ctx.hcc_path_elems))) 1625 { 1626 TAILQ_REMOVE(&client_ctx.hcc_path_elems, pe, next_pe); 1627 free(pe); 1628 } 1629 1630 if (client_ctx.qif_fh) 1631 (void) fclose(client_ctx.qif_fh); 1632 1633 exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 1634} 1635