Skip to content

Commit

Permalink
kTLS: implement sendmsg (aws#4147)
Browse files Browse the repository at this point in the history
Co-authored-by: Lindsay Stewart <slindsay@amazon.com>
  • Loading branch information
toidiu and lrstewart committed Aug 21, 2023
1 parent 1a5e406 commit 346bd1a
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 39 deletions.
59 changes: 55 additions & 4 deletions tests/testlib/s2n_ktls_test_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ ssize_t s2n_test_ktls_recvmsg_io_stuffer(void *io_context, struct msghdr *msg)
return bytes_read;
}

S2N_RESULT s2n_test_init_ktls_io_stuffer_send(struct s2n_connection *conn,
struct s2n_test_ktls_io_stuffer *io)
{
RESULT_ENSURE_REF(conn);
RESULT_ENSURE_REF(io);

RESULT_GUARD_POSIX(s2n_stuffer_growable_alloc(&io->data_buffer, 0));
RESULT_GUARD_POSIX(s2n_stuffer_growable_alloc(&io->ancillary_buffer, 0));
RESULT_GUARD(s2n_ktls_set_sendmsg_cb(conn, s2n_test_ktls_sendmsg_io_stuffer, io));

return S2N_RESULT_OK;
}

S2N_RESULT s2n_test_init_ktls_io_stuffer(struct s2n_connection *server,
struct s2n_connection *client, struct s2n_test_ktls_io_stuffer_pair *io_pair)
{
Expand All @@ -172,13 +185,51 @@ S2N_RESULT s2n_test_init_ktls_io_stuffer(struct s2n_connection *server,
return S2N_RESULT_OK;
}

S2N_CLEANUP_RESULT s2n_ktls_io_stuffer_free(struct s2n_test_ktls_io_stuffer *io)
{
RESULT_ENSURE_REF(io);
RESULT_GUARD_POSIX(s2n_stuffer_free(&io->data_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&io->ancillary_buffer));
return S2N_RESULT_OK;
}

S2N_CLEANUP_RESULT s2n_ktls_io_stuffer_pair_free(struct s2n_test_ktls_io_stuffer_pair *pair)
{
RESULT_ENSURE_REF(pair);
RESULT_GUARD_POSIX(s2n_stuffer_free(&pair->client_in.data_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&pair->client_in.ancillary_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&pair->server_in.data_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&pair->server_in.ancillary_buffer));

RESULT_GUARD(s2n_ktls_io_stuffer_free(&pair->client_in));
RESULT_GUARD(s2n_ktls_io_stuffer_free(&pair->server_in));

return S2N_RESULT_OK;
}

S2N_RESULT s2n_test_validate_data(struct s2n_test_ktls_io_stuffer *ktls_io, uint8_t *expected_data,
uint16_t expected_len)
{
RESULT_ENSURE_REF(ktls_io);
RESULT_ENSURE_REF(expected_data);

struct s2n_stuffer validate_data_stuffer = ktls_io->data_buffer;
RESULT_ENSURE_EQ(s2n_stuffer_data_available(&validate_data_stuffer), expected_len);
uint8_t *data_ptr = s2n_stuffer_raw_read(&validate_data_stuffer, expected_len);
RESULT_ENSURE_REF(data_ptr);
RESULT_ENSURE_EQ(memcmp(data_ptr, expected_data, expected_len), 0);

return S2N_RESULT_OK;
}

S2N_RESULT s2n_test_validate_ancillary(struct s2n_test_ktls_io_stuffer *ktls_io,
uint8_t expected_record_type, uint16_t expected_len)
{
RESULT_ENSURE_REF(ktls_io);

struct s2n_stuffer validate_ancillary_stuffer = ktls_io->ancillary_buffer;
uint8_t tag = 0;
RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&validate_ancillary_stuffer, &tag));
RESULT_ENSURE_EQ(tag, expected_record_type);
uint16_t len = 0;
RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&validate_ancillary_stuffer, &len));
RESULT_ENSURE_EQ(len, expected_len);

