Win: support for local DNS resolvers

Local DNS resolvers, such as Umbrella Roaming Client,
change DNS settings on adapters to 127.0.0.1.

This may not work with openvpn3 because:

 - NRPT rule might be created for "." zone,
which redirects all DNS requests to the server
specified in rule. This takes precendence over adapters'
DNS settings.

 - DNS requests might be blocked on all adapters
except TAP (tap-windows6/wintun/ovpn-dco-win) to prevent
DNS leaks.

To enable compatibility with local DNS resolvers, add
"allowLocalDnsResolvers" core config option, which,
when enabled, makes core to

 - avoid creating NRPT rule for "." zone

 - permit DNS requests to 127.0.0.1 / ::1

Signed-off-by: Lev Stipakov <lev@openvpn.net>
This commit is contained in:
Lev Stipakov
2021-08-02 11:58:16 +03:00
parent 2f28336749
commit 613aa6bf7a
10 changed files with 86 additions and 23 deletions

View File

@@ -437,6 +437,7 @@ namespace openvpn {
int conn_timeout = 0;
bool tun_persist = false;
bool wintun = false;
bool allow_local_dns_resolvers = false;
bool google_dns_fallback = false;
bool synchronous_dns_lookup = false;
bool autologin_sessions = false;
@@ -682,6 +683,7 @@ namespace openvpn {
state->conn_timeout = config.connTimeout;
state->tun_persist = config.tunPersist;
state->wintun = config.wintun;
state->allow_local_dns_resolvers = config.allowLocalDnsResolvers;
state->google_dns_fallback = config.googleDnsFallback;
state->synchronous_dns_lookup = config.synchronousDnsLookup;
state->autologin_sessions = config.autologinSessions;
@@ -964,6 +966,7 @@ namespace openvpn {
cc.conn_timeout = state->conn_timeout;
cc.tun_persist = state->tun_persist;
cc.wintun = state->wintun;
cc.allow_local_dns_resolvers = state->allow_local_dns_resolvers;
cc.google_dns_fallback = state->google_dns_fallback;
cc.synchronous_dns_lookup = state->synchronous_dns_lookup;
cc.autologin_sessions = state->autologin_sessions;

View File

@@ -320,6 +320,10 @@ namespace openvpn {
// Use wintun instead of tap-windows6 on Windows
bool wintun = false;
// On Windows allow DNS resolvers on localhost, such as Umbrella Roaming Client
// This disables adding NRPT rule for "." zone and permits DNS requests to localhost
bool allowLocalDnsResolvers = false;
};
// used to communicate VPN events such as connect, disconnect, etc.

View File

@@ -138,6 +138,7 @@ namespace openvpn {
bool info = false;
bool tun_persist = false;
bool wintun = false;
bool allow_local_dns_resolvers = false;
bool google_dns_fallback = false;
bool synchronous_dns_lookup = false;
std::string private_key_password;
@@ -441,6 +442,7 @@ namespace openvpn {
tunconf->stats = cli_stats;
tunconf->stop = config.stop;
tunconf->wintun = config.wintun;
tunconf->allow_local_dns_resolvers = config.allow_local_dns_resolvers;
if (config.tun_persist)
{
tunconf->tun_persist.reset(new TunWin::TunPersist(true, false, nullptr));

View File

@@ -100,6 +100,7 @@ namespace openvpn {
std::string client_exe; // for validation
int debug_level;
bool wintun;
bool allow_local_dns_resolvers = false;
};
class SetupClient : public TunWin::SetupBase
@@ -174,6 +175,7 @@ namespace openvpn {
ring_buffer->serialize(jreq);
jreq["wintun"] = config->wintun;
jreq["allow_local_dns_resolvers"] = config->allow_local_dns_resolvers;
jreq["confirm_event"] = confirm_event.duplicate_local();
jreq["destroy_event"] = destroy_event.duplicate_local();
jreq["tun"] = pull.to_json(); // convert TunBuilderCapture to JSON
@@ -294,15 +296,16 @@ namespace openvpn {
Win::DestroyEvent destroy_event;
};
virtual TunWin::SetupBase::Ptr new_setup_obj(openvpn_io::io_context& io_context, bool wintun) override
virtual TunWin::SetupBase::Ptr new_setup_obj(openvpn_io::io_context& io_context, bool wintun, bool allow_local_dns_resolvers) override
{
if (config)
{
config->wintun = wintun;
config->allow_local_dns_resolvers = allow_local_dns_resolvers;
return new SetupClient(io_context, config);
}
else
return new TunWin::Setup(io_context, wintun);
return new TunWin::Setup(io_context, wintun, allow_local_dns_resolvers);
}
WinCommandAgent(const OptionList& opt_parent)

View File

@@ -139,10 +139,11 @@ public:
const std::wstring& openvpn_app_path,
Stop* stop,
std::ostream& os,
bool wintun)
bool wintun,
bool allow_local_dns_resolvers)
{
if (!tun)
tun.reset(new TunWin::Setup(io_context_, wintun));
tun.reset(new TunWin::Setup(io_context_, wintun, allow_local_dns_resolvers));
auto th = tun->establish(tbc, openvpn_app_path, stop, os, ring_buffer);
// store VPN interface index to be able to exclude it
// when next time adding bypass route
@@ -578,6 +579,8 @@ private:
bool wintun = json::get_bool_optional(root, "wintun");
bool allow_local_dns_resolvers = json::get_bool_optional(root, "allow_local_dns_resolvers");
// get remote event handles for tun object confirmation/destruction
const std::string confirm_event_hex = json::get_string(root, "confirm_event");
const std::string destroy_event_hex = json::get_string(root, "destroy_event");
@@ -616,7 +619,7 @@ private:
}
// establish the tun setup object
Win::ScopedHANDLE tap_handle(parent()->establish_tun(*tbc, client_exe, nullptr, os, wintun));
Win::ScopedHANDLE tap_handle(parent()->establish_tun(*tbc, client_exe, nullptr, os, wintun, allow_local_dns_resolvers));
// post-establish impersonation
{

View File

@@ -56,6 +56,7 @@ namespace openvpn {
TunProp::Config tun_prop;
int n_parallel = 8; // number of parallel async reads on tun socket
bool wintun = false;
bool allow_local_dns_resolvers = false;
Frame::Ptr frame;
SessionStats::Ptr stats;
@@ -69,9 +70,9 @@ namespace openvpn {
TunWin::SetupBase::Ptr new_setup_obj(openvpn_io::io_context& io_context)
{
if (tun_setup_factory)
return tun_setup_factory->new_setup_obj(io_context, wintun);
return tun_setup_factory->new_setup_obj(io_context, wintun, allow_local_dns_resolvers);
else
return new TunWin::Setup(io_context, wintun);
return new TunWin::Setup(io_context, wintun, allow_local_dns_resolvers);
}
static Ptr new_obj()

View File

@@ -70,7 +70,7 @@ namespace openvpn {
{
typedef RCPtr<SetupFactory> Ptr;
virtual SetupBase::Ptr new_setup_obj(openvpn_io::io_context& io_context, bool wintun) = 0;
virtual SetupBase::Ptr new_setup_obj(openvpn_io::io_context& io_context, bool wintun, bool allow_local_dns_resolvers) = 0;
};
}
}

View File

@@ -68,9 +68,10 @@ namespace openvpn {
public:
typedef RCPtr<Setup> Ptr;
Setup(openvpn_io::io_context& io_context_arg, bool wintun_arg=false)
Setup(openvpn_io::io_context& io_context_arg, bool wintun_arg, bool allow_local_dns_resolvers_arg)
: delete_route_timer(io_context_arg),
wintun(wintun_arg) {}
wintun(wintun_arg),
allow_local_dns_resolvers(allow_local_dns_resolvers_arg) {}
// Set up the TAP device
virtual HANDLE establish(const TunBuilderCapture& pull,
@@ -636,7 +637,7 @@ namespace openvpn {
}
}
}
if (dsfx.empty())
if (dsfx.empty() && !allow_local_dns_resolvers)
dsfx.emplace_back(".");
// DNS server list
@@ -653,8 +654,8 @@ namespace openvpn {
// the TAP adapter.
if (use_wfp && !split_dns && !openvpn_app_path.empty() && (dns.ipv4() || dns.ipv6()))
{
create.add(new ActionWFP(openvpn_app_path, tap.index, true, wfp));
destroy.add(new ActionWFP(openvpn_app_path, tap.index, false, wfp));
create.add(new ActionWFP(openvpn_app_path, tap.index, true, allow_local_dns_resolvers ,wfp));
destroy.add(new ActionWFP(openvpn_app_path, tap.index, false, allow_local_dns_resolvers, wfp));
}
}
@@ -945,6 +946,8 @@ namespace openvpn {
AsioTimer delete_route_timer;
bool wintun = false;
bool allow_local_dns_resolvers = false;
};
}
}

View File

@@ -121,6 +121,7 @@ namespace openvpn {
// Derived from https://github.com/ValdikSS/openvpn-with-patches/commit/3bd4d503d21aa34636e4f97b3e32ae0acca407f0
void block_dns(const std::wstring& openvpn_app_path,
const NET_IFINDEX tap_index,
const bool allow_local_dns_resolvers,
std::ostream& log)
{
// WFP filter/conditions
@@ -156,7 +157,40 @@ namespace openvpn {
filter.weight.uint8 = 0xF;
filter.filterCondition = condition;
// Filter #1 -- permit IPv4 DNS requests from OpenVPN app
if (allow_local_dns_resolvers)
{
// Filter #1 -- permit IPv4 DNS requests to 127.0.0.1
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
filter.action.type = FWP_ACTION_PERMIT;
filter.numFilterConditions = 2;
condition[0].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
condition[0].matchType = FWP_MATCH_EQUAL;
condition[0].conditionValue.type = FWP_UINT16;
condition[0].conditionValue.uint16 = 53;
UINT8 localhost[4] = {1, 0, 0, 127};
condition[1].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
condition[1].matchType = FWP_MATCH_EQUAL;
condition[1].conditionValue.type = FWP_UINT32;
condition[1].conditionValue.uint32 = *(UINT32 *)localhost;
add_filter(&filter, NULL, &filterid);
log << "permit IPv4 DNS requests to 127.0.0.1" << std::endl;
// Filter #2 -- permit IPv6 DNS requests to ::1
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
UINT8 localhostv6[16] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
condition[1].conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
condition[1].conditionValue.byteArray16 = (FWP_BYTE_ARRAY16*)localhostv6;
add_filter(&filter, NULL, &filterid);
log << "permit IPv6 DNS requests to ::1" << std::endl;
}
// Filter #3 -- permit IPv4 DNS requests from OpenVPN app
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
filter.action.type = FWP_ACTION_PERMIT;
filter.numFilterConditions = 2;
@@ -174,13 +208,13 @@ namespace openvpn {
add_filter(&filter, NULL, &filterid);
log << "permit IPv4 DNS requests from OpenVPN app" << std::endl;
// Filter #2 -- permit IPv6 DNS requests from OpenVPN app
// Filter #4 -- permit IPv6 DNS requests from OpenVPN app
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
add_filter(&filter, NULL, &filterid);
log << "permit IPv6 DNS requests from OpenVPN app" << std::endl;
// Filter #3 -- block IPv4 DNS requests from other apps
// Filter #5 -- block IPv4 DNS requests from other apps
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
filter.action.type = FWP_ACTION_BLOCK;
filter.weight.type = FWP_EMPTY;
@@ -189,13 +223,13 @@ namespace openvpn {
add_filter(&filter, NULL, &filterid);
log << "block IPv4 DNS requests from other apps" << std::endl;
// Filter #4 -- block IPv6 DNS requests from other apps
// Filter #6 -- block IPv6 DNS requests from other apps
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
add_filter(&filter, NULL, &filterid);
log << "block IPv6 DNS requests from other apps" << std::endl;
// Filter #5 -- allow IPv4 traffic from TAP
// Filter #7 -- allow IPv4 traffic from TAP
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
filter.action.type = FWP_ACTION_PERMIT;
filter.numFilterConditions = 2;
@@ -208,7 +242,7 @@ namespace openvpn {
add_filter(&filter, NULL, &filterid);
log << "allow IPv4 traffic from TAP" << std::endl;
// Filter #6 -- allow IPv6 traffic from TAP
// Filter #8 -- allow IPv6 traffic from TAP
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
add_filter(&filter, NULL, &filterid);
@@ -326,11 +360,12 @@ namespace openvpn {
void block(const std::wstring& openvpn_app_path,
const NET_IFINDEX tap_index,
const bool allow_local_dns_resolvers,
std::ostream& log)
{
unblock(log);
wfp.reset(new WFP());
wfp->block_dns(openvpn_app_path, tap_index, log);
wfp->block_dns(openvpn_app_path, tap_index, allow_local_dns_resolvers, log);
}
void unblock(std::ostream& log)
@@ -351,11 +386,13 @@ namespace openvpn {
ActionWFP(const std::wstring& openvpn_app_path_arg,
const NET_IFINDEX tap_index_arg,
const bool enable_arg,
const bool allow_local_dns_resolvers_arg,
const WFPContext::Ptr& wfp_arg)
: openvpn_app_path(openvpn_app_path_arg),
tap_index(tap_index_arg),
enable(enable_arg),
wfp(wfp_arg)
wfp(wfp_arg),
allow_local_dns_resolvers(allow_local_dns_resolvers_arg)
{
}
@@ -363,7 +400,7 @@ namespace openvpn {
{
log << to_string() << std::endl;
if (enable)
wfp->block(openvpn_app_path, tap_index, log);
wfp->block(openvpn_app_path, tap_index, allow_local_dns_resolvers, log);
else
wfp->unblock(log);
}
@@ -377,6 +414,7 @@ namespace openvpn {
const std::wstring openvpn_app_path;
const NET_IFINDEX tap_index;
const bool enable;
const bool allow_local_dns_resolvers;
WFPContext::Ptr wfp;
};
}

View File

@@ -717,6 +717,7 @@ int openvpn_client(int argc, char *argv[], const std::string* profile_content)
{ "google-dns", no_argument, nullptr, 'g' },
{ "persist-tun", no_argument, nullptr, 'j' },
{ "wintun", no_argument, nullptr, 'w' },
{ "allow-local-dns-resolvers", no_argument, nullptr, 'l' },
{ "def-keydir", required_argument, nullptr, 'k' },
{ "merge", no_argument, nullptr, 'm' },
{ "version", no_argument, nullptr, 'v' },
@@ -776,6 +777,7 @@ int openvpn_client(int argc, char *argv[], const std::string* profile_content)
bool retryOnAuthFailed = false;
bool tunPersist = false;
bool wintun = false;
bool allowLocalDnsResolvers = false;
bool merge = false;
bool version = false;
bool altProxy = false;
@@ -790,7 +792,7 @@ int openvpn_client(int argc, char *argv[], const std::string* profile_content)
int ch;
optind = 1;
while ((ch = getopt_long(argc, argv, "BAdeTCxfgjwmvaYu:p:r:D:P:6:s:S:t:c:z:M:h:q:U:W:I:G:k:X:R:Z:", longopts, nullptr)) != -1)
while ((ch = getopt_long(argc, argv, "BAdeTCxfgjwmlvaYu:p:r:D:P:6:s:S:t:c:z:M:h:q:U:W:I:G:k:X:R:Z:", longopts, nullptr)) != -1)
{
switch (ch)
{
@@ -901,6 +903,9 @@ int openvpn_client(int argc, char *argv[], const std::string* profile_content)
case 'w':
wintun = true;
break;
case 'l':
allowLocalDnsResolvers = true;
break;
case 'm':
merge = true;
break;
@@ -1006,6 +1011,7 @@ int openvpn_client(int argc, char *argv[], const std::string* profile_content)
config.gremlinConfig = gremlin;
config.info = true;
config.wintun = wintun;
config.allowLocalDnsResolvers = allowLocalDnsResolvers;
config.ssoMethods =ssoMethods;
#if defined(OPENVPN_OVPNCLI_SINGLE_THREAD)
config.clockTickMS = 250;