Skip to content

Commit

Permalink
Reduce allocs in ktls app data send (aws#4181)
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart authored Sep 13, 2023
1 parent f40c6b9 commit 765afea
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 59 deletions.
218 changes: 186 additions & 32 deletions tests/unit/s2n_ktls_io_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "s2n_test.h"
#include "testlib/s2n_ktls_test_utils.h"
#include "testlib/s2n_mem_testlib.h"
#include "testlib/s2n_testlib.h"
#include "tls/s2n_ktls.h"
#include "utils/s2n_random.h"
Expand Down Expand Up @@ -558,6 +559,9 @@ int main(int argc, char **argv)
EXPECT_FAILURE_WITH_ERRNO(
s2n_ktls_sendv_with_offset(&conn, NULL, 1, 0, &blocked),
S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(
s2n_ktls_sendv_with_offset(&conn, NULL, 1, 1, &blocked),
S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(
s2n_ktls_sendv_with_offset(&conn, &test_iovec, 1, 0, NULL),
S2N_ERR_NULL);
Expand Down Expand Up @@ -671,38 +675,20 @@ int main(int argc, char **argv)
EXPECT_OK(s2n_test_validate_data(&out, test_data, sizeof(test_data)));
};

/* Test: Send with very large number of iovecs */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out));

size_t many_iov_lens[1000] = { 0 };
DEFER_CLEANUP(struct s2n_test_iovecs test_iovecs = { 0 }, s2n_test_iovecs_free);
EXPECT_OK(s2n_test_new_iovecs(&test_iovecs, &test_data_blob,
many_iov_lens, s2n_array_len(many_iov_lens)));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t result = s2n_ktls_sendv_with_offset(conn,
test_iovecs.iovecs, test_iovecs.iovecs_count, 0, &blocked);
EXPECT_EQUAL(result, sizeof(test_data));

EXPECT_EQUAL(out.sendmsg_invoked_count, 1);
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_OK(s2n_test_validate_ancillary(&out, TLS_APPLICATION_DATA, sizeof(test_data)));
EXPECT_OK(s2n_test_validate_data(&out, test_data, sizeof(test_data)));
};

/* Test: Send with offset */
{
DEFER_CLEANUP(struct s2n_test_iovecs test_iovecs = { 0 }, s2n_test_iovecs_free);
EXPECT_OK(s2n_test_new_iovecs(&test_iovecs, &test_data_blob,
test_iov_lens, s2n_array_len(test_iov_lens)));

size_t large_test_iov_lens[100] = { 0 };
EXPECT_MEMCPY_SUCCESS(large_test_iov_lens, test_iov_lens, sizeof(test_iov_lens));

DEFER_CLEANUP(struct s2n_test_iovecs large_test_iovecs = { 0 },
s2n_test_iovecs_free);
EXPECT_OK(s2n_test_new_iovecs(&large_test_iovecs, &test_data_blob,
large_test_iov_lens, s2n_array_len(large_test_iov_lens)));

/* Test: Send with invalid / too large offset */
{
const size_t bad_offset = sizeof(test_data) + 1;
Expand All @@ -716,16 +702,38 @@ int main(int argc, char **argv)
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t result = s2n_ktls_sendv_with_offset(conn,
test_iovecs.iovecs, test_iovecs.iovecs_count, bad_offset, &blocked);
ssize_t result = s2n_ktls_sendv_with_offset(conn, large_test_iovecs.iovecs,
large_test_iovecs.iovecs_count, bad_offset, &blocked);
EXPECT_FAILURE_WITH_ERRNO(result, S2N_ERR_INVALID_ARGUMENT);

EXPECT_EQUAL(out.sendmsg_invoked_count, 0);
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_OK(s2n_test_records_in_ancillary(&out, 0));
}
};

/* Test: Send with offset equal to total data size */
{
const size_t offset = sizeof(test_data);

DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

/* Test: Send with all possible valid offsets */
DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
int written = s2n_ktls_sendv_with_offset(conn, large_test_iovecs.iovecs,
large_test_iovecs.iovecs_count, offset, &blocked);
EXPECT_EQUAL(written, 0);

EXPECT_EQUAL(out.sendmsg_invoked_count, 1);
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_OK(s2n_test_records_in_ancillary(&out, 0));
};

/* Test: Send with small iovecs array and all possible valid offsets */
for (size_t offset = 0; offset < sizeof(test_data); offset++) {
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
Expand All @@ -748,6 +756,30 @@ int main(int argc, char **argv)
EXPECT_OK(s2n_test_validate_ancillary(&out, TLS_APPLICATION_DATA, expected_sent));
EXPECT_OK(s2n_test_validate_data(&out, test_data + offset, expected_sent));
}

/* Test: Send with large iovecs array and all possible valid offsets */
for (size_t offset = 0; offset < sizeof(test_data); offset++) {
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out));

const size_t expected_sent = sizeof(test_data) - offset;
EXPECT_TRUE(expected_sent > 0);

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t result = s2n_ktls_sendv_with_offset(conn, large_test_iovecs.iovecs,
large_test_iovecs.iovecs_count, offset, &blocked);
EXPECT_EQUAL(result, expected_sent);

