Skip to content

Commit

Permalink
ktls: add sendfile (aws#4186)
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart authored Sep 9, 2023
1 parent bcec2c5 commit b4f0c5c
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 1 deletion.
2 changes: 1 addition & 1 deletion tests/features/GLOBAL.flags
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-Werror-implicit-function-declaration
-Werror-implicit-function-declaration -Wno-unused-variable
30 changes: 30 additions & 0 deletions tests/features/S2N_LINUX_SENDFILE.c
Original file line number Diff line number Diff line change
@@ -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 <sys/sendfile.h>

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;
}
Empty file.
190 changes: 190 additions & 0 deletions tests/unit/s2n_ktls_io_sendfile_test.c
Original file line number Diff line number Diff line change
@@ -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 <fcntl.h>
#include <sys/socket.h>

#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();
}
2 changes: 2 additions & 0 deletions tls/s2n_ktls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
35 changes: 35 additions & 0 deletions tls/s2n_ktls_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
#endif
#include <sys/socket.h>

#ifdef S2N_LINUX_SENDFILE
#include <sys/sendfile.h>
#endif

#include "error/s2n_errno.h"
#include "tls/s2n_ktls.h"
#include "utils/s2n_result.h"
Expand Down Expand Up @@ -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;
}

0 comments on commit b4f0c5c

Please sign in to comment.