Rename test/unittests to tests/ and test/ to bin/

This commit is contained in:
Dmitri Tikhonov 2020-05-17 12:42:32 -04:00
parent ecfd688117
commit 9a690580c9
92 changed files with 38 additions and 39 deletions

26
bin/CMakeLists.txt Normal file
View file

@ -0,0 +1,26 @@
# Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE.
INCLUDE(CheckFunctionExists)
CHECK_FUNCTION_EXISTS(sendmmsg HAVE_SENDMMSG)
CHECK_FUNCTION_EXISTS(recvmmsg HAVE_RECVMMSG)
CHECK_FUNCTION_EXISTS(open_memstream HAVE_OPEN_MEMSTREAM)
INCLUDE(CheckSymbolExists)
CHECK_SYMBOL_EXISTS(
IP_MTU_DISCOVER
"netinet/in.h"
HAVE_IP_MTU_DISCOVER
)
CHECK_SYMBOL_EXISTS(
IP_DONTFRAG
"netinet/in.h"
HAVE_IP_DONTFRAG
)
INCLUDE(CheckIncludeFiles)
CHECK_INCLUDE_FILES(regex.h HAVE_REGEX)
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/test_config.h)

245
bin/echo_client.c Normal file
View file

@ -0,0 +1,245 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* echo_client.c -- This is really a "line client:" it connects to QUIC server
* and sends it stuff, line by line. It works in tandem with echo_server.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <event2/event.h>
#include "lsquic.h"
#include "test_common.h"
#include "prog.h"
#include "../src/liblsquic/lsquic_logger.h"
struct lsquic_conn_ctx;
struct echo_client_ctx {
struct lsquic_conn_ctx *conn_h;
struct prog *prog;
};
struct lsquic_conn_ctx {
lsquic_conn_t *conn;
struct echo_client_ctx *client_ctx;
};
static lsquic_conn_ctx_t *
echo_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
{
struct echo_client_ctx *client_ctx = stream_if_ctx;
lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h));
conn_h->conn = conn;
conn_h->client_ctx = client_ctx;
client_ctx->conn_h = conn_h;
lsquic_conn_make_stream(conn);
return conn_h;
}
static void
echo_client_on_conn_closed (lsquic_conn_t *conn)
{
lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
LSQ_NOTICE("Connection closed");
prog_stop(conn_h->client_ctx->prog);
free(conn_h);
}
struct lsquic_stream_ctx {
lsquic_stream_t *stream;
struct echo_client_ctx *client_ctx;
struct event *read_stdin_ev;
char buf[0x100];
size_t buf_off;
};
static void
read_stdin (int fd, short what, void *ctx)
{
ssize_t nr;
lsquic_stream_ctx_t *st_h = ctx;
nr = read(fd, st_h->buf + st_h->buf_off++, 1);
LSQ_DEBUG("read %zd bytes from stdin", nr);
if (0 == nr)
{
lsquic_stream_shutdown(st_h->stream, 2);
}
else if (-1 == nr)
{
perror("read");
exit(1);
}
else if ('\n' == st_h->buf[ st_h->buf_off - 1 ])
{
LSQ_DEBUG("read newline: wantwrite");
lsquic_stream_wantwrite(st_h->stream, 1);
lsquic_engine_process_conns(st_h->client_ctx->prog->prog_engine);
}
else if (st_h->buf_off == sizeof(st_h->buf))
{
LSQ_NOTICE("line too long");
exit(2);
}
else
event_add(st_h->read_stdin_ev, NULL);
}
static lsquic_stream_ctx_t *
echo_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
{
lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h));
st_h->stream = stream;
st_h->client_ctx = stream_if_ctx;
st_h->buf_off = 0;
st_h->read_stdin_ev = event_new(prog_eb(st_h->client_ctx->prog),
STDIN_FILENO, EV_READ, read_stdin, st_h);
event_add(st_h->read_stdin_ev, NULL);
return st_h;
}
static void
echo_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
char c;
size_t nr;
nr = lsquic_stream_read(stream, &c, 1);
if (0 == nr)
{
lsquic_stream_shutdown(stream, 2);
return;
}
printf("%c", c);
fflush(stdout);
if ('\n' == c)
{
event_add(st_h->read_stdin_ev, NULL);
lsquic_stream_wantread(stream, 0);
}
}
static void
echo_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
/* Here we make an assumption that we can write the whole buffer.
* Don't do it in a real program.
*/
lsquic_stream_write(stream, st_h->buf, st_h->buf_off);
st_h->buf_off = 0;
lsquic_stream_flush(stream);
lsquic_stream_wantwrite(stream, 0);
lsquic_stream_wantread(stream, 1);
}
static void
echo_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
LSQ_NOTICE("%s called", __func__);
if (st_h->read_stdin_ev)
{
event_del(st_h->read_stdin_ev);
event_free(st_h->read_stdin_ev);
}
free(st_h);
lsquic_conn_close(lsquic_stream_conn(stream));
}
const struct lsquic_stream_if client_echo_stream_if = {
.on_new_conn = echo_client_on_new_conn,
.on_conn_closed = echo_client_on_conn_closed,
.on_new_stream = echo_client_on_new_stream,
.on_read = echo_client_on_read,
.on_write = echo_client_on_write,
.on_close = echo_client_on_close,
};
static void
usage (const char *prog)
{
const char *const slash = strrchr(prog, '/');
if (slash)
prog = slash + 1;
LSQ_NOTICE(
"Usage: %s [opts]\n"
"\n"
"Options:\n"
, prog);
}
int
main (int argc, char **argv)
{
int opt, s;
struct sport_head sports;
struct prog prog;
struct echo_client_ctx client_ctx;
memset(&client_ctx, 0, sizeof(client_ctx));
client_ctx.prog = &prog;
TAILQ_INIT(&sports);
prog_init(&prog, 0, &sports, &client_echo_stream_if, &client_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "h")))
{
switch (opt) {
case 'h':
usage(argv[0]);
prog_print_common_options(&prog, stdout);
exit(0);
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);
}
}
int flags = fcntl(STDIN_FILENO, F_GETFL);
flags |= O_NONBLOCK;
if (0 != fcntl(STDIN_FILENO, F_SETFL, flags))
{
perror("fcntl");
exit(1);
}
if (0 != prog_prep(&prog))
{
LSQ_ERROR("could not prep");
exit(EXIT_FAILURE);
}
if (0 != prog_connect(&prog, NULL, 0))
{
LSQ_ERROR("could not connect");
exit(EXIT_FAILURE);
}
LSQ_DEBUG("entering event loop");
s = prog_run(&prog);
prog_cleanup(&prog);
exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
}

