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