summaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorHåvard Pettersen <havardpe@oath.com>2018-10-09 13:52:35 +0000
committerHåvard Pettersen <havardpe@oath.com>2018-10-09 13:52:35 +0000
commit765b932a3368297044c349050d11f3f57188b1bd (patch)
tree5b65e1ba4758575c6aaae4413b2265d9eee10350 /vespalib
parent58850f92f00b6289ff5feccf3634a34943e760d2 (diff)
added sync crypto socket with test
Diffstat (limited to 'vespalib')
-rw-r--r--vespalib/CMakeLists.txt1
-rw-r--r--vespalib/src/tests/net/sync_crypto_socket/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/net/sync_crypto_socket/sync_crypto_socket_test.cpp110
-rw-r--r--vespalib/src/vespa/vespalib/net/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/net/crypto_socket.h7
-rw-r--r--vespalib/src/vespa/vespalib/net/sync_crypto_socket.cpp99
-rw-r--r--vespalib/src/vespa/vespalib/net/sync_crypto_socket.h34
7 files changed, 257 insertions, 3 deletions
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index 264f274db15..08df6bb2c15 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -59,6 +59,7 @@ vespa_define_module(
src/tests/net/send_fd
src/tests/net/socket
src/tests/net/socket_spec
+ src/tests/net/sync_crypto_socket
src/tests/net/tls/direct_buffer_bio
src/tests/net/tls/openssl_impl
src/tests/net/tls/protocol_snooping
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