summaryrefslogtreecommitdiffhomepage
path: root/windows
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-08-21 15:09:16 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-08-21 15:09:16 +0200
commit97a7508b72495e854e72ba8da1749b3f2eea1862 (patch)
treeede893915872b5a849608f2490876bd660983270 /windows
parentdcb3f3d11b0ede581800d7a1a63ff56ee636b0bb (diff)
parent356bc08e6aec9a950ede353cd927afccb780ed7f (diff)
downloadmullvadvpn-97a7508b72495e854e72ba8da1749b3f2eea1862.tar.xz
mullvadvpn-97a7508b72495e854e72ba8da1749b3f2eea1862.zip
Merge branch 'fix-multihop-entry-leak'
Diffstat (limited to 'windows')
-rw-r--r--windows/winfw/src/winfw/fwcontext.cpp34
-rw-r--r--windows/winfw/src/winfw/fwcontext.h2
-rw-r--r--windows/winfw/src/winfw/mullvadguids.cpp62
-rw-r--r--windows/winfw/src/winfw/mullvadguids.h4
-rw-r--r--windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp154
-rw-r--r--windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h11
-rw-r--r--windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp158
-rw-r--r--windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h7
-rw-r--r--windows/winfw/src/winfw/winfw.cpp20
-rw-r--r--windows/winfw/src/winfw/winfw.h13
10 files changed, 422 insertions, 43 deletions
diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp
index 4ed22737fc..7747d7c822 100644
--- a/windows/winfw/src/winfw/fwcontext.cpp
+++ b/windows/winfw/src/winfw/fwcontext.cpp
@@ -185,6 +185,7 @@ bool FwContext::applyPolicyConnecting
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
+ const std::optional<wfp::IpAddress> &exitEndpointIp,
const std::vector<std::wstring> &relayClients,
const std::optional<std::wstring> &tunnelInterfaceAlias,
const std::optional<WinFwAllowedEndpoint> &allowedEndpoint,
@@ -209,12 +210,16 @@ bool FwContext::applyPolicyConnecting
case WinFwAllowedTunnelTrafficType::All:
{
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>(
+ relayClients,
*tunnelInterfaceAlias,
- std::nullopt
+ std::nullopt,
+ exitEndpointIp
));
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>(
+ relayClients,
*tunnelInterfaceAlias,
- std::nullopt
+ std::nullopt,
+ exitEndpointIp
));
break;
}
@@ -229,12 +234,16 @@ bool FwContext::applyPolicyConnecting
std::nullopt,
});
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>(
+ relayClients,
*tunnelInterfaceAlias,
- onlyEndpoint
+ onlyEndpoint,
+ exitEndpointIp
));
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>(
+ relayClients,
*tunnelInterfaceAlias,
- onlyEndpoint
+ onlyEndpoint,
+ exitEndpointIp
));
break;
}
@@ -253,12 +262,16 @@ bool FwContext::applyPolicyConnecting
})
});
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>(
+ relayClients,
*tunnelInterfaceAlias,
- endpoints
+ endpoints,
+ exitEndpointIp
));
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>(
+ relayClients,
*tunnelInterfaceAlias,
- endpoints
+ endpoints,
+ exitEndpointIp
));
break;
}
@@ -280,6 +293,7 @@ bool FwContext::applyPolicyConnected
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
+ const std::optional<wfp::IpAddress> &exitEndpointIp,
const std::vector<std::wstring> &relayClient,
const std::wstring &tunnelInterfaceAlias,
const std::vector<wfp::IpAddress> &tunnelDnsServers,
@@ -306,13 +320,17 @@ bool FwContext::applyPolicyConnected
}
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>(
+ relayClient,
tunnelInterfaceAlias,
- std::nullopt
+ std::nullopt,
+ exitEndpointIp
));
ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>(
+ relayClient,
tunnelInterfaceAlias,
- std::nullopt
+ std::nullopt,
+ exitEndpointIp
));
const auto status = applyRuleset(ruleset);
diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h
index 92ecce4f4f..8a4e4f0301 100644
--- a/windows/winfw/src/winfw/fwcontext.h
+++ b/windows/winfw/src/winfw/fwcontext.h
@@ -28,6 +28,7 @@ public:
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
+ const std::optional<wfp::IpAddress> &exitEndpointIp,
const std::vector<std::wstring> &relayClients,
const std::optional<std::wstring> &tunnelInterfaceAlias,
const std::optional<WinFwAllowedEndpoint> &allowedEndpoint,
@@ -38,6 +39,7 @@ public:
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
+ const std::optional<wfp::IpAddress> &exitEndpointIp,
const std::vector<std::wstring> &relayClients,
const std::wstring &tunnelInterfaceAlias,
const std::vector<wfp::IpAddress> &tunnelDnsServers,
diff --git a/windows/winfw/src/winfw/mullvadguids.cpp b/windows/winfw/src/winfw/mullvadguids.cpp
index 890c8b0c2e..3029607a11 100644
--- a/windows/winfw/src/winfw/mullvadguids.cpp
+++ b/windows/winfw/src/winfw/mullvadguids.cpp
@@ -134,10 +134,14 @@ MullvadGuids::DetailedIdentityRegistry MullvadGuids::DetailedRegistry(IdentityQu
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2()));
+ registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_ExitIp()));
+ registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_BlockExitIp()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv4_1()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv6_1()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv4_2()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv6_2()));
+ registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_ExitIp()));
+ registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_BlockExitIp()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitNdp_Outbound_Router_Solicitation()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitNdp_Inbound_Router_Advertisement()));
registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitNdp_Outbound_Neighbor_Solicitation()));
@@ -723,6 +727,34 @@ const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2()
}
//static
+const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnel_ExitIp()
+{
+ static const GUID g =
+ {
+ 0x1769c66f,
+ 0xa5ef,
+ 0x1128,
+ { 0xb1, 0x5e, 0xd9, 0xe2, 0x6e, 0xf4, 0xf0, 0x06 }
+ };
+
+ return g;
+}
+
+//static
+const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnel_BlockExitIp()
+{
+ static const GUID g =
+ {
+ 0x276bc66f,
+ 0xf5ef,
+ 0x1128,
+ { 0xb1, 0x5e, 0xd9, 0xe2, 0x6e, 0xf4, 0xf0, 0x06 }
+ };
+
+ return g;
+}
+
+//static
const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_1()
{
static const GUID g =
@@ -780,6 +812,36 @@ const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_2()
}
//static
+const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_ExitIp()
+{
+
+ static const GUID g =
+ {
+ 0x01deb41a,
+ 0xb25d,
+ 0x1e60,
+ { 0xef, 0x12, 0xef, 0x3b, 0x40, 0xc0, 0x8e, 0xdc }
+ };
+
+ return g;
+}
+
+//static
+const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_BlockExitIp()
+{
+
+ static const GUID g =
+ {
+ 0x01deb2b8,
+ 0xb25d,
+ 0x1e60,
+ { 0xef, 0x52, 0xef, 0x3b, 0x40, 0xc0, 0x8e, 0xdc }
+ };
+
+ return g;
+}
+
+//static
const GUID &MullvadGuids::Filter_Baseline_PermitNdp_Outbound_Router_Solicitation()
{
static const GUID g =
diff --git a/windows/winfw/src/winfw/mullvadguids.h b/windows/winfw/src/winfw/mullvadguids.h
index d63af0206d..e2fd7ab276 100644
--- a/windows/winfw/src/winfw/mullvadguids.h
+++ b/windows/winfw/src/winfw/mullvadguids.h
@@ -75,11 +75,15 @@ public:
static const GUID &Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1();
static const GUID &Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2();
static const GUID &Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2();
+ static const GUID &Filter_Baseline_PermitVpnTunnel_ExitIp();
+ static const GUID &Filter_Baseline_PermitVpnTunnel_BlockExitIp();
static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv4_1();
static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv6_1();
static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv4_2();
static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv6_2();
+ static const GUID &Filter_Baseline_PermitVpnTunnelService_ExitIp();
+ static const GUID &Filter_Baseline_PermitVpnTunnelService_BlockExitIp();
static const GUID &Filter_Baseline_PermitNdp_Outbound_Router_Solicitation();
static const GUID &Filter_Baseline_PermitNdp_Inbound_Router_Advertisement();
diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp
index 3aaff601a3..07ad39995b 100644
--- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp
+++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp
@@ -4,6 +4,8 @@
#include <winfw/rules/shared.h>
#include <libwfp/filterbuilder.h>
#include <libwfp/conditionbuilder.h>
+#include <libwfp/conditions/comparison.h>
+#include <libwfp/conditions/conditionapplication.h>
#include <libwfp/conditions/conditioninterface.h>
#include <libwfp/conditions/conditionip.h>
#include <libwfp/conditions/conditionport.h>
@@ -16,11 +18,15 @@ namespace rules::baseline
{
PermitVpnTunnel::PermitVpnTunnel(
+ const std::vector<std::wstring> &relayClients,
const std::wstring &tunnelInterfaceAlias,
- const std::optional<Endpoints> &potentialEndpoints
+ const std::optional<Endpoints> &potentialEndpoints,
+ const std::optional<wfp::IpAddress> &exitEndpointIp
)
- : m_tunnelInterfaceAlias(tunnelInterfaceAlias)
+ : m_relayClients(relayClients)
+ , m_tunnelInterfaceAlias(tunnelInterfaceAlias)
, m_potentialEndpoints(potentialEndpoints)
+ , m_exitEndpointIp(exitEndpointIp)
{
}
@@ -86,8 +92,125 @@ bool PermitVpnTunnel::AddEndpointFilter(const std::optional<Endpoint> &endpoint,
}
+bool PermitVpnTunnel::BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller)
+{
+ if (m_relayClients.empty())
+ {
+ // If "relay clients" is empty, then permit connections to exit from any process
+ return true;
+ }
+
+ wfp::FilterBuilder filterBuilder;
+
+ //
+ // Permit traffic to exit relay from relay clients
+ //
+
+ filterBuilder
+ .description(L"This filter is part of a rule that allows exit IP traffic from select clients")
+ .name(L"Permit inbound exit relay connections on tunnel interface")
+ .provider(MullvadGuids::Provider())
+ .sublayer(MullvadGuids::SublayerBaseline())
+ .key(MullvadGuids::Filter_Baseline_PermitVpnTunnel_ExitIp())
+ .weight(wfp::FilterBuilder::WeightClass::Max)
+ .permit();
+
+ if (exitIp.type() == wfp::IpAddress::Ipv4)
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V4);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V4);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ for (auto relayClient : m_relayClients) {
+ conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient));
+ }
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V6);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V6);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ for (auto relayClient : m_relayClients) {
+ conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient));
+ }
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+
+ //
+ // Block all remaining traffic to the exit
+ //
+
+ {
+ wfp::FilterBuilder filterBuilder;
+
+ filterBuilder
+ .description(L"This filter is part of a rule that blocks exit IP traffic from unexpected clients")
+ .name(L"Block inbound exit relay connections on tunnel interface")
+ .provider(MullvadGuids::Provider())
+ .sublayer(MullvadGuids::SublayerBaseline())
+ .key(MullvadGuids::Filter_Baseline_PermitVpnTunnel_BlockExitIp())
+ .weight(wfp::FilterBuilder::WeightClass::Max)
+ .block();
+
+ if (exitIp.type() == wfp::IpAddress::Ipv4)
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V4);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V4);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V6);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V6);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
bool PermitVpnTunnel::apply(IObjectInstaller &objectInstaller)
{
+ if (m_exitEndpointIp.has_value())
+ {
+ if (!BlockNonRelayClientExit(m_exitEndpointIp.value(), objectInstaller))
+ {
+ return false;
+ }
+ }
if (!m_potentialEndpoints.has_value())
{
return AddEndpointFilter(
@@ -97,20 +220,23 @@ bool PermitVpnTunnel::apply(IObjectInstaller &objectInstaller)
objectInstaller
);
}
- AddEndpointFilter(
- std::make_optional<Endpoint>(m_potentialEndpoints.value().entryEndpoint),
- MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_1(),
- MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1(),
+ if (!AddEndpointFilter(
+ std::make_optional<Endpoint>(m_potentialEndpoints.value().endpoint1),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_1(),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1(),
+ objectInstaller
+ ))
+ {
+ return false;
+ }
+ if (m_potentialEndpoints.value().endpoint2.has_value())
+ {
+ return AddEndpointFilter(
+ m_potentialEndpoints.value().endpoint2.value(),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2(),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2(),
objectInstaller
);
- if (m_potentialEndpoints.value().exitEndpoint.has_value())
- {
- AddEndpointFilter(
- m_potentialEndpoints.value().exitEndpoint.value(),
- MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2(),
- MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2(),
- objectInstaller
- );
}
return true;
}
diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h
index a89ac46d98..e8446f3e35 100644
--- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h
+++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h
@@ -20,22 +20,27 @@ public:
};
struct Endpoints {
- Endpoint entryEndpoint;
- std::optional<Endpoint> exitEndpoint;
+ Endpoint endpoint1;
+ std::optional<Endpoint> endpoint2;
};
PermitVpnTunnel(
+ const std::vector<std::wstring> &relayClients,
const std::wstring &tunnelInterfaceAlias,
- const std::optional<Endpoints> &potentialEndpoints
+ const std::optional<Endpoints> &potentialEndpoints,
+ const std::optional<wfp::IpAddress> &exitEndpoint
);
bool apply(IObjectInstaller &objectInstaller) override;
private:
bool AddEndpointFilter(const std::optional<Endpoint> &endpoint, const GUID &ipv4Guid, const GUID &ipv6Guid, IObjectInstaller &objectInstaller);
+ bool BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller);
+ const std::vector<std::wstring> m_relayClients;
const std::wstring m_tunnelInterfaceAlias;
const std::optional<Endpoints> m_potentialEndpoints;
+ const std::optional<wfp::IpAddress> m_exitEndpointIp;
};
}
diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp
index e5b7ab1f5c..7659ee352c 100644
--- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp
+++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp
@@ -4,6 +4,8 @@
#include <winfw/rules/shared.h>
#include <libwfp/filterbuilder.h>
#include <libwfp/conditionbuilder.h>
+#include <libwfp/conditions/comparison.h>
+#include <libwfp/conditions/conditionapplication.h>
#include <libwfp/conditions/conditioninterface.h>
#include <libwfp/conditions/conditionip.h>
#include <libwfp/conditions/conditionport.h>
@@ -17,11 +19,15 @@ namespace rules::baseline
using Endpoint = PermitVpnTunnel::Endpoint;
PermitVpnTunnelService::PermitVpnTunnelService(
+ const std::vector<std::wstring> &relayClients,
const std::wstring &tunnelInterfaceAlias,
- const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints
+ const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints,
+ const std::optional<wfp::IpAddress> &exitEndpointIp
)
- : m_tunnelInterfaceAlias(tunnelInterfaceAlias)
+ : m_relayClients(relayClients)
+ , m_tunnelInterfaceAlias(tunnelInterfaceAlias)
, m_potentialEndpoints(potentialEndpoints)
+ , m_exitEndpointIp(exitEndpointIp)
{
}
@@ -32,7 +38,6 @@ bool PermitVpnTunnelService::AddEndpointFilter(const std::optional<PermitVpnTunn
filterBuilder
.description(L"This filter is part of a rule that permits hosting services that listen on the tunnel interface")
.provider(MullvadGuids::Provider())
- .layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4)
.sublayer(MullvadGuids::SublayerBaseline())
.weight(wfp::FilterBuilder::WeightClass::Medium)
.permit();
@@ -47,7 +52,7 @@ bool PermitVpnTunnelService::AddEndpointFilter(const std::optional<PermitVpnTunn
.name(L"Permit inbound connections on tunnel interface (IPv4)")
.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4);
- wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V4);
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4);
conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
if (endpoint.has_value())
@@ -70,7 +75,7 @@ bool PermitVpnTunnelService::AddEndpointFilter(const std::optional<PermitVpnTunn
.name(L"Permit inbound connections on tunnel interface (IPv6)")
.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6);
- wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V6);
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6);
conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
if (endpoint.has_value())
@@ -88,8 +93,124 @@ bool PermitVpnTunnelService::AddEndpointFilter(const std::optional<PermitVpnTunn
return true;
}
+bool PermitVpnTunnelService::BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller)
+{
+ if (m_relayClients.empty())
+ {
+ // If "relay clients" is empty, then permit connections to exit from any process
+ return true;
+ }
+
+ wfp::FilterBuilder filterBuilder;
+
+ //
+ // Permit traffic to exit relay from relay clients
+ //
+
+ filterBuilder
+ .description(L"This filter is part of a rule that allows exit IP traffic from select clients")
+ .name(L"Permit inbound exit relay connections on tunnel interface")
+ .provider(MullvadGuids::Provider())
+ .sublayer(MullvadGuids::SublayerBaseline())
+ .key(MullvadGuids::Filter_Baseline_PermitVpnTunnelService_ExitIp())
+ .weight(wfp::FilterBuilder::WeightClass::Max)
+ .permit();
+
+ if (exitIp.type() == wfp::IpAddress::Ipv4)
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ for (auto relayClient : m_relayClients) {
+ conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient));
+ }
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ for (auto relayClient : m_relayClients) {
+ conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient));
+ }
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+
+ //
+ // Block all remaining traffic to the exit
+ //
+
+ {
+ wfp::FilterBuilder filterBuilder;
+
+ filterBuilder
+ .description(L"This filter is part of a rule that blocks exit IP traffic from unexpected clients")
+ .name(L"Block inbound exit relay connections on tunnel interface")
+ .provider(MullvadGuids::Provider())
+ .sublayer(MullvadGuids::SublayerBaseline())
+ .key(MullvadGuids::Filter_Baseline_PermitVpnTunnelService_BlockExitIp())
+ .weight(wfp::FilterBuilder::WeightClass::Max)
+ .block();
+
+ if (exitIp.type() == wfp::IpAddress::Ipv4)
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6);
+
+ wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6);
+
+ conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias));
+ conditionBuilder.add_condition(ConditionIp::Remote(exitIp));
+
+ if (!objectInstaller.addFilter(filterBuilder, conditionBuilder))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
bool PermitVpnTunnelService::apply(IObjectInstaller &objectInstaller)
{
+ if (m_exitEndpointIp.has_value())
+ {
+ if (!BlockNonRelayClientExit(m_exitEndpointIp.value(), objectInstaller))
+ {
+ return false;
+ }
+ }
if (!m_potentialEndpoints.has_value())
{
return AddEndpointFilter(
@@ -99,20 +220,23 @@ bool PermitVpnTunnelService::apply(IObjectInstaller &objectInstaller)
objectInstaller
);
}
- AddEndpointFilter(
- std::make_optional<Endpoint>(m_potentialEndpoints.value().entryEndpoint),
- MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_1(),
- MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_1(),
+ if (!AddEndpointFilter(
+ std::make_optional<Endpoint>(m_potentialEndpoints.value().endpoint1),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_1(),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_1(),
+ objectInstaller
+ ))
+ {
+ return false;
+ }
+ if (m_potentialEndpoints.value().endpoint2.has_value())
+ {
+ return AddEndpointFilter(
+ m_potentialEndpoints.value().endpoint2.value(),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_2(),
+ MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_2(),
objectInstaller
);
- if (m_potentialEndpoints.value().exitEndpoint.has_value())
- {
- AddEndpointFilter(
- m_potentialEndpoints.value().exitEndpoint.value(),
- MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_2(),
- MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_2(),
- objectInstaller
- );
}
return true;
}
diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h
index 8b21dc2264..aa2f805c62 100644
--- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h
+++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h
@@ -15,17 +15,22 @@ class PermitVpnTunnelService : public IFirewallRule
public:
PermitVpnTunnelService(
+ const std::vector<std::wstring> &relayClients,
const std::wstring &tunnelInterfaceAlias,
- const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints
+ const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints,
+ const std::optional<wfp::IpAddress> &exitEndpointIp
);
bool apply(IObjectInstaller &objectInstaller) override;
private:
bool AddEndpointFilter(const std::optional<PermitVpnTunnel::Endpoint> &endpoint, const GUID &ipv4Guid, const GUID &ipv6Guid, IObjectInstaller &objectInstaller);
+ bool BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller);
+ const std::vector<std::wstring> m_relayClients;
const std::wstring m_tunnelInterfaceAlias;
const std::optional<PermitVpnTunnel::Endpoints> m_potentialEndpoints;
+ const std::optional<wfp::IpAddress> m_exitEndpointIp;
};
}
diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp
index 0ed0fe5731..d962965a19 100644
--- a/windows/winfw/src/winfw/winfw.cpp
+++ b/windows/winfw/src/winfw/winfw.cpp
@@ -240,6 +240,7 @@ WINFW_API
WinFw_ApplyPolicyConnecting(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
+ const wchar_t *exitEndpointIp,
const wchar_t **relayClients,
size_t relayClientsLen,
const wchar_t *tunnelInterfaceAlias,
@@ -269,6 +270,14 @@ WinFw_ApplyPolicyConnecting(
THROW_ERROR("Invalid argument: allowedTunnelTraffic");
}
+ const auto exitIpAddr = (exitEndpointIp != nullptr) ? std::make_optional(wfp::IpAddress(exitEndpointIp)) : std::nullopt;
+ const auto entryIpAddr = wfp::IpAddress(relay->ip);
+
+ if (entryIpAddr == exitIpAddr)
+ {
+ THROW_ERROR("Invalid argument: relay IP must not equal exitEndpointIp");
+ }
+
std::vector<std::wstring> relayClientWstrings;
relayClientWstrings.reserve(relayClientsLen);
for(int i = 0; i < relayClientsLen; i++) {
@@ -278,6 +287,7 @@ WinFw_ApplyPolicyConnecting(
return g_fwContext->applyPolicyConnecting(
*settings,
*relay,
+ exitIpAddr,
relayClientWstrings,
tunnelInterfaceAlias != nullptr ? std::make_optional(tunnelInterfaceAlias) : std::nullopt,
MakeOptional(allowedEndpoint),
@@ -309,6 +319,7 @@ WINFW_API
WinFw_ApplyPolicyConnected(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
+ const wchar_t *exitEndpointIp,
const wchar_t **relayClients,
size_t relayClientsLen,
const wchar_t *tunnelInterfaceAlias,
@@ -350,6 +361,14 @@ WinFw_ApplyPolicyConnected(
THROW_ERROR("Invalid argument: nonTunnelDnsServers");
}
+ const auto exitIpAddr = (exitEndpointIp != nullptr) ? std::make_optional(wfp::IpAddress(exitEndpointIp)) : std::nullopt;
+ const auto entryIpAddr = wfp::IpAddress(relay->ip);
+
+ if (entryIpAddr == exitIpAddr)
+ {
+ THROW_ERROR("Invalid argument: relay IP must not equal exitEndpointIp");
+ }
+
std::vector<wfp::IpAddress> convertedTunnelDnsServers;
std::vector<wfp::IpAddress> convertedNonTunnelDnsServers;
@@ -398,6 +417,7 @@ WinFw_ApplyPolicyConnected(
return g_fwContext->applyPolicyConnected(
*settings,
*relay,
+ exitIpAddr,
relayClientWstrings,
tunnelInterfaceAlias,
convertedTunnelDnsServers,
diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h
index f40d835a95..f3e26ea4aa 100644
--- a/windows/winfw/src/winfw/winfw.h
+++ b/windows/winfw/src/winfw/winfw.h
@@ -71,6 +71,7 @@ typedef struct tag_WinFwAllowedTunnelTraffic
}
WinFwAllowedTunnelTraffic;
+
///////////////////////////////////////////////////////////////////////////////
// Functions
///////////////////////////////////////////////////////////////////////////////
@@ -161,6 +162,12 @@ enum WINFW_POLICY_STATUS
// - Communication with the relay server
// - Specified in-tunnel traffic, except DNS.
//
+// Parameters:
+//
+// exitEndpointIp:
+// IP address of the exit relay, if it differs from `relay`. Otherwise, this should be set to
+// `nullptr`.
+//
extern "C"
WINFW_LINKAGE
WINFW_POLICY_STATUS
@@ -168,6 +175,7 @@ WINFW_API
WinFw_ApplyPolicyConnecting(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
+ const wchar_t *exitEndpointIp,
const wchar_t **relayClient,
size_t relayClientLen,
const wchar_t *tunnelInterfaceAlias,
@@ -190,6 +198,10 @@ WinFw_ApplyPolicyConnecting(
// tunnelInterfaceAlias:
// Friendly name of VPN tunnel interface
//
+// exitEndpointIp:
+// IP address of the exit relay, if it differs from `relay`. Otherwise, this should be set to
+// `nullptr`.
+//
extern "C"
WINFW_LINKAGE
WINFW_POLICY_STATUS
@@ -197,6 +209,7 @@ WINFW_API
WinFw_ApplyPolicyConnected(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
+ const wchar_t *exitEndpointIp,
const wchar_t **relayClient,
size_t relayClientLen,
const wchar_t *tunnelInterfaceAlias,