228
bin/echo_server.c Normal file
View file

@ -0,0 +1,228 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* echo_server.c -- QUIC server that echoes back input line by line
*/
#include <assert.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>
#include <unistd.h>
#include "lsquic.h"
#include "test_common.h"
#include "prog.h"
#include "../src/liblsquic/lsquic_logger.h"
struct lsquic_conn_ctx;
struct echo_server_ctx {
TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs;
unsigned max_reqs;
int n_conn;
struct sport_head sports;
struct prog *prog;
};
struct lsquic_conn_ctx {
TAILQ_ENTRY(lsquic_conn_ctx) next_connh;
lsquic_conn_t *conn;
struct echo_server_ctx *server_ctx;
};
static lsquic_conn_ctx_t *
echo_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
{
struct echo_server_ctx *server_ctx = stream_if_ctx;
lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h));
conn_h->conn = conn;
conn_h->server_ctx = server_ctx;
TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh);
LSQ_NOTICE("New connection!");
print_conn_info(conn);
return conn_h;
}
static void
echo_server_on_conn_closed (lsquic_conn_t *conn)
{
lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
if (conn_h->server_ctx->n_conn)
{
--conn_h->server_ctx->n_conn;
LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn);
if (0 == conn_h->server_ctx->n_conn)
prog_stop(conn_h->server_ctx->prog);
}
else
LSQ_NOTICE("Connection closed");
TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh);
free(conn_h);
}
struct lsquic_stream_ctx {
lsquic_stream_t *stream;
struct echo_server_ctx *server_ctx;
char buf[0x100];
size_t buf_off;
};
static lsquic_stream_ctx_t *
echo_server_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
{
lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h));
st_h->stream = stream;
st_h->server_ctx = stream_if_ctx;
st_h->buf_off = 0;
lsquic_stream_wantread(stream, 1);
return st_h;
}
static struct lsquic_conn_ctx *
find_conn_h (const struct echo_server_ctx *server_ctx, lsquic_stream_t *stream)
{
struct lsquic_conn_ctx *conn_h;
lsquic_conn_t *conn;
conn = lsquic_stream_conn(stream);
TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh)
if (conn_h->conn == conn)
return conn_h;
return NULL;
}
static void
echo_server_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
struct lsquic_conn_ctx *conn_h;
size_t nr;
nr = lsquic_stream_read(stream, st_h->buf + st_h->buf_off++, 1);
if (0 == nr)
{
LSQ_NOTICE("EOF: closing connection");
lsquic_stream_shutdown(stream, 2);
conn_h = find_conn_h(st_h->server_ctx, stream);
lsquic_conn_close(conn_h->conn);
}
else if ('\n' == st_h->buf[ st_h->buf_off - 1 ])
{
/* Found end of line: echo it back */
lsquic_stream_wantwrite(stream, 1);
lsquic_stream_wantread(stream, 0);
}
else if (st_h->buf_off == sizeof(st_h->buf))
{
/* Out of buffer space: line too long */
LSQ_NOTICE("run out of buffer space");
lsquic_stream_shutdown(stream, 2);
}
else
{
/* Keep reading */;
}
}
static void
echo_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
lsquic_stream_write(stream, st_h->buf, st_h->buf_off);
st_h->buf_off = 0;
lsquic_stream_flush(stream);
lsquic_stream_wantwrite(stream, 0);
lsquic_stream_wantread(stream, 1);
}
static void
echo_server_on_stream_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
struct lsquic_conn_ctx *conn_h;
LSQ_NOTICE("%s called", __func__);
conn_h = find_conn_h(st_h->server_ctx, stream);
LSQ_WARN("%s: TODO: free connection handler %p", __func__, conn_h);
free(st_h);
}
const struct lsquic_stream_if server_echo_stream_if = {
.on_new_conn = echo_server_on_new_conn,
.on_conn_closed = echo_server_on_conn_closed,
.on_new_stream = echo_server_on_new_stream,
.on_read = echo_server_on_read,
.on_write = echo_server_on_write,
.on_close = echo_server_on_stream_close,
};
static void
usage (const char *prog)
{
const char *const slash = strrchr(prog, '/');
if (slash)
prog = slash + 1;
printf(
"Usage: %s [opts]\n"
"\n"
"Options:\n"
, prog);
}
int
main (int argc, char **argv)
{
int opt, s;
struct prog prog;
struct echo_server_ctx server_ctx;
memset(&server_ctx, 0, sizeof(server_ctx));
server_ctx.prog = &prog;
TAILQ_INIT(&server_ctx.sports);
TAILQ_INIT(&server_ctx.conn_ctxs);
prog_init(&prog, LSENG_SERVER, &server_ctx.sports,
&server_echo_stream_if, &server_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hn:")))
{
switch (opt) {
case 'n':
server_ctx.n_conn = atoi(optarg);
break;
case 'h':
usage(argv[0]);
prog_print_common_options(&prog, stdout);
exit(0);
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);
}
}
if (0 != prog_prep(&prog))
{
LSQ_ERROR("could not prep");
exit(EXIT_FAILURE);
}
LSQ_DEBUG("entering event loop");
s = prog_run(&prog);
prog_cleanup(&prog);
exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
}

1641
bin/http_client.c Normal file

File diff suppressed because it is too large Load diff

1700
bin/http_server.c Normal file

File diff suppressed because it is too large Load diff

518
bin/md5_client.c Normal file
View file

