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