return S2N_RESULT_OK;
}
7 changes: 7 additions & 0 deletions tests/testlib/s2n_ktls_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ struct s2n_test_ktls_io_stuffer_pair {
ssize_t s2n_test_ktls_sendmsg_io_stuffer(void *io_context, const struct msghdr *msg);
ssize_t s2n_test_ktls_recvmsg_io_stuffer(void *io_context, struct msghdr *msg);

S2N_RESULT s2n_test_init_ktls_io_stuffer_send(struct s2n_connection *conn,
struct s2n_test_ktls_io_stuffer *io);
S2N_RESULT s2n_test_init_ktls_io_stuffer(struct s2n_connection *server,
struct s2n_connection *client, struct s2n_test_ktls_io_stuffer_pair *io_pair);
S2N_CLEANUP_RESULT s2n_ktls_io_stuffer_free(struct s2n_test_ktls_io_stuffer *io);
S2N_CLEANUP_RESULT s2n_ktls_io_stuffer_pair_free(struct s2n_test_ktls_io_stuffer_pair *pair);
S2N_RESULT s2n_test_validate_data(struct s2n_test_ktls_io_stuffer *ktls_io, uint8_t *expected_data,
uint16_t expected_len);
S2N_RESULT s2n_test_validate_ancillary(struct s2n_test_ktls_io_stuffer *ktls_io,
uint8_t expected_record_type, uint16_t expected_len);
212 changes: 212 additions & 0 deletions tests/unit/s2n_ktls_io_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,44 @@
*/

#include "s2n_test.h"
#include "testlib/s2n_ktls_test_utils.h"
#include "testlib/s2n_testlib.h"
#include "tls/s2n_ktls.h"
#include "utils/s2n_random.h"

#define S2N_TEST_TO_SEND 10
#define S2N_TEST_MSG_IOVLEN 5

S2N_RESULT s2n_ktls_set_control_data(struct msghdr *msg, char *buf, size_t buf_size,
int cmsg_type, uint8_t record_type);
S2N_RESULT s2n_ktls_get_control_data(struct msghdr *msg, int cmsg_type, uint8_t *record_type);

/* Mock implementation used for validating failure behavior */
struct s2n_test_ktls_io_fail {
size_t invoked_count;
size_t errno_code;
};

static ssize_t s2n_test_ktls_sendmsg_fail(void *io_context, const struct msghdr *msg)
{
struct s2n_test_ktls_io_fail *io_ctx = (struct s2n_test_ktls_io_fail *) io_context;
POSIX_ENSURE_REF(io_ctx);
io_ctx->invoked_count++;
errno = io_ctx->errno_code;
return -1;
}

int main(int argc, char **argv)
{
BEGIN_TEST();

const uint8_t test_record_type = 43;
/* test data */
uint8_t test_data[S2N_TLS_MAXIMUM_FRAGMENT_LENGTH] = { 0 };
struct s2n_blob test_data_blob = { 0 };
EXPECT_SUCCESS(s2n_blob_init(&test_data_blob, test_data, sizeof(test_data)));
EXPECT_OK(s2n_get_public_random_data(&test_data_blob));

/* Test s2n_ktls_set_control_data and s2n_ktls_get_control_data */
{
/* Test: Safety */
Expand Down Expand Up @@ -74,5 +101,190 @@ int main(int argc, char **argv)
};
};

/* Test s2n_ktls_sendmsg */
{
/* Safety */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);

struct iovec msg_iov_valid = { .iov_base = test_data, .iov_len = S2N_TEST_TO_SEND };
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t bytes_written = 0;

EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(NULL, test_record_type, &msg_iov_valid, 1, &blocked, &bytes_written),
S2N_ERR_NULL);
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, NULL, 1, &blocked, &bytes_written),
S2N_ERR_NULL);
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, &msg_iov_valid, 1, NULL, &bytes_written),
S2N_ERR_NULL);
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, &msg_iov_valid, 1, &blocked, NULL),
S2N_ERR_NULL);
};

/* Happy case: msg_iovlen = 1 */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer client_in = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(server, &client_in));

struct iovec msg_iov = { .iov_base = test_data, .iov_len = S2N_TEST_TO_SEND };
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t bytes_written = 0;
EXPECT_OK(s2n_ktls_sendmsg(server, test_record_type, &msg_iov, 1, &blocked, &bytes_written));
EXPECT_EQUAL(bytes_written, S2N_TEST_TO_SEND);
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);

/* confirm sent data */
EXPECT_OK(s2n_test_validate_ancillary(&client_in, test_record_type, S2N_TEST_TO_SEND));
EXPECT_OK(s2n_test_validate_data(&client_in, test_data, S2N_TEST_TO_SEND));

EXPECT_EQUAL(client_in.sendmsg_invoked_count, 1);
};

/* Happy case: msg_iovlen > 1 */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer client_in = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(server, &client_in));

