diff --git a/tests/features/GLOBAL.flags b/tests/features/GLOBAL.flags index 7c88e98c6e3..5cc21e16704 100644 --- a/tests/features/GLOBAL.flags +++ b/tests/features/GLOBAL.flags @@ -1 +1 @@ --Werror-implicit-function-declaration +-Werror-implicit-function-declaration -Wno-unused-variable diff --git a/tests/features/S2N_LINUX_SENDFILE.c b/tests/features/S2N_LINUX_SENDFILE.c new file mode 100644 index 00000000000..4df442a56c5 --- /dev/null +++ b/tests/features/S2N_LINUX_SENDFILE.c @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/* MacOS and BSD have completely different signatures for sendfile, + * and sendfile is provided by different headers. + * Test ONLY for the Linux version. + */ + +#include + +int main() +{ + int out_fd = 0, in_fd = 0; + off_t offset = 0; + size_t count = 0; + ssize_t result = sendfile(out_fd, in_fd, &offset, count); + return 0; +} diff --git a/tests/features/S2N_LINUX_SENDFILE.flags b/tests/features/S2N_LINUX_SENDFILE.flags new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/s2n_ktls_io_sendfile_test.c b/tests/unit/s2n_ktls_io_sendfile_test.c new file mode 100644 index 00000000000..0f757cc2efa --- /dev/null +++ b/tests/unit/s2n_ktls_io_sendfile_test.c @@ -0,0 +1,190 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include + +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" +#include "tls/s2n_ktls.h" +#include "utils/s2n_random.h" + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + +#ifdef S2N_LINUX_SENDFILE + const bool sendfile_supported = true; +#else + const bool sendfile_supported = false; +#endif + + /* Test feature probe */ + { +#if defined(__linux__) + EXPECT_TRUE(sendfile_supported); +#endif +#if defined(__FreeBSD__) + EXPECT_FALSE(sendfile_supported); +#endif + }; + + /* Safety */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t bytes_written = 0; + + EXPECT_FAILURE_WITH_ERRNO( + s2n_sendfile(NULL, 0, 0, 0, &bytes_written, &blocked), + S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO( + s2n_sendfile(conn, 0, 0, 0, NULL, &blocked), + S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO( + s2n_sendfile(conn, 0, 0, 0, &bytes_written, NULL), + S2N_ERR_NULL); + }; + + /* Test s2n_sendfile unsupported */ + if (!sendfile_supported) { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + conn->ktls_send_enabled = true; + + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + EXPECT_SUCCESS(s2n_connection_set_io_pair(conn, &io_pair)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t bytes_written = 0; + int result = s2n_sendfile(conn, 1, 0, 1, &bytes_written, &blocked); + EXPECT_FAILURE_WITH_ERRNO(result, S2N_ERR_UNIMPLEMENTED); + + /* We do not run any further tests */ + END_TEST(); + }; + + /* The one file we know definitely exists is our own executable */ + int ro_file = open(argv[0], O_RDONLY); + EXPECT_TRUE(ro_file > 0); + + /* use pread to read the beginning of the file without updating its offset. + * Careful: if any call to sendfile sets offset=NULL, the file's offset will + * be updated and different data will be read. + */ + uint8_t test_data[100] = { 0 }; + EXPECT_EQUAL(pread(ro_file, test_data, sizeof(test_data), 0), sizeof(test_data)); + + /* Test: successful send */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + conn->ktls_send_enabled = true; + + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + int write_fd = io_pair.server; + int read_fd = io_pair.client; + EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, write_fd)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t bytes_written = 0; + EXPECT_SUCCESS(s2n_sendfile(conn, ro_file, 0, sizeof(test_data), + &bytes_written, &blocked)); + EXPECT_EQUAL(bytes_written, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + uint8_t written[sizeof(test_data)] = { 0 }; + EXPECT_EQUAL(read(read_fd, written, sizeof(written)), sizeof(test_data)); + EXPECT_BYTEARRAY_EQUAL(written, test_data, sizeof(test_data)); + EXPECT_TRUE(read(read_fd, written, sizeof(written)) < 0); + }; + + /* Test: IO error */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + conn->ktls_send_enabled = true; + + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + int write_fd = io_pair.server; + int read_fd = io_pair.client; + EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, write_fd)); + + /* Close one side of the stream to make the fds invalid */ + close(read_fd); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t bytes_written = 0; + int ret = s2n_sendfile(conn, ro_file, 0, sizeof(test_data), + &bytes_written, &blocked); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_IO); + EXPECT_EQUAL(bytes_written, 0); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE); + }; + + /* Test: send blocks */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + conn->ktls_send_enabled = true; + + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + int write_fd = io_pair.server; + EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, write_fd)); + + /* We can force the socket to block by filling up its send buffer. */ + int buffer_size = 0; + socklen_t optlen = sizeof(buffer_size); + EXPECT_EQUAL(getsockopt(write_fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, &optlen), 0); + EXPECT_TRUE(buffer_size > 0); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t bytes_written = 0; + size_t total_bytes_written = 0; + while (true) { + int result = s2n_sendfile(conn, ro_file, 0, sizeof(test_data), + &bytes_written, &blocked); + if (result < 0) { + EXPECT_FAILURE_WITH_ERRNO(result, S2N_ERR_IO_BLOCKED); + EXPECT_EQUAL(bytes_written, 0); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE); + break; + } + + EXPECT_TRUE(bytes_written <= sizeof(test_data)); + EXPECT_TRUE(bytes_written > 0); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + total_bytes_written += bytes_written; + + /* The socket will block before buffer_size bytes are written. + * If we successfully send buffer_size bytes, something is wrong. + */ + EXPECT_TRUE(total_bytes_written < buffer_size); + } + }; + + EXPECT_EQUAL(close(ro_file), 0); + END_TEST(); +} diff --git a/tls/s2n_ktls.h b/tls/s2n_ktls.h index 342681f78a3..c4b94fe9efe 100644 --- a/tls/s2n_ktls.h +++ b/tls/s2n_ktls.h @@ -53,6 +53,8 @@ int s2n_ktls_record_writev(struct s2n_connection *conn, uint8_t content_type, /* These functions will be part of the public API. */ int s2n_connection_ktls_enable_send(struct s2n_connection *conn); int s2n_connection_ktls_enable_recv(struct s2n_connection *conn); +int s2n_sendfile(struct s2n_connection *conn, int in_fd, off_t offset, size_t count, + size_t *bytes_written, s2n_blocked_status *blocked); /* Testing */ typedef int (*s2n_setsockopt_fn)(int socket, int level, int option_name, const void *option_value, diff --git a/tls/s2n_ktls_io.c b/tls/s2n_ktls_io.c index a7272d68596..b0448ff6303 100644 --- a/tls/s2n_ktls_io.c +++ b/tls/s2n_ktls_io.c @@ -25,6 +25,10 @@ #endif #include +#ifdef S2N_LINUX_SENDFILE + #include +#endif + #include "error/s2n_errno.h" #include "tls/s2n_ktls.h" #include "utils/s2n_result.h" @@ -370,3 +374,34 @@ int s2n_ktls_record_writev(struct s2n_connection *conn, uint8_t content_type, POSIX_GUARD(s2n_stuffer_writev_bytes(&conn->out, in, count, offs, to_write)); return to_write; } + +int s2n_sendfile(struct s2n_connection *conn, int in_fd, off_t offset, size_t count, + size_t *bytes_written, s2n_blocked_status *blocked) +{ + POSIX_ENSURE_REF(blocked); + *blocked = S2N_BLOCKED_ON_WRITE; + POSIX_ENSURE_REF(bytes_written); + *bytes_written = 0; + POSIX_ENSURE_REF(conn); + POSIX_ENSURE(conn->ktls_send_enabled, S2N_ERR_KTLS_UNSUPPORTED_CONN); + + int out_fd = 0; + POSIX_GUARD_RESULT(s2n_ktls_get_file_descriptor(conn, S2N_KTLS_MODE_SEND, &out_fd)); + +#ifdef S2N_LINUX_SENDFILE + /* https://man7.org/linux/man-pages/man2/sendfile.2.html */ + ssize_t result = sendfile(out_fd, in_fd, &offset, count); + if (result < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + POSIX_BAIL(S2N_ERR_IO_BLOCKED); + } + POSIX_BAIL(S2N_ERR_IO); + } + *bytes_written = result; +#else + POSIX_BAIL(S2N_ERR_UNIMPLEMENTED); +#endif + + *blocked = S2N_NOT_BLOCKED; + return S2N_SUCCESS; +}