diff options
author | Håvard Pettersen <havardpe@oath.com> | 2018-10-09 13:52:35 +0000 |
---|---|---|
committer | Håvard Pettersen <havardpe@oath.com> | 2018-10-09 13:52:35 +0000 |
commit | 765b932a3368297044c349050d11f3f57188b1bd (patch) | |
tree | 5b65e1ba4758575c6aaae4413b2265d9eee10350 /vespalib/src | |
parent | 58850f92f00b6289ff5feccf3634a34943e760d2 (diff) |
added sync crypto socket with test
Diffstat (limited to 'vespalib/src')
6 files changed, 256 insertions, 3 deletions
diff --git a/vespalib/src/tests/net/sync_crypto_socket/CMakeLists.txt b/vespalib/src/tests/net/sync_crypto_socket/CMakeLists.txt new file mode 100644 index 00000000000..e66e62b68d0 --- /dev/null +++ b/vespalib/src/tests/net/sync_crypto_socket/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_sync_crypto_socket_test_app TEST + SOURCES + sync_crypto_socket_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_sync_crypto_socket_test_app COMMAND vespalib_sync_crypto_socket_test_app) diff --git a/vespalib/src/tests/net/sync_crypto_socket/sync_crypto_socket_test.cpp b/vespalib/src/tests/net/sync_crypto_socket/sync_crypto_socket_test.cpp new file mode 100644 index 00000000000..b299990fd4e --- /dev/null +++ b/vespalib/src/tests/net/sync_crypto_socket/sync_crypto_socket_test.cpp @@ -0,0 +1,110 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/vespalib/net/crypto_engine.h> +#include <vespa/vespalib/net/tls/tls_crypto_engine.h> +#include <vespa/vespalib/net/tls/maybe_tls_crypto_engine.h> +#include <vespa/vespalib/net/sync_crypto_socket.h> +#include <vespa/vespalib/net/selector.h> +#include <vespa/vespalib/net/server_socket.h> +#include <vespa/vespalib/net/socket_handle.h> +#include <vespa/vespalib/net/socket_spec.h> +#include <vespa/vespalib/data/smart_buffer.h> +#include <vespa/vespalib/test/make_tls_options_for_testing.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> + +using namespace vespalib; + +struct SocketPair { + SocketHandle client; + SocketHandle server; + SocketPair() : client(), server() { + int sockets[2]; + ASSERT_EQUAL(0, socketpair(AF_UNIX, SOCK_STREAM | O_NONBLOCK, 0, sockets)); + client.reset(sockets[0]); + server.reset(sockets[1]); + } +}; + +//----------------------------------------------------------------------------- + +vespalib::string read_bytes(SyncCryptoSocket &socket, size_t wanted_bytes) { + SmartBuffer read_buffer(wanted_bytes); + while (read_buffer.obtain().size < wanted_bytes) { + auto chunk = read_buffer.reserve(wanted_bytes - read_buffer.obtain().size); + auto res = socket.read(chunk.data, chunk.size); + ASSERT_TRUE(res > 0); + read_buffer.commit(res); + } + auto data = read_buffer.obtain(); + return vespalib::string(data.data, wanted_bytes); +} + +//----------------------------------------------------------------------------- + +void write_bytes(SyncCryptoSocket &socket, const vespalib::string &message) { + auto res = socket.write(message.data(), message.size()); + ASSERT_EQUAL(size_t(res), message.size()); +} + +//----------------------------------------------------------------------------- + +void verify_socket_io(SyncCryptoSocket &socket, bool is_server) { + vespalib::string client_message = "please pick up, I need to talk to you"; + vespalib::string server_message = "hello, this is the server speaking"; + if(is_server) { + vespalib::string read = read_bytes(socket, client_message.size()); + write_bytes(socket, server_message); + EXPECT_EQUAL(client_message, read); + } else { + write_bytes(socket, client_message); + vespalib::string read = read_bytes(socket, server_message.size()); + EXPECT_EQUAL(server_message, read); + } +} + +//----------------------------------------------------------------------------- + +void verify_crypto_socket(SocketPair &sockets, CryptoEngine &engine, bool is_server) { + SocketHandle &my_handle = is_server ? sockets.server : sockets.client; + my_handle.set_blocking(false); + SyncCryptoSocket::UP my_socket = SyncCryptoSocket::create(engine, std::move(my_handle), is_server); + ASSERT_TRUE(my_socket); + TEST_DO(verify_socket_io(*my_socket, is_server)); +} + +//----------------------------------------------------------------------------- + +TEST_MT_FFF("require that encrypted sync socket io works with NullCryptoEngine", + 2, SocketPair(), NullCryptoEngine(), TimeBomb(60)) +{ + TEST_DO(verify_crypto_socket(f1, f2, (thread_id == 0))); +} + +TEST_MT_FFF("require that encrypted sync socket io works with XorCryptoEngine", + 2, SocketPair(), XorCryptoEngine(), TimeBomb(60)) +{ + TEST_DO(verify_crypto_socket(f1, f2, (thread_id == 0))); +} + +TEST_MT_FFF("require that encrypted sync socket io works with TlsCryptoEngine", + 2, SocketPair(), TlsCryptoEngine(vespalib::test::make_tls_options_for_testing()), TimeBomb(60)) +{ + TEST_DO(verify_crypto_socket(f1, f2, (thread_id == 0))); +} + +TEST_MT_FFF("require that encrypted sync socket io works with MaybeTlsCryptoEngine(true)", + 2, SocketPair(), MaybeTlsCryptoEngine(std::make_shared<TlsCryptoEngine>(vespalib::test::make_tls_options_for_testing()), true), TimeBomb(60)) +{ + TEST_DO(verify_crypto_socket(f1, f2, (thread_id == 0))); +} + +TEST_MT_FFF("require that encrypted sync socket io works with MaybeTlsCryptoEngine(false)", + 2, SocketPair(), MaybeTlsCryptoEngine(std::make_shared<TlsCryptoEngine>(vespalib::test::make_tls_options_for_testing()), false), TimeBomb(60)) +{ + TEST_DO(verify_crypto_socket(f1, f2, (thread_id == 0))); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/vespa/vespalib/net/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/CMakeLists.txt index 2e0d15382d0..53f66c240fc 100644 --- a/vespalib/src/vespa/vespalib/net/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/net/CMakeLists.txt @@ -11,5 +11,6 @@ vespa_add_library(vespalib_vespalib_net OBJECT socket_handle.cpp socket_options.cpp socket_spec.cpp + sync_crypto_socket.cpp DEPENDS ) diff --git a/vespalib/src/vespa/vespalib/net/crypto_socket.h b/vespalib/src/vespa/vespalib/net/crypto_socket.h index f78f7fc0ce7..fa76acbc2cd 100644 --- a/vespalib/src/vespa/vespalib/net/crypto_socket.h +++ b/vespalib/src/vespa/vespalib/net/crypto_socket.h @@ -13,9 +13,10 @@ namespace vespalib { * buffers that may mask io events and pending work. The interface is * simplified by assuming there will be no mid-stream re-negotiation * (no read/write cross-dependencies). Handshaking is explicit and - * up-front. This interface is initially designed for persistent - * transport connections where closing the connection has no - * application-level semantics. + * up-front. Note that in order to ensure the correct behaviour of the + * SyncCryptoSocket wrapper, the read function must not call a + * low-level function that might produce the EWOULDBLOCK/EAGAIN + * 'error' after any application-level data has been obtained. **/ struct CryptoSocket { using UP = std::unique_ptr<CryptoSocket>; diff --git a/vespalib/src/vespa/vespalib/net/sync_crypto_socket.cpp b/vespalib/src/vespa/vespalib/net/sync_crypto_socket.cpp new file mode 100644 index 00000000000..788d0e94bdd --- /dev/null +++ b/vespalib/src/vespa/vespalib/net/sync_crypto_socket.cpp @@ -0,0 +1,99 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "sync_crypto_socket.h" +#include <cassert> + +namespace vespalib { + +namespace { + +ssize_t read_from_buffer(SmartBuffer &src, char *dst, size_t len) { + auto data = src.obtain(); + auto chunk = std::min(len, data.size); + if (chunk > 0) { + memcpy(dst, data.data, chunk); + src.evict(chunk); + } + return chunk; +} + +bool is_blocked(ssize_t res, int error) { + return ((res < 0) && ((error == EWOULDBLOCK) || (error == EAGAIN))); +} + +void set_blocking(int fd) { + SocketHandle handle(fd); + handle.set_blocking(true); + handle.release(); +} + +} // namespace vespalib::<unnamed> + +SyncCryptoSocket::~SyncCryptoSocket() = default; + +ssize_t +SyncCryptoSocket::read(char *buf, size_t len) +{ + if (_buffer.obtain().size > 0) { + return read_from_buffer(_buffer, buf, len); + } else if (len < _socket->min_read_buffer_size()) { + auto dst = _buffer.reserve(_socket->min_read_buffer_size()); + auto res = _socket->read(dst.data, dst.size); + while (is_blocked(res, errno)) { + res = _socket->read(dst.data, dst.size); + } + if (res <= 0) { + return res; + } + _buffer.commit(res); + return read_from_buffer(_buffer, buf, len); + } else { + auto res = _socket->read(buf, len); + while (is_blocked(res, errno)) { + res = _socket->read(buf, len); + } + return res; + } +} + +ssize_t +SyncCryptoSocket::write(const char *buf, size_t len) +{ + size_t written = 0; + while (written < len) { + auto write_res = _socket->write(buf + written, len - written); + assert(write_res != 0); + if (write_res > 0) { + written += write_res; + } else if (!is_blocked(write_res, errno)) { + return write_res; + } + } + auto flush_res = _socket->flush(); + while ((flush_res > 0) || is_blocked(flush_res, errno)) { + flush_res = _socket->flush(); + } + if (flush_res < 0) { + return flush_res; + } + return written; +} + +SyncCryptoSocket::UP +SyncCryptoSocket::create(CryptoEngine &engine, SocketHandle socket, bool is_server) +{ + auto crypto_socket = engine.create_crypto_socket(std::move(socket), is_server); + set_blocking(crypto_socket->get_fd()); + auto hs_res = crypto_socket->handshake(); + while ((hs_res == CryptoSocket::HandshakeResult::NEED_READ) || + (hs_res == CryptoSocket::HandshakeResult::NEED_WRITE)) + { + hs_res = crypto_socket->handshake(); + } + if (hs_res != CryptoSocket::HandshakeResult::DONE) { + return std::unique_ptr<SyncCryptoSocket>(nullptr); + } + return UP(new SyncCryptoSocket(std::move(crypto_socket))); +} + +} // namespace vespalib diff --git a/vespalib/src/vespa/vespalib/net/sync_crypto_socket.h b/vespalib/src/vespa/vespalib/net/sync_crypto_socket.h new file mode 100644 index 00000000000..94e80f1ad53 --- /dev/null +++ b/vespalib/src/vespa/vespalib/net/sync_crypto_socket.h @@ -0,0 +1,34 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <memory> +#include "crypto_socket.h" +#include "crypto_engine.h" +#include <vespa/vespalib/data/smart_buffer.h> + +namespace vespalib { + +/** + * A synchronous wrapper around a CryptoSocket. The create function + * will perform connection handshaking. If handshaking fails, an empty + * unique pointer is returned. The read function blocks until at least + * 1 byte of data can be read, EOF is reached or an error occurs. The + * write function blocks until all data can be written or an error + * occurs. Note that this class is not thread-safe. + **/ +class SyncCryptoSocket +{ +private: + CryptoSocket::UP _socket; + SmartBuffer _buffer; + SyncCryptoSocket(CryptoSocket::UP socket) : _socket(std::move(socket)), _buffer(0) {} +public: + using UP = std::unique_ptr<SyncCryptoSocket>; + ~SyncCryptoSocket(); + ssize_t read(char *buf, size_t len); + ssize_t write(const char *buf, size_t len); + static UP create(CryptoEngine &engine, SocketHandle socket, bool is_server); +}; + +} // namespace vespalib |