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 */ |