diff --git a/include/openvpn-msg.h b/include/openvpn-msg.h
index 7a99335..8b48053 100644
--- a/include/openvpn-msg.h
+++ b/include/openvpn-msg.h
@@ -35,6 +35,8 @@
     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 @@
     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;
diff --git a/src/openvpn/dns.c b/src/openvpn/dns.c
index e22ea00..4528a9c 100644
--- a/src/openvpn/dns.c
+++ b/src/openvpn/dns.c
@@ -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,93 @@
     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;
+
+    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 +599,43 @@
 
     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 */
+}
diff --git a/src/openvpn/dns.h b/src/openvpn/dns.h
index 838ebe1..f24e30b 100644
--- a/src/openvpn/dns.h
+++ b/src/openvpn/dns.h
@@ -26,6 +26,7 @@
 
 #include "buffer.h"
 #include "env_set.h"
+#include "tun.h"
 
 enum dns_security {
     DNS_SECURITY_UNSET,
@@ -147,6 +148,14 @@
 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.
  *
  * @param   o           Pointer to the DNS options to set
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 9371024..36a9bca 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -2008,6 +2008,8 @@
                         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,
@@ -2046,6 +2048,8 @@
         /* 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)
         {
@@ -2134,6 +2138,8 @@
     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;
diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c
index 5042283..d5b7e5d 100644
--- a/src/openvpnserv/interactive.c
+++ b/src/openvpnserv/interactive.c
@@ -24,6 +24,7 @@
 
 #include "service.h"
 
+#include <winerror.h>
 #include <ws2tcpip.h>
 #include <iphlpapi.h>
 #include <userenv.h>
@@ -88,6 +89,7 @@
     wfp_block,
     undo_dns4,
     undo_dns6,
+    undo_nrpt,
     undo_domains,
     undo_ring_buffer,
     undo_wins,
@@ -119,12 +121,20 @@
     flush_neighbors_message_t flush_neighbors;
     wfp_block_message_t wfp_block;
     dns_cfg_message_t dns;
+    nrpt_dns_cfg_message_t nrpt_dns;
     enable_dhcp_message_t dhcp;
     register_ring_buffers_message_t rrb;
     set_mtu_message_t mtu;
     wins_cfg_message_t wins;
 } pipe_message_t;
 
+typedef struct {
+    CHAR addresses[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
+    WCHAR domains[512]; /* MULTI_SZ string */
+    DWORD domains_size; /* bytes in domains */
+} nrpt_exclude_data_t;
+
+
 static DWORD
 AddListItem(list_item_t **pfirst, LPVOID data)
 {
@@ -1946,6 +1956,903 @@
     return err;
 }
 
