litespeed-quic/bin/prog.c

814 lines
22 KiB
C
Raw Normal View History

/* Copyright (c) 2017 - 2021 LiteSpeed Technologies Inc. See LICENSE. */
2017-09-22 21:00:03 +00:00
#include <assert.h>
#ifndef WIN32
#include <arpa/inet.h>
2017-09-22 21:00:03 +00:00
#include <netinet/in.h>
#include <signal.h>
#endif
#include <errno.h>
#include <limits.h>
2017-09-22 21:00:03 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
2017-09-22 21:00:03 +00:00
#include <sys/queue.h>
#ifndef WIN32
2017-09-22 21:00:03 +00:00
#include <unistd.h>
#else
2020-06-03 04:13:30 +00:00
#include "vc_compat.h"
#include "getopt.h"
#pragma warning(disable:4028)
#endif// WIN32
2017-09-22 21:00:03 +00:00
#include <event2/event.h>
#include <lsquic.h>
#include <openssl/ssl.h>
2017-09-22 21:00:03 +00:00
#include "../src/liblsquic/lsquic_hash.h"
#include "../src/liblsquic/lsquic_int_types.h"
#include "../src/liblsquic/lsquic_util.h"
2017-09-22 21:00:03 +00:00
#include "../src/liblsquic/lsquic_logger.h"
#include "test_config.h"
#include "test_cert.h"
2017-09-22 21:00:03 +00:00
#include "test_common.h"
#include "prog.h"
static int prog_stopped;
static const char *s_keylog_dir;
static const char *s_sess_resume_file;
2017-09-22 21:00:03 +00:00
static SSL_CTX * get_ssl_ctx (void *, const struct sockaddr *);
static void keylog_log_line (const SSL *, const char *);
2017-09-22 21:00:03 +00:00
static const struct lsquic_packout_mem_if pmi = {
.pmi_allocate = pba_allocate,
.pmi_release = pba_release,
.pmi_return = pba_release,
2017-09-22 21:00:03 +00:00
};
2020-06-03 04:13:30 +00:00
int
2017-09-22 21:00:03 +00:00
prog_init (struct prog *prog, unsigned flags,
struct sport_head *sports,
const struct lsquic_stream_if *stream_if, void *stream_if_ctx)
{
2020-06-03 04:13:30 +00:00
#ifdef WIN32
WSADATA wsd;
int s = WSAStartup(MAKEWORD(2, 2), &wsd);
if (s != 0)
{
LSQ_ERROR("WSAStartup failed: %d", s);
return -1;
}
#endif
2017-09-22 21:00:03 +00:00
/* prog-specific initialization: */
memset(prog, 0, sizeof(*prog));
prog->prog_engine_flags = flags;
prog->prog_sports = sports;
lsquic_engine_init_settings(&prog->prog_settings, flags);
#if ECN_SUPPORTED
prog->prog_settings.es_ecn = LSQUIC_DF_ECN;
#else
prog->prog_settings.es_ecn = 0;
#endif
2017-09-22 21:00:03 +00:00
prog->prog_api.ea_settings = &prog->prog_settings;
prog->prog_api.ea_stream_if = stream_if;
prog->prog_api.ea_stream_if_ctx = stream_if_ctx;
prog->prog_api.ea_packets_out = sport_packets_out;
prog->prog_api.ea_packets_out_ctx
= prog;
prog->prog_api.ea_pmi = &pmi;
prog->prog_api.ea_pmi_ctx = &prog->prog_pba;
prog->prog_api.ea_get_ssl_ctx = get_ssl_ctx;
#if LSQUIC_PREFERRED_ADDR
if (getenv("LSQUIC_PREFERRED_ADDR4") || getenv("LSQUIC_PREFERRED_ADDR6"))
prog->prog_flags |= PROG_SEARCH_ADDRS;
#endif
2017-09-22 21:00:03 +00:00
/* Non prog-specific initialization: */
lsquic_global_init(flags & LSENG_SERVER ? LSQUIC_GLOBAL_SERVER :
LSQUIC_GLOBAL_CLIENT);
lsquic_log_to_fstream(stderr, LLTS_HHMMSSMS);
lsquic_logger_lopt("=notice");
2020-06-03 04:13:30 +00:00
return 0;
2017-09-22 21:00:03 +00:00
}
static int
prog_add_sport (struct prog *prog, const char *arg)
{
struct service_port *sport;
sport = sport_new(arg, prog);
if (!sport)
return -1;
/* Default settings: */
sport->sp_flags = prog->prog_dummy_sport.sp_flags;
sport->sp_sndbuf = prog->prog_dummy_sport.sp_sndbuf;
sport->sp_rcvbuf = prog->prog_dummy_sport.sp_rcvbuf;
TAILQ_INSERT_TAIL(prog->prog_sports, sport, next_sport);
return 0;
}
void
prog_print_common_options (const struct prog *prog, FILE *out)
{
fprintf(out,
" -0 FILE Provide session resumption file (reading or writing)\n"
#if HAVE_REGEX
" -s SVCPORT Service port. Takes on the form of host:port, host,\n"
" or port. If host is not an IPv4 or IPv6 address, it is\n"
" resolved. If host is not set, the value of SNI is\n"
" used (see the -H flag). If port is not set, the default\n"
" is 443.\n"
#else
" -s SVCPORT Service port. Takes on the form of host:port or host.\n"
" If host is not an IPv4 or IPv6 address, it is resolved.\n"
" If port is not set, the default is 443.\n"
#endif
" Examples:\n"
" 127.0.0.1:12345\n"
" ::1:443\n"
" example.com\n"
" example.com:8443\n"
#if HAVE_REGEX
" 8443\n"
#endif
" If no -s option is given, 0.0.0.0:12345 address\n"
" is used.\n"
#if LSQUIC_DONTFRAG_SUPPORTED
" -D Do not set `do not fragment' flag on outgoing UDP packets\n"
#endif
" -z BYTES Maximum size of outgoing UDP packets (client only).\n"
" Overrides -o base_plpmtu.\n"
2017-09-22 21:00:03 +00:00
" -L LEVEL Log level for all modules. Possible values are `debug',\n"
" `info', `notice', `warn', `error', `alert', `emerg',\n"
" and `crit'.\n"
" -l LEVELS Log levels for modules, e.g.\n"
" -l event=info,engine=debug\n"
" Can be specified more than once.\n"
" -m MAX Maximum number of outgoing packet buffers that can be\n"
" assigned at any one time. By default, there is no max.\n"
" -y style Timestamp style used in log messages. The following styles\n"
" are supported:\n"
" 0 No timestamp\n"
" 1 Millisecond time (this is the default).\n"
" Example: 11:04:05.196\n"
" 2 Full date and millisecond time.\n"
" Example: 2017-03-21 13:43:46.671\n"
" 3 Chrome-like timestamp: date/time.microseconds.\n"
" Example: 1223/104613.946956\n"
" 4 Microsecond time.\n"
" Example: 11:04:05.196308\n"
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
" 5 Full date and microsecond time.\n"
" Example: 2017-03-21 13:43:46.671345\n"
2017-09-22 21:00:03 +00:00
" -S opt=val Socket options. Supported options:\n"
" sndbuf=12345 # Sets SO_SNDBUF\n"
" rcvbuf=12345 # Sets SO_RCVBUF\n"
" -W Use stock PMI (malloc & free)\n"
2017-09-22 21:00:03 +00:00
);
#if HAVE_SENDMMSG
fprintf(out,
" -g Use sendmmsg() to send packets.\n"
);
#endif
#if HAVE_RECVMMSG
fprintf(out,
" -j Use recvmmsg() to receive packets.\n"
);
#endif
2017-09-22 21:00:03 +00:00
if (prog->prog_engine_flags & LSENG_SERVER)
fprintf(out,
" -c CERTSPEC Service specification. The specification is three values\n"
" separated by commas. The values are:\n"
" * Domain name\n"
" * File containing cert in PEM format\n"
" * File containing private key in DER or PEM format\n"
" Example:\n"
" -c www.example.com,/tmp/cert.pem,/tmp/key.pkcs8\n"
);
else
2017-09-22 21:00:03 +00:00
{
if (prog->prog_engine_flags & LSENG_HTTP)
fprintf(out,
" -H host Value of `host' HTTP header. This is also used as SNI\n"
" in Client Hello. This option is used to override the\n"
" `host' part of the address specified using -s flag.\n"
2017-09-22 21:00:03 +00:00
);
else
fprintf(out,
" -H host Value of SNI in CHLO.\n"
);
}
#ifndef WIN32
fprintf(out,
" -G dir SSL keys will be logged to files in this directory.\n"
);
#endif
2017-09-22 21:00:03 +00:00
fprintf(out,
" -k Connect UDP socket. Only meant to be used with clients\n"
" to pick up ICMP errors.\n"
" -i USECS Clock granularity in microseconds. Defaults to %u.\n",
LSQUIC_DF_CLOCK_GRANULARITY
);
2017-09-22 21:00:03 +00:00
fprintf(out,
" -h Print this help screen and exit\n"
);
}
int
prog_set_opt (struct prog *prog, int opt, const char *arg)
{
2020-06-03 04:13:30 +00:00
#ifndef WIN32
struct stat st;
int s;
2020-06-03 04:13:30 +00:00
#endif
2017-09-22 21:00:03 +00:00
switch (opt)
{
#if LSQUIC_DONTFRAG_SUPPORTED
2017-09-22 21:00:03 +00:00
case 'D':
{
struct service_port *sport = TAILQ_LAST(prog->prog_sports, sport_head);
if (!sport)
sport = &prog->prog_dummy_sport;
sport->sp_flags |= SPORT_FRAGMENT_OK;
2017-09-22 21:00:03 +00:00
}
return 0;
#endif
#if HAVE_SENDMMSG
case 'g':
prog->prog_use_sendmmsg = 1;
return 0;
#endif
#if HAVE_RECVMMSG
case 'j':
prog->prog_use_recvmmsg = 1;
return 0;
#endif
2017-09-22 21:00:03 +00:00
case 'm':
prog->prog_packout_max = atoi(arg);
return 0;
case 'z':
prog->prog_max_packet_size = atoi(arg);
return 0;
case 'W':
prog->prog_use_stock_pmi = 1;
return 0;
case 'c':
if (prog->prog_engine_flags & LSENG_SERVER)
{
if (!prog->prog_certs)
prog->prog_certs = lsquic_hash_create();
return load_cert(prog->prog_certs, arg);
}
else
return -1;
2017-09-22 21:00:03 +00:00
case 'H':
if (prog->prog_engine_flags & LSENG_SERVER)
return -1;
prog->prog_hostname = arg;
return 0;
case 'y':
lsquic_log_to_fstream(stderr, atoi(arg));
return 0;
case 'L':
return lsquic_set_log_level(arg);
case 'l':
return lsquic_logger_lopt(arg);
case 'o':
return set_engine_option(&prog->prog_settings,
&prog->prog_version_cleared, arg);
case 'i':
prog->prog_settings.es_clock_granularity = atoi(arg);
return 0;
2017-09-22 21:00:03 +00:00
case 's':
if (0 == (prog->prog_engine_flags & LSENG_SERVER) &&
!TAILQ_EMPTY(prog->prog_sports))
return -1;
return prog_add_sport(prog, arg);
case 'S':
{
struct service_port *sport = TAILQ_LAST(prog->prog_sports, sport_head);
if (!sport)
sport = &prog->prog_dummy_sport;
char *const name = strdup(optarg);
char *val = strchr(name, '=');
if (!val)
{
free(name);
return -1;
}
*val = '\0';
++val;
if (0 == strcasecmp(name, "sndbuf"))
{
sport->sp_flags |= SPORT_SET_SNDBUF;
sport->sp_sndbuf = atoi(val);
free(name);
return 0;
}
else if (0 == strcasecmp(name, "rcvbuf"))
{
sport->sp_flags |= SPORT_SET_RCVBUF;
sport->sp_rcvbuf = atoi(val);
free(name);
return 0;
}
else
{
free(name);
return -1;
}
}
case 'k':
{
struct service_port *sport = TAILQ_LAST(prog->prog_sports, sport_head);
if (!sport)
sport = &prog->prog_dummy_sport;
sport->sp_flags |= SPORT_CONNECT;
}
return 0;
case '0':
s_sess_resume_file = optarg;
return 0;
case 'G':
#ifndef WIN32
if (0 == stat(optarg, &st))
{
if (!S_ISDIR(st.st_mode))
{
LSQ_ERROR("%s is not a directory", optarg);
return -1;
}
}
else
{
s = mkdir(optarg, 0700);
if (s != 0)
{
LSQ_ERROR("cannot create directory %s: %s", optarg,
strerror(errno));
return -1;
}
}
s_keylog_dir = optarg;
if (prog->prog_settings.es_ql_bits)
{
LSQ_NOTICE("QL loss bits turned off because of -G. If you want "
"to turn it on, just override: -G dir -o ql_bits=2");
prog->prog_settings.es_ql_bits = 0;
}
return 0;
#else
LSQ_ERROR("key logging is not supported on Windows");
return -1;
#endif
2017-09-22 21:00:03 +00:00
default:
return 1;
}
}
struct event_base *
prog_eb (struct prog *prog)
{
return prog->prog_eb;
}
int
prog_connect (struct prog *prog, unsigned char *sess_resume, size_t sess_resume_len)
2017-09-22 21:00:03 +00:00
{
struct service_port *sport;
sport = TAILQ_FIRST(prog->prog_sports);
if (NULL == lsquic_engine_connect(prog->prog_engine, N_LSQVER,
(struct sockaddr *) &sport->sp_local_addr,
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
(struct sockaddr *) &sport->sas, sport, NULL,
prog->prog_hostname ? prog->prog_hostname
/* SNI is required for HTTP */
: prog->prog_engine_flags & LSENG_HTTP ? sport->host
: NULL,
prog->prog_max_packet_size, sess_resume, sess_resume_len,
sport->sp_token_buf, sport->sp_token_sz))
2017-09-22 21:00:03 +00:00
return -1;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
prog_process_conns(prog);
2017-09-22 21:00:03 +00:00
return 0;
}
static int
prog_init_client (struct prog *prog)
{
struct service_port *sport;
sport = TAILQ_FIRST(prog->prog_sports);
if (0 != sport_init_client(sport, prog->prog_engine, prog->prog_eb))
return -1;
return 0;
}
static SSL_CTX *
get_ssl_ctx (void *peer_ctx, const struct sockaddr *unused)
{
const struct service_port *const sport = peer_ctx;
return sport->sp_prog->prog_ssl_ctx;
}
static int
prog_new_session_cb (SSL *ssl, SSL_SESSION *session)
{
unsigned char *buf;
size_t bufsz, nw;
FILE *file;
/* Our client is rather limited: only one file and only one ticket
* can be saved. A more flexible client implementation would call
* lsquic_ssl_to_conn() and maybe save more tickets based on its
* own configuration.
*/
if (!s_sess_resume_file)
return 0;
if (0 != lsquic_ssl_sess_to_resume_info(ssl, session, &buf, &bufsz))
{
LSQ_NOTICE("lsquic_ssl_sess_to_resume_info failed");
return 0;
}
file = fopen(s_sess_resume_file, "wb");
if (!file)
{
LSQ_WARN("cannot open %s for writing: %s",
s_sess_resume_file, strerror(errno));
free(buf);
return 0;
}
nw = fwrite(buf, 1, bufsz, file);
if (nw == bufsz)
{
LSQ_INFO("wrote %zd bytes of session resumption information to %s",
nw, s_sess_resume_file);
s_sess_resume_file = NULL; /* Save just one ticket */
}
else
LSQ_WARN("error: fwrite(%s) returns %zd instead of %zd: %s",
s_sess_resume_file, nw, bufsz, strerror(errno));
fclose(file);
free(buf);
return 0;
}
static int
prog_init_ssl_ctx (struct prog *prog)
{
unsigned char ticket_keys[48];
prog->prog_ssl_ctx = SSL_CTX_new(TLS_method());
if (!prog->prog_ssl_ctx)
{
LSQ_ERROR("cannot allocate SSL context");
return -1;
}
SSL_CTX_set_min_proto_version(prog->prog_ssl_ctx, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(prog->prog_ssl_ctx, TLS1_3_VERSION);
SSL_CTX_set_default_verify_paths(prog->prog_ssl_ctx);
/* This is obviously test code: the key is just an array of NUL bytes */
memset(ticket_keys, 0, sizeof(ticket_keys));
if (1 != SSL_CTX_set_tlsext_ticket_keys(prog->prog_ssl_ctx,
ticket_keys, sizeof(ticket_keys)))
{
LSQ_ERROR("SSL_CTX_set_tlsext_ticket_keys failed");
return -1;
}
if (s_keylog_dir)
SSL_CTX_set_keylog_callback(prog->prog_ssl_ctx, keylog_log_line);
if (s_sess_resume_file)
{
SSL_CTX_set_session_cache_mode(prog->prog_ssl_ctx,
SSL_SESS_CACHE_CLIENT);
SSL_CTX_set_early_data_enabled(prog->prog_ssl_ctx, 1);
SSL_CTX_sess_set_new_cb(prog->prog_ssl_ctx, prog_new_session_cb);
}
return 0;
}
static int
prog_init_server (struct prog *prog)
{
struct service_port *sport;
TAILQ_FOREACH(sport, prog->prog_sports, next_sport)
if (0 != sport_init_server(sport, prog->prog_engine, prog->prog_eb))
return -1;
return 0;
}
2017-09-22 21:00:03 +00:00
void
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
prog_process_conns (struct prog *prog)
2017-09-22 21:00:03 +00:00
{
int diff;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
struct timeval timeout;
lsquic_engine_process_conns(prog->prog_engine);
2017-09-22 21:00:03 +00:00
if (lsquic_engine_earliest_adv_tick(prog->prog_engine, &diff))
{
if (diff < 0
|| (unsigned) diff < prog->prog_settings.es_clock_granularity)
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
{
timeout.tv_sec = 0;
timeout.tv_usec = prog->prog_settings.es_clock_granularity;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
}
else
{
timeout.tv_sec = (unsigned) diff / 1000000;
timeout.tv_usec = (unsigned) diff % 1000000;
}
if (!prog_is_stopped())
event_add(prog->prog_timer, &timeout);
2017-09-22 21:00:03 +00:00
}
}
static void
prog_timer_handler (int fd, short what, void *arg)
{
struct prog *const prog = arg;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (!prog_is_stopped())
prog_process_conns(prog);
2017-09-22 21:00:03 +00:00
}
static void
prog_usr1_handler (int fd, short what, void *arg)
{
LSQ_NOTICE("Got SIGUSR1, stopping engine");
prog_stop(arg);
}
static void
prog_usr2_handler (int fd, short what, void *arg)
{
struct prog *const prog = arg;
LSQ_NOTICE("Got SIGUSR2, cool down engine");
prog->prog_flags |= PROG_FLAG_COOLDOWN;
lsquic_engine_cooldown(prog->prog_engine);
prog_process_conns(prog);
}
2017-09-22 21:00:03 +00:00
int
prog_run (struct prog *prog)
{
#ifndef WIN32
2017-09-22 21:00:03 +00:00
prog->prog_usr1 = evsignal_new(prog->prog_eb, SIGUSR1,
prog_usr1_handler, prog);
evsignal_add(prog->prog_usr1, NULL);
prog->prog_usr2 = evsignal_new(prog->prog_eb, SIGUSR2,
prog_usr2_handler, prog);
evsignal_add(prog->prog_usr2, NULL);
#endif
2017-09-22 21:00:03 +00:00
event_base_loop(prog->prog_eb, 0);
return 0;
}
void
prog_cleanup (struct prog *prog)
{
lsquic_engine_destroy(prog->prog_engine);
event_base_free(prog->prog_eb);
if (!prog->prog_use_stock_pmi)
pba_cleanup(&prog->prog_pba);
if (prog->prog_ssl_ctx)
SSL_CTX_free(prog->prog_ssl_ctx);
if (prog->prog_certs)
delete_certs(prog->prog_certs);
2017-09-22 21:00:03 +00:00
lsquic_global_cleanup();
}
void
prog_stop (struct prog *prog)
{
struct service_port *sport;
prog_stopped = 1;
while ((sport = TAILQ_FIRST(prog->prog_sports)))
{
TAILQ_REMOVE(prog->prog_sports, sport, next_sport);
sport_destroy(sport);
}
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
if (prog->prog_timer)
{
event_del(prog->prog_timer);
event_free(prog->prog_timer);
prog->prog_timer = NULL;
}
if (prog->prog_usr1)
{
event_del(prog->prog_usr1);
event_free(prog->prog_usr1);
prog->prog_usr1 = NULL;
}
if (prog->prog_usr2)
{
event_del(prog->prog_usr2);
event_free(prog->prog_usr2);
prog->prog_usr2 = NULL;
}
2017-09-22 21:00:03 +00:00
}
static void *
keylog_open_file (const SSL *ssl)
{
const lsquic_conn_t *conn;
const lsquic_cid_t *cid;
FILE *fh;
int sz;
char id_str[MAX_CID_LEN * 2 + 1];
char path[PATH_MAX];
conn = lsquic_ssl_to_conn(ssl);
cid = lsquic_conn_id(conn);
lsquic_hexstr(cid->idbuf, cid->len, id_str, sizeof(id_str));
sz = snprintf(path, sizeof(path), "%s/%s.keys", s_keylog_dir, id_str);
if ((size_t) sz >= sizeof(path))
{
LSQ_WARN("%s: file too long", __func__);
return NULL;
}
fh = fopen(path, "ab");
if (!fh)
LSQ_WARN("could not open %s for appending: %s", path, strerror(errno));
return fh;
}
static void
keylog_log_line (const SSL *ssl, const char *line)
{
FILE *file;
file = keylog_open_file(ssl);
if (file)
{
fputs(line, file);
fputs("\n", file);
fclose(file);
}
}
static struct ssl_ctx_st *
no_cert (void *cert_lu_ctx, const struct sockaddr *sa_UNUSED, const char *sni)
{
return NULL;
}
2017-09-22 21:00:03 +00:00
int
prog_prep (struct prog *prog)
{
int s;
char err_buf[100];
if (s_keylog_dir && prog->prog_certs)
{
struct lsquic_hash_elem *el;
struct server_cert *cert;
for (el = lsquic_hash_first(prog->prog_certs); el;
el = lsquic_hash_next(prog->prog_certs))
{
cert = lsquic_hashelem_getdata(el);
SSL_CTX_set_keylog_callback(cert->ce_ssl_ctx, keylog_log_line);
}
}
2017-09-22 21:00:03 +00:00
if (0 != lsquic_engine_check_settings(prog->prog_api.ea_settings,
prog->prog_engine_flags, err_buf, sizeof(err_buf)))
{
LSQ_ERROR("Error in settings: %s", err_buf);
return -1;
}
if (!prog->prog_use_stock_pmi)
pba_init(&prog->prog_pba, prog->prog_packout_max);
else
{
prog->prog_api.ea_pmi = NULL;
prog->prog_api.ea_pmi_ctx = NULL;
}
2017-09-22 21:00:03 +00:00
if (TAILQ_EMPTY(prog->prog_sports))
{
if (prog->prog_hostname)
s = prog_add_sport(prog, prog->prog_hostname);
else
s = prog_add_sport(prog, "0.0.0.0:12345");
2017-09-22 21:00:03 +00:00
if (0 != s)
return -1;
}
if (prog->prog_certs)
{
prog->prog_api.ea_lookup_cert = lookup_cert;
prog->prog_api.ea_cert_lu_ctx = prog->prog_certs;
}
else
{
if (prog->prog_engine_flags & LSENG_SERVER)
LSQ_WARN("Not a single service specified. Use -c option.");
prog->prog_api.ea_lookup_cert = no_cert;
}
2017-09-22 21:00:03 +00:00
prog->prog_eb = event_base_new();
prog->prog_engine = lsquic_engine_new(prog->prog_engine_flags,
&prog->prog_api);
if (!prog->prog_engine)
return -1;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
prog->prog_timer = event_new(prog->prog_eb, -1, 0,
prog_timer_handler, prog);
if (0 != prog_init_ssl_ctx(prog))
return -1;
if (prog->prog_engine_flags & LSENG_SERVER)
s = prog_init_server(prog);
else
s = prog_init_client(prog);
2017-09-22 21:00:03 +00:00
if (s != 0)
return -1;
return 0;
}
int
prog_is_stopped (void)
{
return prog_stopped != 0;
}
static void
send_unsent (evutil_socket_t fd, short what, void *arg)
{
struct prog *const prog = arg;
assert(prog->prog_send);
event_del(prog->prog_send);
event_free(prog->prog_send);
prog->prog_send = NULL;
LSQ_DEBUG("on_write event fires");
lsquic_engine_send_unsent_packets(prog->prog_engine);
}
void
prog_sport_cant_send (struct prog *prog, int fd)
{
assert(!prog->prog_send);
LSQ_DEBUG("cannot send: register on_write event");
prog->prog_send = event_new(prog->prog_eb, fd, EV_WRITE, send_unsent, prog);
event_add(prog->prog_send, NULL);
}