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