From 6d7ce2868ca5e42c46a5227aa50aaf0cdf890bc0 Mon Sep 17 00:00:00 2001 From: Nova Date: Wed, 18 Sep 2024 23:57:36 +0330 Subject: [PATCH] fix: Fix linux system proxy bug --- .../qv2ray/v2/proxy/QvProxyConfigurator.cpp | 440 ++++++++++++++++++ .../qv2ray/v2/proxy/QvProxyConfigurator.hpp | 12 + CMakeLists.txt | 1 + main/NekoGui.cpp | 2 +- ui/mainwindow.cpp | 2 +- ui/mainwindow_grpc.cpp | 11 +- 6 files changed, 465 insertions(+), 3 deletions(-) create mode 100644 3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.cpp create mode 100644 3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.hpp diff --git a/3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.cpp b/3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.cpp new file mode 100644 index 0000000..693c5ca --- /dev/null +++ b/3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.cpp @@ -0,0 +1,440 @@ +#include "QvProxyConfigurator.hpp" + +#ifdef Q_OS_WIN +// +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +// +#include +#include +#include +#include +#endif + +#include +#include + +#include "3rdparty/fix_old_qt.h" +#include "3rdparty/qv2ray/wrapper.hpp" +#include "fmt/Preset.hpp" +#include "main/NekoGui.hpp" + +#define QV_MODULE_NAME "SystemProxy" + +#define QSTRN(num) QString::number(num) + +namespace Qv2ray::components::proxy { + + using ProcessArgument = QPair; +#ifdef Q_OS_MACOS + QStringList macOSgetNetworkServices() { + QProcess p; + p.setProgram("/usr/sbin/networksetup"); + p.setArguments(QStringList{"-listallnetworkservices"}); + p.start(); + p.waitForStarted(); + p.waitForFinished(); + LOG(p.errorString()); + auto str = p.readAllStandardOutput(); + auto lines = SplitLines(str); + QStringList result; + + // Start from 1 since first line is unneeded. + for (auto i = 1; i < lines.count(); i++) { + // * means disabled. + if (!lines[i].contains("*")) { + result << lines[i]; + } + } + + LOG("Found " + QSTRN(result.size()) + " network services: " + result.join(";")); + return result; + } +#endif +#ifdef Q_OS_WIN +#define NO_CONST(expr) const_cast(expr) + // static auto DEFAULT_CONNECTION_NAME = + // NO_CONST(L"DefaultConnectionSettings"); + /// + /// INTERNAL FUNCTION + bool __QueryProxyOptions() { + INTERNET_PER_CONN_OPTION_LIST List; + INTERNET_PER_CONN_OPTION Option[5]; + // + unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); + Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; + Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; + Option[2].dwOption = INTERNET_PER_CONN_FLAGS; + Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; + Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER; + // + List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); + List.pszConnection = nullptr; // NO_CONST(DEFAULT_CONNECTION_NAME); + List.dwOptionCount = 5; + List.dwOptionError = 0; + List.pOptions = Option; + + if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) { + LOG("InternetQueryOption failed, GLE=" + QSTRN(GetLastError())); + } + + LOG("System default proxy info:"); + + if (Option[0].Value.pszValue != nullptr) { + LOG(QString::fromWCharArray(Option[0].Value.pszValue)); + } + + if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) { + LOG("PROXY_TYPE_AUTO_PROXY_URL"); + } + + if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) { + LOG("PROXY_TYPE_AUTO_DETECT"); + } + + if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) { + LOG("PROXY_TYPE_DIRECT"); + } + + if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) { + LOG("PROXY_TYPE_PROXY"); + } + + if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) { + LOG("InternetQueryOption failed,GLE=" + QSTRN(GetLastError())); + } + + if (Option[4].Value.pszValue != nullptr) { + LOG(QString::fromStdWString(Option[4].Value.pszValue)); + } + + INTERNET_VERSION_INFO Version; + nSize = sizeof(INTERNET_VERSION_INFO); + InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize); + + if (Option[0].Value.pszValue != nullptr) { + GlobalFree(Option[0].Value.pszValue); + } + + if (Option[3].Value.pszValue != nullptr) { + GlobalFree(Option[3].Value.pszValue); + } + + if (Option[4].Value.pszValue != nullptr) { + GlobalFree(Option[4].Value.pszValue); + } + + return false; + } + bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC) { + INTERNET_PER_CONN_OPTION_LIST list; + DWORD dwBufSize = sizeof(list); + // Fill the list structure. + list.dwSize = sizeof(list); + // NULL == LAN, otherwise connectoid name. + list.pszConnection = nullptr; + + if (nullptr == proxy_full_addr) { + LOG("Clearing system proxy"); + // + list.dwOptionCount = 1; + list.pOptions = new INTERNET_PER_CONN_OPTION[1]; + + // Ensure that the memory was allocated. + if (nullptr == list.pOptions) { + // Return if the memory wasn't allocated. + return false; + } + + // Set flags. + list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS; + list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT; + } else if (isPAC) { + LOG("Setting system proxy for PAC"); + // + list.dwOptionCount = 2; + list.pOptions = new INTERNET_PER_CONN_OPTION[2]; + + if (nullptr == list.pOptions) { + return false; + } + + // Set flags. + list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS; + list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL; + // Set proxy name. + list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; + list.pOptions[1].Value.pszValue = proxy_full_addr; + } else { + LOG("Setting system proxy for Global Proxy"); + // + list.dwOptionCount = 2; + list.pOptions = new INTERNET_PER_CONN_OPTION[2]; + + if (nullptr == list.pOptions) { + return false; + } + + // Set flags. + list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS; + list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY; + // Set proxy name. + list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; + list.pOptions[1].Value.pszValue = proxy_full_addr; + // Set proxy override. + // list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; + // auto localhost = L"localhost"; + // list.pOptions[2].Value.pszValue = NO_CONST(localhost); + } + + // Set proxy for LAN. + if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) { + LOG("InternetSetOption failed for LAN, GLE=" + QSTRN(GetLastError())); + } + + RASENTRYNAME entry; + entry.dwSize = sizeof(entry); + std::vector entries; + DWORD size = sizeof(entry), count; + LPRASENTRYNAME entryAddr = &entry; + auto ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count); + if (ERROR_BUFFER_TOO_SMALL == ret) { + entries.resize(count); + entries[0].dwSize = sizeof(RASENTRYNAME); + entryAddr = entries.data(); + ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count); + } + if (ERROR_SUCCESS != ret) { + LOG("Failed to list entry names"); + return false; + } + + // Set proxy for each connectoid. + for (DWORD i = 0; i < count; ++i) { + list.pszConnection = entryAddr[i].szEntryName; + if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) { + LOG("InternetSetOption failed for connectoid " + QString::fromWCharArray(list.pszConnection) + ", GLE=" + QSTRN(GetLastError())); + } + } + + delete[] list.pOptions; + InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0); + InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0); + return true; + } +#endif + + void SetSystemProxy(int httpPort, int socksPort) { + const QString &address = "127.0.0.1"; + bool hasHTTP = (httpPort > 0 && httpPort < 65536); + bool hasSOCKS = (socksPort > 0 && socksPort < 65536); + +#ifdef Q_OS_WIN + if (!hasHTTP) { + LOG("Nothing?"); + return; + } else { + LOG("Qv2ray will set system proxy to use HTTP"); + } +#else + if (!hasHTTP && !hasSOCKS) { + LOG("Nothing?"); + return; + } + + if (hasHTTP) { + LOG("Qv2ray will set system proxy to use HTTP"); + } + + if (hasSOCKS) { + LOG("Qv2ray will set system proxy to use SOCKS"); + } +#endif + +#ifdef Q_OS_WIN + QString str = NekoGui::dataStore->system_proxy_format; + if (str.isEmpty()) str = Preset::Windows::system_proxy_format[0]; + str = str.replace("{ip}", address) + .replace("{http_port}", Int2String(httpPort)) + .replace("{socks_port}", Int2String(socksPort)); + // + LOG("Windows proxy string: " + str); + auto proxyStrW = new WCHAR[str.length() + 1]; + wcscpy(proxyStrW, str.toStdWString().c_str()); + // + __QueryProxyOptions(); + + if (!__SetProxyOptions(proxyStrW, false)) { + LOG("Failed to set proxy."); + } + + __QueryProxyOptions(); +#elif defined(Q_OS_LINUX) + QList actions; + actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}}; + // + bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" || + qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma"; + const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + + // + // Configure HTTP Proxies for HTTP, FTP and HTTPS + if (hasHTTP) { + // iterate over protocols... + for (const auto &protocol: QStringList{"http", "ftp", "https"}) { + // for GNOME: + { + actions << ProcessArgument{"gsettings", + {"set", "org.gnome.system.proxy." + protocol, "host", address}}; + actions << ProcessArgument{"gsettings", + {"set", "org.gnome.system.proxy." + protocol, "port", QSTRN(httpPort)}}; + } + + // for KDE: + if (isKDE) { + actions << ProcessArgument{"kwriteconfig5", + {"--file", configPath + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", protocol + "Proxy", // + "http://" + address + " " + QSTRN(httpPort)}}; + } + } + } + + // Configure SOCKS5 Proxies + if (hasSOCKS) { + // for GNOME: + { + actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy.socks", "host", address}}; + actions << ProcessArgument{"gsettings", + {"set", "org.gnome.system.proxy.socks", "port", QSTRN(socksPort)}}; + + // for KDE: + if (isKDE) { + actions << ProcessArgument{"kwriteconfig5", + {"--file", configPath + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", "socksProxy", // + "socks://" + address + " " + QSTRN(socksPort)}}; + } + } + } + // Setting Proxy Mode to Manual + { + // for GNOME: + { + actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}}; + } + + // for KDE: + if (isKDE) { + actions << ProcessArgument{"kwriteconfig5", + {"--file", configPath + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", "ProxyType", "1"}}; + } + } + + // Notify kioslaves to reload system proxy configuration. + if (isKDE) { + actions << ProcessArgument{"dbus-send", + {"--type=signal", "/KIO/Scheduler", // + "org.kde.KIO.Scheduler.reparseSlaveConfiguration", // + "string:''"}}; + } + // Execute them all! + // + // note: do not use std::all_of / any_of / none_of, + // because those are short-circuit and cannot guarantee atomicity. + QList results; + for (const auto &action: actions) { + // execute and get the code + const auto returnCode = QProcess::execute(action.first, action.second); + // print out the commands and result codes + DEBUG(QString("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";"))); + // give the code back + results << (returnCode == QProcess::NormalExit); + } + + if (results.count(true) != actions.size()) { + LOG("Something wrong when setting proxies."); + } +#else + + for (const auto &service: macOSgetNetworkServices()) { + LOG("Setting proxy for interface: " + service); + if (hasHTTP) { + QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "on"}); + QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "on"}); + QProcess::execute("/usr/sbin/networksetup", {"-setwebproxy", service, address, QSTRN(httpPort)}); + QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxy", service, address, QSTRN(httpPort)}); + } + + if (hasSOCKS) { + QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "on"}); + QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxy", service, address, QSTRN(socksPort)}); + } + } + +#endif + } + + void ClearSystemProxy() { + LOG("Clearing System Proxy"); + +#ifdef Q_OS_WIN + if (!__SetProxyOptions(nullptr, false)) { + LOG("Failed to clear proxy."); + } +#elif defined(Q_OS_LINUX) + QList actions; + const bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" || + qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma"; + const auto configRoot = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + + // Setting System Proxy Mode to: None + { + // for GNOME: + { + actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "none"}}; + } + + // for KDE: + if (isKDE) { + actions << ProcessArgument{"kwriteconfig5", + {"--file", configRoot + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", "ProxyType", "0"}}; + } + } + + // Notify kioslaves to reload system proxy configuration. + if (isKDE) { + actions << ProcessArgument{"dbus-send", + {"--type=signal", "/KIO/Scheduler", // + "org.kde.KIO.Scheduler.reparseSlaveConfiguration", // + "string:''"}}; + } + + // Execute the Actions + for (const auto &action: actions) { + // execute and get the code + const auto returnCode = QProcess::execute(action.first, action.second); + // print out the commands and result codes + DEBUG(QString("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";"))); + } + +#else + for (const auto &service: macOSgetNetworkServices()) { + LOG("Clearing proxy for interface: " + service); + QProcess::execute("/usr/sbin/networksetup", {"-setautoproxystate", service, "off"}); + QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "off"}); + QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "off"}); + QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "off"}); + } + +#endif + } +} // namespace Qv2ray::components::proxy diff --git a/3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.hpp b/3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.hpp new file mode 100644 index 0000000..eaa2c0f --- /dev/null +++ b/3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include +// +namespace Qv2ray::components::proxy { + void ClearSystemProxy(); + void SetSystemProxy(int http_port, int socks_port); +} // namespace Qv2ray::components::proxy + +using namespace Qv2ray::components; +using namespace Qv2ray::components::proxy; diff --git a/CMakeLists.txt b/CMakeLists.txt index 6473163..7a43add 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ set(PROJECT_SOURCES 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui + 3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.cpp rpc/gRPC.cpp diff --git a/main/NekoGui.cpp b/main/NekoGui.cpp index 2b69e7c..bd22959 100644 --- a/main/NekoGui.cpp +++ b/main/NekoGui.cpp @@ -417,7 +417,7 @@ namespace NekoGui { admin = Windows_IsInAdmin(); #else #ifdef Q_OS_LINUX - admin |= Linux_GetCapString(FindNekorayRealPath()).contains("cap_sys_admin"); + admin |= Linux_GetCapString(FindNekoBoxCoreRealPath()).contains("cap_sys_admin"); #endif admin |= geteuid() == 0; #endif diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index af20860..d42a6b8 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -714,7 +714,7 @@ bool MainWindow::get_elevated_permissions() { } auto n = QMessageBox::warning(GetMessageBoxParent(), software_name, tr("Please run Nekoray as admin"), QMessageBox::Yes | QMessageBox::No); if (n == QMessageBox::Yes) { - auto ret = Linux_Pkexec_SetCapString(NekoGui::FindNekorayRealPath(), "cap_sys_admin=ep"); + auto ret = Linux_Pkexec_SetCapString(NekoGui::FindNekoBoxCoreRealPath(), "cap_sys_admin=ep"); if (ret == 0) { this->exit_reason = 3; on_menu_exit_triggered(); diff --git a/ui/mainwindow_grpc.cpp b/ui/mainwindow_grpc.cpp index e148ca8..ca1ad19 100644 --- a/ui/mainwindow_grpc.cpp +++ b/ui/mainwindow_grpc.cpp @@ -6,8 +6,8 @@ #include "db/traffic/TrafficLooper.hpp" #include "rpc/gRPC.h" #include "ui/widget/MessageBoxTimer.h" +#include "3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.hpp" -#include #include #include #include @@ -343,6 +343,7 @@ void MainWindow::neko_start(int _id) { void MainWindow::neko_set_spmode_system_proxy(bool enable, bool save) { if (enable != NekoGui::dataStore->spmode_system_proxy) { +#ifndef Q_OS_LINUX bool ok; auto error = defaultClient->SetSystemProxy(&ok, enable); if (!ok) { @@ -351,6 +352,14 @@ void MainWindow::neko_set_spmode_system_proxy(bool enable, bool save) { refresh_status(); return; } +#else + if (enable) { + auto socks_port = NekoGui::dataStore->inbound_socks_port; + SetSystemProxy(socks_port, socks_port); + } else { + ClearSystemProxy(); + } +#endif } if (save) {