src/openvpn/block_dns.c
6a33a34d
 /*
  *  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.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
6a33a34d
  *                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.
  *
caa54ac3
  *  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.
6a33a34d
  */
 
6cd7e08d
 #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"
 
445b192a
 #ifdef _WIN32
6a33a34d
 
 #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
 
81d882d5
 /* c38d57d1-05a7-4c33-904f-7fbceee60e82 */
6a33a34d
 DEFINE_GUID(
81d882d5
     FWPM_LAYER_ALE_AUTH_CONNECT_V4,
     0xc38d57d1,
     0x05a7,
     0x4c33,
     0x90, 0x4f, 0x7f, 0xbc, 0xee, 0xe6, 0x0e, 0x82
     );
 
 /* 4a72393b-319f-44bc-84c3-ba54dcb3b6b4 */
6a33a34d
 DEFINE_GUID(
81d882d5
     FWPM_LAYER_ALE_AUTH_CONNECT_V6,
     0x4a72393b,
     0x319f,
     0x44bc,
     0x84, 0xc3, 0xba, 0x54, 0xdc, 0xb3, 0xb6, 0xb4
     );
 
 /* d78e1e87-8644-4ea5-9437-d809ecefc971 */
6a33a34d
 DEFINE_GUID(
81d882d5
     FWPM_CONDITION_ALE_APP_ID,
     0xd78e1e87,
     0x8644,
     0x4ea5,
     0x94, 0x37, 0xd8, 0x09, 0xec, 0xef, 0xc9, 0x71
     );
 
 /* c35a604d-d22b-4e1a-91b4-68f674ee674b */
6a33a34d
 DEFINE_GUID(
81d882d5
     FWPM_CONDITION_IP_REMOTE_PORT,
     0xc35a604d,
     0xd22b,
     0x4e1a,
     0x91, 0xb4, 0x68, 0xf6, 0x74, 0xee, 0x67, 0x4b
     );
 
 /* 4cd62a49-59c3-4969-b7f3-bda5d32890a4 */
6a33a34d
 DEFINE_GUID(
81d882d5
     FWPM_CONDITION_IP_LOCAL_INTERFACE,
     0x4cd62a49,
     0x59c3,
     0x4969,
     0xb7, 0xf3, 0xbd, 0xa5, 0xd3, 0x28, 0x90, 0xa4
     );
6a33a34d
 
fc30dc5f
 /* UUID of WFP sublayer used by all instances of openvpn
81d882d5
  * 2f660d7e-6a37-11e6-a181-001e8c6e04a2 */
fc30dc5f
 DEFINE_GUID(
81d882d5
     OPENVPN_BLOCK_OUTSIDE_DNS_SUBLAYER,
     0x2f660d7e,
     0x6a37,
     0x11e6,
     0xa1, 0x81, 0x00, 0x1e, 0x8c, 0x6e, 0x04, 0xa2
     );
fc30dc5f
 
 static WCHAR *FIREWALL_NAME = L"OpenVPN";
 
27aa8728
 VOID NETIOAPI_API_
 InitializeIpInterfaceEntry(PMIB_IPINTERFACE_ROW Row);
 
6a33a34d
 /*
  * Default msg handler does nothing
  */
 static inline void
81d882d5
 default_msg_handler(DWORD err, const char *msg)
6a33a34d
 {
81d882d5
     return;
6a33a34d
 }
 
 #define CHECK_ERROR(err, msg) \
81d882d5
     if (err) { msg_handler(err, msg); goto out; }
6a33a34d
 
 /*
fc30dc5f
  * Add a persistent sublayer with specified uuid.
  */
 static DWORD
81d882d5
 add_sublayer(GUID uuid)
fc30dc5f
 {
81d882d5
     FWPM_SESSION0 session;
     HANDLE engine = NULL;
     DWORD err = 0;
     FWPM_SUBLAYER0 sublayer;
fc30dc5f
 
81d882d5
     memset(&session, 0, sizeof(session));
     memset(&sublayer, 0, sizeof(sublayer));
fc30dc5f
 
81d882d5
     err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engine);
     if (err != ERROR_SUCCESS)
     {
         goto out;
     }
fc30dc5f
 
81d882d5
     sublayer.subLayerKey = uuid;
     sublayer.displayData.name = FIREWALL_NAME;
     sublayer.displayData.description = FIREWALL_NAME;
     sublayer.flags = 0;
     sublayer.weight = 0x100;
fc30dc5f
 
81d882d5
     /* Add sublayer to the session */
     err = FwpmSubLayerAdd0(engine, &sublayer, NULL);
fc30dc5f
 
 out:
81d882d5
     if (engine)
     {
         FwpmEngineClose0(engine);
     }
     return err;
