win: implement --dns option support with NRPT

Implement support for setting options from --dns. This is hugely
different than what we had so far with DNS related --dhcp-option.

The main difference it that we support split DNS and DNSSEC by making
use of NRPT (Name Resolution Policy Table). Also OpenVPN tries to keep
local DNS resolution working when DNS is redirected into the tunnel. To
prevent this from happening we have --block-outside-dns, in case you
wonder. Basically we collect domains and name server addresses from
network adapters and add so called exclude NRPT rules in addition to the
catch all rule that is pushed by the server.

All is done via the interactive service, since modifying all this
requires the elevated privileges that the openvpn process hopefully
doesn't have.

Change-Id: I576e74f3276362606e9cbd50bb5adbebaaf209cc
Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Lev Stipakov <lstipakov@gmail.com>
Message-Id: <20250414180636.31936-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg31426.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
This commit is contained in:
Heiko Hund 2025-04-14 20:06:26 +02:00 committed by Gert Doering
parent 9d551f9e05
commit 0f3b7d17d5
5 changed files with 1149 additions and 30 deletions

View file

@ -35,6 +35,8 @@ typedef enum {
msg_del_route,
msg_add_dns_cfg,
msg_del_dns_cfg,
msg_add_nrpt_cfg,
msg_del_nrpt_cfg,
msg_add_nbt_cfg,
msg_del_nbt_cfg,
msg_flush_neighbors,
@ -96,6 +98,23 @@ typedef struct {
inet_address_t addr[4]; /* support up to 4 dns addresses */
} dns_cfg_message_t;
typedef enum {
nrpt_dnssec
} nrpt_flags_t;
#define NRPT_ADDR_NUM 8 /* Max. number of addresses */
#define NRPT_ADDR_SIZE 48 /* Max. address strlen + some */
typedef char nrpt_address_t[NRPT_ADDR_SIZE];
typedef struct {
message_header_t header;
interface_t iface;
nrpt_address_t addresses[NRPT_ADDR_NUM];
char resolve_domains[512]; /* double \0 terminated */
char search_domains[512];
nrpt_flags_t flags;
} nrpt_dns_cfg_message_t;
typedef struct {
message_header_t header;
interface_t iface;

View file

@ -29,6 +29,12 @@
#include "dns.h"
#include "socket.h"
#include "options.h"
#ifdef _WIN32
#include "win32.h"
#include "openvpn-msg.h"
#endif
/**
* Parses a string as port and stores it
@ -428,6 +434,122 @@ setenv_dns_options(const struct dns_options *o, struct env_set *es)
gc_free(&gc);
}
#ifdef _WIN32
static void
make_domain_list(const char *what, const struct dns_domain *src,
bool nrpt_domains, char *dst, size_t dst_size)
{
/* NRPT domains need two \0 at the end for REG_MULTI_SZ
* and a leading '.' added in front of the domain name */
size_t term_size = nrpt_domains ? 2 : 1;
size_t leading_dot = nrpt_domains ? 1 : 0;
size_t offset = 0;
memset(dst, 0, dst_size);
while (src)
{
size_t len = strlen(src->name);
if (offset + leading_dot + len + term_size > dst_size)
{
msg(M_WARN, "WARNING: %s truncated", what);
if (offset)
{
/* Remove trailing comma */
*(dst + offset - 1) = '\0';
}
break;
}
if (leading_dot)
{
*(dst + offset++) = '.';
}
strncpy(dst + offset, src->name, len);
offset += len;
src = src->next;
if (src)
{
*(dst + offset++) = ',';
}
}
}
static void
run_up_down_service(bool add, const struct options *o, const struct tuntap *tt)
{
const struct dns_server *server = o->dns_options.servers;
const struct dns_domain *search_domains = o->dns_options.search_domains;
while (true)
{
if (!server)
{
if (add)
{
msg(M_WARN, "WARNING: setting DNS failed, no compatible server profile");
}
return;
}
bool only_standard_server_ports = true;
for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
{
if (server->addr[i].port && server->addr[i].port != 53)
{
only_standard_server_ports = false;
break;
}
}
if ((server->transport == DNS_TRANSPORT_UNSET || server->transport == DNS_TRANSPORT_PLAIN)
&& only_standard_server_ports)
{
break; /* found compatible server */
}
server = server->next;
}
ack_message_t ack;
nrpt_dns_cfg_message_t nrpt = {
.header = {
(add ? msg_add_nrpt_cfg : msg_del_nrpt_cfg),
sizeof(nrpt_dns_cfg_message_t),
0
},
.iface = { .index = tt->adapter_index, .name = "" },
.flags = server->dnssec == DNS_SECURITY_NO ? 0 : nrpt_dnssec,
};
strncpynt(nrpt.iface.name, tt->actual_name, sizeof(nrpt.iface.name));
for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
{
if (server->addr[i].family == AF_UNSPEC)
{
/* No more addresses */
break;
}
if (inet_ntop(server->addr[i].family, &server->addr[i].in,
nrpt.addresses[i], NRPT_ADDR_SIZE) == NULL)
{
msg(M_WARN, "WARNING: could not convert dns server address");
}
}
make_domain_list("dns server resolve domains", server->domains, true,
nrpt.resolve_domains, sizeof(nrpt.resolve_domains));
make_domain_list("dns search domains", search_domains, false,
nrpt.search_domains, sizeof(nrpt.search_domains));
send_msg_iservice(o->msg_channel, &nrpt, sizeof(nrpt), &ack, "DNS");
}
#endif /* _WIN32 */
void
show_dns_options(const struct dns_options *o)
{
@ -506,3 +628,43 @@ show_dns_options(const struct dns_options *o)
gc_free(&gc);
}
void
run_dns_up_down(bool up, struct options *o, const struct tuntap *tt)
{
if (!o->dns_options.servers)
{
return;
}
/* Warn about adding servers of unsupported AF */
const struct dns_server *s = o->dns_options.servers;
while (up && s)
{
size_t bad_count = 0;
for (size_t i = 0; i < s->addr_count; ++i)
{
if ((s->addr[i].family == AF_INET6 && !tt->did_ifconfig_ipv6_setup)
|| (s->addr[i].family == AF_INET && !tt->did_ifconfig_setup))
{
++bad_count;
}
}
if (bad_count == s->addr_count)
{
msg(M_WARN, "DNS server %ld only has address(es) from a family "
"the tunnel is not configured for - it will not be reachable",
s->priority);
}
else if (bad_count)
{
msg(M_WARN, "DNS server %ld has address(es) from a family "
"the tunnel is not configured for", s->priority);
}
s = s->next;
}
#ifdef _WIN32
run_up_down_service(up, o, tt);
#endif /* ifdef _WIN32 */
}

View file

@ -26,6 +26,7 @@
#include "buffer.h"
#include "env_set.h"
#include "tun.h"
enum dns_security {
DNS_SECURITY_UNSET,
@ -146,6 +147,14 @@ void dns_options_preprocess_pull(struct dns_options *o);
*/
void dns_options_postprocess_pull(struct dns_options *o);
/**
* Invokes the action associated with bringing DNS up or down
* @param up Boolean to set this call to "up" when true
* @param o Pointer to the program options
* @param tt Pointer to the connection's tuntap struct
*/
void run_dns_up_down(bool up, struct options *o, const struct tuntap *tt);
/**
* Puts the DNS options into an environment set.
*

View file

@ -2026,6 +2026,8 @@ do_open_tun(struct context *c, int *error_flags)
c->c2.frame.tun_mtu, c->c2.es, &c->net_ctx);
}
run_dns_up_down(true, &c->options, c->c1.tuntap);
/* run the up script */
run_up_down(c->options.up_script,
c->plugins,
@ -2064,6 +2066,8 @@ do_open_tun(struct context *c, int *error_flags)
/* explicitly set the ifconfig_* env vars */
do_ifconfig_setenv(c->c1.tuntap, c->c2.es);
run_dns_up_down(true, &c->options, c->c1.tuntap);
/* run the up script if user specified --up-restart */
if (c->options.up_restart)
{
@ -2152,6 +2156,8 @@ do_close_tun(struct context *c, bool force)
adapter_index = c->c1.tuntap->adapter_index;
#endif
run_dns_up_down(false, &c->options, c->c1.tuntap);
if (force || !(c->sig->signal_received == SIGUSR1 && c->options.persist_tun))
{
static_context = NULL;

File diff suppressed because it is too large Load diff