mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Add system dns toggle
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
#include "qendian.h"
|
#include "qendian.h"
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
#pragma comment(lib, "iphlpapi.lib")
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
@@ -22,6 +23,21 @@
|
|||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusMessage>
|
||||||
|
#include <QDBusReply>
|
||||||
|
#include <QDBusArgument>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include "platforms/linux/daemon/dbustypeslinux.h"
|
||||||
|
|
||||||
|
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
|
||||||
|
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
|
||||||
|
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
|
||||||
|
constexpr const char* DBUS_PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
@@ -30,10 +46,17 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <net/route.h>
|
#include <net/route.h>
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <SystemConfiguration/SystemConfiguration.h>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QRegularExpression>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
|
#include <QPair>
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
||||||
{
|
{
|
||||||
@@ -475,3 +498,182 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
return gateway;
|
return gateway;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPair<QString, QString> NetworkUtilities::getSystemDnsAddress()
|
||||||
|
{
|
||||||
|
QPair<QString, QString> result;
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
// Try systemd-resolved via D-Bus first
|
||||||
|
QDBusConnection bus = QDBusConnection::systemBus();
|
||||||
|
if (bus.isConnected()) {
|
||||||
|
// Try to get DNS from Resolve DNS property using org.freedesktop.DBus.Properties
|
||||||
|
// Use the same approach as in dnsutilslinux.cpp
|
||||||
|
QDBusMessage message = QDBusMessage::createMethodCall(
|
||||||
|
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
|
||||||
|
message << QString(DBUS_RESOLVE_MANAGER);
|
||||||
|
message << QString("DNS");
|
||||||
|
|
||||||
|
QDBusReply<QVariant> dnsReply = bus.call(message);
|
||||||
|
|
||||||
|
if (dnsReply.isValid()) {
|
||||||
|
QDBusArgument dnsArg = qvariant_cast<QDBusArgument>(dnsReply.value());
|
||||||
|
QList<DnsResolver> resolverList = qdbus_cast<QList<DnsResolver>>(dnsArg);
|
||||||
|
|
||||||
|
QStringList dnsServers;
|
||||||
|
for (const auto& resolver : resolverList) {
|
||||||
|
if (resolver.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
QString dnsStr = resolver.toString();
|
||||||
|
if (checkIPv4Format(dnsStr) && !dnsServers.contains(dnsStr)) {
|
||||||
|
dnsServers.append(dnsStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dnsServers.isEmpty()) {
|
||||||
|
result.first = dnsServers.first();
|
||||||
|
if (dnsServers.size() > 1) {
|
||||||
|
result.second = dnsServers.at(1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to /etc/resolv.conf
|
||||||
|
QFile resolvConf("/etc/resolv.conf");
|
||||||
|
if (resolvConf.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QTextStream in(&resolvConf);
|
||||||
|
QStringList dnsServers;
|
||||||
|
|
||||||
|
while (!in.atEnd()) {
|
||||||
|
QString line = in.readLine().trimmed();
|
||||||
|
if (line.startsWith("nameserver")) {
|
||||||
|
QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
|
||||||
|
if (parts.size() >= 2) {
|
||||||
|
QString dns = parts.at(1);
|
||||||
|
if (checkIPv4Format(dns)) {
|
||||||
|
dnsServers.append(dns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dnsServers.isEmpty()) {
|
||||||
|
result.first = dnsServers.first();
|
||||||
|
if (dnsServers.size() > 1) {
|
||||||
|
result.second = dnsServers.at(1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Failed to get system DNS on Linux";
|
||||||
|
return result; // Return empty pair
|
||||||
|
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
// Use GetAdaptersAddresses to get DNS servers
|
||||||
|
ULONG bufferSize = 0;
|
||||||
|
DWORD ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &bufferSize);
|
||||||
|
|
||||||
|
if (ret == ERROR_BUFFER_OVERFLOW) {
|
||||||
|
PIP_ADAPTER_ADDRESSES adapterAddresses = (PIP_ADAPTER_ADDRESSES)malloc(bufferSize);
|
||||||
|
if (adapterAddresses) {
|
||||||
|
ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, adapterAddresses, &bufferSize);
|
||||||
|
|
||||||
|
if (ret == NO_ERROR) {
|
||||||
|
PIP_ADAPTER_ADDRESSES currentAdapter = adapterAddresses;
|
||||||
|
QStringList dnsServers;
|
||||||
|
|
||||||
|
while (currentAdapter) {
|
||||||
|
// Check if adapter is active and has IP addresses
|
||||||
|
if (currentAdapter->OperStatus == IfOperStatusUp &&
|
||||||
|
currentAdapter->FirstUnicastAddress != nullptr) {
|
||||||
|
|
||||||
|
PIP_ADAPTER_DNS_SERVER_ADDRESS dnsServer = currentAdapter->FirstDnsServerAddress;
|
||||||
|
while (dnsServer) {
|
||||||
|
if (dnsServer->Address.lpSockaddr->sa_family == AF_INET) {
|
||||||
|
struct sockaddr_in* sa_in = (struct sockaddr_in*)dnsServer->Address.lpSockaddr;
|
||||||
|
char ipstr[INET_ADDRSTRLEN];
|
||||||
|
inet_ntop(AF_INET, &sa_in->sin_addr, ipstr, INET_ADDRSTRLEN);
|
||||||
|
QString dns = QString::fromLatin1(ipstr);
|
||||||
|
if (checkIPv4Format(dns) && !dnsServers.contains(dns)) {
|
||||||
|
dnsServers.append(dns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dnsServer = dnsServer->Next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAdapter = currentAdapter->Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dnsServers.isEmpty()) {
|
||||||
|
result.first = dnsServers.first();
|
||||||
|
if (dnsServers.size() > 1) {
|
||||||
|
result.second = dnsServers.at(1);
|
||||||
|
}
|
||||||
|
qDebug() << "Got system DNS from Windows:" << result.first << result.second;
|
||||||
|
free(adapterAddresses);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(adapterAddresses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Failed to get system DNS on Windows";
|
||||||
|
return result; // Return empty pair
|
||||||
|
|
||||||
|
#elif defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
|
// Use SCDynamicStore to get DNS from system configuration
|
||||||
|
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("amneziavpn"), nullptr, nullptr);
|
||||||
|
if (store) {
|
||||||
|
CFDictionaryRef dnsDict = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/DNS"));
|
||||||
|
|
||||||
|
if (dnsDict) {
|
||||||
|
CFArrayRef dnsServersArray = (CFArrayRef)CFDictionaryGetValue(dnsDict, CFSTR("ServerAddresses"));
|
||||||
|
|
||||||
|
if (dnsServersArray && CFArrayGetCount(dnsServersArray) > 0) {
|
||||||
|
QStringList dnsServers;
|
||||||
|
|
||||||
|
for (CFIndex i = 0; i < CFArrayGetCount(dnsServersArray); i++) {
|
||||||
|
CFStringRef dnsString = (CFStringRef)CFArrayGetValueAtIndex(dnsServersArray, i);
|
||||||
|
if (dnsString) {
|
||||||
|
char buffer[256];
|
||||||
|
if (CFStringGetCString(dnsString, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
|
||||||
|
QString dns = QString::fromLatin1(buffer);
|
||||||
|
if (checkIPv4Format(dns)) {
|
||||||
|
dnsServers.append(dns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dnsServers.isEmpty()) {
|
||||||
|
result.first = dnsServers.first();
|
||||||
|
if (dnsServers.size() > 1) {
|
||||||
|
result.second = dnsServers.at(1);
|
||||||
|
}
|
||||||
|
qDebug() << "Got system DNS from macOS:" << result.first << result.second;
|
||||||
|
CFRelease(dnsDict);
|
||||||
|
CFRelease(store);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CFRelease(dnsDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
CFRelease(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Failed to get system DNS on macOS";
|
||||||
|
return result; // Return empty pair
|
||||||
|
|
||||||
|
#else
|
||||||
|
qWarning() << "System DNS reading not implemented for this platform";
|
||||||
|
return result; // Return empty pair
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
|
|
||||||
class NetworkUtilities : public QObject
|
class NetworkUtilities : public QObject
|
||||||
@@ -31,6 +32,9 @@ public:
|
|||||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||||
|
|
||||||
|
// Returns pair of (primary DNS, secondary DNS) or empty strings on error
|
||||||
|
static QPair<QString, QString> getSystemDnsAddress();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NETWORKUTILITIES_H
|
#endif // NETWORKUTILITIES_H
|
||||||
|
|||||||
@@ -145,6 +145,15 @@ public:
|
|||||||
setValue("Conf/useAmneziaDns", enabled);
|
setValue("Conf/useAmneziaDns", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool useSystemDnsAddress() const
|
||||||
|
{
|
||||||
|
return value("Conf/useSystemDnsAddress", false).toBool();
|
||||||
|
}
|
||||||
|
void setUseSystemDnsAddress(bool enabled)
|
||||||
|
{
|
||||||
|
setValue("Conf/useSystemDnsAddress", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
QString primaryDns() const;
|
QString primaryDns() const;
|
||||||
QString secondaryDns() const;
|
QString secondaryDns() const;
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ void ConnectionController::openConnection()
|
|||||||
|
|
||||||
auto dns = m_serversModel->getDnsPair(serverIndex);
|
auto dns = m_serversModel->getDnsPair(serverIndex);
|
||||||
|
|
||||||
|
// Check if DNS retrieval failed (empty pair means system DNS retrieval failed)
|
||||||
|
if (dns.first.isEmpty() && dns.second.isEmpty()) {
|
||||||
|
emit connectionErrorOccurred(ErrorCode::InternalError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container);
|
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container);
|
||||||
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
|
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,17 @@ bool SettingsController::isAmneziaDnsEnabled()
|
|||||||
return m_settings->useAmneziaDns();
|
return m_settings->useAmneziaDns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SettingsController::isUseSystemDnsAddressEnabled()
|
||||||
|
{
|
||||||
|
return m_settings->useSystemDnsAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsController::setUseSystemDnsAddress(bool enable)
|
||||||
|
{
|
||||||
|
m_settings->setUseSystemDnsAddress(enable);
|
||||||
|
emit useSystemDnsAddressChanged(enable);
|
||||||
|
}
|
||||||
|
|
||||||
QString SettingsController::getPrimaryDns()
|
QString SettingsController::getPrimaryDns()
|
||||||
{
|
{
|
||||||
return m_settings->primaryDns();
|
return m_settings->primaryDns();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public:
|
|||||||
Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged)
|
Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged)
|
||||||
Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged)
|
Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged)
|
||||||
Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged)
|
Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged)
|
||||||
|
Q_PROPERTY(bool useSystemDnsAddressEnabled READ isUseSystemDnsAddressEnabled WRITE setUseSystemDnsAddress NOTIFY useSystemDnsAddressChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled)
|
Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled)
|
||||||
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
|
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
|
||||||
@@ -38,6 +39,9 @@ public slots:
|
|||||||
void toggleAmneziaDns(bool enable);
|
void toggleAmneziaDns(bool enable);
|
||||||
bool isAmneziaDnsEnabled();
|
bool isAmneziaDnsEnabled();
|
||||||
|
|
||||||
|
bool isUseSystemDnsAddressEnabled();
|
||||||
|
void setUseSystemDnsAddress(bool enable);
|
||||||
|
|
||||||
QString getPrimaryDns();
|
QString getPrimaryDns();
|
||||||
void setPrimaryDns(const QString &dns);
|
void setPrimaryDns(const QString &dns);
|
||||||
|
|
||||||
@@ -117,6 +121,8 @@ signals:
|
|||||||
|
|
||||||
void amneziaDnsToggled(bool enable);
|
void amneziaDnsToggled(bool enable);
|
||||||
|
|
||||||
|
void useSystemDnsAddressChanged(bool enabled);
|
||||||
|
|
||||||
void loggingDisableByWatcher();
|
void loggingDisableByWatcher();
|
||||||
|
|
||||||
void onNotificationStateChanged();
|
void onNotificationStateChanged();
|
||||||
|
|||||||
@@ -603,6 +603,26 @@ QPair<QString, QString> ServersModel::getDnsPair(int serverIndex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if system DNS should be used
|
||||||
|
if (m_settings->useSystemDnsAddress() && apiUtils::isPremiumServer(server)) {
|
||||||
|
auto systemDns = NetworkUtilities::getSystemDnsAddress();
|
||||||
|
bool hasPrimary = !systemDns.first.isEmpty() && NetworkUtilities::checkIPv4Format(systemDns.first);
|
||||||
|
bool hasSecondary = !systemDns.second.isEmpty() && NetworkUtilities::checkIPv4Format(systemDns.second);
|
||||||
|
|
||||||
|
if (hasPrimary || hasSecondary) {
|
||||||
|
if (hasPrimary) {
|
||||||
|
dns.first = systemDns.first;
|
||||||
|
}
|
||||||
|
if (hasSecondary) {
|
||||||
|
dns.second = systemDns.second;
|
||||||
|
}
|
||||||
|
qDebug() << "VpnConfigurator::getDnsForConfig using system DNS:" << dns.first << dns.second;
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to get system DNS for premium config, connection will fail";
|
||||||
|
}
|
||||||
|
return dns;
|
||||||
|
}
|
||||||
|
|
||||||
dns.first = server.value(config_key::dns1).toString();
|
dns.first = server.value(config_key::dns1).toString();
|
||||||
dns.second = server.value(config_key::dns2).toString();
|
dns.second = server.value(config_key::dns2).toString();
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,29 @@ PageType {
|
|||||||
|
|
||||||
DividerType {}
|
DividerType {}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: useSystemDnsSwitch
|
||||||
|
|
||||||
|
visible: ServersModel.processedServerIsPremium
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
text: qsTr("Use system DNS")
|
||||||
|
descriptionText: qsTr("Use system DNS servers")
|
||||||
|
|
||||||
|
checked: SettingsController.useSystemDnsAddressEnabled
|
||||||
|
onToggled: function() {
|
||||||
|
if (checked !== SettingsController.useSystemDnsAddressEnabled) {
|
||||||
|
SettingsController.setUseSystemDnsAddress(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {
|
||||||
|
visible: ServersModel.processedServerIsPremium
|
||||||
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: dnsServersButton
|
id: dnsServersButton
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user