mirror of
https://github.com/amnezia-vpn/win-split-tunnel.git
synced 2026-05-17 08:16:00 +03:00
1700 lines
34 KiB
C++
1700 lines
34 KiB
C++
#include "wfp.h"
|
|
#include "context.h"
|
|
#include "identifiers.h"
|
|
#include "appfilters.h"
|
|
#include "filters.h"
|
|
#include "callouts.h"
|
|
#include "constants.h"
|
|
#include "asyncbind.h"
|
|
#include "logging.h"
|
|
#include "../util.h"
|
|
#include "../eventing/builder.h"
|
|
#include "firewall.h"
|
|
|
|
#include "../trace.h"
|
|
#include "firewall.tmh"
|
|
|
|
namespace firewall
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
//
|
|
// CreateWfpSession()
|
|
//
|
|
// Create dynamic WFP session that will be used for all filters etc.
|
|
//
|
|
NTSTATUS
|
|
CreateWfpSession
|
|
(
|
|
HANDLE *WfpSession
|
|
)
|
|
{
|
|
FWPM_SESSION0 sessionInfo = { 0 };
|
|
|
|
sessionInfo.flags = FWPM_SESSION_FLAG_DYNAMIC;
|
|
|
|
const auto status = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, &sessionInfo, WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
*WfpSession = 0;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
DestroyWfpSession
|
|
(
|
|
HANDLE WfpSession
|
|
)
|
|
{
|
|
return FwpmEngineClose0(WfpSession);
|
|
}
|
|
|
|
//
|
|
// ConfigureWfp()
|
|
//
|
|
// Register structural objects with WFP.
|
|
// Essentially making everything ready for installing callouts and filters.
|
|
//
|
|
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
|
//
|
|
NTSTATUS
|
|
ConfigureWfpTx
|
|
(
|
|
HANDLE WfpSession,
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
FWPM_PROVIDER0 provider = { 0 };
|
|
|
|
const auto ProviderName = L"Mullvad Split Tunnel";
|
|
const auto ProviderDescription = L"Manages filters and callouts that aid in implementing split tunneling";
|
|
|
|
provider.providerKey = ST_FW_PROVIDER_KEY;
|
|
provider.displayData.name = const_cast<wchar_t*>(ProviderName);
|
|
provider.displayData.description = const_cast<wchar_t*>(ProviderDescription);
|
|
|
|
auto status = FwpmProviderAdd0(WfpSession, &provider, NULL);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
FWPM_PROVIDER_CONTEXT1 pc = { 0 };
|
|
|
|
const auto ProviderContextName = L"Mullvad Split Tunnel Provider Context";
|
|
const auto ProviderContextDescription = L"Exposes context data to callouts";
|
|
|
|
FWP_BYTE_BLOB blob = { .size = sizeof(CONTEXT*), .data = (UINT8*)&Context };
|
|
|
|
pc.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
|
|
pc.displayData.name = const_cast<wchar_t*>(ProviderContextName);
|
|
pc.displayData.description = const_cast<wchar_t*>(ProviderContextDescription);
|
|
pc.providerKey = const_cast<GUID*>(&ST_FW_PROVIDER_KEY);
|
|
pc.type = FWPM_GENERAL_CONTEXT;
|
|
pc.dataBuffer = &blob;
|
|
|
|
status = FwpmProviderContextAdd1(WfpSession, &pc, NULL, NULL);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Adding a specific sublayer for split tunneling is futile unless a hard permit
|
|
// applied by the connect callout overrides filters registered by winfw
|
|
// - which it won't.
|
|
//
|
|
// A hard permit applied by a callout doesn't seem to be respected at all.
|
|
//
|
|
// Using a plain filter with no callout, it's possible to sometimes make
|
|
// a hard permit override a lower-weighted block, but it's not entirely consistent.
|
|
//
|
|
// And even then, it's not applicable to what we're doing since the logic
|
|
// applied here cannot be expressed using a plain filter.
|
|
//
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
UnregisterCallouts
|
|
(
|
|
)
|
|
{
|
|
auto s1 = UnregisterCalloutBlockSplitApps();
|
|
auto s2 = UnregisterCalloutPermitSplitApps();
|
|
auto s3 = UnregisterCalloutClassifyConnect();
|
|
auto s4 = UnregisterCalloutClassifyBind();
|
|
|
|
if (!NT_SUCCESS(s1))
|
|
{
|
|
DbgPrint("Could not unregister block-split-apps callout\n");
|
|
|
|
return s1;
|
|
}
|
|
|
|
if (!NT_SUCCESS(s2))
|
|
{
|
|
DbgPrint("Could not unregister permit-split-apps callout\n");
|
|
|
|
return s2;
|
|
}
|
|
|
|
if (!NT_SUCCESS(s3))
|
|
{
|
|
DbgPrint("Could not unregister connect-redirect callout\n");
|
|
|
|
return s3;
|
|
}
|
|
|
|
if (!NT_SUCCESS(s4))
|
|
{
|
|
DbgPrint("Could not unregister bind-redirect callout\n");
|
|
|
|
return s4;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// RegisterCallouts()
|
|
//
|
|
// The reason we need this function is because the called functions are individually
|
|
// safe if called inside a transaction.
|
|
//
|
|
// But a successful call is not undone by destroying the transaction.
|
|
//
|
|
NTSTATUS
|
|
RegisterCallouts
|
|
(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
HANDLE WfpSession
|
|
)
|
|
{
|
|
auto status = RegisterCalloutClassifyBindTx(DeviceObject, WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not register bind-redirect callout\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
status = RegisterCalloutClassifyConnectTx(DeviceObject, WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not register connect-redirect callout\n");
|
|
|
|
goto Unregister_callouts;
|
|
}
|
|
|
|
status = RegisterCalloutPermitSplitAppsTx(DeviceObject, WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not register permit-split-apps callout\n");
|
|
|
|
goto Unregister_callouts;
|
|
}
|
|
|
|
status = RegisterCalloutBlockSplitAppsTx(DeviceObject, WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not register block-split-apps callout\n");
|
|
|
|
goto Unregister_callouts;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Unregister_callouts:
|
|
|
|
const auto s2 = UnregisterCallouts();
|
|
|
|
if (!NT_SUCCESS(s2))
|
|
{
|
|
DbgPrint("One or more callouts could not be unregistered: 0x%X\n", s2);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
PROCESS_SPLIT_VERDICT
|
|
NTAPI
|
|
DummyQueryProcessFunc
|
|
(
|
|
HANDLE ProcessId,
|
|
void *Context
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(ProcessId);
|
|
UNREFERENCED_PARAMETER(Context);
|
|
|
|
return PROCESS_SPLIT_VERDICT::DONT_SPLIT;
|
|
}
|
|
|
|
//
|
|
// ResetClientCallbacks()
|
|
//
|
|
// This function is used if callouts/filters can't be unregistered.
|
|
//
|
|
// It's assumed that the driver was tearing down all subsystems in preparation
|
|
// for unloading.
|
|
//
|
|
// Other parts of the driver may no longer be available so we have to prevent
|
|
// callouts from accessing these parts through previously registered callbacks.
|
|
//
|
|
void
|
|
ResetClientCallbacks
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
Context->Callbacks.QueryProcess = DummyQueryProcessFunc;
|
|
Context->Callbacks.Context = NULL;
|
|
}
|
|
|
|
NTSTATUS
|
|
WfpTransactionBegin
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
auto status = FwpmTransactionBegin0(Context->WfpSession, 0);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not create WFP transaction: 0x%X\n", status);
|
|
|
|
DECLARE_CONST_UNICODE_STRING(errorMessage, L"Could not create WFP transaction");
|
|
|
|
auto evt = eventing::BuildErrorMessageEvent(status, &errorMessage);
|
|
|
|
eventing::Emit(Context->Eventing, &evt);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
WfpTransactionCommit
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
auto status = FwpmTransactionCommit0(Context->WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not commit WFP transaction: 0x%X\n", status);
|
|
|
|
DECLARE_CONST_UNICODE_STRING(errorMessage, L"Could not commit WFP transaction");
|
|
|
|
auto evt = eventing::BuildErrorMessageEvent(status, &errorMessage);
|
|
|
|
eventing::Emit(Context->Eventing, &evt);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
WfpTransactionAbort
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
auto status = FwpmTransactionAbort0(Context->WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not abort WFP transaction: 0x%X\n", status);
|
|
|
|
DECLARE_CONST_UNICODE_STRING(errorMessage, L"Could not abort WFP transaction");
|
|
|
|
auto evt = eventing::BuildErrorMessageEvent(status, &errorMessage);
|
|
|
|
eventing::Emit(Context->Eventing, &evt);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void
|
|
ResetStructure
|
|
(
|
|
ACTIVE_FILTERS *ActiveFilters
|
|
)
|
|
{
|
|
ActiveFilters->BindRedirectIpv4 = false;
|
|
ActiveFilters->BindRedirectIpv6 = false;
|
|
ActiveFilters->ConnectRedirectIpv4 = false;
|
|
ActiveFilters->ConnectRedirectIpv6 = false;
|
|
ActiveFilters->BlockTunnelIpv4 = false;
|
|
ActiveFilters->BlockTunnelIpv6 = false;
|
|
ActiveFilters->PermitNonTunnelIpv4 = false;
|
|
ActiveFilters->PermitNonTunnelIpv6 = false;
|
|
}
|
|
|
|
//
|
|
// RegisterFiltersForModeTx()
|
|
//
|
|
// Register filters according to mode.
|
|
// Assumes no filters are installed to begin with.
|
|
//
|
|
// Will update ActiveFilters.
|
|
//
|
|
NTSTATUS
|
|
RegisterFiltersForModeTx
|
|
(
|
|
HANDLE WfpSession,
|
|
SPLITTING_MODE Mode,
|
|
const ST_IP_ADDRESSES *IpAddresses,
|
|
ACTIVE_FILTERS *ActiveFilters
|
|
)
|
|
{
|
|
#define RFFM_SUCCEED_OR_RETURN(status, record) if(NT_SUCCESS(status)){ *record = true; } else { return status; }
|
|
|
|
ResetStructure(ActiveFilters);
|
|
|
|
switch (Mode)
|
|
{
|
|
case SPLITTING_MODE::MODE_1:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->PermitNonTunnelIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->BlockTunnelIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->PermitNonTunnelIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->BlockTunnelIpv6
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_2:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->PermitNonTunnelIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->BlockTunnelIpv4
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_3:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->PermitNonTunnelIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->BlockTunnelIpv4
|
|
);
|
|
|
|
//
|
|
// Pass NULL for tunnel IP since Mode-3 doesn't have a tunnel IPv6 interface.
|
|
//
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, NULL),
|
|
&ActiveFilters->PermitNonTunnelIpv6
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_4:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->PermitNonTunnelIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->BlockTunnelIpv4
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->BlockTunnelIpv6
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_5:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->PermitNonTunnelIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->BlockTunnelIpv6
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_6:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->PermitNonTunnelIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->BlockTunnelIpv6
|
|
);
|
|
|
|
//
|
|
// Pass NULL for tunnel IP since Mode-6 doesn't have a tunnel IPv4 interface.
|
|
//
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, NULL),
|
|
&ActiveFilters->PermitNonTunnelIpv4
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_7:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBindRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->BindRedirectIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
|
|
&ActiveFilters->ConnectRedirectIpv6
|
|
);
|
|
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->PermitNonTunnelIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->BlockTunnelIpv6
|
|
);
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->BlockTunnelIpv4
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_8:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
|
|
&ActiveFilters->BlockTunnelIpv4
|
|
);
|
|
|
|
//
|
|
// Pass NULL for tunnel IP since Mode-8 doesn't have a tunnel IPv6 interface.
|
|
//
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, NULL),
|
|
&ActiveFilters->PermitNonTunnelIpv6
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_9:
|
|
{
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterBlockTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
|
|
&ActiveFilters->BlockTunnelIpv6
|
|
);
|
|
|
|
//
|
|
// Pass NULL for tunnel IP since Mode-9 doesn't have a tunnel IPv4 interface.
|
|
//
|
|
|
|
RFFM_SUCCEED_OR_RETURN
|
|
(
|
|
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, NULL),
|
|
&ActiveFilters->PermitNonTunnelIpv4
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
};
|
|
|
|
DbgPrint("Non-actionable SPLITTING_MODE argument\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
NTSTATUS
|
|
RemoveActiveFiltersTx
|
|
(
|
|
HANDLE WfpSession,
|
|
const ACTIVE_FILTERS *ActiveFilters
|
|
)
|
|
{
|
|
#define RAF_SUCCEED_OR_RETURN(status) if(!NT_SUCCESS(status)){ return status; }
|
|
|
|
if (ActiveFilters->BindRedirectIpv4)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterBindRedirectIpv4Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->BindRedirectIpv6)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterBindRedirectIpv6Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->ConnectRedirectIpv4)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterConnectRedirectIpv4Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->ConnectRedirectIpv6)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterConnectRedirectIpv6Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->BlockTunnelIpv4)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterBlockTunnelIpv4Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->BlockTunnelIpv6)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterBlockTunnelIpv6Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->PermitNonTunnelIpv4)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterPermitNonTunnelIpv4Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
if (ActiveFilters->PermitNonTunnelIpv6)
|
|
{
|
|
RAF_SUCCEED_OR_RETURN
|
|
(
|
|
RemoveFilterPermitNonTunnelIpv6Tx(WfpSession)
|
|
);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
struct TUNNEL_ADDRESS_POINTERS
|
|
{
|
|
const IN_ADDR *TunnelIpv4;
|
|
const IN6_ADDR *TunnelIpv6;
|
|
};
|
|
|
|
//
|
|
// SelectTunnelAddresses()
|
|
//
|
|
// Select addresses based on mode. Both addresses are not valid in all modes.
|
|
//
|
|
NTSTATUS
|
|
SelectTunnelAddresses
|
|
(
|
|
const ST_IP_ADDRESSES *IpAddresses,
|
|
SPLITTING_MODE SplittingMode,
|
|
TUNNEL_ADDRESS_POINTERS *AddressPointers
|
|
)
|
|
{
|
|
AddressPointers->TunnelIpv4 = NULL;
|
|
AddressPointers->TunnelIpv6 = NULL;
|
|
|
|
switch (SplittingMode)
|
|
{
|
|
case SPLITTING_MODE::MODE_1:
|
|
case SPLITTING_MODE::MODE_4:
|
|
case SPLITTING_MODE::MODE_7:
|
|
{
|
|
AddressPointers->TunnelIpv4 = &IpAddresses->TunnelIpv4;
|
|
AddressPointers->TunnelIpv6 = &IpAddresses->TunnelIpv6;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_2:
|
|
case SPLITTING_MODE::MODE_3:
|
|
case SPLITTING_MODE::MODE_8:
|
|
{
|
|
AddressPointers->TunnelIpv4 = &IpAddresses->TunnelIpv4;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
case SPLITTING_MODE::MODE_5:
|
|
case SPLITTING_MODE::MODE_6:
|
|
case SPLITTING_MODE::MODE_9:
|
|
{
|
|
AddressPointers->TunnelIpv6 = &IpAddresses->TunnelIpv6;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
};
|
|
|
|
DbgPrint("Non-actionable SPLITTING_MODE argument\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
struct ALE_REAUTHORIZATION_FILTER_IDS
|
|
{
|
|
UINT64 OutboundFilterIdV4;
|
|
UINT64 InboundFilterIdV4;
|
|
UINT64 OutboundFilterIdV6;
|
|
UINT64 InboundFilterIdV6;
|
|
};
|
|
|
|
//
|
|
// AddAleReauthorizationFiltersTx()
|
|
//
|
|
// Add dummy filters to trigger an ALE reauthorization in the following layers:
|
|
//
|
|
// FWPM_LAYER_ALE_AUTH_CONNECT_V4
|
|
// FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
|
// FWPM_LAYER_ALE_AUTH_CONNECT_V6
|
|
// FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6
|
|
//
|
|
NTSTATUS
|
|
AddAleReauthorizationFiltersTx
|
|
(
|
|
HANDLE WfpSession,
|
|
ALE_REAUTHORIZATION_FILTER_IDS *ReauthFilters
|
|
)
|
|
{
|
|
RtlZeroMemory(ReauthFilters, sizeof(*ReauthFilters));
|
|
|
|
//
|
|
// Add IPv4 outbound filter.
|
|
//
|
|
// The single condition for IPv4 layers is:
|
|
//
|
|
// REMOTE_ADDRESS == 1.3.3.7
|
|
//
|
|
|
|
FWPM_FILTER0 filter = { 0 };
|
|
|
|
const auto FilterName = L"Mullvad Split Tunnel ALE reauthorization filter";
|
|
const auto FilterDescription = L"Forces an ALE reauthorization to occur";
|
|
|
|
filter.displayData.name = const_cast<wchar_t*>(FilterName);
|
|
filter.displayData.description = const_cast<wchar_t*>(FilterDescription);
|
|
filter.providerKey = const_cast<GUID*>(&ST_FW_PROVIDER_KEY);
|
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
|
filter.weight.type = FWP_UINT64;
|
|
filter.weight.uint64 = const_cast<UINT64*>(&ST_MAX_FILTER_WEIGHT);
|
|
filter.action.type = FWP_ACTION_BLOCK;
|
|
|
|
FWPM_FILTER_CONDITION0 cond;
|
|
|
|
cond.fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
|
cond.matchType = FWP_MATCH_EQUAL;
|
|
cond.conditionValue.type = FWP_UINT32;
|
|
cond.conditionValue.uint32 = 0x01030307;
|
|
|
|
filter.filterCondition = &cond;
|
|
filter.numFilterConditions = 1;
|
|
|
|
auto status = FwpmFilterAdd0(WfpSession, &filter, NULL, &ReauthFilters->OutboundFilterIdV4);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Add IPv4 inbound filter.
|
|
//
|
|
|
|
RtlZeroMemory(&filter.filterKey, sizeof(filter.filterKey));
|
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
|
|
|
status = FwpmFilterAdd0(WfpSession, &filter, NULL, &ReauthFilters->InboundFilterIdV4);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Add IPv6 outbound filter.
|
|
//
|
|
// The single condition for IPv6 layers is the same as for IPv4 layers,
|
|
// but the address is encoded as an IPv6 address.
|
|
//
|
|
|
|
RtlZeroMemory(&filter.filterKey, sizeof(filter.filterKey));
|
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
|
|
|
const FWP_BYTE_ARRAY16 ipv6RemoteAddress = { .byteArray16 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 3, 3, 7 } };
|
|
|
|
cond.conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
|
|
cond.conditionValue.byteArray16 = const_cast<FWP_BYTE_ARRAY16*>(&ipv6RemoteAddress);
|
|
|
|
status = FwpmFilterAdd0(WfpSession, &filter, NULL, &ReauthFilters->OutboundFilterIdV6);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Add IPv6 inbound filter.
|
|
//
|
|
|
|
RtlZeroMemory(&filter.filterKey, sizeof(filter.filterKey));
|
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
|
|
|
return FwpmFilterAdd0(WfpSession, &filter, NULL, &ReauthFilters->InboundFilterIdV6);
|
|
}
|
|
|
|
NTSTATUS
|
|
RemoveAleReauthorizationFilters
|
|
(
|
|
CONTEXT *Context,
|
|
ALE_REAUTHORIZATION_FILTER_IDS *ReauthFilters
|
|
)
|
|
{
|
|
auto status = WfpTransactionBegin(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status = FwpmFilterDeleteById0(Context->WfpSession, ReauthFilters->OutboundFilterIdV4))
|
|
|| !NT_SUCCESS(status = FwpmFilterDeleteById0(Context->WfpSession, ReauthFilters->InboundFilterIdV4))
|
|
|| !NT_SUCCESS(status = FwpmFilterDeleteById0(Context->WfpSession, ReauthFilters->OutboundFilterIdV6))
|
|
|| !NT_SUCCESS(status = FwpmFilterDeleteById0(Context->WfpSession, ReauthFilters->InboundFilterIdV6)))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
status = WfpTransactionCommit(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Abort:
|
|
|
|
WfpTransactionAbort(Context);
|
|
|
|
return status;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
//
|
|
// Initialize()
|
|
//
|
|
// Initialize data structures and locks etc.
|
|
//
|
|
// Configure WFP.
|
|
//
|
|
// We don't actually need a transaction here, if there are any failures
|
|
// we destroy the entire WFP session, which resets everything.
|
|
//
|
|
NTSTATUS
|
|
Initialize
|
|
(
|
|
CONTEXT **Context,
|
|
PDEVICE_OBJECT DeviceObject,
|
|
const CALLBACKS *Callbacks,
|
|
procbroker::CONTEXT *ProcessEventBroker,
|
|
eventing::CONTEXT *Eventing
|
|
)
|
|
{
|
|
auto context = (CONTEXT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(CONTEXT), ST_POOL_TAG);
|
|
|
|
if (context == NULL)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(context, sizeof(*context));
|
|
|
|
InitializeListHead(&context->PendedBinds.Records);
|
|
|
|
context->Callbacks = *Callbacks;
|
|
context->ProcessEventBroker = ProcessEventBroker;
|
|
context->Eventing = Eventing;
|
|
|
|
auto status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->IpAddresses.Lock);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("WdfSpinLockCreate() failed 0x%X\n", status);
|
|
|
|
context->IpAddresses.Lock = NULL;
|
|
|
|
goto Abort;
|
|
}
|
|
|
|
status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->PendedBinds.Lock);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
|
|
|
|
context->PendedBinds.Lock = NULL;
|
|
|
|
goto Abort_delete_ip_lock;
|
|
}
|
|
|
|
status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->Transaction.Lock);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
|
|
|
|
context->Transaction.Lock = NULL;
|
|
|
|
goto Abort_delete_bind_lock;
|
|
}
|
|
|
|
status = CreateWfpSession(&context->WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort_delete_transaction_lock;
|
|
}
|
|
|
|
status = ConfigureWfpTx(context->WfpSession, context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort_destroy_session;
|
|
}
|
|
|
|
status = RegisterCallouts(DeviceObject, context->WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort_destroy_session;
|
|
}
|
|
|
|
status = appfilters::Initialize(context->WfpSession, &context->AppFiltersContext);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort_unregister_callouts;
|
|
}
|
|
|
|
status = procbroker::Subscribe(ProcessEventBroker, HandleProcessEvent, context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort_teardown_appfilters;
|
|
}
|
|
|
|
*Context = context;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Abort_teardown_appfilters:
|
|
|
|
appfilters::TearDown(&context->AppFiltersContext);
|
|
|
|
Abort_unregister_callouts:
|
|
|
|
{
|
|
const auto s2 = UnregisterCallouts();
|
|
|
|
if (!NT_SUCCESS(s2))
|
|
{
|
|
DbgPrint("One or more callouts could not be unregistered: 0x%X\n", s2);
|
|
}
|
|
}
|
|
|
|
Abort_destroy_session:
|
|
|
|
DestroyWfpSession(context->WfpSession);
|
|
|
|
Abort_delete_transaction_lock:
|
|
|
|
WdfObjectDelete(context->Transaction.Lock);
|
|
|
|
Abort_delete_bind_lock:
|
|
|
|
WdfObjectDelete(context->PendedBinds.Lock);
|
|
|
|
Abort_delete_ip_lock:
|
|
|
|
WdfObjectDelete(context->IpAddresses.Lock);
|
|
|
|
Abort:
|
|
|
|
ExFreePoolWithTag(context, ST_POOL_TAG);
|
|
|
|
*Context = NULL;
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// TearDown()
|
|
//
|
|
// Destroy WFP session along with all filters.
|
|
// Release resources.
|
|
//
|
|
// If the return value is not successful, it means the following:
|
|
//
|
|
// The context used by callouts has been updated to make callouts return early,
|
|
// thereby avoiding crashes.
|
|
//
|
|
// The callouts are still registered with the system so the driver cannot be unloaded.
|
|
//
|
|
NTSTATUS
|
|
TearDown
|
|
(
|
|
CONTEXT **Context
|
|
)
|
|
{
|
|
auto context = *Context;
|
|
|
|
*Context = NULL;
|
|
|
|
//
|
|
// Clean up adjacent systems.
|
|
//
|
|
|
|
procbroker::CancelSubscription(context->ProcessEventBroker, HandleProcessEvent);
|
|
|
|
FailPendedBinds(context);
|
|
|
|
WdfObjectDelete(context->PendedBinds.Lock);
|
|
|
|
appfilters::TearDown(&context->AppFiltersContext);
|
|
|
|
//
|
|
// Since we're using a dynamic session we don't actually
|
|
// have to remove all WFP objects one by one.
|
|
//
|
|
// Everything will be cleaned up when the session is ended.
|
|
//
|
|
// (Except for callout registrations.)
|
|
//
|
|
|
|
auto status = DestroyWfpSession(context->WfpSession);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("WFP session could not be cleaned up: 0x%X\n", status);
|
|
|
|
ResetClientCallbacks(context);
|
|
|
|
// Leak context structure.
|
|
return status;
|
|
}
|
|
|
|
status = UnregisterCallouts();
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("One or more callouts could not be unregistered: 0x%X\n", status);
|
|
|
|
ResetClientCallbacks(context);
|
|
|
|
// Leak context structure.
|
|
return status;
|
|
}
|
|
|
|
WdfObjectDelete(context->IpAddresses.Lock);
|
|
|
|
WdfObjectDelete(context->Transaction.Lock);
|
|
|
|
ExFreePoolWithTag(context, ST_POOL_TAG);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// EnableSplitting()
|
|
//
|
|
// Register all filters required for splitting.
|
|
//
|
|
NTSTATUS
|
|
EnableSplitting
|
|
(
|
|
CONTEXT *Context,
|
|
const ST_IP_ADDRESSES *IpAddresses
|
|
)
|
|
{
|
|
NT_ASSERT(!Context->SplittingEnabled);
|
|
NT_ASSERT(!Context->Transaction.Active);
|
|
|
|
if (Context->SplittingEnabled || Context->Transaction.Active)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// There are no readers at this time so we can update at leasure and without
|
|
// taking the lock.
|
|
//
|
|
// IP addresses and mode should be updated before filters are committed.
|
|
//
|
|
|
|
Context->IpAddresses.Addresses = *IpAddresses;
|
|
|
|
auto status = DetermineSplittingMode
|
|
(
|
|
&Context->IpAddresses.Addresses,
|
|
&Context->IpAddresses.SplittingMode
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Update WFP inside a transaction.
|
|
//
|
|
|
|
status = WfpTransactionBegin(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// There are no filters installed at this time.
|
|
// Just go ahead and install the required filters.
|
|
//
|
|
|
|
ACTIVE_FILTERS activeFilters;
|
|
|
|
status = RegisterFiltersForModeTx
|
|
(
|
|
Context->WfpSession,
|
|
Context->IpAddresses.SplittingMode,
|
|
&Context->IpAddresses.Addresses,
|
|
&activeFilters
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
//
|
|
// Commit filters.
|
|
//
|
|
|
|
status = WfpTransactionCommit(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
Context->SplittingEnabled = true;
|
|
Context->ActiveFilters = activeFilters;
|
|
|
|
LogActivatedSplittingMode(Context->IpAddresses.SplittingMode);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Abort:
|
|
|
|
WfpTransactionAbort(Context);
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// DisableSplitting()
|
|
//
|
|
// Remove all filters associated with splitting.
|
|
//
|
|
NTSTATUS
|
|
DisableSplitting
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
NT_ASSERT(Context->SplittingEnabled);
|
|
|
|
if (!Context->SplittingEnabled)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// Use double transaction because resetting appfilters requires this.
|
|
//
|
|
|
|
auto status = TransactionBegin(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
status = RemoveActiveFiltersTx(Context->WfpSession, &Context->ActiveFilters);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
status = appfilters::ResetTx2(Context->AppFiltersContext);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
status = TransactionCommit(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
Context->SplittingEnabled = false;
|
|
|
|
ResetStructure(&Context->ActiveFilters);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Abort:
|
|
|
|
TransactionAbort(Context);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
RegisterUpdatedIpAddresses
|
|
(
|
|
CONTEXT *Context,
|
|
const ST_IP_ADDRESSES *IpAddresses
|
|
)
|
|
{
|
|
if (!Context->SplittingEnabled)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Determine which mode we're entering into.
|
|
//
|
|
|
|
SPLITTING_MODE newMode;
|
|
|
|
auto status = DetermineSplittingMode(IpAddresses, &newMode);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Use a double transaction
|
|
//
|
|
// Remove all generic filters, and add back still relevant ones
|
|
//
|
|
|
|
status = TransactionBegin(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
status = RemoveActiveFiltersTx(Context->WfpSession, &Context->ActiveFilters);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
ACTIVE_FILTERS newActiveFilters;
|
|
|
|
status = RegisterFiltersForModeTx
|
|
(
|
|
Context->WfpSession,
|
|
newMode,
|
|
IpAddresses,
|
|
&newActiveFilters
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
//
|
|
// Update any app-specific filters.
|
|
//
|
|
|
|
TUNNEL_ADDRESS_POINTERS addressPointers;
|
|
|
|
status = SelectTunnelAddresses(IpAddresses, newMode, &addressPointers);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
status = appfilters::UpdateFiltersTx2
|
|
(
|
|
Context->AppFiltersContext,
|
|
addressPointers.TunnelIpv4,
|
|
addressPointers.TunnelIpv6
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
//
|
|
// Finalize.
|
|
//
|
|
|
|
status = TransactionCommit(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
auto intermediateNonPagedAddresses = *IpAddresses;
|
|
|
|
WdfSpinLockAcquire(Context->IpAddresses.Lock);
|
|
|
|
Context->IpAddresses.Addresses = intermediateNonPagedAddresses;
|
|
Context->IpAddresses.SplittingMode = newMode;
|
|
|
|
WdfSpinLockRelease(Context->IpAddresses.Lock);
|
|
|
|
Context->ActiveFilters = newActiveFilters;
|
|
|
|
LogActivatedSplittingMode(newMode);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Abort:
|
|
|
|
TransactionAbort(Context);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
TransactionBegin
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
NT_ASSERT(Context->SplittingEnabled);
|
|
|
|
if (!Context->SplittingEnabled)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
WdfWaitLockAcquire(Context->Transaction.Lock, NULL);
|
|
|
|
auto status = WfpTransactionBegin(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
goto Abort;
|
|
}
|
|
|
|
status = appfilters::TransactionBegin(Context->AppFiltersContext);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not create appfilters transaction: 0x%X\n", status);
|
|
|
|
goto Abort_cancel_wfp;
|
|
}
|
|
|
|
Context->Transaction.OwnerId = PsGetCurrentThreadId();
|
|
Context->Transaction.Active = true;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Abort_cancel_wfp:
|
|
|
|
WfpTransactionAbort(Context);
|
|
|
|
Abort:
|
|
|
|
WdfWaitLockRelease(Context->Transaction.Lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
TransactionCommit
|
|
(
|
|
CONTEXT *Context,
|
|
bool ForceAleReauthorization
|
|
)
|
|
{
|
|
NT_ASSERT(Context->SplittingEnabled);
|
|
NT_ASSERT(Context->Transaction.Active);
|
|
|
|
if (!Context->SplittingEnabled || !Context->Transaction.Active)
|
|
{
|
|
DbgPrint(__FUNCTION__ " called outside transaction\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (Context->Transaction.OwnerId != PsGetCurrentThreadId())
|
|
{
|
|
DbgPrint(__FUNCTION__ " called by other than transaction owner\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
ALE_REAUTHORIZATION_FILTER_IDS reauthFilters;
|
|
|
|
if (ForceAleReauthorization)
|
|
{
|
|
auto status = AddAleReauthorizationFiltersTx(Context->WfpSession, &reauthFilters);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrint("Could not add ALE reauthorization filters\n");
|
|
|
|
return status;
|
|
}
|
|
}
|
|
|
|
auto status = WfpTransactionCommit(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
appfilters::TransactionCommit(Context->AppFiltersContext);
|
|
|
|
Context->Transaction.OwnerId = NULL;
|
|
Context->Transaction.Active = false;
|
|
|
|
if (ForceAleReauthorization)
|
|
{
|
|
status = RemoveAleReauthorizationFilters(Context, &reauthFilters);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
//
|
|
// This is bad to the extent that we were unable to remove filters which no longer
|
|
// serve a purpose.
|
|
//
|
|
// However, the filters aren't using unique GUIDs as identifiers, and they're using
|
|
// dummy conditions that won't match any traffic.
|
|
//
|
|
// So filters will merely be wasting a tiny amount of system resources.
|
|
//
|
|
|
|
DbgPrint("Could not remove ALE reauthorization filters: 0x%X\n", status);
|
|
|
|
DECLARE_CONST_UNICODE_STRING(errorMessage, L"Could not remove ALE reauthorization filters");
|
|
|
|
auto evt = eventing::BuildErrorMessageEvent(status, &errorMessage);
|
|
|
|
eventing::Emit(Context->Eventing, &evt);
|
|
}
|
|
}
|
|
|
|
WdfWaitLockRelease(Context->Transaction.Lock);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
TransactionAbort
|
|
(
|
|
CONTEXT *Context
|
|
)
|
|
{
|
|
NT_ASSERT(Context->SplittingEnabled);
|
|
NT_ASSERT(Context->Transaction.Active);
|
|
|
|
if (!Context->SplittingEnabled || !Context->Transaction.Active)
|
|
{
|
|
DbgPrint(__FUNCTION__ " called outside transaction\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (Context->Transaction.OwnerId != PsGetCurrentThreadId())
|
|
{
|
|
DbgPrint(__FUNCTION__ " called by other than transaction owner\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
auto status = WfpTransactionAbort(Context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
appfilters::TransactionAbort(Context->AppFiltersContext);
|
|
|
|
Context->Transaction.OwnerId = NULL;
|
|
Context->Transaction.Active = false;
|
|
|
|
WdfWaitLockRelease(Context->Transaction.Lock);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
RegisterAppBecomingSplitTx
|
|
(
|
|
CONTEXT *Context,
|
|
const LOWER_UNICODE_STRING *ImageName
|
|
)
|
|
{
|
|
NT_ASSERT(Context->SplittingEnabled);
|
|
NT_ASSERT(Context->Transaction.Active);
|
|
|
|
if (!Context->SplittingEnabled || !Context->Transaction.Active)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (Context->Transaction.OwnerId != PsGetCurrentThreadId())
|
|
{
|
|
DbgPrint(__FUNCTION__ " called by other than transaction owner\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// We're in a transaction so IP addresses won't be updated on another thread.
|
|
//
|
|
|
|
TUNNEL_ADDRESS_POINTERS addressPointers;
|
|
|
|
auto status = SelectTunnelAddresses
|
|
(
|
|
&Context->IpAddresses.Addresses,
|
|
Context->IpAddresses.SplittingMode,
|
|
&addressPointers
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
return appfilters::RegisterFilterBlockAppTunnelTrafficTx2
|
|
(
|
|
Context->AppFiltersContext,
|
|
ImageName,
|
|
addressPointers.TunnelIpv4,
|
|
addressPointers.TunnelIpv6
|
|
);
|
|
}
|
|
|
|
NTSTATUS
|
|
RegisterAppBecomingUnsplitTx
|
|
(
|
|
CONTEXT *Context,
|
|
const LOWER_UNICODE_STRING *ImageName
|
|
)
|
|
{
|
|
NT_ASSERT(Context->SplittingEnabled);
|
|
NT_ASSERT(Context->Transaction.Active);
|
|
|
|
if (!Context->SplittingEnabled || !Context->Transaction.Active)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (Context->Transaction.OwnerId != PsGetCurrentThreadId())
|
|
{
|
|
DbgPrint(__FUNCTION__ " called by other than transaction owner\n");
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
return appfilters::RemoveFilterBlockAppTunnelTrafficTx2(Context->AppFiltersContext, ImageName);
|
|
}
|
|
|
|
} // namespace firewall
|