EXPECT_EQUAL(out.sendmsg_invoked_count, 1);
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_OK(s2n_test_validate_ancillary(&out, TLS_APPLICATION_DATA, expected_sent));
EXPECT_OK(s2n_test_validate_data(&out, test_data + offset, expected_sent));
}
};

/* Test: Partial write */
Expand Down Expand Up @@ -799,6 +831,128 @@ int main(int argc, char **argv)
EXPECT_EQUAL(io_ctx.invoked_count, 1);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE);
};

/* Test: Memory usage */
{
const size_t iov_lens[100] = { 10, 5, 0, 1, 100, 100, 10 };
const size_t small_iov_lens_count = 10;
const size_t large_iov_lens_count = s2n_array_len(iov_lens);

DEFER_CLEANUP(struct s2n_test_iovecs small_iovecs = { 0 }, s2n_test_iovecs_free);
EXPECT_OK(s2n_test_new_iovecs(&small_iovecs, &test_data_blob,
iov_lens, small_iov_lens_count));

DEFER_CLEANUP(struct s2n_test_iovecs large_iovecs = { 0 }, s2n_test_iovecs_free);
EXPECT_OK(s2n_test_new_iovecs(&large_iovecs, &test_data_blob,
iov_lens, large_iov_lens_count));

const size_t one_iovec_size = sizeof(struct iovec);
const size_t large_iovecs_size = large_iovecs.iovecs_count * one_iovec_size;

struct {
struct s2n_test_iovecs *iovecs;
size_t offset;
uint32_t expected_malloc;
uint32_t expected_malloc_count;
} test_cases[] = {
/* Small iovecs never require an allocation */
{
.iovecs = &small_iovecs,
.offset = 1,
.expected_malloc_count = 0,
},
{
.iovecs = &small_iovecs,
.offset = iov_lens[0],
.expected_malloc_count = 0,
},
{
.iovecs = &small_iovecs,
.offset = iov_lens[0] + 1,
.expected_malloc_count = 0,
},
/* Large iovecs with offset evenly divisible by the iov_lens do
* not require an alloc.
* Example: { x, y, z }, offset=x -> { y, z }
*/
{
.iovecs = &large_iovecs,
.offset = iov_lens[0],
.expected_malloc_count = 0,
},
{
.iovecs = &large_iovecs,
.offset = iov_lens[0] + iov_lens[1],
.expected_malloc_count = 0,
},
/* Large iovecs with offset not evenly divisible by the iov_lens
* modify an entry so require an alloc.
* Example: { x, y, z }, offset=1 -> { x-1, y, z }
*/
{
.iovecs = &large_iovecs,
.offset = 1,
.expected_malloc_count = 1,
.expected_malloc = large_iovecs_size,
},
{
.iovecs = &large_iovecs,
.offset = iov_lens[0] + 1,
.expected_malloc_count = 1,
.expected_malloc = large_iovecs_size - one_iovec_size,
},
/* Large iovecs that become small iovecs when the offset
* is applied do not require an alloc.
*/
{
.iovecs = &large_iovecs,
.offset = sizeof(test_data) - 1,
.expected_malloc_count = 0,
},
/* No alloc if the entire large iovec is skipped */
{
.iovecs = &large_iovecs,
.offset = sizeof(test_data),
.expected_malloc_count = 0,
},
};

for (size_t i = 0; i < s2n_array_len(test_cases); i++) {
struct iovec *iovecs = test_cases[i].iovecs->iovecs;
const size_t iovecs_count = test_cases[i].iovecs->iovecs_count;
const size_t offset = test_cases[i].offset;

const size_t expected_send = sizeof(test_data) - offset;

DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out));

/* Preemptively allocate sendmsg memory to avoid false positives */
EXPECT_SUCCESS(s2n_stuffer_resize(&out.data_buffer, expected_send));
EXPECT_SUCCESS(s2n_stuffer_resize(&out.ancillary_buffer, 100));

DEFER_CLEANUP(struct s2n_mem_test_cb_scope scope = { 0 },
s2n_mem_test_free_callbacks);
EXPECT_OK(s2n_mem_test_init_callbacks(&scope));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t result = s2n_ktls_sendv_with_offset(conn, iovecs, iovecs_count,
offset, &blocked);
EXPECT_EQUAL(result, sizeof(test_data) - offset);

size_t malloc_count = test_cases[i].expected_malloc_count;
EXPECT_OK(s2n_mem_test_assert_malloc_count(malloc_count));
if (malloc_count) {
EXPECT_OK(s2n_mem_test_assert_malloc(test_cases[i].expected_malloc));
}
EXPECT_OK(s2n_mem_test_assert_all_freed());
}
};
};

/* Test: s2n_ktls_send_cb */
Expand All @@ -814,8 +968,8 @@ int main(int argc, char **argv)
/* Safety */
{
struct s2n_test_ktls_io_stuffer ctx = { 0 };
EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(NULL, test_data, 1), S2N_ERR_IO);
EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(&ctx, NULL, 1), S2N_ERR_IO);
EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(NULL, test_data, 1), S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(&ctx, NULL, 1), S2N_ERR_NULL);
};

/* Test: Basic write succeeds */
Expand Down
Loading

0 comments on commit 765afea

Please sign in to comment.