+/**
+ * Checks if DHCP is enabled for an interface
+ *
+ * @param  key        HKEY of the interface to check for
+ *
+ * @return BOOL set to TRUE if DHCP is enabled, or FALSE if
+ *         disabled or an error occurred
+ */
+static BOOL
+IsDhcpEnabled(HKEY key)
+{
+    DWORD dhcp;
+    DWORD size = sizeof(dhcp);
+    LSTATUS err;
+
+    err = RegGetValueA(key, NULL, "EnableDHCP", RRF_RT_REG_DWORD, NULL, (PBYTE)&dhcp, &size);
+    if (err != NO_ERROR)
+    {
+        MsgToEventLog(M_SYSERR, TEXT("IsDhcpEnabled: "
+                                     "Could not read DHCP status (%lu)"), err);
+        return FALSE;
+    }
+
+    return dhcp ? TRUE : FALSE;
+}
+
+/**
+ * Set name servers from a NRPT address list
+ *
+ * @param itf_id        the VPN interface ID to set the name servers for
+ * @param addresses     the list of NRPT addresses
+ *
+ * @return LSTATUS NO_ERROR in case of success, a Windows error code otherwise
+ */
+static LSTATUS
+SetNameServerAddresses(PWSTR itf_id, const nrpt_address_t *addresses)
+{
+    const short families[] = { AF_INET, AF_INET6 };
+    for (int i = 0; i < _countof(families); i++)
+    {
+        short family = families[i];
+
+        /* Create a comma sparated list of addresses of this family */
+        int offset = 0;
+        char addr_list[NRPT_ADDR_SIZE * NRPT_ADDR_NUM];
+        for (int j = 0; j < NRPT_ADDR_NUM && addresses[j][0]; j++)
+        {
+            if ((family == AF_INET6 && strchr(addresses[j], ':') == NULL)
+                || (family == AF_INET && strchr(addresses[j], ':') != NULL))
+            {
+                /* Address family doesn't match, skip this one */
+                continue;
+            }
+            if (offset)
+            {
+                addr_list[offset++] = ',';
+            }
+            strcpy(addr_list + offset, addresses[j]);
+            offset += strlen(addresses[j]);
+        }
+
+        if (offset == 0)
+        {
+            /* No address for this family to set */
+            continue;
+        }
+
+        /* Set name server addresses */
+        LSTATUS err = SetNameServers(itf_id, family, addr_list);
+        if (err)
+        {
+            return err;
+        }
+    }
+    return NO_ERROR;
+}
+
+/**
+ * Get DNS server IPv4 addresses of an interface
+ *
+ * @param  itf_key    registry key of the IPv4 interface data
+ * @param  addrs      pointer to the buffer addresses are returned in
+ * @param  size       pointer to the size of the buffer, contains the
+ *                    size of the addresses on return
+ *
+ * @return LSTATUS NO_ERROR on success, a Windows error code otherwise
+ */
+static LSTATUS
+GetItfDnsServersV4(HKEY itf_key, PSTR addrs, PDWORD size)
+{
+    addrs[*size - 1] = '\0';
+
+    LSTATUS err;
+    DWORD s = *size;
+    err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
+    if (err && err != ERROR_FILE_NOT_FOUND)
+    {
+        *size = 0;
+        return err;
+    }
+
+    /* Try DHCP addresses if we don't have some already */
+    if (!strchr(addrs, '.') && IsDhcpEnabled(itf_key))
+    {
+        s = *size;
+        RegGetValueA(itf_key, NULL, "DhcpNameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
+        if (err)
+        {
+            *size = 0;
+            return err;
+        }
+    }
+
+    if (strchr(addrs, '.'))
+    {
+        *size = s;
+        return NO_ERROR;
+    }
+
+    *size = 0;
+    return ERROR_FILE_NOT_FOUND;
+}
+
+/**
+ * Get DNS server IPv6 addresses of an interface
+ *
+ * @param  itf_key    registry key of the IPv6 interface data
+ * @param  addrs      pointer to the buffer addresses are returned in
+ * @param  size       pointer to the size of the buffer
+ *
+ * @return LSTATUS NO_ERROR on success, a Windows error code otherwise
+ */
+static LSTATUS
+GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
+{
+    addrs[*size - 1] = '\0';
+
+    LSTATUS err;
+    DWORD s = *size;
+    err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
+    if (err && err != ERROR_FILE_NOT_FOUND)
+    {
+        *size = 0;
+        return err;
+    }
+
+    /* Try DHCP addresses if we don't have some already */
+    if (!strchr(addrs, ':') && IsDhcpEnabled(itf_key))
+    {
+        IN6_ADDR in_addrs[8];
+        DWORD in_addrs_size = sizeof(in_addrs);
+        err = RegGetValueA(itf_key, NULL, "Dhcpv6DNSServers", RRF_RT_REG_BINARY, NULL,
+                           (PBYTE)in_addrs, &in_addrs_size);
+        if (err)
+        {
+            *size = 0;
+            return err;
+        }
+
+        s = *size;
+        PSTR pos = addrs;
+        size_t in_addrs_read = in_addrs_size / sizeof(IN6_ADDR);
+        for (size_t i = 0; i < in_addrs_read; ++i)
+        {
+            if (i != 0)
+            {
+                /* Add separator */
+                *pos++ = ',';
+                s--;
+            }
+
+            if (inet_ntop(AF_INET6, &in_addrs[i],
+                          pos, s) != NULL)
+            {
+                *size = 0;
+                return ERROR_MORE_DATA;
+            }
+
+            size_t addr_len = strlen(pos);
+            pos += addr_len;
+            s -= addr_len;
+        }
+        s = strlen(addrs) + 1;
+    }
+
+    if (strchr(addrs, ':'))
+    {
+        *size = s;
+        return NO_ERROR;
+    }
+
+    *size = 0;
+    return ERROR_FILE_NOT_FOUND;
+}
+
+/**
+ * Return interface specific domain suffix(es)
+ *
+ * The \p domains paramter will be set to a MULTI_SZ domains string.
+ * In case of an error or if no domains are found for the interface
+ * \p size is set to 0 and the contents of \p domains are invalid.
+ * Note that the domains could have been set by DHCP or manually.
+ *
+ * @param  itf        HKEY of the interface to read from
+ * @param  domains    PWSTR buffer to return the domain(s) in
+ * @param  size       pointer to size of the domains buffer in bytes. Will be
+ *                    set to the size of the string returned, including
+ *                    the terminating zeros or 0.
+ *
+ * @return LSTATUS NO_ERROR if the domain suffix(es) were read successfully,
+ *         ERROR_FILE_NOT_FOUND if no domain was found for the interface,
+ *         ERROR_MORE_DATA if the list did not fit into the buffer,
+ *         any other error indicates an error while reading from the registry.
+ */
+static LSTATUS
+GetItfDnsDomains(HKEY itf, PWSTR domains, PDWORD size)
+{
+    if (domains == NULL || size == 0)
+    {
+        return ERROR_INVALID_PARAMETER;
+    }
+
+    LSTATUS err = ERROR_FILE_NOT_FOUND;
+    const DWORD buf_size = *size;
+    const size_t one_glyph = sizeof(*domains);
+    PWSTR values[] = { L"SearchList", L"Domain", L"DhcpDomainSearchList", L"DhcpDomain", NULL};
+
+    for (int i = 0; values[i]; i++)
+    {
+        err = RegGetValueW(itf, NULL, values[i], RRF_RT_REG_SZ, NULL, (PBYTE)domains, size);
+        if (!err && *size > one_glyph && wcschr(domains, '.'))
+        {
+            /*
+             * Found domain(s), now convert them:
+             *   - prefix each domain with a dot
+             *   - convert comma separated list to MULTI_SZ
+             */
+            PWCHAR pos = domains;
+            const DWORD buf_len = buf_size / one_glyph;
+            while (TRUE)
+            {
+                /* Terminate the domain at the next comma */
+                PWCHAR comma = wcschr(pos, ',');
+                if (comma)
+                {
+                    *comma = '\0';
+                }
+
+                /* Check for enough space to convert this domain */
+                size_t converted_size = pos - domains;
+                size_t domain_len = wcslen(pos) + 1;
+                size_t domain_size = domain_len * one_glyph;
+                size_t extra_size = 2 * one_glyph;
+                if (converted_size + domain_size + extra_size > buf_size)
+                {
+                    /* Domain doesn't fit, bad luck if it's the first one */
+                    *pos = '\0';
+                    *size = converted_size == 0 ? 0 : *size + 1;
+                    return ERROR_MORE_DATA;
+                }
+
+                /* Prefix domain at pos with the dot */
+                memmove(pos + 1, pos, buf_size - converted_size - one_glyph);
+                domains[buf_len - 1] = '\0';
+                *pos = '.';
+                *size += 1;
+
+                if (!comma)
+                {
+                    /* Conversion is done */
+                    *(pos + domain_len) = '\0';
+                    *size += 1;
+                    return NO_ERROR;
+                }
+
+                pos = comma + 1;
+            }
+        }
+    }
+
+    *size = 0;
+    return err;
+}
+
+/**
+ * Check if an interface is connected and up
+ *
+ * @param  iid_str    the interface GUID as string
+ *
+ * @return TRUE if the interface is connected and up, FALSE otherwise or in
+ *         case an error happened
+ */
+static BOOL
+IsInterfaceConnected(PWSTR iid_str)
+{
+    GUID iid;
+    BOOL res = FALSE;
+    MIB_IF_ROW2 itf_row;
+
+    /* Get LUID from GUID string */
+    if (IIDFromString(iid_str, &iid) != S_OK
+        || ConvertInterfaceGuidToLuid(&iid, &itf_row.InterfaceLuid) != NO_ERROR)
+    {
+        MsgToEventLog(M_SYSERR, TEXT("IsInterfaceConnected: "
+                                     "could not convert interface %s GUID to LUID"), iid_str);
+        goto out;
+    }
+
+    /* Look up interface status */
+    if (GetIfEntry2(&itf_row) != NO_ERROR)
+    {
+        MsgToEventLog(M_SYSERR, TEXT("IsInterfaceConnected: "
+                                     "could not get interface %s status"), iid_str);
+        goto out;
+    }
+
+    if (itf_row.MediaConnectState == MediaConnectStateConnected
+        && itf_row.OperStatus == IfOperStatusUp)
+    {
+        res = TRUE;
+    }
+
+out:
+    return res;
+}
+
+/**
+ * Collect interface DNS settings to be used in excluding NRPT rules. This is
+ * needed so that local DNS keeps working even when a catch all NRPT rule is
+ * installed by a VPN connection.
+ *
+ * @param  data       pointer to the data structures the values are returned in
+ * @param  data_size  number of exclude data structures pointed to
+ */
+static void
+GetNrptExcludeData(nrpt_exclude_data_t *data, size_t data_size)
+{
+    HKEY v4_itfs = INVALID_HANDLE_VALUE;
+    HKEY v6_itfs = INVALID_HANDLE_VALUE;
+
+    if (!GetInterfacesKey(AF_INET, &v4_itfs)
+        || !GetInterfacesKey(AF_INET6, &v6_itfs))
+    {
+        goto out;
+    }
+
+    size_t i = 0;
+    DWORD enum_index = 0;
+    while (i < data_size)
+    {
+        WCHAR itf_guid[MAX_PATH];
+        DWORD itf_guid_len = _countof(itf_guid);
+        LSTATUS err = RegEnumKeyExW(v4_itfs, enum_index++, itf_guid, &itf_guid_len,
+                                    NULL, NULL, NULL, NULL);
+        if (err)
+        {
+            if (err != ERROR_NO_MORE_ITEMS)
+            {
+                MsgToEventLog(M_SYSERR, TEXT("GetNrptExcludeData: "
+                                             "could not enumerate interfaces (%lu)"), err);
+            }
+            goto out;
+        }
+
+        /* Ignore interfaces that are not connected or disabled */
+        if (!IsInterfaceConnected(itf_guid))
+        {
+            continue;
+        }
+
+        HKEY v4_itf;
+        if (RegOpenKeyExW(v4_itfs, itf_guid, 0, KEY_READ, &v4_itf) != NO_ERROR)
+        {
+            MsgToEventLog(M_SYSERR, TEXT("GetNrptExcludeData: "
+                                         "could not open interface %s v4 registry key"), itf_guid);
+            goto out;
+        }
+
+        /* Get the DNS domain(s) for exclude routing */
+        data[i].domains_size = sizeof(data[0].domains);
+        memset(data[i].domains, 0, data[i].domains_size);
+        err = GetItfDnsDomains(v4_itf, data[i].domains, &data[i].domains_size);
+        if (err)
+        {
+            if (err != ERROR_FILE_NOT_FOUND)
+            {
+                MsgToEventLog(M_SYSERR, TEXT("GetNrptExcludeData: "
+                                             "could not read interface %s domain suffix"), itf_guid);
+            }
+            goto next_itf;
+        }
+
+        /* Get the IPv4 DNS servers */
+        DWORD v4_addrs_size = sizeof(data[0].addresses);
+        err = GetItfDnsServersV4(v4_itf, data[i].addresses, &v4_addrs_size);
+        if (err && err != ERROR_FILE_NOT_FOUND)
+        {
+            MsgToEventLog(M_SYSERR,
+                          TEXT("GetNrptExcludeData: could not read interface %s v4 name servers (%ld)"),
+                          itf_guid, err);
+            goto next_itf;
+        }
+
+        /* Get the IPv6 DNS servers, if there's space left */
+        PSTR v6_addrs = data[i].addresses + v4_addrs_size;
+        DWORD v6_addrs_size = sizeof(data[0].addresses) - v4_addrs_size;
+        if (v6_addrs_size > NRPT_ADDR_SIZE)
+        {
+            HKEY v6_itf;
+            if (RegOpenKeyExW(v6_itfs, itf_guid, 0, KEY_READ, &v6_itf) != NO_ERROR)
+            {
+                MsgToEventLog(M_SYSERR, TEXT("GetNrptExcludeData: "
+                                             "could not open interface %s v6 registry key"), itf_guid);
+                goto next_itf;
+            }
+            err = GetItfDnsServersV6(v6_itf, v6_addrs, &v6_addrs_size);
+            RegCloseKey(v6_itf);
+            if (err && err != ERROR_FILE_NOT_FOUND)
+            {
+                MsgToEventLog(M_SYSERR,
+                              TEXT("GetNrptExcludeData: could not read interface %s v6 name servers (%ld)"),
+                              itf_guid, err);
+                goto next_itf;
+            }
+        }
+
+        if (v4_addrs_size || v6_addrs_size)
+        {
+            /* Replace comma-delimters with semicolons, as required by NRPT */
+            for (int j = 0; j < sizeof(data[0].addresses) && data[i].addresses[j]; j++)
+            {
+                if (data[i].addresses[j] == ',')
+                {
+                    data[i].addresses[j] = ';';
+                }
+            }
+            ++i;
+        }
+
+next_itf:
+        RegCloseKey(v4_itf);
+    }
+
+out:
+    RegCloseKey(v6_itfs);
+    RegCloseKey(v4_itfs);
+}
+
+/**
+ * Set a NRPT rule (subkey) and its values in the registry
+ *
+ * @param  nrpt_key   NRPT registry key handle
+ * @param  subkey     subkey string to create
+ * @param  address    name server address string
+ * @param  domains    domains to resolve by this server as MULTI_SZ
+ * @param  dom_size   size of domains in bytes including the terminators
+ * @param  dnssec     boolean to determine if DNSSEC is to be enabled
+ *
+ * @return NO_ERROR on success, or Windows error code
+ */
+static DWORD
+SetNrptRule(HKEY nrpt_key, PCWSTR subkey, PCSTR address,
+            PCWSTR domains, DWORD dom_size, BOOL dnssec)
+{
+    /* Create rule subkey */
+    DWORD err = NO_ERROR;
+    HKEY rule_key;
+    err = RegCreateKeyExW(nrpt_key, subkey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &rule_key, NULL);
+    if (err)
+    {
+        return err;
+    }
+
+    /* Set name(s) for DNS routing */
+    err = RegSetValueExW(rule_key, L"Name", 0, REG_MULTI_SZ, (PBYTE)domains, dom_size);
+    if (err)
+    {
+        goto out;
+    }
+
+    /* Set DNS Server address */
+    err = RegSetValueExA(rule_key, "GenericDNSServers", 0, REG_SZ, (PBYTE)address, strlen(address) + 1);
+    if (err)
+    {
+        goto out;
+    }
+
+    DWORD reg_val;
+    /* Set DNSSEC if required */
+    if (dnssec)
+    {
+        reg_val = 1;
+        err = RegSetValueExA(rule_key, "DNSSECValidationRequired", 0, REG_DWORD, (PBYTE)&reg_val, sizeof(reg_val));
+        if (err)
+        {
+            goto out;
+        }
+
+        reg_val = 0;
+        err = RegSetValueExA(rule_key, "DNSSECQueryIPSECRequired", 0, REG_DWORD, (PBYTE)&reg_val, sizeof(reg_val));
+        if (err)
+        {
+            goto out;
+        }
+
+        reg_val = 0;
+        err = RegSetValueExA(rule_key, "DNSSECQueryIPSECEncryption", 0, REG_DWORD, (PBYTE)&reg_val, sizeof(reg_val));
+        if (err)
+        {
+            goto out;
+        }
+    }
+
+    /* Set NRPT config options */
+    reg_val = dnssec ? 0x0000000A : 0x00000008;
+    err = RegSetValueExA(rule_key, "ConfigOptions", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));
+    if (err)
+    {
+        goto out;
+    }
+
+    /* Mandatory NRPT version */
+    reg_val = 2;
+    err = RegSetValueExA(rule_key, "Version", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));
+    if (err)
+    {
+        goto out;
+    }
+
+out:
+    if (err)
+    {
+        RegDeleteKeyW(nrpt_key, subkey);
+    }
+    RegCloseKey(rule_key);
+    return err;
+}
+
+/**
+ * Set NRPT exclude rules to accompany a catch all rule. This is done so that
+ * local resolution of names is not interfered with in case the VPN resolves
+ * all names.
+ *
+ * @param  nrpt_key   the registry key to set the rules under
+ * @param  ovpn_pid   the PID of the openvpn process
+ */
+static void
+SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid)
+{
+    nrpt_exclude_data_t data[8]; /* data from up to 8 interfaces */
+    memset(data, 0, sizeof(data));
+    GetNrptExcludeData(data, _countof(data));
+
+    unsigned n = 0;
+    for (int i = 0; i < _countof(data); ++i)
+    {
+        DWORD err;
+        WCHAR subkey[48];
+        swprintf(subkey, _countof(subkey), L"OpenVPNDNSRoutingX-%02x-%lu", ++n, ovpn_pid);
+
+        nrpt_exclude_data_t *d = &data[i];
+        err = SetNrptRule(nrpt_key, subkey, d->addresses, d->domains, d->domains_size, FALSE);
+        if (err)
+        {
+            MsgToEventLog(M_ERR, TEXT("SetNrptExcludeRules: "
+                                      "failed to set rule %s (%lu)"), subkey, err);
+        }
+    }
+}
+
+/**
+ * Set NRPT rules for a openvpn process
+ *
+ * @param  nrpt_key   the registry key to set the rules under
+ * @param  addresses  name server addresses
+ * @param  domains    optional list of split routing domains
+ * @param  dnssec     boolean whether DNSSEC is to be used
+ * @param  ovpn_pid   the PID of the openvpn process
+ *
+ * @return NO_ERROR on success, or a Windows error code
+ */
+static DWORD
+SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses,
+             const char *domains, BOOL dnssec, DWORD ovpn_pid)
+{
+    DWORD err = NO_ERROR;
+    PWSTR wide_domains = L".\0"; /* DNS route everything by default */
+    DWORD dom_size = 6;
+
+    /* Prepare DNS routing domains / split DNS */
+    if (domains[0])
+    {
+        size_t domains_len = strlen(domains);
+        dom_size = domains_len + 2; /* len + the trailing NULs */
+
+        wide_domains = utf8to16_size(domains, dom_size);
+        dom_size *= sizeof(*wide_domains);
+        if (!wide_domains)
+        {
+            return ERROR_OUTOFMEMORY;
+        }
+        /* Make a MULTI_SZ from a comma separated list */
+        for (size_t i = 0; i < domains_len; ++i)
+        {
+            if (wide_domains[i] == ',')
+            {
+                wide_domains[i] = 0;
+            }
+        }
+    }
+    else
+    {
+        SetNrptExcludeRules(nrpt_key, ovpn_pid);
+    }
+
+    /* Create address string list */
+    CHAR addr_list[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
+    PSTR pos = addr_list;
+    for (int i = 0; i < NRPT_ADDR_NUM && addresses[i][0]; ++i)
+    {
+        if (i != 0)
+        {
+            *pos++ = ';';
+        }
+        strcpy(pos, addresses[i]);
+        pos += strlen(pos);
+    }
+
+    WCHAR subkey[MAX_PATH];
+    swprintf(subkey, _countof(subkey), L"OpenVPNDNSRouting-%lu", ovpn_pid);
+    err = SetNrptRule(nrpt_key, subkey, addr_list, wide_domains, dom_size, dnssec);
+    if (err)
+    {
+        MsgToEventLog(M_ERR, TEXT("SetNrptRules: "
+                                  "failed to set rule %s (%lu)"), subkey, err);
+    }
+
+    free(wide_domains);
+    return err;
+}
+
+/**
+ * Return the registry key where NRPT rules are stored
+ *
+ * @param  key        pointer to the HKEY it is returned in
+ * @param  gpol       pointer to BOOL the use of GPOL hive is returned in
+ *
+ * @return NO_ERROR on success, or a Windows error code
+ */
+static LSTATUS
+OpenNrptBaseKey(PHKEY key, PBOOL gpol)
+{
+    /*
+     * Registry keys Name Service Policy Table (NRPT) rules can be stored at.
+     * When the group policy key exists, NRPT rules must be placed there.
+     * It is created when NRPT rules are pushed via group policy and it
+     * remains in the registry even if the last GP-NRPT rule is deleted.
+     */
+    static PCSTR gpol_key = "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig";
+    static PCSTR sys_key = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig";
+
+    HKEY nrpt;
+    *gpol = TRUE;
+    LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, gpol_key, 0, KEY_ALL_ACCESS, &nrpt);
+    if (err == ERROR_FILE_NOT_FOUND)
+    {
+        *gpol = FALSE;
+        err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, sys_key, 0, KEY_ALL_ACCESS, &nrpt);
+        if (err)
+        {
+            nrpt = INVALID_HANDLE_VALUE;
+        }
+    }
+    *key = nrpt;
+    return err;
+}
+
+/**
+ * Delete OpenVPN NRPT rules from the registry
+ *
+ * If the pid parameter is 0 all NRPT rules added by OpenVPN are deleted.
+ * In all other cases only rules matching the pid are deleted.
+ *
+ * @param  pid        PID of the process to delete the rules for or 0
+ * @param  gpol
+ *
+ * @return BOOL to indicate if rules were deleted
+ */
+static BOOL
+DeleteNrptRules(DWORD pid, PBOOL gpol)
+{
+    HKEY key;
+    LSTATUS err = OpenNrptBaseKey(&key, gpol);
+    if (err)
+    {
+        MsgToEventLog(M_SYSERR, TEXT("DeleteNrptRules: "
+                                     "could not open NRPT base key (%lu)"), err);
+        return FALSE;
+    }
+
+    /* PID suffix string to compare against later */
+    WCHAR pid_str[16];
+    size_t pidlen = 0;
+    if (pid)
+    {
+        swprintf(pid_str, _countof(pid_str), L"-%lu", pid);
+        pidlen = wcslen(pid_str);
+    }
+
+    int deleted = 0;
+    DWORD enum_index = 0;
+    while (TRUE)
+    {
+        WCHAR name[MAX_PATH];
+        DWORD namelen = _countof(name);
+        err = RegEnumKeyExW(key, enum_index++, name, &namelen, NULL, NULL, NULL, NULL);
+        if (err)
+        {
+            if (err != ERROR_NO_MORE_ITEMS)
+            {
+                MsgToEventLog(M_SYSERR, TEXT("DeleteNrptRules: "
+                                             "could not enumerate NRPT rules (%lu)"), err);
+            }
+            break;
+        }
+
+        /* Keep rule if name doesn't match */
+        if (wcsncmp(name, L"OpenVPNDNSRouting", 17) != 0
+            || (pid && wcsncmp(name + namelen - pidlen, pid_str, pidlen) != 0))
+        {
+            continue;
+        }
+
+        if (RegDeleteKeyW(key, name) == NO_ERROR)
+        {
+            enum_index--;
+            deleted++;
+        }
+    }
+
+    RegCloseKey(key);
+    return deleted ? TRUE : FALSE;
+}
+
+/**
+ * Delete a process' NRPT rules and apply the reduced set of rules
+ *
+ * @param ovpn_pid  OpenVPN process id to delete rules for
+ */
+static void
+UndoNrptRules(DWORD ovpn_pid)
+{
+    BOOL gpol;
+    if (DeleteNrptRules(ovpn_pid, &gpol))
+    {
+        ApplyDnsSettings(gpol);
+    }
+}
+
+/**
+ * Add Name Resolution Policy Table (NRPT) rules as documented in
+ * https://msdn.microsoft.com/en-us/library/ff957356.aspx for DNS name
+ * resolution, as well as DNS search domain(s), if given.
+ *
+ * @param  msg        config messages sent by the openvpn process
+ * @param  ovpn_pid   process id of the sending openvpn process
+ * @param  lists      undo lists for this process
+ *
+ * @return NO_ERROR on success, or a Windows error code
+ */
+static DWORD
+HandleDNSConfigNrptMessage(const nrpt_dns_cfg_message_t *msg,
+                           DWORD ovpn_pid, undo_lists_t *lists)
+{
+    /*
+     * Use a non-const reference with limited scope to
+     * enforce null-termination of strings from client
+     */
+    {
+        nrpt_dns_cfg_message_t *msgptr = (nrpt_dns_cfg_message_t *) msg;
+        msgptr->iface.name[_countof(msg->iface.name) - 1] = '\0';
+        msgptr->search_domains[_countof(msg->search_domains) - 1] = '\0';
+        msgptr->resolve_domains[_countof(msg->resolve_domains) - 1] = '\0';
+        for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
+        {
+            msgptr->addresses[i][_countof(msg->addresses[0]) - 1] = '\0';
+        }
+    }
+
+    /* Make sure we have the VPN interface name */
+    if (msg->iface.name[0] == 0)
+    {
+        return ERROR_MESSAGE_DATA;
+    }
+
+    /* Some sanity checks on the add message data */
+    if (msg->header.type == msg_add_nrpt_cfg)
+    {
+        /* At least one name server address is set */
+        if (msg->addresses[0][0] == 0)
+        {
+            return ERROR_MESSAGE_DATA;
+        }
+        /* Resolve domains are double zero terminated (MULTI_SZ) */
+        const char *rdom = msg->resolve_domains;
+        size_t rdom_size = sizeof(msg->resolve_domains);
+        size_t rdom_len = strlen(rdom);
+        if (rdom_len && (rdom_len + 1 >= rdom_size || rdom[rdom_len + 2] != 0))
+        {
+            return ERROR_MESSAGE_DATA;
+        }
+    }
+
+    BOOL gpol_nrpt = FALSE;
+    BOOL gpol_list = FALSE;
+
+    WCHAR iid[64];
+    DWORD iid_err = InterfaceIdString(msg->iface.name, iid, sizeof(iid));
+    if (iid_err)
+    {
+        return iid_err;
+    }
+
+    /* Delete previously set values for this instance first, if any */
+    PDWORD undo_pid = RemoveListItem(&(*lists)[undo_nrpt], CmpAny, NULL);
+    if (undo_pid)
+    {
+        if (*undo_pid != ovpn_pid)
+        {
+            MsgToEventLog(M_INFO, TEXT("HandleDNSConfigNrptMessage: "
+                                       "PID stored for undo doesn't match: %lu vs %lu. "
+                                       "This is likely an error. Cleaning up anyway."),
+                          *undo_pid, ovpn_pid);
+        }
+        DeleteNrptRules(*undo_pid, &gpol_nrpt);
+        free(undo_pid);
+
+        ResetNameServers(iid, AF_INET);
+        ResetNameServers(iid, AF_INET6);
+    }
+    SetDnsSearchDomains(msg->iface.name, NULL, &gpol_list, lists);
+
+    if (msg->header.type == msg_del_nrpt_cfg)
+    {
+        ApplyDnsSettings(gpol_nrpt || gpol_list);
+        return NO_ERROR; /* Done dealing with del message */
+    }
+
+    HKEY key;
+    LSTATUS err = OpenNrptBaseKey(&key, &gpol_nrpt);
+    if (err)
+    {
+        goto out;
+    }
+
+    /* Add undo information first in case there's no heap left */
+    PDWORD pid = malloc(sizeof(ovpn_pid));
+    if (!pid)
+    {
+        err = ERROR_OUTOFMEMORY;
+        goto out;
+    }
+    *pid = ovpn_pid;
+    if (AddListItem(&(*lists)[undo_nrpt], pid))
+    {
+        err = ERROR_OUTOFMEMORY;
+        free(pid);
+        goto out;
+    }
+
+    /* Set NRPT rules */
+    BOOL dnssec = (msg->flags & nrpt_dnssec) != 0;
+    err = SetNrptRules(key, msg->addresses, msg->resolve_domains, dnssec, ovpn_pid);
+    if (err)
+    {
+        goto out;
+    }
+
+    /* Set name servers */
+    err = SetNameServerAddresses(iid, msg->addresses);
+    if (err)
+    {
+        goto out;
+    }
+
+    /* Set search domains, if any */
+    if (msg->search_domains[0])
+    {
+        err = SetDnsSearchDomains(msg->iface.name, msg->search_domains, &gpol_list, lists);
+    }
+
+    ApplyDnsSettings(gpol_nrpt || gpol_list);
+
+out:
+    return err;
+}
+
 static DWORD
 HandleWINSConfigMessage(const wins_cfg_message_t *msg, undo_lists_t *lists)
 {
@@ -2201,7 +3108,7 @@
 }
 
 static VOID