fc30dc5f
 }
 
 /*
6a33a34d
  * 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
81d882d5
 add_block_dns_filters(HANDLE *engine_handle,
                       int index,
                       const WCHAR *exe_path,
                       block_dns_msg_handler_t msg_handler
6a33a34d
                       )
 {
81d882d5
     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)
fc30dc5f
     {
81d882d5
         msg_handler = default_msg_handler;
fc30dc5f
     }
 
81d882d5
     /* 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");
         }
fc30dc5f
     }
 
81d882d5
     err = ConvertInterfaceIndexToLuid(index, &tapluid);
     CHECK_ERROR(err, "Convert interface index to luid failed");
6a33a34d
 
81d882d5
     err = FwpmGetAppIdFromFileName0(exe_path, &openvpnblob);
     CHECK_ERROR(err, "Get byte blob for openvpn executable name failed");
6a33a34d
 
81d882d5
     /* 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;
6a33a34d
 
81d882d5
     /* First filter. Permit IPv4 DNS queries from OpenVPN itself. */
     Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
     Filter.action.type = FWP_ACTION_PERMIT;
6a33a34d
 
81d882d5
     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;
6a33a34d
 
81d882d5
     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;
6a33a34d
 
81d882d5
     err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
     CHECK_ERROR(err, "Add filter to permit IPv4 port 53 traffic from OpenVPN failed");
6a33a34d
 
81d882d5
     /* Second filter. Permit IPv6 DNS queries from OpenVPN itself. */
     Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
6a33a34d
 
81d882d5
     err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
     CHECK_ERROR(err, "Add filter to permit IPv6 port 53 traffic from OpenVPN failed");
6a33a34d
 
81d882d5
     msg_handler(0, "Block_DNS: Added permit filters for exe_path");
6a33a34d
 
81d882d5
     /* 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;
6a33a34d
 
81d882d5
     err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
     CHECK_ERROR(err, "Add filter to block IPv4 DNS traffic failed");
6a33a34d
 
81d882d5
     /* Forth filter. Block all IPv6 DNS queries. */
     Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
6a33a34d
 
81d882d5
     err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
     CHECK_ERROR(err, "Add filter to block IPv6 DNS traffic failed");
6a33a34d
 
81d882d5
     msg_handler(0, "Block_DNS: Added block filters for all interfaces");
fc30dc5f
 
81d882d5
     /* 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 */
fc30dc5f
 
81d882d5
     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;
6a33a34d
 
81d882d5
     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;
6a33a34d
 
81d882d5
     err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
     CHECK_ERROR(err, "Add filter to permit IPv4 DNS traffic through TAP failed");
6a33a34d
 
81d882d5
     /* Sixth filter. Permit IPv6 DNS queries from TAP.
      * Use same weight as IPv4 filter */
     Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
6a33a34d
 
81d882d5
     err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
     CHECK_ERROR(err, "Add filter to permit IPv6 DNS traffic through TAP failed");
6a33a34d
 
81d882d5
     msg_handler(0, "Block_DNS: Added permit filters for TAP interface");
6a33a34d
 
 out:
 
81d882d5
     if (openvpnblob)
     {
         FwpmFreeMemory0((void **)&openvpnblob);
     }
6a33a34d
 
81d882d5
     if (err && *engine_handle)
6a33a34d
     {
81d882d5
         FwpmEngineClose0(*engine_handle);
         *engine_handle = NULL;
6a33a34d
     }
 
81d882d5
     return err;
6a33a34d
 }
 
 DWORD
81d882d5
 delete_block_dns_filters(HANDLE engine_handle)
6a33a34d
 {
81d882d5
     DWORD err = 0;
     /*
      * For dynamic sessions closing the engine removes all filters added in the session
      */
     if (engine_handle)
6a33a34d
     {
81d882d5
         err = FwpmEngineClose0(engine_handle);
6a33a34d
     }
81d882d5
     return err;
6a33a34d
 }
 
27aa8728
 /*
42292435
  * Return interface metric value for the specified interface index.
27aa8728
  *
  * Arguments:
  *   index         : The index of TAP adapter.
  *   family        : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).
42292435
  *   is_auto       : On return set to true if automatic metric is in use.
  *                   Unused if NULL.
  *
  * Returns positive metric value or -1 on error.
27aa8728
  */
 int
42292435
 get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto)
27aa8728
 {
     DWORD err = 0;
     MIB_IPINTERFACE_ROW ipiface;
     InitializeIpInterfaceEntry(&ipiface);
     ipiface.Family = family;
     ipiface.InterfaceIndex = index;
42292435
 
     if (is_auto)
     {
         *is_auto = 0;
     }
27aa8728
     err = GetIpInterfaceEntry(&ipiface);
42292435
 
     /* 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)
27aa8728
     {
42292435
         if (is_auto)
27aa8728
         {
42292435
             *is_auto = ipiface.UseAutomaticMetric;
27aa8728
         }
42292435
         return (int)ipiface.Metric;
27aa8728
     }
42292435
     return -1;
27aa8728
 }
 
 /*
  * 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;
 }
 
81d882d5
 #endif /* ifdef _WIN32 */