| /* |
| * OpenVPN -- An application to securely tunnel IP networks |
| * over a single TCP/UDP port, with support for SSL/TLS-based |
| * session authentication and key exchange, |
| * packet encryption, packet authentication, and |
| * packet compression. |
| * |
| * Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net> |
| * 2015-2016 <iam@valdikss.org.ru> |
| * 2016 Selva Nair <selva.nair@gmail.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #elif defined(_MSC_VER) |
| #include "config-msvc.h" |
| #endif |
| #ifdef HAVE_CONFIG_VERSION_H |
| #include "config-version.h" |
| #endif |
| |
| #include "syshead.h" |
| |
| #ifdef _WIN32 |
| |
| #include <fwpmu.h> |
| #include <initguid.h> |
| #include <fwpmtypes.h> |
| #include <winsock2.h> |
| #include <ws2ipdef.h> |
| #include <iphlpapi.h> |
| #include "block_dns.h" |
| |
| /* |
| * WFP-related defines and GUIDs not in mingw32 |
| */ |
| |
| #ifndef FWPM_SESSION_FLAG_DYNAMIC |
| #define FWPM_SESSION_FLAG_DYNAMIC 0x00000001 |
| #endif |
| |
| /* c38d57d1-05a7-4c33-904f-7fbceee60e82 */ |
| DEFINE_GUID( |
| FWPM_LAYER_ALE_AUTH_CONNECT_V4, |
| 0xc38d57d1, |
| 0x05a7, |
| 0x4c33, |
| 0x90, 0x4f, 0x7f, 0xbc, 0xee, 0xe6, 0x0e, 0x82 |
| ); |
| |
| /* 4a72393b-319f-44bc-84c3-ba54dcb3b6b4 */ |
| DEFINE_GUID( |
| FWPM_LAYER_ALE_AUTH_CONNECT_V6, |
| 0x4a72393b, |
| 0x319f, |
| 0x44bc, |
| 0x84, 0xc3, 0xba, 0x54, 0xdc, 0xb3, 0xb6, 0xb4 |
| ); |
| |
| /* d78e1e87-8644-4ea5-9437-d809ecefc971 */ |
| DEFINE_GUID( |
| FWPM_CONDITION_ALE_APP_ID, |
| 0xd78e1e87, |
| 0x8644, |
| 0x4ea5, |
| 0x94, 0x37, 0xd8, 0x09, 0xec, 0xef, 0xc9, 0x71 |
| ); |
| |
| /* c35a604d-d22b-4e1a-91b4-68f674ee674b */ |
| DEFINE_GUID( |
| FWPM_CONDITION_IP_REMOTE_PORT, |
| 0xc35a604d, |
| 0xd22b, |
| 0x4e1a, |
| 0x91, 0xb4, 0x68, 0xf6, 0x74, 0xee, 0x67, 0x4b |
| ); |
| |
| /* 4cd62a49-59c3-4969-b7f3-bda5d32890a4 */ |
| DEFINE_GUID( |
| FWPM_CONDITION_IP_LOCAL_INTERFACE, |
| 0x4cd62a49, |
| 0x59c3, |
| 0x4969, |
| 0xb7, 0xf3, 0xbd, 0xa5, 0xd3, 0x28, 0x90, 0xa4 |
| ); |
| |
| /* UUID of WFP sublayer used by all instances of openvpn |
| * 2f660d7e-6a37-11e6-a181-001e8c6e04a2 */ |
| DEFINE_GUID( |
| OPENVPN_BLOCK_OUTSIDE_DNS_SUBLAYER, |
| 0x2f660d7e, |
| 0x6a37, |
| 0x11e6, |
| 0xa1, 0x81, 0x00, 0x1e, 0x8c, 0x6e, 0x04, 0xa2 |
| ); |
| |
| static WCHAR *FIREWALL_NAME = L"OpenVPN"; |
| |
| VOID NETIOAPI_API_ |
| InitializeIpInterfaceEntry(PMIB_IPINTERFACE_ROW Row); |
| |
| /* |
| * Default msg handler does nothing |
| */ |
| static inline void |
| default_msg_handler(DWORD err, const char *msg) |
| { |
| return; |
| } |
| |
| #define CHECK_ERROR(err, msg) \ |
| if (err) { msg_handler(err, msg); goto out; } |
| |
| /* |
| * Add a persistent sublayer with specified uuid. |
| */ |
| static DWORD |
| add_sublayer(GUID uuid) |
| { |
| FWPM_SESSION0 session; |
| HANDLE engine = NULL; |
| DWORD err = 0; |
| FWPM_SUBLAYER0 sublayer; |
| |
| memset(&session, 0, sizeof(session)); |
| memset(&sublayer, 0, sizeof(sublayer)); |
| |
| err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engine); |
| if (err != ERROR_SUCCESS) |
| { |
| goto out; |
| } |
| |
| sublayer.subLayerKey = uuid; |
| sublayer.displayData.name = FIREWALL_NAME; |
| sublayer.displayData.description = FIREWALL_NAME; |
| sublayer.flags = 0; |
| sublayer.weight = 0x100; |
| |
| /* Add sublayer to the session */ |
| err = FwpmSubLayerAdd0(engine, &sublayer, NULL); |
| |
| out: |
| if (engine) |
| { |
| FwpmEngineClose0(engine); |
| } |
| return err; |
| } |
| |
| /* |
| * Block outgoing port 53 traffic except for |
| * (i) adapter with the specified index |
| * OR |
| * (ii) processes with the specified executable path |
| * The firewall filters added here are automatically removed when the process exits or |
| * on calling delete_block_dns_filters(). |
| * Arguments: |
| * engine_handle : On successful return contains the handle for a newly opened fwp session |
| * in which the filters are added. |
| * May be closed by passing to delete_block_dns_filters to remove the filters. |
| * index : The index of adapter for which traffic is permitted. |
| * exe_path : Path of executable for which traffic is permitted. |
| * msg_handler : An optional callback function for error reporting. |
| * Returns 0 on success, a non-zero status code of the last failed action on failure. |
| */ |
| |
| DWORD |
| add_block_dns_filters(HANDLE *engine_handle, |
| int index, |
| const WCHAR *exe_path, |
| block_dns_msg_handler_t msg_handler |
| ) |
| { |
| FWPM_SESSION0 session = {0}; |
| FWPM_SUBLAYER0 *sublayer_ptr = NULL; |
| NET_LUID tapluid; |
| UINT64 filterid; |
| FWP_BYTE_BLOB *openvpnblob = NULL; |
| FWPM_FILTER0 Filter = {0}; |
| FWPM_FILTER_CONDITION0 Condition[2] = {0}; |
| DWORD err = 0; |
| |
| if (!msg_handler) |
| { |
| msg_handler = default_msg_handler; |
| } |
| |
| /* Add temporary filters which don't survive reboots or crashes. */ |
| session.flags = FWPM_SESSION_FLAG_DYNAMIC; |
| |
| *engine_handle = NULL; |
| |
| err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, engine_handle); |
| CHECK_ERROR(err, "FwpEngineOpen: open fwp session failed"); |
| msg_handler(0, "Block_DNS: WFP engine opened"); |
| |
| /* Check sublayer exists and add one if it does not. */ |
| if (FwpmSubLayerGetByKey0(*engine_handle, &OPENVPN_BLOCK_OUTSIDE_DNS_SUBLAYER, &sublayer_ptr) |
| == ERROR_SUCCESS) |
| { |
| msg_handler(0, "Block_DNS: Using existing sublayer"); |
| FwpmFreeMemory0((void **)&sublayer_ptr); |
| } |
| else |
| { /* Add a new sublayer -- as another process may add it in the meantime, |
| * do not treat "already exists" as an error */ |
| err = add_sublayer(OPENVPN_BLOCK_OUTSIDE_DNS_SUBLAYER); |
| |
| if (err == FWP_E_ALREADY_EXISTS || err == ERROR_SUCCESS) |
| { |
| msg_handler(0, "Block_DNS: Added a persistent sublayer with pre-defined UUID"); |
| } |
| else |
| { |
| CHECK_ERROR(err, "add_sublayer: failed to add persistent sublayer"); |
| } |
| } |
| |
| err = ConvertInterfaceIndexToLuid(index, &tapluid); |
| CHECK_ERROR(err, "Convert interface index to luid failed"); |
| |
| err = FwpmGetAppIdFromFileName0(exe_path, &openvpnblob); |
| CHECK_ERROR(err, "Get byte blob for openvpn executable name failed"); |
| |
| /* Prepare filter. */ |
| Filter.subLayerKey = OPENVPN_BLOCK_OUTSIDE_DNS_SUBLAYER; |
| Filter.displayData.name = FIREWALL_NAME; |
| Filter.weight.type = FWP_UINT8; |
| Filter.weight.uint8 = 0xF; |
| Filter.filterCondition = Condition; |
| Filter.numFilterConditions = 2; |
| |
| /* First filter. Permit IPv4 DNS queries from OpenVPN itself. */ |
| Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; |
| Filter.action.type = FWP_ACTION_PERMIT; |
| |
| Condition[0].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT; |
| Condition[0].matchType = FWP_MATCH_EQUAL; |
| Condition[0].conditionValue.type = FWP_UINT16; |
| Condition[0].conditionValue.uint16 = 53; |
| |
| Condition[1].fieldKey = FWPM_CONDITION_ALE_APP_ID; |
| Condition[1].matchType = FWP_MATCH_EQUAL; |
| Condition[1].conditionValue.type = FWP_BYTE_BLOB_TYPE; |
| Condition[1].conditionValue.byteBlob = openvpnblob; |
| |
| err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid); |
| CHECK_ERROR(err, "Add filter to permit IPv4 port 53 traffic from OpenVPN failed"); |
| |
| /* Second filter. Permit IPv6 DNS queries from OpenVPN itself. */ |
| Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; |
| |
| err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid); |
| CHECK_ERROR(err, "Add filter to permit IPv6 port 53 traffic from OpenVPN failed"); |
| |
| msg_handler(0, "Block_DNS: Added permit filters for exe_path"); |
| |
| /* Third filter. Block all IPv4 DNS queries. */ |
| Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; |
| Filter.action.type = FWP_ACTION_BLOCK; |
| Filter.weight.type = FWP_EMPTY; |
| Filter.numFilterConditions = 1; |
| |
| err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid); |
| CHECK_ERROR(err, "Add filter to block IPv4 DNS traffic failed"); |
| |
| /* Forth filter. Block all IPv6 DNS queries. */ |
| Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; |
| |
| err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid); |
| CHECK_ERROR(err, "Add filter to block IPv6 DNS traffic failed"); |
| |
| msg_handler(0, "Block_DNS: Added block filters for all interfaces"); |
| |
| /* Fifth filter. Permit IPv4 DNS queries from TAP. |
| * Use a non-zero weight so that the permit filters get higher priority |
| * over the block filter added with automatic weighting */ |
| |
| Filter.weight.type = FWP_UINT8; |
| Filter.weight.uint8 = 0xE; |
| Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; |
| Filter.action.type = FWP_ACTION_PERMIT; |
| Filter.numFilterConditions = 2; |
| |
| Condition[1].fieldKey = FWPM_CONDITION_IP_LOCAL_INTERFACE; |
| Condition[1].matchType = FWP_MATCH_EQUAL; |
| Condition[1].conditionValue.type = FWP_UINT64; |
| Condition[1].conditionValue.uint64 = &tapluid.Value; |
| |
| err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid); |
| CHECK_ERROR(err, "Add filter to permit IPv4 DNS traffic through TAP failed"); |
| |
| /* Sixth filter. Permit IPv6 DNS queries from TAP. |
| * Use same weight as IPv4 filter */ |
| Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; |
| |
| err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid); |
| CHECK_ERROR(err, "Add filter to permit IPv6 DNS traffic through TAP failed"); |
| |
| msg_handler(0, "Block_DNS: Added permit filters for TAP interface"); |
| |
| out: |
| |
| if (openvpnblob) |
| { |
| FwpmFreeMemory0((void **)&openvpnblob); |
| } |
| |
| if (err && *engine_handle) |
| { |
| FwpmEngineClose0(*engine_handle); |
| *engine_handle = NULL; |
| } |
| |
| return err; |
| } |
| |
| DWORD |
| delete_block_dns_filters(HANDLE engine_handle) |
| { |
| DWORD err = 0; |
| /* |
| * For dynamic sessions closing the engine removes all filters added in the session |
| */ |
| if (engine_handle) |
| { |
| err = FwpmEngineClose0(engine_handle); |
| } |
| return err; |
| } |
| |
| /* |
| * Return interface metric value for the specified interface index. |
| * |
| * Arguments: |
| * index : The index of TAP adapter. |
| * family : Address family (AF_INET for IPv4 and AF_INET6 for IPv6). |
| * is_auto : On return set to true if automatic metric is in use. |
| * Unused if NULL. |
| * |
| * Returns positive metric value or -1 on error. |
| */ |
| int |
| get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto) |
| { |
| DWORD err = 0; |
| MIB_IPINTERFACE_ROW ipiface; |
| InitializeIpInterfaceEntry(&ipiface); |
| ipiface.Family = family; |
| ipiface.InterfaceIndex = index; |
| |
| if (is_auto) |
| { |
| *is_auto = 0; |
| } |
| err = GetIpInterfaceEntry(&ipiface); |
| |
| /* On Windows metric is never > INT_MAX so return value of int is ok. |
| * But we check for overflow nevertheless. |
| */ |
| if (err == NO_ERROR && ipiface.Metric <= INT_MAX) |
| { |
| if (is_auto) |
| { |
| *is_auto = ipiface.UseAutomaticMetric; |
| } |
| return (int)ipiface.Metric; |
| } |
| return -1; |
| } |
| |
| /* |
| * 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 */ |