aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHåvard Pettersen <havardpe@oath.com>2018-10-17 13:16:18 +0000
committerHåvard Pettersen <havardpe@oath.com>2018-10-19 10:19:19 +0000
commita50c6e8b47c6d6e1e7f82dfced1b3cd8f9fc87cd (patch)
tree2c5a9d60b99af15fadb741b3f0ed391eef3e05e7
parent1700fa27f85166c25af98316c4a695dd38c80ff8 (diff)
half_close for crypto sockets
-rw-r--r--vespalib/src/tests/net/crypto_socket/crypto_socket_test.cpp55
-rw-r--r--vespalib/src/vespa/vespalib/net/crypto_engine.cpp11
-rw-r--r--vespalib/src/vespa/vespalib/net/crypto_socket.h37
-rw-r--r--vespalib/src/vespa/vespalib/net/socket_handle.cpp6
-rw-r--r--vespalib/src/vespa/vespalib/net/socket_handle.h1
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp35
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h6
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.cpp1
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.h1
9 files changed, 149 insertions, 4 deletions
diff --git a/vespalib/src/tests/net/crypto_socket/crypto_socket_test.cpp b/vespalib/src/tests/net/crypto_socket/crypto_socket_test.cpp
index 91babc51476..67077f86f1e 100644
--- a/vespalib/src/tests/net/crypto_socket/crypto_socket_test.cpp
+++ b/vespalib/src/tests/net/crypto_socket/crypto_socket_test.cpp
@@ -76,6 +76,11 @@ void flush(CryptoSocket &socket) {
ASSERT_TRUE((res == 0) || is_blocked(res));
}
+void half_close(CryptoSocket &socket) {
+ auto res = socket.half_close();
+ ASSERT_TRUE((res == 0) || is_blocked(res));
+}
+
//-----------------------------------------------------------------------------
vespalib::string read_bytes(CryptoSocket &socket, SmartBuffer &read_buffer, size_t wanted_bytes) {
@@ -86,7 +91,25 @@ vespalib::string read_bytes(CryptoSocket &socket, SmartBuffer &read_buffer, size
drain(socket, read_buffer);
}
auto data = read_buffer.obtain();
- return vespalib::string(data.data, wanted_bytes);
+ vespalib::string message(data.data, wanted_bytes);
+ read_buffer.evict(message.size());
+ return message;
+}
+
+//-----------------------------------------------------------------------------
+
+void read_EOF(CryptoSocket &socket, SmartBuffer &read_buffer) {
+ ASSERT_EQUAL(read_buffer.obtain().size, 0u);
+ SingleFdSelector selector(socket.get_fd());
+ ASSERT_TRUE(selector.wait_readable());
+ size_t chunk_size = std::max(size_t(4096), socket.min_read_buffer_size());
+ auto chunk = read_buffer.reserve(chunk_size);
+ auto res = socket.read(chunk.data, chunk.size);
+ while (is_blocked(res)) {
+ ASSERT_TRUE(selector.wait_readable());
+ res = socket.read(chunk.data, chunk.size);
+ }
+ ASSERT_EQUAL(res, 0);
}
//-----------------------------------------------------------------------------
@@ -106,6 +129,35 @@ void write_bytes(CryptoSocket &socket, const vespalib::string &message) {
//-----------------------------------------------------------------------------
+void write_EOF(CryptoSocket &socket) {
+ SingleFdSelector selector(socket.get_fd());
+ ASSERT_TRUE(selector.wait_writable());
+ auto res = socket.half_close();
+ while (is_blocked(res)) {
+ ASSERT_TRUE(selector.wait_writable());
+ res = socket.half_close();
+ }
+ ASSERT_EQUAL(res, 0);
+}
+
+//-----------------------------------------------------------------------------
+
+void verify_graceful_shutdown(CryptoSocket &socket, SmartBuffer &read_buffer, bool is_server) {
+ if(is_server) {
+ TEST_DO(write_EOF(socket));
+ TEST_DO(read_EOF(socket, read_buffer));
+ TEST_DO(read_EOF(socket, read_buffer));
+ TEST_DO(read_EOF(socket, read_buffer));
+ } else {
+ TEST_DO(read_EOF(socket, read_buffer));
+ TEST_DO(read_EOF(socket, read_buffer));
+ TEST_DO(read_EOF(socket, read_buffer));
+ TEST_DO(write_EOF(socket));
+ }
+}
+
+//-----------------------------------------------------------------------------
+
void verify_socket_io(CryptoSocket &socket, SmartBuffer &read_buffer, 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";
@@ -153,6 +205,7 @@ void verify_crypto_socket(SocketPair &sockets, CryptoEngine &engine, bool is_ser
TEST_DO(verify_handshake(*my_socket));
drain(*my_socket, read_buffer);
TEST_DO(verify_socket_io(*my_socket, read_buffer, is_server));
+ TEST_DO(verify_graceful_shutdown(*my_socket, read_buffer, is_server));
}
//-----------------------------------------------------------------------------
diff --git a/vespalib/src/vespa/vespalib/net/crypto_engine.cpp b/vespalib/src/vespa/vespalib/net/crypto_engine.cpp
index d45b0cea1c5..cdce2d41bf6 100644
--- a/vespalib/src/vespa/vespalib/net/crypto_engine.cpp
+++ b/vespalib/src/vespa/vespalib/net/crypto_engine.cpp
@@ -46,6 +46,7 @@ public:
ssize_t drain(char *, size_t) override { return 0; }
ssize_t write(const char *buf, size_t len) override { return _socket.write(buf, len); }
ssize_t flush() override { return 0; }
+ ssize_t half_close() override { return _socket.half_close(); }
};
class XorCryptoSocket : public CryptoSocket
@@ -168,6 +169,16 @@ public:
}
return 0; // done
}
+ ssize_t half_close() override {
+ auto flush_res = flush();
+ while (flush_res > 0) {
+ flush_res = flush();
+ }
+ if (flush_res < 0) {
+ return flush_res;
+ }
+ return _socket.half_close();
+ }
};
CryptoEngine::SP create_default_crypto_engine() {
diff --git a/vespalib/src/vespa/vespalib/net/crypto_socket.h b/vespalib/src/vespa/vespalib/net/crypto_socket.h
index fa76acbc2cd..3a95cf33add 100644
--- a/vespalib/src/vespa/vespalib/net/crypto_socket.h
+++ b/vespalib/src/vespa/vespalib/net/crypto_socket.h
@@ -88,6 +88,43 @@ struct CryptoSocket {
**/
virtual ssize_t flush() = 0;
+ /**
+ * Signal the end of outgoing data. Note that this might require
+ * writing data to the underlying socket to notify the client that
+ * no more data will be sent. This function should be treated as a
+ * combination of write and flush and should be re-tried after the
+ * socket becomes writable if EWOULDBLOCK is returned. Neither
+ * write nor flush should be called after this function is
+ * called. When this function indicates success (returns 0) all
+ * pending data has been written to the underlying socket and the
+ * write aspect of the socket has been shut down. Performing
+ * half_close on one end of a connection will eventually lead to
+ * the other end receiving EOF after all application data has been
+ * read. Note that closing the socket immediately after performing
+ * half_close might still result in data loss since there is no
+ * way of knowing when the data has actually been sent on the
+ * network.
+ *
+ * Ideal graceful shutdown is initiated by one end performing
+ * half_close on the connection. When the other end receives EOF
+ * it performs half_close on its end of the connection. When both
+ * ends have received EOF the sockets can be closed. The ideal
+ * scenario is broken by two things: (1) the two generals paradox,
+ * which proves that both endpoints coming to an agreement about
+ * the connection being gracefully shut down is not possible. (2)
+ * clients tend to do random things with the connection, leaving
+ * it up to the server to be the more responsible party.
+ *
+ * Real-life graceful-ish shutdown (server-side) should be
+ * performed by doing half_close on the server end of the
+ * connection. Any incoming data should be read in the hope of
+ * getting EOF. The socket should be closed when either EOF is
+ * reached, a read error occurred (typically connection reset by
+ * peer or similar) or a timeout is reached (application
+ * equivalent of the linger option on the socket).
+ **/
+ virtual ssize_t half_close() = 0;
+
virtual ~CryptoSocket();
};
diff --git a/vespalib/src/vespa/vespalib/net/socket_handle.cpp b/vespalib/src/vespa/vespalib/net/socket_handle.cpp
index 36eae46031f..aaaad1624bb 100644
--- a/vespalib/src/vespa/vespalib/net/socket_handle.cpp
+++ b/vespalib/src/vespa/vespalib/net/socket_handle.cpp
@@ -47,6 +47,12 @@ SocketHandle::shutdown()
}
int
+SocketHandle::half_close()
+{
+ return ::shutdown(_fd, SHUT_WR);
+}
+
+int
SocketHandle::get_so_error() const
{
if (!valid()) {
diff --git a/vespalib/src/vespa/vespalib/net/socket_handle.h b/vespalib/src/vespa/vespalib/net/socket_handle.h
index 1d942d8530f..0bf3f6b43c7 100644
--- a/vespalib/src/vespa/vespalib/net/socket_handle.h
+++ b/vespalib/src/vespa/vespalib/net/socket_handle.h
@@ -59,6 +59,7 @@ public:
ssize_t write(const char *buf, size_t len);
SocketHandle accept();
void shutdown();
+ int half_close();
int get_so_error() const;
};
diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp
index 28aede69d22..569cce6db7b 100644
--- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp
@@ -96,15 +96,19 @@ ssize_t
CryptoCodecAdapter::read(char *buf, size_t len)
{
auto drain_res = drain(buf, len);
- if (drain_res != 0) {
+ if ((drain_res != 0) || _got_tls_close) {
return drain_res;
}
auto fill_res = fill_input();
if (fill_res <= 0) {
+ if (fill_res == 0) {
+ fill_res = -1;
+ errno = EIO;
+ }
return fill_res;
}
drain_res = drain(buf, len);
- if (drain_res != 0) {
+ if ((drain_res != 0) || _got_tls_close) {
return drain_res;
}
errno = EWOULDBLOCK;
@@ -120,6 +124,9 @@ CryptoCodecAdapter::drain(char *buf, size_t len)
errno = EIO;
return -1;
}
+ if (res.closed()) {
+ _got_tls_close = true;
+ }
_input.evict(res.bytes_consumed);
return res.bytes_produced;
}
@@ -163,4 +170,28 @@ CryptoCodecAdapter::flush()
return 0; // done
}
+ssize_t
+CryptoCodecAdapter::half_close()
+{
+ auto flush_res = flush_all();
+ if ((flush_res < 0)) {
+ return flush_res;
+ }
+ if (!_encoded_tls_close) {
+ auto dst = _output.reserve(_codec->min_encode_buffer_size());
+ auto res = _codec->half_close(dst.data, dst.size);
+ if (res.failed) {
+ errno = EIO;
+ return -1;
+ }
+ _output.commit(res.bytes_produced);
+ _encoded_tls_close = true;
+ }
+ flush_res = flush_all();
+ if ((flush_res < 0)) {
+ return flush_res;
+ }
+ return _socket.half_close();
+}
+
} // namespace vespalib::net::tls
diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h
index af2db3525d3..6af04d67d41 100644
--- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h
+++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h
@@ -20,6 +20,8 @@ private:
SmartBuffer _output;
SocketHandle _socket;
std::unique_ptr<CryptoCodec> _codec;
+ bool _got_tls_close;
+ bool _encoded_tls_close;
bool is_blocked(ssize_t res, int error) const {
return ((res < 0) && ((error == EWOULDBLOCK) || (error == EAGAIN)));
@@ -30,7 +32,8 @@ private:
ssize_t flush_all(); // -1/0 -> error/ok
public:
CryptoCodecAdapter(SocketHandle socket, std::unique_ptr<CryptoCodec> codec)
- : _input(64 * 1024), _output(64 * 1024), _socket(std::move(socket)), _codec(std::move(codec)) {}
+ : _input(64 * 1024), _output(64 * 1024), _socket(std::move(socket)), _codec(std::move(codec)),
+ _got_tls_close(false), _encoded_tls_close(false) {}
void inject_read_data(const char *buf, size_t len) override;
int get_fd() const override { return _socket.get(); }
HandshakeResult handshake() override;
@@ -39,6 +42,7 @@ public:
ssize_t drain(char *, size_t) override;
ssize_t write(const char *buf, size_t len) override;
ssize_t flush() override;
+ ssize_t half_close() override;
};
} // namespace vespalib::net::tls
diff --git a/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.cpp b/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.cpp
index 1e8fbce13eb..cd5c4114844 100644
--- a/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.cpp
@@ -78,6 +78,7 @@ public:
}
ssize_t write(const char *buf, size_t len) override { return _socket.write(buf, len); }
ssize_t flush() override { return 0; }
+ ssize_t half_close() override { return _socket.half_close(); }
};
} // namespace vespalib::<unnamed>
diff --git a/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.h b/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.h
index 3c7963cc948..9d3a190829a 100644
--- a/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.h
+++ b/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_socket.h
@@ -30,6 +30,7 @@ public:
ssize_t drain(char *buf, size_t len) override { return _socket->drain(buf, len); }
ssize_t write(const char *buf, size_t len) override { return _socket->write(buf, len); }
ssize_t flush() override { return _socket->flush(); }
+ ssize_t half_close() override { return _socket->half_close(); }
};
} // namespace vespalib