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:
parent
d5eeb78ed9
commit
f33fe76658
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user