From: ValdikSS Date: Wed, 10 May 2017 18:47:53 +0000 (+0300) Subject: Set a low interface metric for tap adapter when block-outside-dns is in use X-Git-Tag: v2.5_beta1~695 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=27aa87283f6e766507287649aa5a63f1f5172645;p=thirdparty%2Fopenvpn.git Set a low interface metric for tap adapter when block-outside-dns is in use 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 Acked-by: Selva Nair 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 --- diff --git a/src/openvpn/block_dns.c b/src/openvpn/block_dns.c index e31765eed..d916aff28 100644 --- a/src/openvpn/block_dns.c +++ b/src/openvpn/block_dns.c @@ -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 */ diff --git a/src/openvpn/block_dns.h b/src/openvpn/block_dns.h index a7dadc468..dc607ff6d 100644 --- a/src/openvpn/block_dns.h +++ b/src/openvpn/block_dns.h @@ -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 diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 3c22d67a4..e5720344e 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -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!"); } diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c index 18e7aee18..0cbf5fde5 100644 --- a/src/openvpn/win32.c +++ b/src/openvpn/win32.c @@ -60,6 +60,12 @@ */ 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; diff --git a/src/openvpn/win32.h b/src/openvpn/win32.h index 4ee44fd36..cd1f101c2 100644 --- a/src/openvpn/win32.h +++ b/src/openvpn/win32.h @@ -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 diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c index 2bce598a7..68ab0576b 100644 --- a/src/openvpnserv/interactive.c +++ b/src/openvpnserv/interactive.c @@ -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; }