mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
feat: seamless WG switch on Windows for shared client IPs
This commit is contained in:
@@ -360,9 +360,9 @@ void VpnTrafficGuard::applyPolicy(Tunnel* tunnel)
|
|||||||
const QString peer = tunnel->remoteAddress();
|
const QString peer = tunnel->remoteAddress();
|
||||||
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
if (!peer.isEmpty()) iface->addExclusionRoute(peer);
|
if (!peer.isEmpty()) iface->addExclusionRoute(ifname, peer);
|
||||||
for (const QString& addr : excluded) {
|
for (const QString& addr : excluded) {
|
||||||
iface->addExclusionRoute(addr);
|
iface->addExclusionRoute(ifname, addr);
|
||||||
}
|
}
|
||||||
for (const QString& prefix : prefixes) {
|
for (const QString& prefix : prefixes) {
|
||||||
iface->addAllowedIp(ifname, prefix);
|
iface->addAllowedIp(ifname, prefix);
|
||||||
@@ -390,9 +390,9 @@ void VpnTrafficGuard::revokePolicy(Tunnel* tunnel)
|
|||||||
iface->delAllowedIp(ifname, prefix);
|
iface->delAllowedIp(ifname, prefix);
|
||||||
}
|
}
|
||||||
for (const QString& addr : excluded) {
|
for (const QString& addr : excluded) {
|
||||||
iface->delExclusionRoute(addr);
|
iface->delExclusionRoute(ifname, addr);
|
||||||
}
|
}
|
||||||
if (!peer.isEmpty()) iface->delExclusionRoute(peer);
|
if (!peer.isEmpty()) iface->delExclusionRoute(ifname, peer);
|
||||||
});
|
});
|
||||||
#else
|
#else
|
||||||
Q_UNUSED(tunnel)
|
Q_UNUSED(tunnel)
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) {
|
|||||||
|
|
||||||
// Bring up the wireguard interface if not already done.
|
// Bring up the wireguard interface if not already done.
|
||||||
if (!wg->interfaceExists()) {
|
if (!wg->interfaceExists()) {
|
||||||
if (!wg->addInterface(config)) {
|
InterfaceConfig bringupConfig = config;
|
||||||
|
bringupConfig.m_deferAddressSetup = (m_primaryIfname != ifname);
|
||||||
|
if (!wg->addInterface(bringupConfig)) {
|
||||||
logger.error() << "Interface creation failed.";
|
logger.error() << "Interface creation failed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -126,6 +128,14 @@ bool Daemon::setPrimary(const QString& ifname, const InterfaceConfig& config) {
|
|||||||
const QString priorPrimary = m_primaryIfname;
|
const QString priorPrimary = m_primaryIfname;
|
||||||
m_primaryIfname = ifname;
|
m_primaryIfname = ifname;
|
||||||
|
|
||||||
|
if (!priorPrimary.isEmpty() && priorPrimary != ifname) {
|
||||||
|
if (WireguardUtils* oldWg = m_tunnels.value(priorPrimary)) {
|
||||||
|
const InterfaceConfig& oldConfig = m_connections.value(priorPrimary).m_config;
|
||||||
|
oldWg->removeDeviceAddresses(oldConfig.m_deviceIpv4Address, oldConfig.m_deviceIpv6Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg->applyDeviceAddresses(config.m_deviceIpv4Address, config.m_deviceIpv6Address);
|
||||||
|
|
||||||
auto failure_guard = qScopeGuard([this, ifname, priorPrimary] {
|
auto failure_guard = qScopeGuard([this, ifname, priorPrimary] {
|
||||||
deactivateTunnel(ifname);
|
deactivateTunnel(ifname);
|
||||||
m_primaryIfname = priorPrimary;
|
m_primaryIfname = priorPrimary;
|
||||||
@@ -190,13 +200,14 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::addExclusionRoute(const QString &addr) {
|
bool Daemon::addExclusionRoute(const QString &ifname, const QString &addr) {
|
||||||
IPAddress prefix(addr);
|
IPAddress prefix(addr);
|
||||||
if (m_excludedAddrSet.contains(prefix)) {
|
if (m_excludedAddrSet.contains(prefix)) {
|
||||||
m_excludedAddrSet[prefix]++;
|
m_excludedAddrSet[prefix]++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
WireguardUtils* wg = primaryWgutils();
|
WireguardUtils* wg = wgutilsFor(ifname);
|
||||||
|
if (!wg) wg = primaryWgutils();
|
||||||
if (!wg || !wg->addExclusionRoute(prefix)) {
|
if (!wg || !wg->addExclusionRoute(prefix)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -204,7 +215,7 @@ bool Daemon::addExclusionRoute(const QString &addr) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::delExclusionRoute(const QString &addr) {
|
bool Daemon::delExclusionRoute(const QString &ifname, const QString &addr) {
|
||||||
IPAddress prefix(addr);
|
IPAddress prefix(addr);
|
||||||
if (!m_excludedAddrSet.contains(prefix)) {
|
if (!m_excludedAddrSet.contains(prefix)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -214,7 +225,8 @@ bool Daemon::delExclusionRoute(const QString &addr) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
m_excludedAddrSet.remove(prefix);
|
m_excludedAddrSet.remove(prefix);
|
||||||
WireguardUtils* wg = primaryWgutils();
|
WireguardUtils* wg = wgutilsFor(ifname);
|
||||||
|
if (!wg) wg = primaryWgutils();
|
||||||
return wg && wg->deleteExclusionRoute(prefix);
|
return wg && wg->deleteExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ class Daemon : public QObject {
|
|||||||
virtual bool deactivate(bool emitSignals = true);
|
virtual bool deactivate(bool emitSignals = true);
|
||||||
virtual QJsonObject getStatus();
|
virtual QJsonObject getStatus();
|
||||||
|
|
||||||
bool addExclusionRoute(const QString &addr);
|
bool addExclusionRoute(const QString &ifname, const QString &addr);
|
||||||
bool delExclusionRoute(const QString &addr);
|
bool delExclusionRoute(const QString &ifname, const QString &addr);
|
||||||
bool addAllowedIp(const QString &ifname, const QString &prefix);
|
bool addAllowedIp(const QString &ifname, const QString &prefix);
|
||||||
bool delAllowedIp(const QString &ifname, const QString &prefix);
|
bool delAllowedIp(const QString &ifname, const QString &prefix);
|
||||||
bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers);
|
bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers);
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class InterfaceConfig {
|
|||||||
QString m_transportPacketMagicHeader;
|
QString m_transportPacketMagicHeader;
|
||||||
QMap<QString, QString> m_specialJunk;
|
QMap<QString, QString> m_specialJunk;
|
||||||
QString m_ifname;
|
QString m_ifname;
|
||||||
|
bool m_deferAddressSetup = false;
|
||||||
|
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
QString toWgConf(
|
QString toWgConf(
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ class WireguardUtils : public QObject {
|
|||||||
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
||||||
virtual bool deleteInterface() = 0;
|
virtual bool deleteInterface() = 0;
|
||||||
|
|
||||||
|
virtual bool applyDeviceAddresses(const QString& ipv4Address, const QString& ipv6Address) { return true; }
|
||||||
|
virtual bool removeDeviceAddresses(const QString& ipv4Address, const QString& ipv6Address) { return true; }
|
||||||
|
|
||||||
virtual bool updatePeer(const InterfaceConfig& config) = 0;
|
virtual bool updatePeer(const InterfaceConfig& config) = 0;
|
||||||
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
||||||
virtual QList<PeerStatus> getPeerStatus() = 0;
|
virtual QList<PeerStatus> getPeerStatus() = 0;
|
||||||
|
|||||||
@@ -58,9 +58,12 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSet<quint64> WindowsRouteMonitor::s_vpnLuids;
|
||||||
|
|
||||||
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
||||||
: QObject(parent), m_luid(luid) {
|
: QObject(parent), m_luid(luid) {
|
||||||
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
||||||
|
s_vpnLuids.insert(luid);
|
||||||
logger.debug() << "WindowsRouteMonitor created.";
|
logger.debug() << "WindowsRouteMonitor created.";
|
||||||
|
|
||||||
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
|
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
|
||||||
@@ -69,8 +72,8 @@ WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
|||||||
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
||||||
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
||||||
CancelMibChangeNotify2(m_routeHandle);
|
CancelMibChangeNotify2(m_routeHandle);
|
||||||
|
s_vpnLuids.remove(m_luid);
|
||||||
|
|
||||||
flushRouteTable(m_exclusionRoutes);
|
|
||||||
flushRouteTable(m_clonedRoutes);
|
flushRouteTable(m_clonedRoutes);
|
||||||
logger.debug() << "WindowsRouteMonitor destroyed.";
|
logger.debug() << "WindowsRouteMonitor destroyed.";
|
||||||
}
|
}
|
||||||
@@ -95,7 +98,8 @@ void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
|
|||||||
// Rebuild the list of interfaces that are valid for routing.
|
// Rebuild the list of interfaces that are valid for routing.
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
MIB_IPINTERFACE_ROW* row = &table->Table[i];
|
MIB_IPINTERFACE_ROW* row = &table->Table[i];
|
||||||
if (row->InterfaceLuid.Value == m_luid) {
|
// Skip any VPN wintun (own or sibling) so exclusion routes never pick one.
|
||||||
|
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!row->Connected) {
|
if (!row->Connected) {
|
||||||
@@ -126,8 +130,8 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
|||||||
nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
|
nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
// Ignore routes into the VPN interface.
|
// Skip any VPN wintun (own or sibling).
|
||||||
if (row->InterfaceLuid.Value == m_luid) {
|
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||||
@@ -239,14 +243,16 @@ QHostAddress WindowsRouteMonitor::prefixToAddress(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const {
|
bool WindowsRouteMonitor::isRouteExcluded(void* ptable,
|
||||||
auto i = m_exclusionRoutes.constBegin();
|
const IP_ADDRESS_PREFIX* dest) const {
|
||||||
while (i != m_exclusionRoutes.constEnd()) {
|
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
||||||
const MIB_IPFORWARD_ROW2* row = i.value();
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
|
const MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
|
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
||||||
|
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
||||||
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -272,8 +278,8 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
|||||||
|
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
// Ignore routes into the VPN interface.
|
// Skip any VPN wintun (own or sibling).
|
||||||
if (row->InterfaceLuid.Value == m_luid) {
|
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Ignore the default route
|
// Ignore the default route
|
||||||
@@ -286,7 +292,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Ignore routes which should be excluded.
|
// Ignore routes which should be excluded.
|
||||||
if (isRouteExcluded(&row->DestinationPrefix)) {
|
if (isRouteExcluded(table, &row->DestinationPrefix)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
||||||
@@ -375,11 +381,6 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_exclusionRoutes.contains(prefix)) {
|
|
||||||
logger.warning() << "Exclusion route already exists";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate and initialize the MIB routing table row.
|
// Allocate and initialize the MIB routing table row.
|
||||||
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
|
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
|
||||||
InitializeIpForwardEntry(data);
|
InitializeIpForwardEntry(data);
|
||||||
@@ -427,8 +428,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||||||
updateCapturedRoutes(family, table);
|
updateCapturedRoutes(family, table);
|
||||||
updateExclusionRoute(data, table);
|
updateExclusionRoute(data, table);
|
||||||
FreeMibTable(table);
|
FreeMibTable(table);
|
||||||
|
delete data;
|
||||||
m_exclusionRoutes[prefix] = data;
|
m_ownedExclusionRoutes.insert(prefix);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,23 +437,39 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
|||||||
logger.debug() << "Deleting exclusion route for"
|
logger.debug() << "Deleting exclusion route for"
|
||||||
<< prefix.address().toString();
|
<< prefix.address().toString();
|
||||||
|
|
||||||
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
m_ownedExclusionRoutes.remove(prefix);
|
||||||
if (data == nullptr) {
|
|
||||||
return true;
|
PMIB_IPFORWARD_TABLE2 table;
|
||||||
|
DWORD result = GetIpForwardTable2(AF_UNSPEC, &table);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to fetch routing table:" << result;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD result = DeleteIpForwardEntry2(data);
|
const bool isV4 = prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
const ADDRESS_FAMILY addrFamily =
|
||||||
logger.error() << "Failed to delete route to"
|
isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
|
||||||
<< prefix.toString()
|
: static_cast<ADDRESS_FAMILY>(AF_INET6);
|
||||||
<< "result:" << result;
|
bool deleted = false;
|
||||||
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
|
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
||||||
|
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
||||||
|
if (row->DestinationPrefix.Prefix.si_family != addrFamily) continue;
|
||||||
|
if (row->DestinationPrefix.PrefixLength != prefix.prefixLength()) continue;
|
||||||
|
if (prefixToAddress(&row->DestinationPrefix) != prefix.address()) continue;
|
||||||
|
DWORD r = DeleteIpForwardEntry2(row);
|
||||||
|
if (r == NO_ERROR || r == ERROR_NOT_FOUND) {
|
||||||
|
deleted = true;
|
||||||
|
} else {
|
||||||
|
logger.error() << "Failed to delete route to" << prefix.toString()
|
||||||
|
<< "result:" << r;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
FreeMibTable(table);
|
||||||
// Captured routes might have changed.
|
updateCapturedRoutes(addrFamily);
|
||||||
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
|
return deleted;
|
||||||
|
|
||||||
delete data;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsRouteMonitor::flushRouteTable(
|
void WindowsRouteMonitor::flushRouteTable(
|
||||||
@@ -492,8 +509,24 @@ void WindowsRouteMonitor::routeChanged() {
|
|||||||
|
|
||||||
updateInterfaceMetrics(AF_UNSPEC);
|
updateInterfaceMetrics(AF_UNSPEC);
|
||||||
updateCapturedRoutes(AF_UNSPEC, table);
|
updateCapturedRoutes(AF_UNSPEC, table);
|
||||||
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
|
||||||
updateExclusionRoute(data, table);
|
for (const IPAddress& prefix : m_ownedExclusionRoutes) {
|
||||||
|
const bool isV4 =
|
||||||
|
prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
|
const ADDRESS_FAMILY addrFamily =
|
||||||
|
isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
|
||||||
|
: static_cast<ADDRESS_FAMILY>(AF_INET6);
|
||||||
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
|
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
||||||
|
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
||||||
|
if (row->DestinationPrefix.Prefix.si_family != addrFamily) continue;
|
||||||
|
if (row->DestinationPrefix.PrefixLength != prefix.prefixLength()) continue;
|
||||||
|
if (prefixToAddress(&row->DestinationPrefix) != prefix.address()) continue;
|
||||||
|
MIB_IPFORWARD_ROW2 copy = *row;
|
||||||
|
updateExclusionRoute(©, table);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeMibTable(table);
|
FreeMibTable(table);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
|
|
||||||
@@ -28,7 +29,6 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
|
|
||||||
bool addExclusionRoute(const IPAddress& prefix);
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
bool deleteExclusionRoute(const IPAddress& prefix);
|
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||||
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
|
|
||||||
|
|
||||||
quint64 getLuid() const { return m_luid; }
|
quint64 getLuid() const { return m_luid; }
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
void routeChanged();
|
void routeChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const;
|
bool isRouteExcluded(void* table, const IP_ADDRESS_PREFIX* dest) const;
|
||||||
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||||
const IP_ADDRESS_PREFIX* dest);
|
const IP_ADDRESS_PREFIX* dest);
|
||||||
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
||||||
@@ -47,7 +47,7 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
void updateCapturedRoutes(int family);
|
void updateCapturedRoutes(int family);
|
||||||
void updateCapturedRoutes(int family, void* table);
|
void updateCapturedRoutes(int family, void* table);
|
||||||
|
|
||||||
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
QSet<IPAddress> m_ownedExclusionRoutes;
|
||||||
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
||||||
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
||||||
|
|
||||||
@@ -57,6 +57,8 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
|
|
||||||
const quint64 m_luid = 0;
|
const quint64 m_luid = 0;
|
||||||
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
|
static QSet<quint64> s_vpnLuids;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* WINDOWSROUTEMONITOR_H */
|
#endif /* WINDOWSROUTEMONITOR_H */
|
||||||
|
|||||||
@@ -107,12 +107,21 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
|||||||
configString.truncate(peerStart);
|
configString.truncate(peerStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
qsizetype dnsStart = configString.indexOf("DNS = ");
|
auto stripLine = [&](const QString& key) {
|
||||||
if (dnsStart >= 0) {
|
qsizetype start = configString.startsWith(key + " = ")
|
||||||
qsizetype dnsEnd = configString.indexOf('\n', dnsStart);
|
? 0
|
||||||
if (dnsEnd >= 0) {
|
: configString.indexOf("\n" + key + " = ");
|
||||||
configString.remove(dnsStart, dnsEnd - dnsStart + 1);
|
if (start < 0) return;
|
||||||
}
|
if (start != 0) start += 1;
|
||||||
|
qsizetype end = configString.indexOf('\n', start);
|
||||||
|
if (end < 0) return;
|
||||||
|
configString.remove(start, end - start + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
stripLine("DNS");
|
||||||
|
if (config.m_deferAddressSetup) {
|
||||||
|
// Wintun rejects duplicate IPv4; daemon will assign at swap time.
|
||||||
|
stripLine("Address");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ifname = config.m_ifname.isEmpty() ? s_defaultInterfaceName() : config.m_ifname;
|
m_ifname = config.m_ifname.isEmpty() ? s_defaultInterfaceName() : config.m_ifname;
|
||||||
@@ -143,6 +152,60 @@ bool WireguardUtilsWindows::deleteInterface() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::applyDeviceAddresses(const QString& ipv4Address,
|
||||||
|
const QString& ipv6Address) {
|
||||||
|
auto applyOne = [&](const QString& addr, int family, uint8_t prefix) -> bool {
|
||||||
|
if (addr.isEmpty()) return true;
|
||||||
|
const QByteArray ip = addr.section('/', 0, 0).toUtf8();
|
||||||
|
MIB_UNICASTIPADDRESS_ROW row;
|
||||||
|
InitializeUnicastIpAddressEntry(&row);
|
||||||
|
row.InterfaceLuid.Value = m_luid;
|
||||||
|
row.Address.si_family = static_cast<ADDRESS_FAMILY>(family);
|
||||||
|
row.OnLinkPrefixLength = prefix;
|
||||||
|
row.DadState = IpDadStatePreferred;
|
||||||
|
void* dst = (family == AF_INET)
|
||||||
|
? static_cast<void*>(&row.Address.Ipv4.sin_addr)
|
||||||
|
: static_cast<void*>(&row.Address.Ipv6.sin6_addr);
|
||||||
|
if (InetPtonA(family, ip.constData(), dst) != 1) {
|
||||||
|
logger.error() << "applyDeviceAddresses: cannot parse" << addr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DWORD r = CreateUnicastIpAddressEntry(&row);
|
||||||
|
logger.debug() << "Apply" << addr << "to" << m_ifname << "result:" << r;
|
||||||
|
return r == NO_ERROR || r == ERROR_OBJECT_ALREADY_EXISTS;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!applyOne(ipv4Address, AF_INET, 32)) return false;
|
||||||
|
if (!applyOne(ipv6Address, AF_INET6, 128)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::removeDeviceAddresses(const QString& ipv4Address,
|
||||||
|
const QString& ipv6Address) {
|
||||||
|
auto removeOne = [&](const QString& addr, int family) -> bool {
|
||||||
|
if (addr.isEmpty()) return true;
|
||||||
|
const QByteArray ip = addr.section('/', 0, 0).toUtf8();
|
||||||
|
MIB_UNICASTIPADDRESS_ROW row;
|
||||||
|
InitializeUnicastIpAddressEntry(&row);
|
||||||
|
row.InterfaceLuid.Value = m_luid;
|
||||||
|
row.Address.si_family = static_cast<ADDRESS_FAMILY>(family);
|
||||||
|
void* dst = (family == AF_INET)
|
||||||
|
? static_cast<void*>(&row.Address.Ipv4.sin_addr)
|
||||||
|
: static_cast<void*>(&row.Address.Ipv6.sin6_addr);
|
||||||
|
if (InetPtonA(family, ip.constData(), dst) != 1) {
|
||||||
|
logger.error() << "removeDeviceAddresses: cannot parse" << addr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DWORD r = DeleteUnicastIpAddressEntry(&row);
|
||||||
|
logger.debug() << "Remove" << addr << "from" << m_ifname << "result:" << r;
|
||||||
|
return r == NO_ERROR || r == ERROR_NOT_FOUND;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ok = removeOne(ipv4Address, AF_INET);
|
||||||
|
ok &= removeOne(ipv6Address, AF_INET6);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||||
QByteArray publicKey =
|
QByteArray publicKey =
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
|||||||
bool addInterface(const InterfaceConfig& config) override;
|
bool addInterface(const InterfaceConfig& config) override;
|
||||||
bool deleteInterface() override;
|
bool deleteInterface() override;
|
||||||
|
|
||||||
|
bool applyDeviceAddresses(const QString& ipv4Address, const QString& ipv6Address) override;
|
||||||
|
bool removeDeviceAddresses(const QString& ipv4Address, const QString& ipv6Address) override;
|
||||||
|
|
||||||
bool updatePeer(const InterfaceConfig& config) override;
|
bool updatePeer(const InterfaceConfig& config) override;
|
||||||
bool deletePeer(const InterfaceConfig& config) override;
|
bool deletePeer(const InterfaceConfig& config) override;
|
||||||
QList<PeerStatus> getPeerStatus() override;
|
QList<PeerStatus> getPeerStatus() override;
|
||||||
|
|||||||
@@ -169,22 +169,10 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
|
|||||||
const bool isWg = VpnProtocol::isWireGuardBased(container);
|
const bool isWg = VpnProtocol::isWireGuardBased(container);
|
||||||
const QString preAllocatedIfname = isWg ? allocateIfname() : QString();
|
const QString preAllocatedIfname = isWg ? allocateIfname() : QString();
|
||||||
|
|
||||||
bool seamlessSwitch = m_active
|
// Seamless WG -> WG switch path: already connected via Tunnel, new container is also WG.
|
||||||
&& m_connectionState == Vpn::ConnectionState::Connected
|
if (m_active
|
||||||
&& isWg;
|
&& m_connectionState == Vpn::ConnectionState::Connected
|
||||||
#ifdef Q_OS_WIN
|
&& isWg) {
|
||||||
if (seamlessSwitch) {
|
|
||||||
auto clientIpFor = [](const QJsonObject& cfg) {
|
|
||||||
const QString proto = cfg.value("protocol").toString();
|
|
||||||
return cfg.value(proto + "_config_data").toObject().value(configKey::clientIp).toString();
|
|
||||||
};
|
|
||||||
if (clientIpFor(vpnConfiguration) == clientIpFor(m_active->config())) {
|
|
||||||
seamlessSwitch = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (seamlessSwitch) {
|
|
||||||
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
|
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
|
||||||
releaseIfname(preAllocatedIfname);
|
releaseIfname(preAllocatedIfname);
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ class IpcInterface
|
|||||||
SLOT( int routeAddList(const QString &gw, const QStringList &ips) );
|
SLOT( int routeAddList(const QString &gw, const QStringList &ips) );
|
||||||
SLOT( bool clearSavedRoutes() );
|
SLOT( bool clearSavedRoutes() );
|
||||||
SLOT( bool routeDeleteList(const QString &gw, const QStringList &ip) );
|
SLOT( bool routeDeleteList(const QString &gw, const QStringList &ip) );
|
||||||
SLOT( bool addExclusionRoute(const QString &addr) );
|
SLOT( bool addExclusionRoute(const QString &ifname, const QString &addr) );
|
||||||
SLOT( bool delExclusionRoute(const QString &addr) );
|
SLOT( bool delExclusionRoute(const QString &ifname, const QString &addr) );
|
||||||
SLOT( bool addAllowedIp(const QString &ifname, const QString &prefix) );
|
SLOT( bool addAllowedIp(const QString &ifname, const QString &prefix) );
|
||||||
SLOT( bool delAllowedIp(const QString &ifname, const QString &prefix) );
|
SLOT( bool delAllowedIp(const QString &ifname, const QString &prefix) );
|
||||||
SLOT( bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) );
|
SLOT( bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) );
|
||||||
|
|||||||
+4
-4
@@ -93,14 +93,14 @@ bool IpcServer::routeDeleteList(const QString &gw, const QStringList &ips)
|
|||||||
return Router::routeDeleteList(gw, ips);
|
return Router::routeDeleteList(gw, ips);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::addExclusionRoute(const QString &addr)
|
bool IpcServer::addExclusionRoute(const QString &ifname, const QString &addr)
|
||||||
{
|
{
|
||||||
return Daemon::instance() && Daemon::instance()->addExclusionRoute(addr);
|
return Daemon::instance() && Daemon::instance()->addExclusionRoute(ifname, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::delExclusionRoute(const QString &addr)
|
bool IpcServer::delExclusionRoute(const QString &ifname, const QString &addr)
|
||||||
{
|
{
|
||||||
return Daemon::instance() && Daemon::instance()->delExclusionRoute(addr);
|
return Daemon::instance() && Daemon::instance()->delExclusionRoute(ifname, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::addAllowedIp(const QString &ifname, const QString &prefix)
|
bool IpcServer::addAllowedIp(const QString &ifname, const QString &prefix)
|
||||||
|
|||||||
+2
-2
@@ -23,8 +23,8 @@ public:
|
|||||||
virtual int routeAddList(const QString &gw, const QStringList &ips) override;
|
virtual int routeAddList(const QString &gw, const QStringList &ips) override;
|
||||||
virtual bool clearSavedRoutes() override;
|
virtual bool clearSavedRoutes() override;
|
||||||
virtual bool routeDeleteList(const QString &gw, const QStringList &ips) override;
|
virtual bool routeDeleteList(const QString &gw, const QStringList &ips) override;
|
||||||
virtual bool addExclusionRoute(const QString &addr) override;
|
virtual bool addExclusionRoute(const QString &ifname, const QString &addr) override;
|
||||||
virtual bool delExclusionRoute(const QString &addr) override;
|
virtual bool delExclusionRoute(const QString &ifname, const QString &addr) override;
|
||||||
virtual bool addAllowedIp(const QString &ifname, const QString &prefix) override;
|
virtual bool addAllowedIp(const QString &ifname, const QString &prefix) override;
|
||||||
virtual bool delAllowedIp(const QString &ifname, const QString &prefix) override;
|
virtual bool delAllowedIp(const QString &ifname, const QString &prefix) override;
|
||||||
virtual bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) override;
|
virtual bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) override;
|
||||||
|
|||||||
Reference in New Issue
Block a user