struct iovec msg_iov[S2N_TEST_MSG_IOVLEN] = { 0 };
size_t total_sent = 0;
for (size_t i = 0; i < S2N_TEST_MSG_IOVLEN; i++) {
msg_iov[i].iov_base = test_data + total_sent;
msg_iov[i].iov_len = S2N_TEST_TO_SEND;
total_sent += S2N_TEST_TO_SEND;
}

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t bytes_written = 0;
EXPECT_OK(s2n_ktls_sendmsg(server, test_record_type, msg_iov, S2N_TEST_MSG_IOVLEN, &blocked, &bytes_written));
EXPECT_EQUAL(bytes_written, total_sent);
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);

/* confirm sent data */
EXPECT_OK(s2n_test_validate_ancillary(&client_in, test_record_type, total_sent));
EXPECT_OK(s2n_test_validate_data(&client_in, test_data, total_sent));
/* validate only 1 record was sent */
EXPECT_EQUAL(s2n_stuffer_data_available(&client_in.ancillary_buffer),
S2N_TEST_KTLS_MOCK_HEADER_SIZE);

EXPECT_EQUAL(client_in.sendmsg_invoked_count, 1);
};

/* Simulate a blocked network and handle a S2N_ERR_IO_BLOCKED error */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer client_in = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(server, &client_in));
/* disable growable to simulate blocked/network buffer full */
client_in.data_buffer.growable = false;

struct iovec msg_iov = { .iov_base = test_data, .iov_len = S2N_TEST_TO_SEND };
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t blocked_invoked_count = 5;
size_t bytes_written = 0;
for (size_t i = 0; i < blocked_invoked_count; i++) {
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, &msg_iov, 1, &blocked, &bytes_written),
S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE);
}

/* enable growable to unblock write */
/* cppcheck-suppress redundantAssignment */
client_in.data_buffer.growable = true;
EXPECT_OK(s2n_ktls_sendmsg(server, test_record_type, &msg_iov, 1, &blocked, &bytes_written));
EXPECT_EQUAL(bytes_written, S2N_TEST_TO_SEND);

/* confirm sent data */
EXPECT_OK(s2n_test_validate_ancillary(&client_in, test_record_type, S2N_TEST_TO_SEND));
EXPECT_OK(s2n_test_validate_data(&client_in, test_data, S2N_TEST_TO_SEND));

EXPECT_EQUAL(client_in.sendmsg_invoked_count, blocked_invoked_count + 1);
};

/* Both EWOULDBLOCK and EAGAIN should return a S2N_ERR_IO_BLOCKED error */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
struct s2n_test_ktls_io_fail io_ctx = { 0 };
EXPECT_OK(s2n_ktls_set_sendmsg_cb(server, s2n_test_ktls_sendmsg_fail, &io_ctx));

struct iovec msg_iov = { .iov_base = test_data, .iov_len = S2N_TEST_TO_SEND };
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t bytes_written = 0;

io_ctx.errno_code = EWOULDBLOCK;
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, &msg_iov, 1, &blocked, &bytes_written),
S2N_ERR_IO_BLOCKED);

/* cppcheck-suppress redundantAssignment */
io_ctx.errno_code = EAGAIN;
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, &msg_iov, 1, &blocked, &bytes_written),
S2N_ERR_IO_BLOCKED);

EXPECT_EQUAL(io_ctx.invoked_count, 2);
};

/* Handle a S2N_ERR_IO error */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
struct s2n_test_ktls_io_fail io_ctx = {
.errno_code = EINVAL,
};
EXPECT_OK(s2n_ktls_set_sendmsg_cb(server, s2n_test_ktls_sendmsg_fail, &io_ctx));

struct iovec msg_iov = { .iov_base = test_data, .iov_len = S2N_TEST_TO_SEND };
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t bytes_written = 0;
EXPECT_ERROR_WITH_ERRNO(
s2n_ktls_sendmsg(server, test_record_type, &msg_iov, 1, &blocked, &bytes_written),
S2N_ERR_IO);
/* Blocked status intentionally not reset to preserve legacy s2n_send behavior */
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE);

EXPECT_EQUAL(io_ctx.invoked_count, 1);
};

/* Should be able to invoke s2n_ktls_sendmsg with '0' data */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer client_in = { 0 },
s2n_ktls_io_stuffer_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(server, &client_in));

struct iovec msg_iov = { .iov_base = test_data, .iov_len = S2N_TEST_TO_SEND };
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t bytes_written = 0;

size_t iovlen_zero = 0;
EXPECT_OK(s2n_ktls_sendmsg(server, test_record_type, &msg_iov, iovlen_zero, &blocked, &bytes_written));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_EQUAL(bytes_written, 0);