@ -0,0 +1,518 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* md5_client.c -- This client sends one or more files to MD5 QUIC server
* for MD5 sum calculation.
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <event2/event.h>
#include <openssl/md5.h>
#include "lsquic.h"
#include "test_common.h"
#include "prog.h"
#include "../src/liblsquic/lsquic_logger.h"
#include "../src/liblsquic/lsquic_int_types.h"
#include "../src/liblsquic/lsquic_varint.h"
#include "../src/liblsquic/lsquic_hq.h"
#include "../src/liblsquic/lsquic_sfcw.h"
#include "../src/liblsquic/lsquic_hash.h"
#include "../src/liblsquic/lsquic_stream.h"
/* Set to non-zero value to test out what happens when reset is sent */
#define RESET_AFTER_N_WRITES 0
static int g_write_file = 1;
#define LOCAL_BUF_SIZE 0x100
static struct {
unsigned stream_id; /* If set, reset this stream ID */
off_t offset; /* Reset it after writing this many bytes */
} g_reset_stream;
struct file {
LIST_ENTRY(file) next_file;
const char *filename;
struct lsquic_reader reader;
int fd;
unsigned priority;
enum {
FILE_RESET = (1 << 0),
} file_flags;
size_t md5_off;
char md5str[MD5_DIGEST_LENGTH * 2];
};
struct lsquic_conn_ctx;
struct client_ctx {
struct lsquic_conn_ctx *conn_h;
LIST_HEAD(, file) files;
unsigned n_files;
struct file *cur_file;
lsquic_engine_t *engine;
struct service_port *sport;
struct prog *prog;
};
struct lsquic_conn_ctx {
lsquic_conn_t *conn;
struct client_ctx *client_ctx;
};
static lsquic_conn_ctx_t *
client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
{
struct client_ctx *client_ctx = stream_if_ctx;
lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h));
conn_h->conn = conn;
conn_h->client_ctx = client_ctx;
client_ctx->conn_h = conn_h;
assert(client_ctx->n_files > 0);
unsigned n = client_ctx->n_files;
while (n--)
lsquic_conn_make_stream(conn);
print_conn_info(conn);
return conn_h;
}
static void
client_on_goaway_received (lsquic_conn_t *conn)
{
LSQ_NOTICE("GOAWAY received");
}
static void
client_on_conn_closed (lsquic_conn_t *conn)
{
lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
LSQ_NOTICE("Connection closed");
prog_stop(conn_h->client_ctx->prog);
free(conn_h);
}
struct lsquic_stream_ctx {
lsquic_stream_t *stream;
struct client_ctx *client_ctx;
struct file *file;
struct event *read_stdin_ev;
struct {
int initialized;
size_t size,
off;
} small;
};
static lsquic_stream_ctx_t *
client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
{
struct client_ctx *const client_ctx = stream_if_ctx;
if (!stream)
{
assert(client_ctx->n_files > 0);
LSQ_NOTICE("%s: got null stream: no more streams possible; # files: %u",
__func__, client_ctx->n_files);
--client_ctx->n_files;
if (0 == client_ctx->n_files)
{
LSQ_DEBUG("closing connection");
lsquic_conn_close(client_ctx->conn_h->conn);
}
return NULL;
}
lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h));
st_h->stream = stream;
st_h->client_ctx = stream_if_ctx;
if (LIST_EMPTY(&st_h->client_ctx->files))
{
/* XXX: perhaps we should not be able to write immediately: there may
* be internal memory constraints...
*/
lsquic_stream_write(stream, "client request", 14);
(void) lsquic_stream_flush(stream);
lsquic_stream_wantwrite(stream, 0);
lsquic_stream_wantread(stream, 1);
}
else
{
st_h->file = LIST_FIRST(&st_h->client_ctx->files);
if (g_write_file)
{
st_h->file->fd = -1;
st_h->file->reader.lsqr_read = test_reader_read;
st_h->file->reader.lsqr_size = test_reader_size;
st_h->file->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->file->filename);
if (!st_h->file->reader.lsqr_ctx)
exit(1);
}
else
{
st_h->file->fd = open(st_h->file->filename, O_RDONLY);
if (st_h->file->fd < 0)
{
LSQ_ERROR("could not open %s for reading: %s",
st_h->file->filename, strerror(errno));
exit(1);
}
}
LIST_REMOVE(st_h->file, next_file);
lsquic_stream_set_priority(stream, st_h->file->priority);
lsquic_stream_wantwrite(stream, 1);
}
return st_h;
}
static size_t
buf_reader_size (void *reader_ctx)
{
lsquic_stream_ctx_t *const st_h = reader_ctx;
struct stat st;
off_t off;
if (st_h->small.initialized)
goto initialized;
if (0 != fstat(st_h->file->fd, &st))
{
LSQ_ERROR("fstat failed: %s", strerror(errno));
goto err;
}
off = lseek(st_h->file->fd, 0, SEEK_CUR);
if (off == (off_t) -1)
{
LSQ_ERROR("lseek failed: %s", strerror(errno));
goto err;
}
if (st.st_size < off)
{
LSQ_ERROR("size mismatch");
goto err;
}
st_h->small.initialized = 1;
st_h->small.off = off;
st_h->small.size = st.st_size;
initialized:
if (st_h->small.size - st_h->small.off > LOCAL_BUF_SIZE)
return LOCAL_BUF_SIZE;
else
return st_h->small.size - st_h->small.off;
err:
close(st_h->file->fd);
st_h->file->fd = 0;
return 0;
}
static size_t
buf_reader_read (void *reader_ctx, void *buf, size_t count)
{
lsquic_stream_ctx_t *const st_h = reader_ctx;
ssize_t nr;
unsigned char local_buf[LOCAL_BUF_SIZE];
assert(st_h->small.initialized);
if (count > sizeof(local_buf))
count = sizeof(local_buf);
nr = read(st_h->file->fd, local_buf, count);
if (nr < 0)
{
LSQ_ERROR("read: %s", strerror(errno));
close(st_h->file->fd);
st_h->file->fd = 0;
return 0;
}
memcpy(buf, local_buf, nr);
st_h->small.off += nr;
return nr;
}
static void
client_file_on_write_buf (lsquic_stream_ctx_t *st_h)
{
ssize_t nw;
struct lsquic_reader reader = {
.lsqr_read = buf_reader_read,
.lsqr_size = buf_reader_size,
.lsqr_ctx = st_h,
};
if (g_reset_stream.stream_id == lsquic_stream_id(st_h->stream) &&
lseek(st_h->file->fd, 0, SEEK_CUR) >= g_reset_stream.offset)
{
lsquic_stream_reset(st_h->stream, 0x01 /* QUIC_INTERNAL_ERROR */);
g_reset_stream.stream_id = 0; /* Reset only once */
}
nw = lsquic_stream_writef(st_h->stream, &reader);
if (-1 == nw)
{
if (ECONNRESET == errno)
st_h->file->file_flags |= FILE_RESET;
LSQ_WARN("lsquic_stream_read: %s", strerror(errno));
lsquic_stream_close(st_h->stream);
return;
}
#if RESET_AFTER_N_WRITES
static int write_count = 0;
if (write_count++ > RESET_AFTER_N_WRITES)
lsquic_stream_reset(st_h->stream, 0);
#endif
if (0 == nw)
{
(void) close(st_h->file->fd);
if (0 == lsquic_stream_shutdown(st_h->stream, 1))
lsquic_stream_wantread(st_h->stream, 1);
else
{
if (ECONNRESET == errno)
st_h->file->file_flags |= FILE_RESET;
LSQ_WARN("lsquic_stream_shutdown: %s", strerror(errno));
lsquic_stream_close(st_h->stream);
}
}
}
static void
client_file_on_write_efficient (lsquic_stream_t *stream,
lsquic_stream_ctx_t *st_h)
{
ssize_t nw;
nw = lsquic_stream_writef(stream, &st_h->file->reader);
if (nw < 0)
{
LSQ_ERROR("write error: %s", strerror(errno));
exit(1);
}
if (nw == 0)
{
destroy_lsquic_reader_ctx(st_h->file->reader.lsqr_ctx);
st_h->file->reader.lsqr_ctx = NULL;
if (0 == lsquic_stream_shutdown(st_h->stream, 1))
lsquic_stream_wantread(st_h->stream, 1);
else
{
if (ECONNRESET == errno)
st_h->file->file_flags |= FILE_RESET;
LSQ_WARN("lsquic_stream_shutdown: %s", strerror(errno));
lsquic_stream_close(st_h->stream);
}
}
}
static void
client_file_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
if (g_write_file)
client_file_on_write_efficient(stream, st_h);
else
client_file_on_write_buf(st_h);
}
static void
client_file_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
char buf;
/* We expect to read in 32-character MD5 string */
size_t ntoread = sizeof(st_h->file->md5str) - st_h->file->md5_off;
if (0 == ntoread)
{
lsquic_stream_wantread(stream, 0);
/* XXX What about an error (due to RST_STREAM) here: how are we to
* handle it?
*/
/* Expect a FIN */
if (0 == lsquic_stream_read(stream, &buf, sizeof(buf)))
{
LSQ_NOTICE("%.*s %s", (int) sizeof(st_h->file->md5str),
st_h->file->md5str,
st_h->file->filename);
fflush(stdout);
LSQ_DEBUG("# of files: %d", st_h->client_ctx->n_files);
lsquic_stream_shutdown(stream, 0);
}
else
LSQ_ERROR("expected FIN from stream!");
}
else
{
ssize_t nr = lsquic_stream_read(stream,
st_h->file->md5str + st_h->file->md5_off, ntoread);
if (-1 == nr)
{
if (ECONNRESET == errno)
st_h->file->file_flags |= FILE_RESET;
LSQ_WARN("lsquic_stream_read: %s", strerror(errno));
lsquic_stream_close(stream);
return;
}
else
st_h->file->md5_off += nr;
}
}
static void
client_file_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
--st_h->client_ctx->n_files;
LSQ_NOTICE("%s called for stream %"PRIu64", # files: %u", __func__,
lsquic_stream_id(stream), st_h->client_ctx->n_files);
if (0 == st_h->client_ctx->n_files)
lsquic_conn_close(st_h->client_ctx->conn_h->conn);
if (!(st_h->file->file_flags & FILE_RESET) && 0 == RESET_AFTER_N_WRITES)
assert(st_h->file->md5_off == sizeof(st_h->file->md5str));
if (st_h->file->reader.lsqr_ctx)
{
destroy_lsquic_reader_ctx(st_h->file->reader.lsqr_ctx);
st_h->file->reader.lsqr_ctx = NULL;
}
if (st_h->file->fd >= 0)
(void) close(st_h->file->fd);
free(st_h->file);
free(st_h);
}
const struct lsquic_stream_if client_file_stream_if = {
.on_new_conn = client_on_new_conn,
.on_goaway_received = client_on_goaway_received,
.on_conn_closed = client_on_conn_closed,
.on_new_stream = client_on_new_stream,
.on_read = client_file_on_read,
.on_write = client_file_on_write,
.on_close = client_file_on_close,
};
static void
usage (const char *prog)
{
const char *const slash = strrchr(prog, '/');
if (slash)
prog = slash + 1;
printf(
"Usage: %s [opts]\n"
"\n"
"Options:\n"
" -f FILE File to send to the server -- must be specified at least\n"
" once.\n"
" -b Use buffering API for sending files over rather than\n"
" the efficient version.\n"
" -p PRIORITY Applicatble to previous file specified with -f\n"
" -r STREAM_ID:OFFSET\n"
" Reset stream STREAM_ID after sending more that OFFSET bytes.\n"
, prog);
}
int
main (int argc, char **argv)
{
int opt, s;
struct sport_head sports;
struct prog prog;
struct client_ctx client_ctx;
struct file *file;
file = NULL;
memset(&client_ctx, 0, sizeof(client_ctx));
client_ctx.prog = &prog;
TAILQ_INIT(&sports);
prog_init(&prog, 0, &sports, &client_file_stream_if, &client_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "bhr:f:p:")))
{
switch (opt) {
case 'p':
if (file)
file->priority = atoi(optarg);
else
{
fprintf(stderr, "No file to apply priority to\n");
exit(1);
}
break;
case 'b':
g_write_file = 0;
break;
case 'f':
file = calloc(1, sizeof(*file));
LIST_INSERT_HEAD(&client_ctx.files, file, next_file);
++client_ctx.n_files;
file->filename = optarg;
break;
case 'r':
g_reset_stream.stream_id = atoi(optarg);
g_reset_stream.offset = atoi(strchr(optarg, ':') + 1);
break;
case 'h':
usage(argv[0]);
prog_print_common_options(&prog, stdout);
exit(0);
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);
}
}
if (LIST_EMPTY(&client_ctx.files))
{
fprintf(stderr, "please specify one of more files using -f\n");
exit(1);
}
if (0 != prog_prep(&prog))
{
LSQ_ERROR("could not prep");
exit(EXIT_FAILURE);
}
client_ctx.sport = TAILQ_FIRST(&sports);
if (0 != prog_connect(&prog, NULL, 0))
{
LSQ_ERROR("could not connect");
exit(EXIT_FAILURE);
}
LSQ_DEBUG("entering event loop");
s = prog_run(&prog);
prog_cleanup(&prog);
exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
}

