]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Set a low interface metric for tap adapter when block-outside-dns is in use
authorValdikSS <iam@valdikss.org.ru>
Wed, 10 May 2017 18:47:53 +0000 (21:47 +0300)
committerDavid Sommerseth <davids@openvpn.net>
Wed, 10 May 2017 23:17:03 +0000 (01:17 +0200)
Windows 10 before Creators Update used to resolve DNS using all
available adapters and IP addresses in parallel. Now it still
resolves addresses using all available adapters but in a round-robin
way, beginning with random adapter.
This behaviour introduces significant delay when block-outside-dns is
in use. Fortunately, setting low metric for the TAP interface solves
this issue, making Windows always pick TAP adapter first and disable
round-robin.

Signed-off-by: ValdikSS <iam@valdikss.org.ru>
Acked-by: Selva Nair <selva.nair@gmail.com>
Message-Id: <20170510184753.27145-1-valdikss@gmail.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg14624.html
Signed-off-by: David Sommerseth <davids@openvpn.net>
src/openvpn/block_dns.c
src/openvpn/block_dns.h
src/openvpn/init.c
src/openvpn/win32.c
src/openvpn/win32.h
src/openvpnserv/interactive.c

index e31765eed63a937f71ea81136c3047c6e274d26e..d916aff2865c03fb3477e7fa170a03f9724665f7 100644 (file)
@@ -110,6 +110,9 @@ DEFINE_GUID(
 
 static WCHAR *FIREWALL_NAME = L"OpenVPN";
 
+VOID NETIOAPI_API_
+InitializeIpInterfaceEntry(PMIB_IPINTERFACE_ROW Row);
+
 /*
  * Default msg handler does nothing
  */
@@ -341,4 +344,79 @@ delete_block_dns_filters(HANDLE engine_handle)
     return err;
 }
 
+/*
+ * Returns interface metric value for specified interface index.
+ *
+ * Arguments:
+ *   index         : The index of TAP adapter.
+ *   family        : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).
+ * Returns positive metric value or zero for automatic metric on success,
+ * a less then zero error code on failure.
+ */
+
+int
+get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family)
+{
+    DWORD err = 0;
+    MIB_IPINTERFACE_ROW ipiface;
+    InitializeIpInterfaceEntry(&ipiface);
+    ipiface.Family = family;
+    ipiface.InterfaceIndex = index;
+    err = GetIpInterfaceEntry(&ipiface);
+    if (err == NO_ERROR)
+    {
+        if (ipiface.UseAutomaticMetric)
+        {
+            return 0;
+        }
+        return ipiface.Metric;
+    }
+    return -err;
+}
+
+/*
+ * Sets interface metric value for specified interface index.
+ *
+ * Arguments:
+ *   index         : The index of TAP adapter.
+ *   family        : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).
+ *   metric        : Metric value. 0 for automatic metric.
+ * Returns 0 on success, a non-zero status code of the last failed action on failure.
+ */
+
+DWORD
+set_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family,
+                     const ULONG metric)
+{
+    DWORD err = 0;
+    MIB_IPINTERFACE_ROW ipiface;
+    InitializeIpInterfaceEntry(&ipiface);
+    ipiface.Family = family;
+    ipiface.InterfaceIndex = index;
+    err = GetIpInterfaceEntry(&ipiface);
+    if (err == NO_ERROR)
+    {
+        if (family == AF_INET)
+        {
+            /* required for IPv4 as per MSDN */
+            ipiface.SitePrefixLength = 0;
+        }
+        ipiface.Metric = metric;
+        if (metric == 0)
+        {
+            ipiface.UseAutomaticMetric = TRUE;
+        }
+        else
+        {
+            ipiface.UseAutomaticMetric = FALSE;
+        }
+        err = SetIpInterfaceEntry(&ipiface);
+        if (err == NO_ERROR)
+        {
+            return 0;
+        }
+    }
+    return err;
+}
+
 #endif /* ifdef _WIN32 */
index a7dadc468121b1ca8fd099e1d9ed6b7a59e24ba6..dc607ff6dad994102e690e8a03e3e9957c8a4670 100644 (file)
@@ -27,6 +27,9 @@
 #ifndef OPENVPN_BLOCK_DNS_H
 #define OPENVPN_BLOCK_DNS_H
 
+/* Any value less than 5 should work fine. 3 is choosen without any real reason. */
+#define BLOCK_DNS_IFACE_METRIC 3
+
 typedef void (*block_dns_msg_handler_t) (DWORD err, const char *msg);
 
 DWORD
