tunsafe-clang15/network_win32_dnsblock.cpp

429 lines
13 KiB
C++

// SPDX-License-Identifier: AGPL-1.0-only
// Copyright (C) 2018 Ludvig Strigeus <info@tunsafe.com>. All Rights Reserved.
#include "stdafx.h"
#include "tunsafe_types.h"
#include "network_win32_dnsblock.h"
#include <fwpmu.h>
#include <fwpmtypes.h>
#include <string.h>
#pragma comment (lib, "Fwpuclnt.lib")
static const GUID TUNSAFE_DNS_SUBLAYER = {0x1ce6cce2, 0xcc8f, 0x4175, { 0xac, 0x7b, 0x95, 0xfd, 0xe8, 0x95, 0x80, 0x92}};
static const GUID TUNSAFE_GLOBAL_BLOCK_SUBLAYER = {0x1ce6cce2, 0xcc8f, 0x4175,{0xac, 0x7b, 0x95, 0xfd, 0xe8, 0x95, 0x80, 0x93}};
static bool GetFwpmAppIdFromCurrentProcess(FWP_BYTE_BLOB **appid) {
wchar_t module_filename[MAX_PATH];
DWORD err = GetModuleFileNameW(NULL, module_filename, ARRAYSIZE(module_filename));
if (err == 0 || err == ARRAYSIZE(module_filename))
return false;
err = FwpmGetAppIdFromFileName0(module_filename, appid);
if (err != 0)
return false;
return true;
}
static uint8 internet_fw_blocking_state;
static inline bool FwpmFilterAddCheckedAleConnect(HANDLE handle, FWPM_FILTER0 *filter, bool also_ipv6, int idx) {
DWORD err;
UINT64 dummy;
filter->layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
err = FwpmFilterAdd0(handle, filter, NULL, &dummy);
if (err != 0) {
RERROR("FwpmFilterAdd0 #%d failed (%s): %d", idx, "ipv4", err);
return false;
}
if (also_ipv6) {
filter->layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
err = FwpmFilterAdd0(handle, filter, NULL, &dummy);
if (err != 0) {
RERROR("FwpmFilterAdd0 #%d failed (%s): %d", idx, "ipv6", err);
return false;
}
}
return true;
}
DnsBlocker::DnsBlocker() {
also_ipv6_ = false;
handle_ = NULL;
}
DnsBlocker::~DnsBlocker() {
RestoreDns();
}
bool DnsBlocker::BlockDnsExceptOnAdapter(const NET_LUID &luid, bool also_ipv6) {
FWPM_SUBLAYER0 *sublayer = NULL;
FWP_BYTE_BLOB *fwp_appid = NULL;
FWPM_FILTER0 filter;
FWPM_FILTER_CONDITION0 filter_condition[2];
DWORD err;
HANDLE handle = NULL;
// Check if it already matches
if (handle_ != NULL) {
if (memcmp(&luid, &luid_, sizeof(luid)) == 0 && also_ipv6_)
return true;
FwpmEngineClose0(handle_);
handle_ = NULL;
}
{
FWPM_SESSION0 session = {0};
session.flags = FWPM_SESSION_FLAG_DYNAMIC;
err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &handle);
if (err != 0) {
RERROR("FwpmEngineOpen0 failed: %d", err);
goto getout;
}
}
{
FWPM_SUBLAYER0 sublayer = {0};
sublayer.subLayerKey = TUNSAFE_DNS_SUBLAYER;
sublayer.displayData.name = L"TunSafe DNS Block";
sublayer.weight = 0x100;
err = FwpmSubLayerAdd0(handle, &sublayer, NULL);
if (err != 0) {
RERROR("FwpmSubLayerAdd0 failed: %d", err);
goto getout;
}
}
if (!GetFwpmAppIdFromCurrentProcess(&fwp_appid)) {
RERROR("GetFwpmAppIdFromCurrentProcess failed");
goto getout;
}
// Allow all queries to port 53 from our process
memset(&filter, 0, sizeof(filter));
filter_condition[0].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
filter_condition[0].matchType = FWP_MATCH_EQUAL;
filter_condition[0].conditionValue.type = FWP_UINT16;
filter_condition[0].conditionValue.uint16 = 53;
filter_condition[1].fieldKey = FWPM_CONDITION_ALE_APP_ID;
filter_condition[1].matchType = FWP_MATCH_EQUAL;
filter_condition[1].conditionValue.type = FWP_BYTE_BLOB_TYPE;
filter_condition[1].conditionValue.byteBlob = fwp_appid;
filter.filterCondition = filter_condition;
filter.numFilterConditions = 2;
filter.subLayerKey = TUNSAFE_DNS_SUBLAYER;
filter.displayData.name = L"TunSafe DNS Block";
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = 15;
filter.action.type = FWP_ACTION_PERMIT;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 1))
goto getout;
// Allow DNS queries from TAP
filter_condition[1].fieldKey = FWPM_CONDITION_IP_LOCAL_INTERFACE;
filter_condition[1].conditionValue.type = FWP_UINT64;
filter_condition[1].conditionValue.uint64 = (uint64*)&luid.Value;
filter.weight.uint8 = 14;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 2))
goto getout;
// Block all IPv4 and IPv6
filter.numFilterConditions = 1;
filter.weight.type = FWP_EMPTY;
filter.action.type = FWP_ACTION_BLOCK;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 3))
goto getout;
goto success;
getout:
if (handle != NULL) {
FwpmEngineClose0(handle);
handle = NULL;
}
success:
if (fwp_appid)
FwpmFreeMemory0((void **)&fwp_appid);
handle_ = handle;
also_ipv6_ = also_ipv6;
luid_ = luid;
return handle != NULL;
}
void DnsBlocker::RestoreDns() {
HANDLE h = handle_;
if (h) {
handle_ = NULL;
FwpmEngineClose0(h);
}
}
static bool RemovePersistentInternetBlockingInner(HANDLE handle, bool destroy_sublayer) {
FWPM_FILTER_ENUM_TEMPLATE0 enum_template = {0};
HANDLE enum_handle = NULL;
DWORD err;
UINT32 num_returned;
FWPM_FILTER0 **filter = NULL;
for (int iptype = 0; iptype < 2; iptype++) {
enum_template.layerKey = iptype == 0 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
enum_template.actionMask = 0xffffffff;
err = FwpmFilterCreateEnumHandle0(handle, &enum_template, &enum_handle);
if (err != 0) {
RERROR("FwpmFilterCreateEnumHandle0 failed: %d", err);
goto getout;
}
do {
err = FwpmFilterEnum0(handle, enum_handle, 256, &filter, &num_returned);
if (err != 0) {
RERROR("FwpmFilterEnum0 failed: %d", err);
goto getout;
}
for (UINT32 i = 0; i < num_returned; i++) {
FWPM_FILTER0 *cur_filter = filter[i];
if (memcmp(&cur_filter->subLayerKey, &TUNSAFE_GLOBAL_BLOCK_SUBLAYER, sizeof(GUID)) == 0 && (destroy_sublayer || cur_filter->numFilterConditions != 0)) {
err = FwpmFilterDeleteById0(handle, cur_filter->filterId);
if (err != 0)
RERROR("FwpmFilterDeleteById0 failed: %d", err);
}
}
FwpmFreeMemory0((void**)&filter);
} while (num_returned == 256);
FwpmFilterDestroyEnumHandle0(handle, enum_handle);
enum_handle = NULL;
}
if (destroy_sublayer) {
err = FwpmSubLayerDeleteByKey0(handle, &TUNSAFE_GLOBAL_BLOCK_SUBLAYER);
if (err != 0 && err != FWP_E_SUBLAYER_NOT_FOUND) {
RERROR("FwpmSubLayerDeleteByKey0 failed: %d", err);
goto getout;
}
}
getout:
if (enum_handle != NULL) {
FwpmFilterDestroyEnumHandle0(handle, enum_handle);
}
return false;
}
struct LastKillswitchSettings {
NET_LUID luid_to_allow;
bool also_ipv6;
bool allow_local_networks;
};
static LastKillswitchSettings last_killswitch_settings;
bool AddKillSwitchFirewall(const NET_LUID &luid_to_allow, bool also_ipv6, bool allow_local_networks) {
FWPM_SUBLAYER0 *sublayer_p = NULL;
FWP_BYTE_BLOB *fwp_appid = NULL;
FWPM_FILTER0 filter;
FWPM_FILTER_CONDITION0 filter_condition[3];
DWORD err;
HANDLE handle = NULL;
bool success = false;
LastKillswitchSettings new_settings;
new_settings.luid_to_allow = luid_to_allow;
new_settings.also_ipv6 = also_ipv6;
new_settings.allow_local_networks = allow_local_networks;
{
FWPM_SESSION0 session = {0};
err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &handle);
if (err != 0) {
RERROR("FwpmEngineOpen0 failed: %d", err);
goto getout;
}
}
if (FwpmSubLayerGetByKey0(handle, &TUNSAFE_GLOBAL_BLOCK_SUBLAYER, &sublayer_p) == 0) {
// The sublayer already exists
FwpmFreeMemory0((void **)&sublayer_p);
if (memcmp(&last_killswitch_settings, &new_settings, sizeof(new_settings)) != 0)
RemovePersistentInternetBlockingInner(handle, false);
} else {
// Add new sublayer
FWPM_SUBLAYER0 sublayer = {0};
sublayer.subLayerKey = TUNSAFE_GLOBAL_BLOCK_SUBLAYER;
sublayer.displayData.name = L"TunSafe Global Block";
sublayer.weight = 0x101;
err = FwpmSubLayerAdd0(handle, &sublayer, NULL);
if (err != 0) {
RERROR("FwpmSubLayerAdd0 failed: %d", err);
goto getout;
}
}
if (!GetFwpmAppIdFromCurrentProcess(&fwp_appid)) {
RERROR("GetFwpmAppIdFromCurrentProcess failed");
goto getout;
}
// Allow all outgoing queries from our process
memset(&filter, 0, sizeof(filter));
filter_condition[0].fieldKey = FWPM_CONDITION_ALE_APP_ID;
filter_condition[0].matchType = FWP_MATCH_EQUAL;
filter_condition[0].conditionValue.type = FWP_BYTE_BLOB_TYPE;
filter_condition[0].conditionValue.byteBlob = fwp_appid;
filter.numFilterConditions = 1;
filter.filterCondition = filter_condition;
filter.subLayerKey = TUNSAFE_GLOBAL_BLOCK_SUBLAYER;
filter.displayData.name = L"TunSafe Global Block";
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = 15;
filter.action.type = FWP_ACTION_PERMIT;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 1))
goto getout;
// Permit all queries going out on TUN
filter_condition[0].fieldKey = FWPM_CONDITION_IP_LOCAL_INTERFACE;
filter_condition[0].conditionValue.type = FWP_UINT64;
filter_condition[0].conditionValue.uint64 = (uint64*)&luid_to_allow.Value;
filter_condition[0].matchType = FWP_MATCH_EQUAL;
filter.weight.uint8 = 14;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 2))
goto getout;
// Permit everything that's loopback
filter_condition[0].fieldKey = FWPM_CONDITION_INTERFACE_TYPE;
filter_condition[0].conditionValue.type = FWP_UINT32;
filter_condition[0].conditionValue.uint32 = 24;
filter_condition[0].matchType = FWP_MATCH_EQUAL;
filter.weight.uint8 = 13;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 3))
goto getout;
// Permit everything going out to local networks
// '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
if (allow_local_networks) {
static const FWP_V4_ADDR_AND_MASK kLocalNetworkAddrMask[3] = {
{0x0A000000, 0xff000000}, // 10.0.0.0/8
{0xAC100000, 0xfff00000}, // 172.16.0.0/12
{0xC0A80000, 0xffff0000}, // 192.168.0.0/16
};
for (int i = 0; i < 3; i++) {
FWP_V4_ADDR_AND_MASK addr_mask = kLocalNetworkAddrMask[i];
filter.numFilterConditions = 1;
filter_condition[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
filter_condition[0].conditionValue.type = FWP_V4_ADDR_MASK;
filter_condition[0].conditionValue.v4AddrMask = &addr_mask;
filter_condition[0].matchType = FWP_MATCH_EQUAL;
filter.weight.uint8 = 12;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, false, 4))
goto getout;
}
}
// Permit all queries on the DHCP port (It uses 68 on the local side and 67 on the remote side)
filter_condition[2].fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
filter_condition[2].matchType = FWP_MATCH_EQUAL;
filter_condition[2].conditionValue.type = FWP_UINT16;
filter_condition[2].conditionValue.uint16 = 68;
filter_condition[1].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
filter_condition[1].matchType = FWP_MATCH_EQUAL;
filter_condition[1].conditionValue.type = FWP_UINT16;
filter_condition[1].conditionValue.uint16 = 67;
filter.numFilterConditions = 3;
filter_condition[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
filter_condition[0].conditionValue.type = FWP_UINT8;
filter_condition[0].conditionValue.uint8 = 17; // UDP
filter_condition[0].matchType = FWP_MATCH_EQUAL;
filter.weight.uint8 = 12;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 5))
goto getout;
// Block the rest
filter.numFilterConditions = 0;
filter.weight.type = FWP_EMPTY;
filter.action.type = FWP_ACTION_BLOCK;
if (!FwpmFilterAddCheckedAleConnect(handle, &filter, also_ipv6, 6))
goto getout;
success = true;
last_killswitch_settings = new_settings;
getout:
if (!success)
memset(&last_killswitch_settings, 0, sizeof(last_killswitch_settings));
if (handle != NULL) {
if (!success)
RemovePersistentInternetBlockingInner(handle, true);
FwpmEngineClose0(handle);
handle = NULL;
}
if (fwp_appid)
FwpmFreeMemory0((void **)&fwp_appid);
return success;
}
void RemoveKillSwitchFirewall() {
DWORD err;
HANDLE handle = NULL;
FWPM_SUBLAYER0 *sublayer_p = NULL;
{
FWPM_SESSION0 session = {0};
err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &handle);
if (err != 0) {
RERROR("FwpmEngineOpen0 failed: %d", err);
goto getout;
}
}
if (FwpmSubLayerGetByKey0(handle, &TUNSAFE_GLOBAL_BLOCK_SUBLAYER, &sublayer_p) == 0) {
// The sublayer exists
FwpmFreeMemory0((void **)&sublayer_p);
} else {
goto getout;
}
RemovePersistentInternetBlockingInner(handle, true);
getout:
if (handle != NULL) {
FwpmEngineClose0(handle);
handle = NULL;
}
}
bool GetKillSwitchFirewallActive() {
DWORD err;
HANDLE handle = NULL;
FWPM_SUBLAYER0 *sublayer_p = NULL;
bool result;
{
FWPM_SESSION0 session = {0};
err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &handle);
if (err != 0) {
RERROR("FwpmEngineOpen0 failed: %d", err);
goto getout;
}
}
if (FwpmSubLayerGetByKey0(handle, &TUNSAFE_GLOBAL_BLOCK_SUBLAYER, &sublayer_p) == 0) {
// The sublayer already exists
FwpmFreeMemory0((void **)&sublayer_p);
result = true;
} else {
result = false;
}
getout:
if (handle != NULL) {
FwpmEngineClose0(handle);
handle = NULL;
}
return result;
}