]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Set IPv6 DNS servers using interactive service
authorSelva Nair <selva.nair@gmail.com>
Thu, 24 Nov 2016 03:35:27 +0000 (22:35 -0500)
committerGert Doering <gert@greenie.muc.de>
Thu, 24 Nov 2016 10:58:57 +0000 (11:58 +0100)
- Any existing addresses are deleted before adding
- On close_tun all addresses are deleted (only if any were added)

Signed-off-by: Selva Nair <selva.nair@gmail.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <1479958527-29491-1-git-send-email-selva.nair@gmail.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg13222.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
doc/openvpn.8
include/openvpn-msg.h
src/openvpn/tun.c
src/openvpnserv/Makefile.am
src/openvpnserv/common.c
src/openvpnserv/interactive.c
src/openvpnserv/service.h

index 0aa04449de70b5dbf505cdf413a15522536949de..dd09c703e7d6e685f5b97eaa2d11161322e68711 100644 (file)
@@ -5696,7 +5696,7 @@ this option to set secondary DNS server addresses.
 Set primary domain name server IPv6 address.  Repeat
 this option to set secondary DNS server IPv6 addresses.
 
-Note: currently this is handled using netsh and requires admin rights (the
+Note: currently this is handled using netsh (the
 existing DHCP code can only do IPv4 DHCP, and that protocol only
 permits IPv4 addresses anywhere).  The option will be put into the
 environment, so an
index 4c13acf3532876411e3c871c1d6ab59698a6af2a..f7fbdd2dce0082d1ea246d6373793e1e4a3c6904 100644 (file)
@@ -79,10 +79,9 @@ typedef struct {
   message_header_t header;
   interface_t iface;
   char domains[512];
-  struct in_addr primary_ipv4;
-  struct in_addr secondary_ipv4;
-  struct in_addr6 primary_ipv6;
-  struct in_addr6 secondary_ipv6;
+  short family;
+  int addr_len;
+  inet_address_t addr[4]; /* support up to 4 dns addresses */
 } dns_cfg_message_t;
 
 typedef struct {
index 3ebf2b2b531c8dd273083e8978c96e50050daf8a..560b1a8e153cb1a46b8462768c09aab487944e9c 100644 (file)
@@ -135,6 +135,74 @@ out:
   return ret;
 }
 
+static bool
+do_dns6_service (bool add, const struct tuntap *tt)
+{
+  DWORD len;
+  bool ret = false;
+  ack_message_t ack;
+  struct gc_arena gc = gc_new ();
+  HANDLE pipe = tt->options.msg_channel;
+  int addr_len = add ? tt->options.dns6_len : 0;
+
+  if (addr_len == 0 && add) /* no addresses to add */
+      return true;
+
+  dns_cfg_message_t dns = {
+    .header = {
+      (add ? msg_add_dns_cfg : msg_del_dns_cfg),
+      sizeof (dns_cfg_message_t),
+      0 },
+    .iface = { .index = tt->adapter_index, .name = "" },
+    .domains = "",
+    .family = AF_INET6,
+    .addr_len = addr_len
+  };
+
+  /* interface name is required */
+  strncpy (dns.iface.name, tt->actual_name, sizeof (dns.iface.name));
+  dns.iface.name[sizeof (dns.iface.name) - 1] = '\0';
+
+  if (addr_len > _countof(dns.addr))
+    {
+      addr_len = _countof(dns.addr);
+      dns.addr_len = addr_len;
+      msg(M_WARN, "Number of IPv6 DNS addresses sent to service truncated to %d",
+          addr_len);
+    }
+
+  for (int i = 0; i < addr_len; ++i)
+    {
+      dns.addr[i].ipv6 = tt->options.dns6[i];
+    }
+
+  msg (D_LOW, "%s IPv6 dns servers on '%s' (if_index = %d) using service",
+       (add ? "Setting" : "Deleting"), dns.iface.name, dns.iface.index);
+
+  if (!WriteFile (pipe, &dns, sizeof (dns), &len, NULL) ||
+      !ReadFile (pipe, &ack, sizeof (ack), &len, NULL))
+    {
+      msg (M_WARN, "TUN: could not talk to service: %s [%lu]",
+           strerror_win32 (GetLastError (), &gc), GetLastError ());
+      goto out;
+    }
+
+  if (ack.error_number != NO_ERROR)
+    {
+      msg (M_WARN, "TUN: %s IPv6 dns failed using service: %s [status=%u if_name=%s]",
+           (add ? "adding" : "deleting"), strerror_win32 (ack.error_number, &gc),
+           ack.error_number, dns.iface.name);
+      goto out;
+    }
+
+  msg (M_INFO, "IPv6 dns servers %s using service", (add ? "set" : "deleted"));
+  ret = true;
+
+out:
+  gc_free (&gc);
+  return ret;
+}
+
 #endif
 
 #ifdef TARGET_SOLARIS
@@ -1384,7 +1452,7 @@ do_ifconfig (struct tuntap *tt,
        else if (tt->options.msg_channel)
          {
            do_address_service (true, AF_INET6, tt);
-           /* TODO: do_dns6_service() */
+           do_dns6_service (true, tt);
          }
        else
          {
@@ -5596,6 +5664,8 @@ close_tun (struct tuntap *tt)
           if (tt->options.msg_channel)
             {
               do_address_service (false, AF_INET6, tt);
+             if (tt->options.dns6_len > 0)
+                 do_dns6_service (false, tt);
             }
           else
             {
index 3521a342e04298c2bd2ae5e29070533e62f8db7c..58ecd91198f96401a2848cb2b27118f124d9f06f 100644 (file)
@@ -26,7 +26,7 @@ openvpnserv_CFLAGS = \
        -municode -D_UNICODE \
        -UNTDDI_VERSION -U_WIN32_WINNT \
        -D_WIN32_WINNT=_WIN32_WINNT_VISTA
-openvpnserv_LDADD = -ladvapi32 -luserenv -liphlpapi -lfwpuclnt -lrpcrt4 -lshlwapi -lnetapi32 -lws2_32
+openvpnserv_LDADD = -ladvapi32 -luserenv -liphlpapi -lfwpuclnt -lrpcrt4 -lshlwapi -lnetapi32 -lws2_32 -lntdll
 endif
 
 openvpnserv_SOURCES = \
index dba4724e021c711daa7850c81b1b341b1cca350d..eafee205d25b59fe8247e55affbea2e32e6b496d 100644 (file)
@@ -216,3 +216,15 @@ MsgToEventLog (DWORD flags, LPCTSTR format, ...)
 
   return error;
 }
+
+/* Convert a utf8 string to utf16. Caller should free the result */
+wchar_t *
+utf8to16 (const char *utf8)
+{
+  int n = MultiByteToWideChar (CP_UTF8, 0, utf8, -1, NULL, 0);
+  wchar_t *utf16 = malloc (n * sizeof (wchar_t));
+  if (!utf16)
+    return NULL;
+  MultiByteToWideChar (CP_UTF8, 0, utf8, -1, utf16, n);
+  return utf16;
+}
index ffaa1710cf0ee130d4e64e8cb2020bc103eb31a7..608bb0c3778b93e7f0dbb944fe6e1210143b52b7 100644 (file)
 #include <sddl.h>
 #include <shellapi.h>
 
+#ifdef HAVE_VERSIONHELPERS_H
+#include <versionhelpers.h>
+#else
+#include "compat-versionhelpers.h"
+#endif
+
 #include "openvpn-msg.h"
 #include "validate.h"
 #include "block_dns.h"
@@ -82,6 +88,8 @@ typedef enum {
   address,
   route,
   block_dns,
+  undo_dns4,
+  undo_dns6,
   _undo_type_max
 } undo_type_t;
 typedef list_item_t* undo_lists_t[_undo_type_max];
@@ -962,6 +970,156 @@ HandleRegisterDNSMessage (void)
   return err;
 }
 
+/**
+ * Run the command: netsh interface $proto $action dns $if_name $addr [validate=no]
+ * @param  action      "delete" or "add"
+ * @param  proto       "ipv6" or "ip"
+ * @param  if_name     "name_of_interface"
+ * @param  addr         IPv4 (for proto = ip) or IPv6 address as a string
+ *
+ * If addr is null and action = "delete" all addresses are deleted.
+ */
+static DWORD
+netsh_dns_cmd (const wchar_t *action, const wchar_t *proto, const wchar_t *if_name, const wchar_t *addr)
+{
+  DWORD err = 0;
+  int timeout = 30000; /* in msec */
+  wchar_t argv0[MAX_PATH];
+
+  if (!addr)
+    {
+      if (wcscmp(action, L"delete") == 0)
+          addr = L"all";
+      else /* nothing to do -- return success*/
+          goto out;
+    }
+
+  /* Path of netsh */
+  int n = GetSystemDirectory (argv0, MAX_PATH);
+  if (n > 0 && n < MAX_PATH) /* got system directory */
+   {
+      wcsncat(argv0, L"\\netsh.exe", MAX_PATH - n - 1);
+   }
+  else
+   {
+      wcsncpy(argv0, L"C:\\Windows\\system32\\netsh.exe", MAX_PATH);
+   }
+
+  /* cmd template:
+   * netsh interface $proto $action dns $if_name $addr [validate=no]
+   */
+  const wchar_t *fmt = L"netsh interface %s %s dns \"%s\" %s";
+
+  /* max cmdline length in wchars -- include room for worst case and some */
+  int ncmdline = wcslen(fmt) + wcslen(if_name) + wcslen(addr) + 32 + 1;
+  wchar_t *cmdline = malloc(ncmdline*sizeof(wchar_t));
+  if (!cmdline)
+  {
+     err = ERROR_OUTOFMEMORY;
+     goto out;
+  }
+
+  openvpn_sntprintf (cmdline, ncmdline, fmt, proto, action, if_name, addr);
+
+  if (IsWindows7OrGreater())
+    {
+      wcsncat(cmdline, L" validate=no", ncmdline - wcslen(cmdline) - 1);
+    }
+  err = ExecCommand (argv0, cmdline, timeout);
+
+out:
+  free (cmdline);
+  return err;
+}
+
+/* Delete all IPv4 or IPv6 dns servers for an interface */
+static DWORD
+DeleteDNS(short family, wchar_t *if_name)
+{
+   wchar_t *proto = (family == AF_INET6) ? L"ipv6" : L"ip";
+   return netsh_dns_cmd (L"delete", proto, if_name, NULL);
+}
+
+/* Add an IPv4 or IPv6 dns server to an interface */
+static DWORD
+AddDNS(short family, wchar_t *if_name, wchar_t *addr)
+{
+   wchar_t *proto = (family == AF_INET6) ? L"ipv6" : L"ip";
+   return netsh_dns_cmd (L"add", proto, if_name, addr);
+}
+
+static BOOL
+CmpWString (LPVOID item, LPVOID str)
+{
+  return (wcscmp (item, str) == 0) ? TRUE : FALSE;
+}
+
+static DWORD
+HandleDNSConfigMessage (const dns_cfg_message_t *msg, undo_lists_t *lists)
+{
+  DWORD err = 0;
+  wchar_t addr[46]; /* large enough to hold string representation of an ipv4 / ipv6 address */
+  undo_type_t undo_type = (msg->family == AF_INET6) ? undo_dns4 : undo_dns6;
+  int addr_len = msg->addr_len;
+
+  /* sanity check */
+  if (addr_len > _countof(msg->addr))
+     addr_len = _countof(msg->addr);
+
+  if (!msg->iface.name[0])  /* interface name is required */
+      return ERROR_MESSAGE_DATA;
+
+  wchar_t *wide_name = utf8to16(msg->iface.name); /* utf8 to wide-char */
+  if (!wide_name)
+    return ERROR_OUTOFMEMORY;
+
+  /* We delete all current addresses before adding any
+   * OR if the message type is del_dns_cfg
+   */
+  if (addr_len > 0 || msg->header.type == msg_del_dns_cfg)
+    {
+      err = DeleteDNS(msg->family, wide_name);
+      if (err)
+        goto out;
+      free (RemoveListItem (&(*lists)[undo_type], CmpWString, wide_name));
+    }
+
+  if (msg->header.type == msg_del_dns_cfg)  /* job done */
+      goto out;
+
+  for (int i = 0; i < addr_len; ++i)
+    {
+      if (msg->family == AF_INET6)
+          RtlIpv6AddressToStringW (&msg->addr[i].ipv6, addr);
+      else
+           RtlIpv4AddressToStringW (&msg->addr[i].ipv4, addr);
+      err = AddDNS(msg->family, wide_name, addr);
+      if (i == 0 && err)
+          goto out;
+      /* We do not check for duplicate addresses, so any error in adding
+       * additional addresses is ignored.
+       */
+    }
+
+  if (msg->addr_len > 0)
+    {
+      wchar_t *tmp_name = wcsdup(wide_name);
+      if (!tmp_name || AddListItem(&(*lists)[undo_type], tmp_name))
+        {
+           free(tmp_name);
+           DeleteDNS(msg->family, wide_name);
+           err = ERROR_OUTOFMEMORY;
+           goto out;
+        }
+    }
+
+  err = 0;
+
+out:
+  free(wide_name);
+  return err;
+}
+
 static VOID
 HandleMessage (HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists)
 {
@@ -972,6 +1130,7 @@ HandleMessage (HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_list
     route_message_t route;
     flush_neighbors_message_t flush_neighbors;
     block_dns_message_t block_dns;
+    dns_cfg_message_t dns;
   } msg;
   ack_message_t ack = {
     .header = {
@@ -1017,6 +1176,11 @@ HandleMessage (HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_list
         ack.error_number = HandleRegisterDNSMessage ();
         break;
 
+    case msg_add_dns_cfg:
+    case msg_del_dns_cfg:
+        ack.error_number = HandleDNSConfigMessage (&msg.dns, lists);
+        break;
+
     default:
       ack.error_number = ERROR_MESSAGE_TYPE;
       MsgToEventLog (MSG_FLAGS_ERROR, TEXT("Unknown message type %d"), msg.header.type);
@@ -1048,6 +1212,14 @@ Undo (undo_lists_t *lists)
               DeleteRoute (item->data);
               break;
 
+            case undo_dns4:
+              DeleteDNS(AF_INET, item->data);
+              break;
+
+            case undo_dns6:
+              DeleteDNS(AF_INET6, item->data);
+              break;
+
             case block_dns:
               delete_block_dns_filters (item->data);
               item->data = NULL;
index 94bfb0794e1065d9a921292f2b41e36240d5776c..c5d745f6d8b0b69cbdc7175c9a5f982199b5eb7c 100644 (file)
@@ -89,4 +89,7 @@ BOOL ReportStatusToSCMgr (SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)
 LPCTSTR GetLastErrorText ();
 DWORD MsgToEventLog (DWORD flags, LPCTSTR lpszMsg, ...);
 
+/* Convert a utf8 string to utf16. Caller should free the result */
+wchar_t *utf8to16 (const char *utf8);
+
 #endif