struct iovec msg_iov_len_zero = { .iov_base = test_data, .iov_len = 0 };
EXPECT_OK(s2n_ktls_sendmsg(server, test_record_type, &msg_iov_len_zero, 1, &blocked, &bytes_written));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_EQUAL(bytes_written, 0);

EXPECT_EQUAL(client_in.sendmsg_invoked_count, 2);
};
};

END_TEST();
}
35 changes: 3 additions & 32 deletions tests/unit/s2n_ktls_test_utils_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,11 @@ S2N_RESULT s2n_ktls_set_control_data(struct msghdr *msg, char *buf, size_t buf_s
int cmsg_type, uint8_t record_type);
S2N_RESULT s2n_ktls_get_control_data(struct msghdr *msg, int cmsg_type, uint8_t *record_type);

S2N_RESULT s2n_test_validate_data(struct s2n_test_ktls_io_stuffer *ktls_io, uint8_t *expected_data, uint16_t expected_len)
{
RESULT_ENSURE_REF(ktls_io);
RESULT_ENSURE_REF(expected_data);

struct s2n_stuffer validate_data_stuffer = ktls_io->data_buffer;
RESULT_ENSURE_EQ(s2n_stuffer_data_available(&validate_data_stuffer), expected_len);
uint8_t *data_ptr = s2n_stuffer_raw_read(&validate_data_stuffer, expected_len);
RESULT_ENSURE_REF(data_ptr);
EXPECT_BYTEARRAY_EQUAL(data_ptr, expected_data, expected_len);

return S2N_RESULT_OK;
}

S2N_RESULT s2n_test_validate_ancillary(struct s2n_test_ktls_io_stuffer *ktls_io, uint8_t expected_record_type, uint16_t expected_len)
{
RESULT_ENSURE_REF(ktls_io);

struct s2n_stuffer validate_ancillary_stuffer = ktls_io->ancillary_buffer;
uint8_t tag = 0;
RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&validate_ancillary_stuffer, &tag));
RESULT_ENSURE_EQ(tag, expected_record_type);
uint16_t len;
RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&validate_ancillary_stuffer, &len));
RESULT_ENSURE_EQ(len, expected_len);

return S2N_RESULT_OK;
}

int main(int argc, char **argv)
{
BEGIN_TEST();

uint8_t test_record_type = 43;
const uint8_t test_record_type = 43;
/* test data */
uint8_t test_data[S2N_TLS_MAXIMUM_FRAGMENT_LENGTH] = { 0 };
struct s2n_blob test_data_blob = { 0 };
Expand Down Expand Up @@ -157,7 +128,7 @@ int main(int argc, char **argv)
EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair));

size_t total_sent = 0;
struct iovec send_msg_iov[sizeof(struct iovec) * S2N_TEST_MSG_IOVLEN] = { 0 };
struct iovec send_msg_iov[S2N_TEST_MSG_IOVLEN] = { 0 };
for (size_t i = 0; i < S2N_TEST_MSG_IOVLEN; i++) {
send_msg_iov[i].iov_base = test_data + total_sent;
send_msg_iov[i].iov_len = S2N_TEST_TO_SEND;
Expand Down Expand Up @@ -317,7 +288,7 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_stuffer_alloc(&io_pair.client_in.data_buffer, S2N_TEST_TO_SEND));

uint8_t *test_data_ptr = test_data;
struct iovec send_msg_iov[sizeof(struct iovec) * S2N_TEST_MSG_IOVLEN] = { 0 };
struct iovec send_msg_iov[S2N_TEST_MSG_IOVLEN] = { 0 };
for (size_t i = 0; i < S2N_TEST_MSG_IOVLEN; i++) {
send_msg_iov[i].iov_base = (void *) test_data_ptr;
send_msg_iov[i].iov_len = S2N_TEST_TO_SEND;
Expand Down
6 changes: 4 additions & 2 deletions tls/s2n_ktls.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ typedef enum {
} s2n_ktls_mode;

bool s2n_ktls_is_supported_on_platform();
S2N_RESULT s2n_ktls_get_file_descriptor(struct s2n_connection *conn, s2n_ktls_mode ktls_mode,
int *fd);
S2N_RESULT s2n_ktls_get_file_descriptor(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, int *fd);

S2N_RESULT s2n_ktls_sendmsg(struct s2n_connection *conn, uint8_t record_type, const struct iovec *msg_iov,
size_t msg_iovlen, s2n_blocked_status *blocked, size_t *bytes_written);

/* These functions will be part of the public API. */
int s2n_connection_ktls_enable_send(struct s2n_connection *conn);
Expand Down
Loading

0 comments on commit 346bd1a

Please sign in to comment.