@@ -36,5 +39,32 @@ DWORD
 add_block_dns_filters(HANDLE *engine, int iface_index, const WCHAR *exe_path,
                       block_dns_msg_handler_t msg_handler_callback);
 
+/**
+ * Returns interface metric value for specified interface index.
+ *
+ * @param index The index of TAP adapter
+ * @param family Address family (AF_INET for IPv4 and AF_INET6 for IPv6)
+ *
+ * @return positive metric value or zero for automatic metric on success,
+ * a less then zero error code on failure.
+ */
+
+int
+get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family);
+
+/**
+ * Sets interface metric value for specified interface index.
+ *
+ * @param index The index of TAP adapter
+ * @param family Address family (AF_INET for IPv4 and AF_INET6 for IPv6)
+ * @param metric Metric value. 0 for automatic metric
+ *
+ * @return 0 on success, a non-zero status code of the last failed action on failure.
+ */
+
+DWORD
+set_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family,
+                     const ULONG metric);
+
 #endif
 #endif
index 3c22d67a46a54cf1229ac7f5cb924af30e2a6dd1..e5720344e2a6696e0d757f6b8c305afa6a52d38a 100644 (file)
@@ -1837,7 +1837,7 @@ do_close_tun(struct context *c, bool force)
 #if defined(_WIN32)
             if (c->options.block_outside_dns)
             {
-                if (!win_wfp_uninit(c->options.msg_channel))
+                if (!win_wfp_uninit(adapter_index, c->options.msg_channel))
                 {
                     msg(M_FATAL, "Uninitialising WFP failed!");
                 }
@@ -1877,7 +1877,7 @@ do_close_tun(struct context *c, bool force)
 #if defined(_WIN32)
             if (c->options.block_outside_dns)
             {
-                if (!win_wfp_uninit(c->options.msg_channel))
+                if (!win_wfp_uninit(adapter_index, c->options.msg_channel))
                 {
                     msg(M_FATAL, "Uninitialising WFP failed!");
                 }
index 18e7aee187cbcf30014e18e4bf58af8936f6add9..0cbf5fde5a7e61bbb3a5405ac09be26392f66ae6 100644 (file)
  */
 static HANDLE m_hEngineHandle = NULL; /* GLOBAL */
 
+/*
+ * TAP adapter original metric value
+ */
+static int tap_metric_v4 = -1; /* GLOBAL */
+static int tap_metric_v6 = -1; /* GLOBAL */
+
 /*
  * Windows internal socket API state (opaque).
  */
@@ -1337,6 +1343,27 @@ win_wfp_block_dns(const NET_IFINDEX index, const HANDLE msg_channel)
 
     status = add_block_dns_filters(&m_hEngineHandle, index, openvpnpath,
                                    block_dns_msg_handler);
+    if (status == 0)
+    {
+        tap_metric_v4 = get_interface_metric(index, AF_INET);
+        tap_metric_v6 = get_interface_metric(index, AF_INET6);
+        if (tap_metric_v4 < 0)
+        {
+            /* error, should not restore metric */
+            tap_metric_v4 = -1;
+        }
+        if (tap_metric_v6 < 0)
+        {
+            /* error, should not restore metric */
+            tap_metric_v6 = -1;
+        }
+        status = set_interface_metric(index, AF_INET, BLOCK_DNS_IFACE_METRIC);
+        if (!status)
+        {
+            set_interface_metric(index, AF_INET6, BLOCK_DNS_IFACE_METRIC);
+        }
+    }
+
     ret = (status == 0);
 
 out:
@@ -1345,19 +1372,27 @@ out:
 }
 
 bool
-win_wfp_uninit(const HANDLE msg_channel)
+win_wfp_uninit(const NET_IFINDEX index, const HANDLE msg_channel)
 {
     dmsg(D_LOW, "Uninitializing WFP");
 
     if (msg_channel)
     {
         msg(D_LOW, "Using service to delete block dns filters");
-        win_block_dns_service(false, -1, msg_channel);
+        win_block_dns_service(false, index, msg_channel);
     }
     else
     {
         delete_block_dns_filters(m_hEngineHandle);
         m_hEngineHandle = NULL;
+        if (tap_metric_v4 >= 0)
+        {
+            set_interface_metric(index, AF_INET, tap_metric_v4);
+        }
+        if (tap_metric_v6 >= 0)
+        {
+            set_interface_metric(index, AF_INET6, tap_metric_v6);
+        }
     }
 
     return true;
index 4ee44fd3606a9ac602006e2902faee3b426afe36..cd1f101c2d42a0b91e5bdbe79e7e524d6b36c9a6 100644 (file)
@@ -293,7 +293,7 @@ WCHAR *wide_string(const char *utf8, struct gc_arena *gc);
 
 bool win_wfp_block_dns(const NET_IFINDEX index, const HANDLE msg_channel);
 
-bool win_wfp_uninit(const HANDLE msg_channel);
+bool win_wfp_uninit(const NET_IFINDEX index, const HANDLE msg_channel);
 
 #define WIN_XP 0
 #define WIN_VISTA 1
index 2bce598a77ba3d576628ba8921cbf12db3608839..68ab0576be0cbf936ba51e299e476ccef0c9311f 100644 (file)
@@ -94,6 +94,13 @@ typedef enum {
 } undo_type_t;
 typedef list_item_t *undo_lists_t[_undo_type_max];
 
+typedef struct {
+    HANDLE engine;
+    int index;
+    int metric_v4;
+    int metric_v6;
+} block_dns_data_t;
+
 
 static DWORD
 AddListItem(list_item_t **pfirst, LPVOID data)
@@ -885,6 +892,7 @@ static DWORD
 HandleBlockDNSMessage(const block_dns_message_t *msg, undo_lists_t *lists)
 {
     DWORD err = 0;
+    block_dns_data_t *interface_data;
     HANDLE engine = NULL;
     LPCWSTR exe_path;
 
@@ -901,16 +909,57 @@ HandleBlockDNSMessage(const block_dns_message_t *msg, undo_lists_t *lists)
         err = add_block_dns_filters(&engine, msg->iface.index, exe_path, BlockDNSErrHandler);
         if (!err)
         {
-            err = AddListItem(&(*lists)[block_dns], engine);
+            interface_data = malloc(sizeof(block_dns_data_t));
+            if (!interface_data)
+            {
+                return ERROR_OUTOFMEMORY;
+            }
+            interface_data->engine = engine;
+            interface_data->index = msg->iface.index;
+            interface_data->metric_v4 = get_interface_metric(msg->iface.index,
+                                                             AF_INET);
+            if (interface_data->metric_v4 < 0)
+            {
+                interface_data->metric_v4 = -1;
+            }
+            interface_data->metric_v6 = get_interface_metric(msg->iface.index,
+                                                             AF_INET6);
+            if (interface_data->metric_v6 < 0)
+            {
+                interface_data->metric_v6 = -1;
+            }
+            err = AddListItem(&(*lists)[block_dns], interface_data);
+            if (!err)
+            {
+                err = set_interface_metric(msg->iface.index, AF_INET,
+                                           BLOCK_DNS_IFACE_METRIC);
+                if (!err)
+                {
+                    set_interface_metric(msg->iface.index, AF_INET6,
+                                         BLOCK_DNS_IFACE_METRIC);
+                }
+            }
         }
     }
     else
     {
-        engine = RemoveListItem(&(*lists)[block_dns], CmpEngine, NULL);
-        if (engine)
+        interface_data = RemoveListItem(&(*lists)[block_dns], CmpEngine, NULL);
+        if (interface_data)
         {
+            engine = interface_data->engine;
             err = delete_block_dns_filters(engine);
             engine = NULL;
+            if (interface_data->metric_v4 >= 0)
+            {
+                set_interface_metric(msg->iface.index, AF_INET,
+                                     interface_data->metric_v4);
+            }
+            if (interface_data->metric_v6 >= 0)
+            {
+                set_interface_metric(msg->iface.index, AF_INET6,
+                                     interface_data->metric_v6);
+            }
+            free(interface_data);
         }
         else
         {
@@ -1325,6 +1374,7 @@ static VOID
 Undo(undo_lists_t *lists)
 {
     undo_type_t type;
+    block_dns_data_t *interface_data;
     for (type = 0; type < _undo_type_max; type++)
     {
         list_item_t **pnext = &(*lists)[type];
@@ -1350,8 +1400,18 @@ Undo(undo_lists_t *lists)
                     break;
 
                 case block_dns:
-                    delete_block_dns_filters(item->data);
-                    item->data = NULL;
+                    interface_data = (block_dns_data_t*)(item->data);
+                    delete_block_dns_filters(interface_data->engine);
+                    if (interface_data->metric_v4 >= 0)
+                    {
+                        set_interface_metric(interface_data->index, AF_INET,
+                                             interface_data->metric_v4);
+                    }
+                    if (interface_data->metric_v6 >= 0)
+                    {
+                        set_interface_metric(interface_data->index, AF_INET6,
+                                             interface_data->metric_v6);
+                    }
                     break;
             }