340
bin/md5_server.c Normal file
View file

@ -0,0 +1,340 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* md5_server.c -- Read one or more streams from the client and return
* MD5 sum of the payload.
*/
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>
#include <unistd.h>
#include <openssl/md5.h>
#include <event2/event.h>
#include "lsquic.h"
#include "test_common.h"
#include "prog.h"
#include "../src/liblsquic/lsquic_logger.h"
static int g_really_calculate_md5 = 1;
/* Turn on to test whether stream reset is being sent when stream is closed
* prematurely.
*/
static struct {
unsigned stream_id;
unsigned long limit;
unsigned long n_read;
} g_premature_close;
struct lsquic_conn_ctx;
struct server_ctx {
TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs;
unsigned max_reqs;
int n_conn;
time_t expiry;
struct sport_head sports;
struct prog *prog;
};
struct lsquic_conn_ctx {
TAILQ_ENTRY(lsquic_conn_ctx) next_connh;
lsquic_conn_t *conn;
unsigned n_reqs, n_closed;
struct server_ctx *server_ctx;
};
static lsquic_conn_ctx_t *
server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
{
struct server_ctx *server_ctx = stream_if_ctx;
lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h));
conn_h->conn = conn;
conn_h->server_ctx = server_ctx;
TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh);
LSQ_NOTICE("New connection!");
print_conn_info(conn);
return conn_h;
}
static void
server_on_conn_closed (lsquic_conn_t *conn)
{
lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
int stopped;
if (conn_h->server_ctx->expiry && conn_h->server_ctx->expiry < time(NULL))
{
LSQ_NOTICE("reached engine expiration time, shut down");
prog_stop(conn_h->server_ctx->prog);
stopped = 1;
}
else
stopped = 0;
if (conn_h->server_ctx->n_conn)
{
--conn_h->server_ctx->n_conn;
LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn);
if (0 == conn_h->server_ctx->n_conn && !stopped)
prog_stop(conn_h->server_ctx->prog);
}
else
LSQ_NOTICE("Connection closed");
TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh);
free(conn_h);
}
struct lsquic_stream_ctx {
lsquic_stream_t *stream;
struct server_ctx *server_ctx;
MD5_CTX md5ctx;
unsigned char md5sum[MD5_DIGEST_LENGTH];
char md5str[MD5_DIGEST_LENGTH * 2 + 1];
};
static struct lsquic_conn_ctx *
find_conn_h (const struct server_ctx *server_ctx, lsquic_stream_t *stream)
{
struct lsquic_conn_ctx *conn_h;
lsquic_conn_t *conn;
conn = lsquic_stream_conn(stream);
TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh)
if (conn_h->conn == conn)
return conn_h;
return NULL;
}
static lsquic_stream_ctx_t *
server_md5_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
{
struct lsquic_conn_ctx *conn_h;
lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h));
st_h->stream = stream;
st_h->server_ctx = stream_if_ctx;
lsquic_stream_wantread(stream, 1);
if (g_really_calculate_md5)
MD5_Init(&st_h->md5ctx);
conn_h = find_conn_h(st_h->server_ctx, stream);
assert(conn_h);
conn_h->n_reqs++;
LSQ_NOTICE("request #%u", conn_h->n_reqs);
if (st_h->server_ctx->max_reqs &&
conn_h->n_reqs >= st_h->server_ctx->max_reqs)
{
/* The assert guards the assumption that after the we mark the
* connection as going away, no new streams are opened and thus
* this callback is not called.
*/
assert(conn_h->n_reqs == st_h->server_ctx->max_reqs);
LSQ_NOTICE("reached maximum requests: %u, going away",
st_h->server_ctx->max_reqs);
lsquic_conn_going_away(conn_h->conn);
}
return st_h;
}
static void
server_md5_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
char buf[0x1000];
ssize_t nr;
nr = lsquic_stream_read(stream, buf, sizeof(buf));
if (-1 == nr)
{
/* This should never return an error if we only call read() once
* per callback.
*/
perror("lsquic_stream_read");
lsquic_stream_shutdown(stream, 0);
return;
}
if (g_premature_close.limit &&
g_premature_close.stream_id == lsquic_stream_id(stream))
{
g_premature_close.n_read += nr;
if (g_premature_close.n_read > g_premature_close.limit)
{
LSQ_WARN("Done after reading %lu bytes", g_premature_close.n_read);
lsquic_stream_shutdown(stream, 0);
return;
}
}
if (nr)
{
if (g_really_calculate_md5)
MD5_Update(&st_h->md5ctx, buf, nr);
}
else
{
lsquic_stream_wantread(stream, 0);
if (g_really_calculate_md5)
{
MD5_Final(st_h->md5sum, &st_h->md5ctx);
snprintf(st_h->md5str, sizeof(st_h->md5str),
"%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x"
, st_h->md5sum[0]
, st_h->md5sum[1]
, st_h->md5sum[2]
, st_h->md5sum[3]
, st_h->md5sum[4]
, st_h->md5sum[5]
, st_h->md5sum[6]
, st_h->md5sum[7]
, st_h->md5sum[8]
, st_h->md5sum[9]
, st_h->md5sum[10]
, st_h->md5sum[11]
, st_h->md5sum[12]
, st_h->md5sum[13]
, st_h->md5sum[14]
, st_h->md5sum[15]
);
}
else
{
memset(st_h->md5str, '0', sizeof(st_h->md5str) - 1);
st_h->md5str[sizeof(st_h->md5str) - 1] = '\0';
}
lsquic_stream_wantwrite(stream, 1);
lsquic_stream_shutdown(stream, 0);
}
}
static void
server_md5_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
ssize_t nw;
nw = lsquic_stream_write(stream, st_h->md5str, sizeof(st_h->md5str) - 1);
if (-1 == nw)
{
perror("lsquic_stream_write");
return;
}
lsquic_stream_wantwrite(stream, 0);
lsquic_stream_shutdown(stream, 1);
}
static void
server_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
struct lsquic_conn_ctx *conn_h;
LSQ_NOTICE("%s called", __func__);
conn_h = find_conn_h(st_h->server_ctx, stream);
conn_h->n_closed++;
if (st_h->server_ctx->max_reqs &&
conn_h->n_closed >= st_h->server_ctx->max_reqs)
{
assert(conn_h->n_closed == st_h->server_ctx->max_reqs);
LSQ_NOTICE("closing connection after completing %u requests",
conn_h->n_closed);
lsquic_conn_close(conn_h->conn);
}
free(st_h);
}
const struct lsquic_stream_if server_md5_stream_if = {
.on_new_conn = server_on_new_conn,
.on_conn_closed = server_on_conn_closed,
.on_new_stream = server_md5_on_new_stream,
.on_read = server_md5_on_read,
.on_write = server_md5_on_write,
.on_close = server_on_close,
};
static void
usage (const char *prog)
{
const char *const slash = strrchr(prog, '/');
if (slash)
prog = slash + 1;
printf(
"Usage: %s [opts]\n"
"\n"
"Options:\n"
" -e EXPIRY Stop engine after this many seconds. The expiration is\n"
" checked when connections are closed.\n"
, prog);
}
int
main (int argc, char **argv)
{
int opt, s;
struct prog prog;
struct server_ctx server_ctx;
memset(&server_ctx, 0, sizeof(server_ctx));
TAILQ_INIT(&server_ctx.conn_ctxs);
server_ctx.prog = &prog;
TAILQ_INIT(&server_ctx.sports);
prog_init(&prog, LSENG_SERVER, &server_ctx.sports,
&server_md5_stream_if, &server_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hr:Fn:e:p:")))
{
switch (opt) {
case 'F':
g_really_calculate_md5 = 0;
break;
case 'p':
g_premature_close.stream_id = atoi(optarg);
g_premature_close.limit = atoi(strchr(optarg, ':') + 1);
break;
case 'r':
server_ctx.max_reqs = atoi(optarg);
break;
case 'e':
server_ctx.expiry = time(NULL) + atoi(optarg);
break;
case 'n':
server_ctx.n_conn = atoi(optarg);
break;
case 'h':
usage(argv[0]);
prog_print_common_options(&prog, stdout);
exit(0);
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);
}
}
if (0 != prog_prep(&prog))
{
LSQ_ERROR("could not prep");
exit(EXIT_FAILURE);
}
LSQ_DEBUG("entering event loop");
s = prog_run(&prog);
prog_cleanup(&prog);
exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
}

