0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00

[UCONNECT-1027] perform async DNS resolution in a detached thread

When ASIO performs an async DNS resolution, it relies on the
getaddrinfo() syscall in order to obtain a result.

This syscall is non-interruptible by design, which means that, in case
of sudden stop command received by the user, the core will not be able
to terminate all its threads until the getaddrinfo() has returned
(either by timeout or with a result).

If the the external core user is synchronously waiting for it to
terminate (i.e. like a UI), this behaviour will lead to the entire
client hanging.

To avoid this issue, this commit converts each asynchronous DNS
resolution to a synchrnous one performed in a detached thread.

This way, if the core wants to stop, it can do so without waiting for
the DNS thread to join. Otherwise, this change should not lead to any
functional difference.

Signed-off-by: Yuriy Barnovych <yuriy@openvpn.net>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
This commit is contained in:
Antonio Quartulli 2018-11-27 18:27:57 +10:00
parent d5eeb78ed9
commit f33fe76658
No known key found for this signature in database
GPG Key ID: F4556C5945830E6D
5 changed files with 109 additions and 53 deletions

View File

@ -33,6 +33,7 @@
#include <utility>
#include <openvpn/io/io.hpp>
#include <openvpn/asio/asiowork.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/rc.hpp>
@ -53,6 +54,79 @@
#endif
namespace openvpn {
template<typename RESOLVER_TYPE>
class AsyncResolvable: public virtual RC<thread_unsafe_refcount>
{
private:
typedef RCPtr<AsyncResolvable> Ptr;
openvpn_io::io_context& io_context;
std::unique_ptr<AsioWork> asio_work;
public:
AsyncResolvable(openvpn_io::io_context& io_context_arg)
: io_context(io_context_arg)
{
}
virtual void resolve_callback(const openvpn_io::error_code& error,
typename RESOLVER_TYPE::results_type results) = 0;
// mimic the asynchronous DNS resolution by performing a
// synchronous one in a detached thread.
//
// This strategy has the advantage of allowing the core to
// stop/exit without waiting for the getaddrinfo() (used
// internally) to terminate.
// Note: getaddrinfo() is non-interruptible by design.
//
// In other words, we are re-creating exactly what ASIO would
// normally do in case of async_resolve(), with the difference
// that here we have control over the resolving thread and we
// can easily detach it. Deatching the internal thread created
// by ASIO would not be feasible as it is not exposed.
void async_resolve_name(const std::string& host, const std::string& port)
{
// there might be nothing else in the main io_context queue
// right now, therefore we use AsioWork to prevent the loop
// from exiting while we perform the DNS resolution in the
// detached thread.
asio_work.reset(new AsioWork(io_context));
std::thread resolve_thread([self=Ptr(this), host, port]() {
openvpn_io::io_context io_context(1);
openvpn_io::error_code error;
RESOLVER_TYPE resolver(io_context);
typename RESOLVER_TYPE::results_type results;
results = resolver.resolve(host, port, error);
openvpn_io::post(self->io_context, [self, results, error]() {
OPENVPN_ASYNC_HANDLER;
self->resolve_callback(error, results);
});
// the AsioWork can be released now that we have posted
// something else to the main io_context queue
self->asio_work.reset();
});
// detach the thread so that the client won't need to wait for
// it to join.
resolve_thread.detach();
}
// to be called by the child class when the core wants to stop
// and we don't need to wait for the detached thread any longer.
// It simulates a resolve abort
void async_resolve_cancel()
{
asio_work.reset();
}
};
typedef AsyncResolvable<openvpn_io::ip::udp::resolver> AsyncResolvableUDP;
typedef AsyncResolvable<openvpn_io::ip::tcp::resolver> AsyncResolvableTCP;
class RemoteList : public RC<thread_unsafe_refcount>
{
@ -262,7 +336,7 @@ namespace openvpn {
// This is useful in tun_persist mode, where it may be necessary
// to pre-resolve all potential remote server items prior
// to initial tunnel establishment.
class PreResolve : public RC<thread_unsafe_refcount>
class PreResolve : public virtual RC<thread_unsafe_refcount>, AsyncResolvableTCP
{
public:
typedef RCPtr<PreResolve> Ptr;
@ -276,7 +350,7 @@ namespace openvpn {
PreResolve(openvpn_io::io_context& io_context_arg,
const RemoteList::Ptr& remote_list_arg,
const SessionStats::Ptr& stats_arg)
: resolver(io_context_arg),
: AsyncResolvableTCP(io_context_arg),
notify_callback(nullptr),
remote_list(remote_list_arg),
stats(stats_arg),
@ -312,7 +386,7 @@ namespace openvpn {
{
notify_callback = nullptr;
index = 0;
resolver.cancel();
async_resolve_cancel();
}
private:
@ -335,14 +409,8 @@ namespace openvpn {
}
else
{
// call into Asio to do the resolve operation
OPENVPN_LOG_REMOTELIST("*** PreResolve RESOLVE on " << item.server_host << " : " << item.server_port);
resolver.async_resolve(item.server_host, item.server_port,
[self=Ptr(this)](const openvpn_io::error_code& error, openvpn_io::ip::tcp::resolver::results_type results)
{
OPENVPN_ASYNC_HANDLER;
self->resolve_callback(error, results);
});
async_resolve_name(item.server_host, item.server_port);
return;
}
}
@ -363,7 +431,7 @@ namespace openvpn {
// callback on resolve completion
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::tcp::resolver::results_type results)
openvpn_io::ip::tcp::resolver::results_type results) override
{
if (notify_callback && index < remote_list->list.size())
{
@ -384,7 +452,6 @@ namespace openvpn {
}
}
openvpn_io::ip::tcp::resolver resolver;
NotifyCallback* notify_callback;
RemoteList::Ptr remote_list;
SessionStats::Ptr stats;

View File

@ -117,7 +117,8 @@ namespace openvpn {
class Client : public TransportClient,
public TunClient,
public KoRekey::Receiver,
public SessionStats::DCOTransportSource
public SessionStats::DCOTransportSource,
public AsyncResolvableUDP
{
friend class ClientConfig;
@ -473,7 +474,8 @@ namespace openvpn {
Client(openvpn_io::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent* parent_arg)
: io_context(io_context_arg),
: AsyncResolvableUDP(io_context),
io_context(io_context_arg),
halt(false),
state(new TunProp::State()),
config(config_arg),
@ -498,17 +500,13 @@ namespace openvpn {
else
{
transport_parent->transport_pre_resolve();
udp().resolver.async_resolve(server_host, server_port,
[self=Ptr(this)](const openvpn_io::error_code& error, openvpn_io::ip::udp::resolver::results_type results)
{
self->do_resolve_udp(error, results);
});
async_resolve_name(server_host, server_port);
}
}
// called after DNS resolution has succeeded or failed
void do_resolve_udp(const openvpn_io::error_code& error,
openvpn_io::ip::udp::resolver::results_type results)
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::udp::resolver::results_type results)
{
if (!halt)
{

View File

@ -209,7 +209,7 @@ namespace openvpn {
{}
};
class Client : public TransportClient
class Client : public TransportClient, AsyncResolvableTCP
{
typedef RCPtr<Client> Ptr;
@ -245,12 +245,8 @@ namespace openvpn {
{
// resolve it
parent->transport_pre_resolve();
resolver.async_resolve(proxy_host, proxy_port,
[self=Ptr(this)](const openvpn_io::error_code& error, openvpn_io::ip::tcp::resolver::results_type results)
{
OPENVPN_ASYNC_HANDLER;
self->do_resolve_(error, results);
});
async_resolve_name(proxy_host, proxy_port);
}
}
}
@ -340,7 +336,7 @@ namespace openvpn {
Client(openvpn_io::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent* parent_arg)
: io_context(io_context_arg),
: AsyncResolvableTCP(io_context_arg),
socket(io_context_arg),
config(config_arg),
parent(parent_arg),
@ -860,12 +856,13 @@ namespace openvpn {
socket.close();
resolver.cancel();
async_resolve_cancel();
}
}
// do DNS resolve
void do_resolve_(const openvpn_io::error_code& error,
openvpn_io::ip::tcp::resolver::results_type results)
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::tcp::resolver::results_type results) override
{
if (!halt)
{
@ -1008,7 +1005,6 @@ namespace openvpn {
std::string server_host;
std::string server_port;
openvpn_io::io_context& io_context;
openvpn_io::ip::tcp::socket socket;
ClientConfig::Ptr config;
TransportClientParent* parent;

View File

@ -74,7 +74,7 @@ namespace openvpn {
{}
};
class Client : public TransportClient
class Client : public TransportClient, AsyncResolvableTCP
{
typedef RCPtr<Client> Ptr;
@ -102,12 +102,8 @@ namespace openvpn {
else
{
parent->transport_pre_resolve();
resolver.async_resolve(server_host, server_port,
[self=Ptr(this)](const openvpn_io::error_code& error, openvpn_io::ip::tcp::resolver::results_type results)
{
OPENVPN_ASYNC_HANDLER;
self->do_resolve_(error, results);
});
async_resolve_name(server_host, server_port);
}
}
}
@ -175,7 +171,8 @@ namespace openvpn {
Client(openvpn_io::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent* parent_arg)
: io_context(io_context_arg),
: AsyncResolvableTCP(io_context_arg),
io_context(io_context_arg),
socket(io_context_arg),
config(config_arg),
parent(parent_arg),
@ -249,12 +246,13 @@ namespace openvpn {
socket.close();
resolver.cancel();
async_resolve_cancel();
}
}
// do DNS resolve
void do_resolve_(const openvpn_io::error_code& error,
openvpn_io::ip::tcp::resolver::results_type results)
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::tcp::resolver::results_type results) override
{
if (!halt)
{

View File

@ -74,7 +74,7 @@ namespace openvpn {
{}
};
class Client : public TransportClient
class Client : public TransportClient, AsyncResolvableUDP
{
typedef RCPtr<Client> Ptr;
@ -101,16 +101,11 @@ namespace openvpn {
{
openvpn_io::error_code error;
openvpn_io::ip::udp::resolver::results_type results = resolver.resolve(server_host, server_port, error);
do_resolve_(error, results);
resolve_callback(error, results);
}
else
{
resolver.async_resolve(server_host, server_port,
[self=Ptr(this)](const openvpn_io::error_code& error, openvpn_io::ip::udp::resolver::results_type results)
{
OPENVPN_ASYNC_HANDLER;
self->do_resolve_(error, results);
});
async_resolve_name(server_host, server_port);
}
}
}
@ -181,7 +176,8 @@ namespace openvpn {
Client(openvpn_io::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent* parent_arg)
: socket(io_context_arg),
: AsyncResolvableUDP(io_context_arg),
socket(io_context_arg),
config(config_arg),
parent(parent_arg),
resolver(io_context_arg),
@ -236,12 +232,13 @@ namespace openvpn {
impl->stop();
socket.close();
resolver.cancel();
async_resolve_cancel();
}
}
// called after DNS resolution has succeeded or failed
void do_resolve_(const openvpn_io::error_code& error,
openvpn_io::ip::udp::resolver::results_type results)
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::udp::resolver::results_type results) override
{
if (!halt)
{