-HandleMessage(HANDLE pipe, HANDLE ovpn_proc,
+HandleMessage(HANDLE pipe, PPROCESS_INFORMATION proc_info,
               DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists)
 {
     pipe_message_t msg;
@@ -2264,6 +3171,14 @@
             ack.error_number = HandleDNSConfigMessage(&msg.dns, lists);
             break;
 
+        case msg_add_nrpt_cfg:
+        case msg_del_nrpt_cfg:
+        {
+            DWORD ovpn_pid = proc_info->dwProcessId;
+            ack.error_number = HandleDNSConfigNrptMessage(&msg.nrpt_dns, ovpn_pid, lists);
+        }
+        break;
+
         case msg_add_wins_cfg:
         case msg_del_wins_cfg:
             ack.error_number = HandleWINSConfigMessage(&msg.wins, lists);
@@ -2279,7 +3194,8 @@
         case msg_register_ring_buffers:
             if (msg.header.size == sizeof(msg.rrb))
             {
-                ack.error_number = HandleRegisterRingBuffers(&msg.rrb, ovpn_proc, lists);
+                HANDLE ovpn_hnd = proc_info->hProcess;
+                ack.error_number = HandleRegisterRingBuffers(&msg.rrb, ovpn_hnd, lists);
             }
             break;
 
@@ -2330,6 +3246,8 @@
                     ResetNameServers(item->data, AF_INET6);
                     break;
 
+                case undo_nrpt:
+                    UndoNrptRules(*(PDWORD)item->data);
                     break;
 
                 case undo_domains:
@@ -2653,7 +3571,7 @@
             break;
         }
 
-        HandleMessage(ovpn_pipe, proc_info.hProcess, bytes, 1, &exit_event, &undo_lists);
+        HandleMessage(ovpn_pipe, &proc_info, bytes, 1, &exit_event, &undo_lists);
     }
 
     WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);
@@ -2849,24 +3767,28 @@
 static void
 CleanupRegistry()
 {
-    HKEY key;
-    DWORD changed = 0;
+    BOOL changed = FALSE;
+
+    /* Clean up leftover NRPT rules */
+    BOOL gpol_nrpt;
+    changed = DeleteNrptRules(0, &gpol_nrpt);
 
     /* Clean up leftover DNS search list fragments */
+    HKEY key;
     BOOL gpol_list;
     GetDnsSearchListKey(NULL, &gpol_list, &key);
     if (key != INVALID_HANDLE_VALUE)
     {
         if (ResetDnsSearchDomains(key))
         {
-            changed++;
+            changed = TRUE;
         }
         RegCloseKey(key);
     }
 
     if (changed)
     {
-        ApplyDnsSettings(gpol_list);
+        ApplyDnsSettings(gpol_nrpt || gpol_list);
     }
 }
 
