summaryrefslogtreecommitdiff
path: root/regress
diff options
context:
space:
mode:
authorJoel Sing <jsing@cvs.openbsd.org>2020-10-15 18:05:07 +0000
committerJoel Sing <jsing@cvs.openbsd.org>2020-10-15 18:05:07 +0000
commit021714eb4b7ec9dd39289addb1389fdbdb5da4d4 (patch)
tree85096471df4911ebc46123330cda3871d760dc1a /regress
parent8b8069105e379a4602f3b0513308c8938b05aae5 (diff)
Test DTLS timeouts and retransmissions by dropping specific messages.
Provide a BIO that can drop specific messages in order to trigger and test DTLS timeouts and retransmissions. Note that the SSL buffering BIO (bbio) has to be removed to ensure that handshake messages are sent individually. This would have detected the recent DTLS breakage with retransmissions for a flight that includes a CCS.
Diffstat (limited to 'regress')
-rw-r--r--regress/lib/libssl/dtls/dtlstest.c304
1 files changed, 299 insertions, 5 deletions
diff --git a/regress/lib/libssl/dtls/dtlstest.c b/regress/lib/libssl/dtls/dtlstest.c
index 166302db488..c25800be195 100644
--- a/regress/lib/libssl/dtls/dtlstest.c
+++ b/regress/lib/libssl/dtls/dtlstest.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: dtlstest.c,v 1.2 2020/10/15 17:51:58 jsing Exp $ */
+/* $OpenBSD: dtlstest.c,v 1.3 2020/10/15 18:05:06 jsing Exp $ */
/*
* Copyright (c) 2020 Joel Sing <jsing@openbsd.org>
*
@@ -35,6 +35,177 @@ char dtls_cookie[32];
int debug = 0;
+static void
+hexdump(const unsigned char *buf, size_t len)
+{
+ size_t i;
+
+ for (i = 1; i <= len; i++)
+ fprintf(stderr, " 0x%02hhx,%s", buf[i - 1], i % 8 ? "" : "\n");
+
+ if (len % 8)
+ fprintf(stderr, "\n");
+}
+
+#define BIO_C_DROP_PACKET 1000
+#define BIO_C_DROP_RANDOM 1001
+
+struct bio_packet_monkey_ctx {
+ unsigned int drop_rand;
+ unsigned int drop_mask;
+};
+
+static int
+bio_packet_monkey_new(BIO *bio)
+{
+ struct bio_packet_monkey_ctx *ctx;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
+ return 0;
+
+ bio->flags = 0;
+ bio->init = 1;
+ bio->num = 0;
+ bio->ptr = ctx;
+
+ return 1;
+}
+
+static int
+bio_packet_monkey_free(BIO *bio)
+{
+ struct bio_packet_monkey_ctx *ctx;
+
+ if (bio == NULL)
+ return 1;
+
+ ctx = bio->ptr;
+ free(ctx);
+
+ return 1;
+}
+
+static long
+bio_packet_monkey_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ struct bio_packet_monkey_ctx *ctx;
+
+ ctx = bio->ptr;
+
+ switch (cmd) {
+ case BIO_C_DROP_PACKET:
+ if (num < 1 || num > 31)
+ return 0;
+ ctx->drop_mask |= 1 << ((unsigned int)num - 1);
+ return 1;
+
+ case BIO_C_DROP_RANDOM:
+ if (num < 0 || num > UINT_MAX)
+ return 0;
+ ctx->drop_rand = (unsigned int)num;
+ return 1;
+ }
+
+ if (bio->next_bio == NULL)
+ return 0;
+
+ return BIO_ctrl(bio->next_bio, cmd, num, ptr);
+}
+
+static int
+bio_packet_monkey_read(BIO *bio, char *out, int out_len)
+{
+ struct bio_packet_monkey_ctx *ctx = bio->ptr;
+ int ret;
+
+ if (ctx == NULL || bio->next_bio == NULL)
+ return 0;
+
+ ret = BIO_read(bio->next_bio, out, out_len);
+
+ BIO_clear_retry_flags(bio);
+ if (ret <= 0 && BIO_should_retry(bio->next_bio))
+ BIO_set_retry_read(bio);
+
+ return ret;
+}
+
+static int
+bio_packet_monkey_write(BIO *bio, const char *in, int in_len)
+{
+ struct bio_packet_monkey_ctx *ctx = bio->ptr;
+ int drop = 0;
+ int ret;
+
+ if (ctx == NULL || bio->next_bio == NULL)
+ return 0;
+
+ if (ctx->drop_rand > 0) {
+ drop = arc4random_uniform(ctx->drop_rand) == 0;
+ } else if (ctx->drop_mask > 0) {
+ drop = ctx->drop_mask & 1;
+ ctx->drop_mask >>= 1;
+ }
+ if (debug) {
+ fprintf(stderr, "DEBUG: %s packet...\n",
+ drop ? "dropping" : "writing");
+ hexdump(in, in_len);
+ }
+ if (drop)
+ return in_len;
+
+ ret = BIO_write(bio->next_bio, in, in_len);
+
+ BIO_clear_retry_flags(bio);
+ if (ret <= 0 && BIO_should_retry(bio->next_bio))
+ BIO_set_retry_write(bio);
+
+ return ret;
+}
+
+static int
+bio_packet_monkey_puts(BIO *bio, const char *str)
+{
+ return bio_packet_monkey_write(bio, str, strlen(str));
+}
+
+static const BIO_METHOD bio_packet_monkey = {
+ .type = BIO_TYPE_BUFFER,
+ .name = "packet monkey",
+ .bread = bio_packet_monkey_read,
+ .bwrite = bio_packet_monkey_write,
+ .bputs = bio_packet_monkey_puts,
+ .ctrl = bio_packet_monkey_ctrl,
+ .create = bio_packet_monkey_new,
+ .destroy = bio_packet_monkey_free
+};
+
+static const BIO_METHOD *
+BIO_f_packet_monkey(void)
+{
+ return &bio_packet_monkey;
+}
+
+static BIO *
+BIO_new_packet_monkey(void)
+{
+ return BIO_new(BIO_f_packet_monkey());
+}
+
+static int
+BIO_packet_monkey_drop(BIO *bio, int num)
+{
+ return BIO_ctrl(bio, BIO_C_DROP_PACKET, num, NULL);
+}
+
+#if 0
+static int
+BIO_packet_monkey_drop_random(BIO *bio, int num)
+{
+ return BIO_ctrl(bio, BIO_C_DROP_RANDOM, num, NULL);
+}
+#endif
+
static int
datagram_pair(int *client_sock, int *server_sock,
struct sockaddr_in *server_sin)
@@ -109,6 +280,17 @@ dtls_cookie_verify(SSL *ssl, const unsigned char *cookie,
memcmp(cookie, dtls_cookie, sizeof(dtls_cookie)) == 0;
}
+static void
+dtls_info_callback(const SSL *ssl, int type, int val)
+{
+ /*
+ * Squeal's ahead... remove the bbio from the info callback, so we can
+ * drop specific messages. Ideally this would be an option for the SSL.
+ */
+ if (ssl->wbio == ssl->bbio)
+ ((SSL *)ssl)->wbio = BIO_pop(ssl->wbio);
+}
+
static SSL *
dtls_client(int sock, struct sockaddr_in *server_sin, long mtu)
{
@@ -305,13 +487,19 @@ do_client_server_loop(SSL *client, ssl_func client_func, SSL *server,
return client_done && server_done;
}
+#define MAX_PACKET_DROPS 32
+
struct dtls_test {
const unsigned char *desc;
- const long mtu;
- const long ssl_options;
+ long mtu;
+ long ssl_options;
+ int client_bbio_off;
+ int server_bbio_off;
+ uint8_t client_drops[MAX_PACKET_DROPS];
+ uint8_t server_drops[MAX_PACKET_DROPS];
};
-static struct dtls_test dtls_tests[] = {
+static const struct dtls_test dtls_tests[] = {
{
.desc = "DTLS without cookies",
.ssl_options = 0,
@@ -323,18 +511,116 @@ static struct dtls_test dtls_tests[] = {
{
.desc = "DTLS with low MTU",
.mtu = 256,
+ .ssl_options = 0,
},
{
.desc = "DTLS with low MTU and cookies",
.mtu = 256,
.ssl_options = SSL_OP_COOKIE_EXCHANGE,
},
+ {
+ .desc = "DTLS with dropped server response",
+ .ssl_options = 0,
+ .server_drops = { 1 },
+ },
+ {
+ .desc = "DTLS with two dropped server responses",
+ .ssl_options = 0,
+ .server_drops = { 1, 2 },
+ },
+ {
+ .desc = "DTLS with dropped ServerHello",
+ .ssl_options = 0,
+ .server_bbio_off = 1,
+ .server_drops = { 1 },
+ },
+ {
+ .desc = "DTLS with dropped server Certificate",
+ .ssl_options = 0,
+ .server_bbio_off = 1,
+ .server_drops = { 2 },
+ },
+ {
+ .desc = "DTLS with dropped ServerKeyExchange",
+ .ssl_options = 0,
+ .server_bbio_off = 1,
+ .server_drops = { 3 },
+ },
+#if 0
+ /*
+ * These three currently result in the server accept completing and the
+ * client looping on a timeout. Presumably the server should not
+ * complete until the client Finished is received...
+ */
+ {
+ .desc = "DTLS with dropped ServerHelloDone",
+ .ssl_options = 0,
+ .server_bbio_off = 1,
+ .server_drops = { 4 },
+ },
+ {
+ .desc = "DTLS with dropped server CCS",
+ .ssl_options = 0,
+ .server_bbio_off = 1,
+ .server_drops = { 5 },
+ },
+ {
+ .desc = "DTLS with dropped server Finished",
+ .ssl_options = 0,
+ .server_bbio_off = 1,
+ .server_drops = { 6 },
+ },
+#endif
+ {
+ .desc = "DTLS with dropped ClientKeyExchange",
+ .ssl_options = 0,
+ .client_bbio_off = 1,
+ .client_drops = { 2 },
+ },
+ {
+ .desc = "DTLS with dropped Client CCS",
+ .ssl_options = 0,
+ .client_bbio_off = 1,
+ .client_drops = { 3 },
+ },
+ {
+ .desc = "DTLS with dropped client Finished",
+ .ssl_options = 0,
+ .client_bbio_off = 1,
+ .client_drops = { 4 },
+ },
};
#define N_DTLS_TESTS (sizeof(dtls_tests) / sizeof(*dtls_tests))
+static void
+dtlstest_packet_monkey(SSL *ssl, const uint8_t drops[])
+{
+ BIO *bio_monkey;
+ BIO *bio;
+ int i;
+
+ if ((bio_monkey = BIO_new_packet_monkey()) == NULL)
+ errx(1, "packet monkey");
+
+ for (i = 0; i < MAX_PACKET_DROPS; i++) {
+ if (drops[i] == 0)
+ break;
+ if (!BIO_packet_monkey_drop(bio_monkey, drops[i]))
+ errx(1, "drop failure");
+ }
+
+ if ((bio = SSL_get_wbio(ssl)) == NULL)
+ errx(1, "SSL has NULL bio");
+
+ BIO_up_ref(bio);
+ bio = BIO_push(bio_monkey, bio);
+
+ SSL_set_bio(ssl, bio, bio);
+}
+
static int
-dtlstest(struct dtls_test *dt)
+dtlstest(const struct dtls_test *dt)
{
SSL *client = NULL, *server = NULL;
struct sockaddr_in server_sin;
@@ -353,6 +639,14 @@ dtlstest(struct dtls_test *dt)
if ((server = dtls_server(server_sock, dt->ssl_options, dt->mtu)) == NULL)
goto failure;
+ if (dt->client_bbio_off)
+ SSL_set_info_callback(client, dtls_info_callback);
+ if (dt->server_bbio_off)
+ SSL_set_info_callback(server, dtls_info_callback);
+
+ dtlstest_packet_monkey(client, dt->client_drops);
+ dtlstest_packet_monkey(server, dt->server_drops);
+
pfd[0].fd = client_sock;
pfd[0].events = POLLOUT;
pfd[1].fd = server_sock;