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