724
bin/prog.c Normal file
View file

@ -0,0 +1,724 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#ifndef WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#endif
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/queue.h>
#ifndef WIN32
#include <unistd.h>
#else
#include <getopt.h>
#pragma warning(disable:4028)
#endif// WIN32
#include <event2/event.h>
#include <lsquic.h>
#include <openssl/ssl.h>
#include "../src/liblsquic/lsquic_hash.h"
#include "../src/liblsquic/lsquic_int_types.h"
#include "../src/liblsquic/lsquic_util.h"
#include "../src/liblsquic/lsquic_logger.h"
#include "test_config.h"
#include "test_cert.h"
#include "test_common.h"
#include "prog.h"
static int prog_stopped;
static SSL_CTX * get_ssl_ctx (void *);
static const struct lsquic_packout_mem_if pmi = {
.pmi_allocate = pba_allocate,
.pmi_release = pba_release,
.pmi_return = pba_release,
};
void
prog_init (struct prog *prog, unsigned flags,
struct sport_head *sports,
const struct lsquic_stream_if *stream_if, void *stream_if_ctx)
{
/* 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
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
/* 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");
}
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,
#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. The default is 1370\n"
" bytes for IPv4 socket and 1350 bytes for IPv6 socket\n"
" -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"
" 5 Full date and microsecond time.\n"
" Example: 2017-03-21 13:43:46.671345\n"
" -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"
);
#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
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
{
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"
);
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
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
);
fprintf(out,
" -h Print this help screen and exit\n"
);
}
int
prog_set_opt (struct prog *prog, int opt, const char *arg)
{
struct stat st;
int s;
switch (opt)
{
#if LSQUIC_DONTFRAG_SUPPORTED
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;
}
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
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;
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;
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 '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;
}
}
prog->prog_keylog_dir = optarg;
return 0;
#else
LSQ_ERROR("key logging is not supported on Windows");
return -1;
#endif
default:
return 1;
}
}
struct event_base *
prog_eb (struct prog *prog)
{
return prog->prog_eb;
}
int
prog_connect (struct prog *prog, unsigned char *zero_rtt, size_t zero_rtt_len)
{
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,
(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, zero_rtt, zero_rtt_len,
sport->sp_token_buf, sport->sp_token_sz))
return -1;
prog_process_conns(prog);
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 service_port *const sport = peer_ctx;
return sport->sp_prog->prog_ssl_ctx;
}
static int
prog_init_server (struct prog *prog)
{
struct service_port *sport;
unsigned char ticket_keys[48];
prog->prog_ssl_ctx = SSL_CTX_new(TLS_method());
if (prog->prog_ssl_ctx)
{
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;
}
}
else
LSQ_WARN("cannot create SSL context");
TAILQ_FOREACH(sport, prog->prog_sports, next_sport)
if (0 != sport_init_server(sport, prog->prog_engine, prog->prog_eb))
return -1;
return 0;
}
void
prog_process_conns (struct prog *prog)
{
int diff;
struct timeval timeout;
lsquic_engine_process_conns(prog->prog_engine);
if (lsquic_engine_earliest_adv_tick(prog->prog_engine, &diff))
{
if (diff < 0
|| (unsigned) diff < prog->prog_settings.es_clock_granularity)
{
timeout.tv_sec = 0;
timeout.tv_usec = prog->prog_settings.es_clock_granularity;
}
else
{
timeout.tv_sec = (unsigned) diff / 1000000;
timeout.tv_usec = (unsigned) diff % 1000000;
}
if (!prog_is_stopped())
event_add(prog->prog_timer, &timeout);
}
}
static void
prog_timer_handler (int fd, short what, void *arg)
{
struct prog *const prog = arg;
if (!prog_is_stopped())
prog_process_conns(prog);
}
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);
}
int
prog_run (struct prog *prog)
{
#ifndef WIN32
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
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);
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);
}
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;
}
}
static void *
keylog_open (void *ctx, lsquic_conn_t *conn)
{
const struct prog *const prog = ctx;
const lsquic_cid_t *cid;
FILE *fh;
int sz;
char id_str[MAX_CID_LEN * 2 + 1];
char path[PATH_MAX];
cid = lsquic_conn_id(conn);
lsquic_hexstr(cid->idbuf, cid->len, id_str, sizeof(id_str));
sz = snprintf(path, sizeof(path), "%s/%s.keys", prog->prog_keylog_dir,
id_str);
if ((size_t) sz >= sizeof(path))
{
LSQ_WARN("%s: file too long", __func__);
return NULL;
}
fh = fopen(path, "w");
if (!fh)
LSQ_WARN("could not open %s for writing: %s", path, strerror(errno));
return fh;
}
static void
keylog_log_line (void *handle, const char *line)
{
size_t len;
len = strlen(line);
if (len < sizeof("QUIC_") - 1 || strncmp(line, "QUIC_", 5))
fputs("QUIC_", handle);
fputs(line, handle);
fputs("\n", handle);
fflush(handle);
}
static void
keylog_close (void *handle)
{
fclose(handle);
}
static const struct lsquic_keylog_if keylog_if =
{
.kli_open = keylog_open,
.kli_log_line = keylog_log_line,
.kli_close = keylog_close,
};
static struct ssl_ctx_st *
no_cert (void *cert_lu_ctx, const struct sockaddr *sa_UNUSED, const char *sni)
{
return NULL;
}
int
prog_prep (struct prog *prog)
{
int s;
char err_buf[100];
if (prog->prog_keylog_dir)
{
prog->prog_api.ea_keylog_if = &keylog_if;
prog->prog_api.ea_keylog_ctx = prog;
}
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;
}
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");
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;
}
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;
prog->prog_timer = event_new(prog->prog_eb, -1, 0,
prog_timer_handler, prog);
if (prog->prog_engine_flags & LSENG_SERVER)
s = prog_init_server(prog);
else
s = prog_init_client(prog);
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);
}

119
bin/prog.h Normal file
View file

@ -0,0 +1,119 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* prog.h -- common setup and options for QUIC program
*/
#ifndef PROG_H
#define PROG_H 1
#include "test_config.h"
struct event;
struct event_base;
struct lsquic_hash;
struct sport_head;
struct ssl_ctx_st;
struct prog
{
struct packout_buf_allocator prog_pba;
struct lsquic_engine_settings prog_settings;
struct lsquic_engine_api prog_api;
unsigned prog_engine_flags;
struct service_port prog_dummy_sport; /* Use for options */
unsigned prog_packout_max;
unsigned short prog_max_packet_size;
int prog_version_cleared;
unsigned long prog_read_count;
#if HAVE_SENDMMSG
int prog_use_sendmmsg;
#endif
#if HAVE_RECVMMSG
int prog_use_recvmmsg;
#endif
int prog_use_stock_pmi;
struct event_base *prog_eb;
struct event *prog_timer,
*prog_send,
*prog_usr1;
struct event *prog_usr2;
struct ssl_ctx_st *prog_ssl_ctx;
struct lsquic_hash *prog_certs;
struct event *prog_event_sni;
char *prog_susp_sni;
struct sport_head *prog_sports;
struct lsquic_engine *prog_engine;
const char *prog_hostname;
int prog_ipver; /* 0, 4, or 6 */
const char *prog_keylog_dir;
enum {
PROG_FLAG_COOLDOWN = 1 << 0,
#if LSQUIC_PREFERRED_ADDR
PROG_SEARCH_ADDRS = 1 << 1,
#endif
} prog_flags;
};
void
prog_init (struct prog *, unsigned lsquic_engine_flags, struct sport_head *,
const struct lsquic_stream_if *, void *stream_if_ctx);
#if HAVE_SENDMMSG
# define SENDMMSG_FLAG "g"
#else
# define SENDMMSG_FLAG ""
#endif
#if HAVE_RECVMMSG
# define RECVMMSG_FLAG "j"
#else
# define RECVMMSG_FLAG ""
#endif
#if LSQUIC_DONTFRAG_SUPPORTED
# define IP_DONTFRAG_FLAG "D"
#else
# define IP_DONTFRAG_FLAG ""
#endif
#define PROG_OPTS "i:km:c:y:L:l:o:H:s:S:Y:z:G:W" RECVMMSG_FLAG SENDMMSG_FLAG \
IP_DONTFRAG_FLAG
/* Returns:
* 0 Applied
* 1 Not applicable
* -1 Error
*/
int
prog_set_opt (struct prog *, int opt, const char *arg);
struct event_base *
prog_eb (struct prog *);
int
prog_run (struct prog *);
void
prog_cleanup (struct prog *);
void
prog_stop (struct prog *);
int
prog_prep (struct prog *);
int
prog_connect (struct prog *, unsigned char *, size_t);
void
prog_print_common_options (const struct prog *, FILE *);
int
prog_is_stopped (void);
void
prog_process_conns (struct prog *);
void
prog_sport_cant_send (struct prog *, int fd);
#endif

176
bin/test_cert.c Normal file
View file

@ -0,0 +1,176 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include "lsquic_types.h"
#include "lsquic.h"
#include "../src/liblsquic/lsquic_logger.h"
#include "../src/liblsquic/lsquic_hash.h"
#include "test_cert.h"
static int
select_alpn (SSL *ssl, const unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen, void *arg)
{
const unsigned char alpn[] = "\x5h3-25\x5h3-27";
int r;
r = SSL_select_next_proto((unsigned char **) out, outlen, in, inlen,
alpn, sizeof(alpn));
if (r == OPENSSL_NPN_NEGOTIATED)
return SSL_TLSEXT_ERR_OK;
else
{
LSQ_WARN("no supported protocol can be selected from %.*s",
(int) inlen, (char *) in);
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
}
int
load_cert (struct lsquic_hash *certs, const char *optarg)
{
int rv = -1;
char *sni, *cert_file, *key_file;
struct server_cert *cert = NULL;
EVP_PKEY *pkey = NULL;
FILE *f = NULL;
sni = strdup(optarg);
cert_file = strchr(sni, ',');
if (!cert_file)
goto end;
*cert_file = '\0';
++cert_file;
key_file = strchr(cert_file, ',');
if (!key_file)
goto end;
*key_file = '\0';
++key_file;
cert = calloc(1, sizeof(*cert));
cert->ce_sni = strdup(sni);
cert->ce_ssl_ctx = SSL_CTX_new(TLS_method());
if (!cert->ce_ssl_ctx)
{
LSQ_ERROR("SSL_CTX_new failed");
goto end;
}
SSL_CTX_set_min_proto_version(cert->ce_ssl_ctx, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(cert->ce_ssl_ctx, TLS1_3_VERSION);
SSL_CTX_set_default_verify_paths(cert->ce_ssl_ctx);
SSL_CTX_set_alpn_select_cb(cert->ce_ssl_ctx, select_alpn, NULL);
SSL_CTX_set_early_data_enabled(cert->ce_ssl_ctx, 1); /* XXX */
if (1 != SSL_CTX_use_certificate_chain_file(cert->ce_ssl_ctx, cert_file))
{
LSQ_ERROR("SSL_CTX_use_certificate_chain_file failed: %s", cert_file);
goto end;
}
if (strstr(key_file, ".pkcs8"))
{
f = fopen(key_file, "r");
if (!f)
{
LSQ_ERROR("fopen(%s) failed: %s", cert_file, strerror(errno));
goto end;
}
pkey = d2i_PrivateKey_fp(f, NULL);
fclose(f);
f = NULL;
if (!pkey)
{
LSQ_ERROR("Reading private key from %s failed", key_file);
goto end;
}
if (!SSL_CTX_use_PrivateKey(cert->ce_ssl_ctx, pkey))
{
LSQ_ERROR("SSL_CTX_use_PrivateKey failed");
goto end;
}
}
else if (1 != SSL_CTX_use_PrivateKey_file(cert->ce_ssl_ctx, key_file,
SSL_FILETYPE_PEM))
{
LSQ_ERROR("SSL_CTX_use_PrivateKey_file failed");
goto end;
}
const int was = SSL_CTX_set_session_cache_mode(cert->ce_ssl_ctx, 1);
LSQ_DEBUG("set SSL session cache mode to 1 (was: %d)", was);
if (lsquic_hash_insert(certs, cert->ce_sni, strlen(cert->ce_sni), cert,
&cert->ce_hash_el))
rv = 0;
else
LSQ_WARN("cannot insert cert for %s into hash table", cert->ce_sni);
end:
free(sni);
if (rv != 0)
{ /* Error: free cert and its components */
if (cert)
{
free(cert->ce_sni);
free(cert);
}
}
return rv;
}
struct ssl_ctx_st *
lookup_cert (void *cert_lu_ctx, const struct sockaddr *sa_UNUSED,
const char *sni)
{
struct lsquic_hash_elem *el;
struct server_cert *server_cert;
if (!cert_lu_ctx)
return NULL;
if (sni)
el = lsquic_hash_find(cert_lu_ctx, sni, strlen(sni));
else
{
LSQ_INFO("SNI is not set");
el = lsquic_hash_first(cert_lu_ctx);
}
if (el)
{
server_cert = lsquic_hashelem_getdata(el);
if (server_cert)
return server_cert->ce_ssl_ctx;
}
return NULL;
}
void
delete_certs (struct lsquic_hash *certs)
{
struct lsquic_hash_elem *el;
struct server_cert *cert;
for (el = lsquic_hash_first(certs); el; el = lsquic_hash_next(certs))
{
cert = lsquic_hashelem_getdata(el);
SSL_CTX_free(cert->ce_ssl_ctx);
free(cert->ce_sni);
free(cert);
}
lsquic_hash_destroy(certs);
}

28
bin/test_cert.h Normal file
View file

@ -0,0 +1,28 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
#ifndef TEST_CERT_H
#define TEST_CERT_H
struct lsquic_hash;
struct ssl_ctx_st;
struct sockaddr;
struct server_cert
{
char *ce_sni;
struct ssl_ctx_st *ce_ssl_ctx;
struct lsquic_hash_elem ce_hash_el;
};
int
load_cert (struct lsquic_hash *, const char *optarg);
struct ssl_ctx_st *
lookup_cert (void *cert_lu_ctx, const struct sockaddr * /*unused */,
const char *sni);
void
delete_certs (struct lsquic_hash *);
#endif

2207
bin/test_common.c Normal file

File diff suppressed because it is too large Load diff

128
bin/test_common.h Normal file
View file

@ -0,0 +1,128 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* Test client's and server's common components.
*/
#ifndef TEST_COMMON_H
#define TEST_COMMON_H 1
#if __linux__
# include <net/if.h> /* For IFNAMSIZ */
#endif
struct lsquic_engine;
struct lsquic_engine_settings;
struct lsquic_out_spec;
struct event_base;
struct event;
struct packets_in;
struct lsquic_conn;
struct prog;
struct reader_ctx;
enum sport_flags
{
#if LSQUIC_DONTFRAG_SUPPORTED
SPORT_FRAGMENT_OK = (1 << 0),
#endif
SPORT_SET_SNDBUF = (1 << 1), /* SO_SNDBUF */
SPORT_SET_RCVBUF = (1 << 2), /* SO_RCVBUF */
SPORT_SERVER = (1 << 3),
SPORT_CONNECT = (1 << 4),
};
struct service_port {
TAILQ_ENTRY(service_port) next_sport;
#ifndef WIN32
int fd;
#else
SOCKET fd;
#endif
#if __linux__
uint32_t n_dropped;
int drop_init;
char if_name[IFNAMSIZ];
#endif
struct event *ev;
struct lsquic_engine *engine;
void *conn_ctx;
char host[80];
struct sockaddr_storage sas;
struct sockaddr_storage sp_local_addr;
struct packets_in *packs_in;
enum sport_flags sp_flags;
int sp_sndbuf; /* If SPORT_SET_SNDBUF is set */
int sp_rcvbuf; /* If SPORT_SET_RCVBUF is set */
struct prog *sp_prog;
unsigned char *sp_token_buf;
size_t sp_token_sz;
};
TAILQ_HEAD(sport_head, service_port);
struct service_port *
sport_new (const char *optarg, struct prog *);
void
sport_destroy (struct service_port *);
int
sport_init_server (struct service_port *, struct lsquic_engine *,
struct event_base *);
int
sport_init_client (struct service_port *, struct lsquic_engine *,
struct event_base *);
int
sport_packets_out (void *ctx, const struct lsquic_out_spec *, unsigned count);
int
sport_set_token (struct service_port *, const char *);
int
set_engine_option (struct lsquic_engine_settings *,
int *version_cleared, const char *name_value);
struct packout_buf;
struct packout_buf_allocator
{
unsigned n_out, /* Number of buffers outstanding */
max; /* Maximum outstanding. Zero mean no limit */
SLIST_HEAD(, packout_buf) free_packout_bufs;
};
void
pba_init (struct packout_buf_allocator *, unsigned max);
void *
pba_allocate (void *packout_buf_allocator, void*, unsigned short, char);
void
pba_release (void *packout_buf_allocator, void *, void *obj, char);
void
pba_cleanup (struct packout_buf_allocator *);
void
print_conn_info (const struct lsquic_conn *conn);
size_t
test_reader_size (void *void_ctx);
size_t
test_reader_read (void *void_ctx, void *buf, size_t count);
struct reader_ctx *
create_lsquic_reader_ctx (const char *filename);
void
destroy_lsquic_reader_ctx (struct reader_ctx *ctx);
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define LITESPEED_ID "lsquic" "/" TOSTRING(LSQUIC_MAJOR_VERSION) "." \
TOSTRING(LSQUIC_MINOR_VERSION) "." TOSTRING(LSQUIC_PATCH_VERSION)
#endif

22
bin/test_config.h.in Normal file
View file

@ -0,0 +1,22 @@
#ifndef LSQUIC_CONFIG_H
#define LSQUIC_CONFIG_H
#cmakedefine HAVE_SENDMMSG 1
#cmakedefine HAVE_RECVMMSG 1
#cmakedefine HAVE_OPEN_MEMSTREAM 1
#cmakedefine HAVE_IP_DONTFRAG 1
#cmakedefine HAVE_IP_MTU_DISCOVER 1
#cmakedefine HAVE_REGEX 1
#define LSQUIC_DONTFRAG_SUPPORTED (HAVE_IP_DONTFRAG || HAVE_IP_MTU_DISCOVER)
/* TODO: presumably it's the same on FreeBSD, test it.
* See https://github.com/quicwg/base-drafts/wiki/ECN-in-QUIC
*/
#if __linux__ || defined(__FreeBSD__)
#define ECN_SUPPORTED 1
#else
#define ECN_SUPPORTED 0
#endif
#endif