Skip to content

Commit 452386c

Browse files
authored
Merge pull request #10005 from Icinga/graceful-tls-disconnect
Add a dedicated method for disconnecting TLS connections
2 parents 3642ca3 + a506d56 commit 452386c

File tree

6 files changed

+74
-42
lines changed

6 files changed

+74
-42
lines changed

lib/base/tlsstream.cpp

+64
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "base/logger.hpp"
88
#include "base/configuration.hpp"
99
#include "base/convert.hpp"
10+
#include "base/defer.hpp"
11+
#include "base/io-engine.hpp"
1012
#include <boost/asio/ssl/context.hpp>
1113
#include <boost/asio/ssl/verify_context.hpp>
1214
#include <boost/asio/ssl/verify_mode.hpp>
@@ -103,3 +105,65 @@ void UnbufferedAsioTlsStream::BeforeHandshake(handshake_type type)
103105
}
104106
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
105107
}
108+
109+
/**
110+
* Forcefully close the connection, typically (details are up to the operating system) using a TCP RST.
111+
*/
112+
void AsioTlsStream::ForceDisconnect()
113+
{
114+
if (!lowest_layer().is_open()) {
115+
// Already disconnected, nothing to do.
116+
return;
117+
}
118+
119+
boost::system::error_code ec;
120+
121+
// Close the socket. In case the connection wasn't shut down cleanly by GracefulDisconnect(), the operating system
122+
// will typically terminate the connection with a TCP RST. Otherwise, this just releases the file descriptor.
123+
lowest_layer().close(ec);
124+
}
125+
126+
/**
127+
* Try to cleanly shut down the connection. This involves sending a TLS close_notify shutdown alert and terminating the
128+
* underlying TCP connection. Sending these additional messages can block, hence the method takes a yield context and
129+
* internally implements a timeout of 10 seconds for the operation after which the connection is forcefully terminated
130+
* using ForceDisconnect().
131+
*
132+
* @param strand Asio strand used for other operations on this connection.
133+
* @param yc Yield context for Asio coroutines
134+
*/
135+
void AsioTlsStream::GracefulDisconnect(boost::asio::io_context::strand& strand, boost::asio::yield_context& yc)
136+
{
137+
if (!lowest_layer().is_open()) {
138+
// Already disconnected, nothing to do.
139+
return;
140+
}
141+
142+
{
143+
Timeout::Ptr shutdownTimeout(new Timeout(strand.context(), strand, boost::posix_time::seconds(10),
144+
[this](boost::asio::yield_context yc) {
145+
// Forcefully terminate the connection if async_shutdown() blocked more than 10 seconds.
146+
ForceDisconnect();
147+
}
148+
));
149+
Defer cancelTimeout ([&shutdownTimeout]() {
150+
shutdownTimeout->Cancel();
151+
});
152+
153+
// Close the TLS connection, effectively uses SSL_shutdown() to send a close_notify shutdown alert to the peer.
154+
boost::system::error_code ec;
155+
next_layer().async_shutdown(yc[ec]);
156+
}
157+
158+
if (!lowest_layer().is_open()) {
159+
// Connection got closed in the meantime, most likely by the timeout, so nothing more to do.
160+
return;
161+
}
162+
163+
// Shut down the TCP connection.
164+
boost::system::error_code ec;
165+
lowest_layer().shutdown(lowest_layer_type::shutdown_both, ec);
166+
167+
// Clean up the connection (closes the file descriptor).
168+
ForceDisconnect();
169+
}

lib/base/tlsstream.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ class AsioTlsStream : public boost::asio::buffered_stream<UnbufferedAsioTlsStrea
111111
{
112112
}
113113

114+
void ForceDisconnect();
115+
void GracefulDisconnect(boost::asio::io_context::strand& strand, boost::asio::yield_context& yc);
116+
114117
private:
115118
inline
116119
AsioTlsStream(UnbufferedAsioTlsStreamParams init)

lib/methods/ifwapichecktask.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ static void DoIfwNetIo(
102102
}
103103

104104
{
105+
// Using async_shutdown() instead of AsioTlsStream::GracefulDisconnect() as this whole function
106+
// is already guarded by a timeout based on the check timeout.
105107
boost::system::error_code ec;
106108
sslConn.async_shutdown(yc[ec]);
107109
}

lib/remote/apilistener.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,9 @@ void ApiListener::NewClientHandlerInternal(
719719
// Ignore the error, but do not throw an exception being swallowed at all cost.
720720
// https://github.com/Icinga/icinga2/issues/7351
721721
boost::system::error_code ec;
722+
723+
// Using async_shutdown() instead of AsioTlsStream::GracefulDisconnect() as this whole function
724+
// is already guarded by a timeout based on the connect timeout.
722725
sslConn.async_shutdown(yc[ec]);
723726
}
724727
});

lib/remote/httpserverconnection.cpp

+1-15
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,9 @@ void HttpServerConnection::Disconnect(boost::asio::yield_context yc)
8888
Log(LogInformation, "HttpServerConnection")
8989
<< "HTTP client disconnected (from " << m_PeerAddress << ")";
9090

91-
/*
92-
* Do not swallow exceptions in a coroutine.
93-
* https://github.com/Icinga/icinga2/issues/7351
94-
* We must not catch `detail::forced_unwind exception` as
95-
* this is used for unwinding the stack.
96-
*
97-
* Just use the error_code dummy here.
98-
*/
99-
boost::system::error_code ec;
100-
10191
m_CheckLivenessTimer.cancel();
10292

103-
m_Stream->lowest_layer().cancel(ec);
104-
105-
m_Stream->next_layer().async_shutdown(yc[ec]);
106-
107-
m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec);
93+
m_Stream->GracefulDisconnect(m_IoStrand, yc);
10894

10995
auto listener (ApiListener::GetInstance());
11096

lib/remote/jsonrpcconnection.cpp

+1-27
Original file line numberDiff line numberDiff line change
@@ -268,36 +268,10 @@ void JsonRpcConnection::Disconnect()
268268

269269
m_WriterDone.Wait(yc);
270270

271-
/*
272-
* Do not swallow exceptions in a coroutine.
273-
* https://github.com/Icinga/icinga2/issues/7351
274-
* We must not catch `detail::forced_unwind exception` as
275-
* this is used for unwinding the stack.
276-
*
277-
* Just use the error_code dummy here.
278-
*/
279-
boost::system::error_code ec;
280-
281271
m_CheckLivenessTimer.cancel();
282272
m_HeartbeatTimer.cancel();
283273

284-
m_Stream->lowest_layer().cancel(ec);
285-
286-
Timeout::Ptr shutdownTimeout (new Timeout(
287-
m_IoStrand.context(),
288-
m_IoStrand,
289-
boost::posix_time::seconds(10),
290-
[this, keepAlive](asio::yield_context yc) {
291-
boost::system::error_code ec;
292-
m_Stream->lowest_layer().cancel(ec);
293-
}
294-
));
295-
296-
m_Stream->next_layer().async_shutdown(yc[ec]);
297-
298-
shutdownTimeout->Cancel();
299-
300-
m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec);
274+
m_Stream->GracefulDisconnect(m_IoStrand, yc);
301275
});
302276
}
303277
}

0 commit comments

Comments
 (0)