From 0773d196de3b5ef010eb8096fc23dc4e0f01a963 Mon Sep 17 00:00:00 2001 From: chenyuanbo Date: Mon, 21 Sep 2026 13:59:38 +0800 Subject: [PATCH] refactor: improve VPN DNS target device discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Extract device matching logic into reusable helper functions (isVpnTunnelDeviceType and tryDeviceTarget) 2. Replace the hardcoded Tun connection type check with a broader Ip4Config-based matching approach 3. Add Pass 1: match any ActiveConnection sharing the same Ip4Config as the VPN (handles tun-like devices with separate ACs) 4. Add Pass 2: device-level fallback matching devices whose own Ip4Config equals the VPN's (handles virtual interfaces without a separate AC, such as ppp/xfrm) 5. Expand supported tunnel device types to include Ppp and WireGuard in addition to Tun, Generic, and IpTunnel 6. Introduce getDeviceIp4Config helper to query the Ip4Config property directly from the device interface The previous implementation only matched connections explicitly typed as Tun, which missed other virtual tunnel types like ppp, wireguard, and xfrm that can also carry VPN DNS. Rewriting the lookup around shared Ip4Config provides a more robust and type-agnostic way to locate the correct device for applying VPN DNS mode. Influence: 1. Verify VPN DNS is applied correctly on tun/tap based VPN connections 2. Test DNS application on ppp-based VPNs (L2TP/PPTP/SSTP) 3. Test DNS application on WireGuard VPN connections 4. Test DNS application on xfrm/ip-tunnel devices 5. Verify behavior when multiple ActiveConnections share the same Ip4Config as the VPN 6. Confirm fallback path works when no ActiveConnection matches but the device-level Ip4Config matches 7. Check that the debug log "[DNS-TRACE] No matching VPN virtual device found" is emitted only when no target is found 8. Verify DNS priority handling (dnsPriority != 0 triggers DNS collection and skips devices with empty DNS) refactor: 改进 VPN DNS 目标设备发现逻辑 1. 将设备匹配逻辑抽取为可复用的辅助函数(isVpnTunnelDeviceType 和 tryDeviceTarget) 2. 用基于 Ip4Config 的更广泛匹配方式替换原先硬编码的 Tun 连接类型判断 3. 新增 Pass 1:匹配与 VPN 共享同一 Ip4Config 的其它 ActiveConnection(处理 tun 系拥有独立 AC 的场景) 4. 新增 Pass 2:设备级兜底,匹配自身 Ip4Config 与 VPN 相同的设备(处理无 独立 AC 的虚拟口,如 ppp/xfrm) 5. 扩展支持的隧道设备类型,在 Tun、Generic、IpTunnel 基础上新增 Ppp 和 WireGuard 6. 新增 getDeviceIp4Config 辅助函数,直接从设备接口查询 Ip4Config 属性 原有实现只能匹配显式声明为 Tun 类型的连接,遗漏了 ppp、wireguard、xfrm 等同样可承载 VPN DNS 的虚拟隧道类型。改为围绕共享 Ip4Config 进行查找,能 够以更健壮、与类型无关的方式定位应用 VPN DNS 模式的正确设备。 Influence: 1. 验证基于 tun/tap 的 VPN 连接中 VPN DNS 能正确应用 2. 测试基于 ppp 的 VPN(L2TP/PPTP/SSTP)的 DNS 应用 3. 测试 WireGuard VPN 连接的 DNS 应用 4. 测试 xfrm/ip-tunnel 设备的 DNS 应用 5. 验证当多个 ActiveConnection 与 VPN 共享同一 Ip4Config 时的行为 6. 确认在无 ActiveConnection 匹配但设备级 Ip4Config 匹配时兜底路径生效 7. 检查调试日志 "[DNS-TRACE] No matching VPN virtual device found" 仅在 未找到目标时输出 8. 验证 DNS 优先级处理(dnsPriority != 0 时触发 DNS 收集并跳过 DNS 为空 的设备) PMS: BUG-377741 --- .../session/vpndnsmode/vpndnsmodehandler.cpp | 90 ++++++++++++------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/network-service-plugin/src/session/vpndnsmode/vpndnsmodehandler.cpp b/network-service-plugin/src/session/vpndnsmode/vpndnsmodehandler.cpp index af543791..c3c2558a 100644 --- a/network-service-plugin/src/session/vpndnsmode/vpndnsmodehandler.cpp +++ b/network-service-plugin/src/session/vpndnsmode/vpndnsmodehandler.cpp @@ -22,6 +22,7 @@ using namespace network::sessionservice; namespace { constexpr const char *const NMService = "org.freedesktop.NetworkManager"; constexpr const char *const NMConnActiveInterface = "org.freedesktop.NetworkManager.Connection.Active"; +constexpr const char *const NMDeviceInterface = "org.freedesktop.NetworkManager.Device"; QString getActiveConnectionIp4Config(const QString &acPath) { @@ -29,6 +30,12 @@ QString getActiveConnectionIp4Config(const QString &acPath) return iface.property("Ip4Config").value().path(); } +QString getDeviceIp4Config(const QString &devPath) +{ + QDBusInterface iface(NMService, devPath, NMDeviceInterface, QDBusConnection::systemBus()); + return iface.property("Ip4Config").value().path(); +} + QList collectDnsFromDevice(const NetworkManager::Device::Ptr &dev) { QList addrList; @@ -51,6 +58,39 @@ struct VpnDnsModeApplyTarget QList dnsServers; }; +// 可承载 VPN DNS 的虚拟隧道设备类型:tun/tap、NM 未细分的虚拟口(如 xfrm)、 +// ip-tunnel、ppp(L2TP/PPTP/SSTP)以及 wireguard。 +bool isVpnTunnelDeviceType(NetworkManager::Device::Type type) +{ + return type == NetworkManager::Device::Type::Tun + || type == NetworkManager::Device::Type::Generic + || type == NetworkManager::Device::Type::IpTunnel + || type == NetworkManager::Device::Type::Ppp + || type == NetworkManager::Device::Type::WireGuard; +} + +bool tryDeviceTarget(const NetworkManager::Device::Ptr &dev, int dnsPriority, + VpnDnsModeApplyTarget *target) +{ + if (dev.isNull() || !isVpnTunnelDeviceType(dev->type())) + return false; + + const int ifindex = static_cast(if_nametoindex(dev->interfaceName().toStdString().c_str())); + if (ifindex <= 0) + return false; + + QList dnsList; + if (dnsPriority != 0) { + dnsList = collectDnsFromDevice(dev); + if (dnsList.isEmpty()) + return false; + } + + target->ifindex = ifindex; + target->dnsServers = dnsList; + return true; +} + bool findDnsModeApplyTarget(const NetworkManager::ActiveConnection::Ptr &vpnAc, int dnsPriority, VpnDnsModeApplyTarget *target) { @@ -63,45 +103,35 @@ bool findDnsModeApplyTarget(const NetworkManager::ActiveConnection::Ptr &vpnAc, return false; } + // Pass 1:与 VPN AC 共享同一 Ip4Config 的其它 ActiveConnection(tun 系等存在独立 AC 的场景)。 const NetworkManager::ActiveConnection::List allActiveConnections = NetworkManager::activeConnections(); - for (const NetworkManager::ActiveConnection::Ptr &tunAc : allActiveConnections) { - if (tunAc.isNull() || tunAc->connection().isNull() || tunAc == vpnAc) + for (const NetworkManager::ActiveConnection::Ptr &ac : allActiveConnections) { + if (ac.isNull() || ac->connection().isNull() || ac == vpnAc) continue; - if (tunAc->connection()->settings()->connectionType() != NetworkManager::ConnectionSettings::ConnectionType::Tun) + if (getActiveConnectionIp4Config(ac->path()) != vpnIp4ConfigPath) continue; - const QString tunIp4ConfigPath = getActiveConnectionIp4Config(tunAc->path()); - if (tunIp4ConfigPath != vpnIp4ConfigPath) + for (const QString &devPath : ac->devices()) { + if (tryDeviceTarget(NetworkManager::findNetworkInterface(devPath), dnsPriority, target)) + return true; + } + } + + // Pass 2:设备级兜底,设备自身 Ip4Config 与 VPN 相同(虚拟口无独立 AC 的场景,如 ppp/xfrm)。 + const NetworkManager::Device::List allDevices = NetworkManager::networkInterfaces(); + for (const NetworkManager::Device::Ptr &dev : allDevices) { + if (dev.isNull() || !isVpnTunnelDeviceType(dev->type())) + continue; + + if (getDeviceIp4Config(dev->uni()) != vpnIp4ConfigPath) continue; - for (const QString &devPath : tunAc->devices()) { - NetworkManager::Device::Ptr dev = NetworkManager::findNetworkInterface(devPath); - if (dev.isNull()) - continue; - - if (dev->type() != NetworkManager::Device::Type::Tun && - dev->type() != NetworkManager::Device::Type::Generic && - dev->type() != NetworkManager::Device::Type::IpTunnel) - continue; - - const int ifindex = static_cast(if_nametoindex(dev->interfaceName().toStdString().c_str())); - if (ifindex <= 0) - continue; - - QList dnsList; - if (dnsPriority != 0) { - dnsList = collectDnsFromDevice(dev); - if (dnsList.isEmpty()) - continue; - } - - target->ifindex = ifindex; - target->dnsServers = dnsList; + if (tryDeviceTarget(dev, dnsPriority, target)) return true; - } } - qCDebug(DSM()) << "[DNS-TRACE] No matching Tun AC found for VPN:" << vpnAc->id(); + + qCDebug(DSM()) << "[DNS-TRACE] No matching VPN virtual device found for VPN:" << vpnAc->id(); return false; } } // namespace