feat: prepare for route rules

This commit is contained in:
unknown 2024-06-01 08:46:32 +03:30
parent 43f4de3fe0
commit 2bc9fd210c
No known key found for this signature in database
GPG Key ID: C2CA486E4F771093
5 changed files with 44 additions and 153 deletions

View File

@ -763,100 +763,4 @@ namespace NekoGui {
if (!experimentalObj.isEmpty()) status->result->coreConfig.insert("experimental", experimentalObj);
}
QString WriteVPNSingBoxConfig() {
// tun user rule
auto match_out = dataStore->vpn_rule_white ? "nekoray-socks" : "direct";
auto no_match_out = dataStore->vpn_rule_white ? "direct" : "nekoray-socks";
QString process_name_rule = dataStore->vpn_rule_process.trimmed();
if (!process_name_rule.isEmpty()) {
auto arr = SplitLinesSkipSharp(process_name_rule);
QJsonObject rule{{"outbound", match_out},
{"process_name", QList2QJsonArray(arr)}};
process_name_rule = "," + QJsonObject2QString(rule, false);
}
QString cidr_rule = dataStore->vpn_rule_cidr.trimmed();
if (!cidr_rule.isEmpty()) {
auto arr = SplitLinesSkipSharp(cidr_rule);
QJsonObject rule{{"outbound", match_out},
{"ip_cidr", QList2QJsonArray(arr)}};
cidr_rule = "," + QJsonObject2QString(rule, false);
}
// TODO bypass ext core process path?
// auth
QString socks_user_pass;
if (dataStore->inbound_auth->NeedAuth()) {
socks_user_pass = R"( "username": "%1", "password": "%2", )";
socks_user_pass = socks_user_pass.arg(dataStore->inbound_auth->username, dataStore->inbound_auth->password);
}
// gen config
auto configFn = ":/neko/vpn/sing-box-vpn.json";
if (QFile::exists("vpn/sing-box-vpn.json")) configFn = "vpn/sing-box-vpn.json";
auto config = ReadFileText(configFn)
.replace("//%IPV6_ADDRESS%", dataStore->vpn_ipv6 ? R"("inet6_address": "fdfe:dcba:9876::1/126",)" : "")
.replace("//%SOCKS_USER_PASS%", socks_user_pass)
.replace("//%PROCESS_NAME_RULE%", process_name_rule)
.replace("//%CIDR_RULE%", cidr_rule)
.replace("%MTU%", Int2String(dataStore->vpn_mtu))
.replace("%STACK%", Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation))
.replace("%TUN_NAME%", genTunName())
.replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false")
.replace("%FINAL_OUT%", no_match_out)
.replace("%DNS_ADDRESS%", BOX_UNDERLYING_DNS)
.replace("%FAKE_DNS_INBOUND%", dataStore->fake_dns ? "tun-in" : "empty")
.replace("%PORT%", Int2String(dataStore->inbound_socks_port));
// hook.js
auto source = qjs::ReadHookJS();
if (!source.isEmpty()) {
qjs::QJS js(source);
auto js_result = js.EvalFunction("hook.hook_tun_config", config);
if (config != js_result) {
MW_show_log("hook.js modified your Tun config.");
config = js_result;
}
}
// write config
QFile file;
file.setFileName(QFileInfo(configFn).fileName());
file.open(QIODevice::ReadWrite | QIODevice::Truncate);
file.write(config.toUtf8());
file.close();
return QFileInfo(file).absoluteFilePath();
}
QString WriteVPNLinuxScript(const QString &protectPath, const QString &configPath) {
#ifdef Q_OS_WIN
return {};
#endif
// gen script
auto scriptFn = ":/neko/vpn/vpn-run-root.sh";
if (QFile::exists("vpn/vpn-run-root.sh")) scriptFn = "vpn/vpn-run-root.sh";
auto script = ReadFileText(scriptFn)
.replace("./nekobox_core", QApplication::applicationDirPath() + "/nekobox_core")
.replace("$PROTECT_LISTEN_PATH", protectPath)
.replace("$CONFIG_PATH", configPath)
.replace("$TABLE_FWMARK", "514");
// hook.js
auto source = qjs::ReadHookJS();
if (!source.isEmpty()) {
qjs::QJS js(source);
auto js_result = js.EvalFunction("hook.hook_tun_script", script);
if (script != js_result) {
MW_show_log("hook.js modified your Tun script.");
script = js_result;
}
}
// write script
QFile file2;
file2.setFileName(QFileInfo(scriptFn).fileName());
file2.open(QIODevice::ReadWrite | QIODevice::Truncate);
file2.write(script.toUtf8());
file2.close();
return QFileInfo(file2).absoluteFilePath();
}
} // namespace NekoGui

View File

@ -52,8 +52,4 @@ namespace NekoGui {
QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,
const std::shared_ptr<BuildConfigStatus> &status);
QString WriteVPNSingBoxConfig();
QString WriteVPNLinuxScript(const QString &protectPath, const QString &configPath);
} // namespace NekoGui

44
db/RouteRuleEntity.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "main/NekoGui.hpp"
namespace NekoGui {
class RouteRule : public JsonStore {
public:
int ip_version = 0;
QList<QString> network;
QList<QString> protocol;
QList<QString> domain;
QList<QString> domain_suffix;
QList<QString> domain_keyword;
QList<QString> domain_regex;
QList<QString> source_ip_cidr;
bool* source_ip_is_private = nullptr;
QList<QString> ip_cidr;
bool* ip_is_private = nullptr;
QList<QString> source_port;
QList<QString> source_port_range;
QList<QString> port;
QList<QString> port_range;
QList<QString> process_name;
QList<QString> process_path;
QList<QString> rule_set;
bool invert = false;
int outboundID = -1;
QList<QString> check_for_errors();
QJsonObject get_rule_json();
};
class RoutingChain : public JsonStore {
public:
int id = -1;
QString name = "";
QList<std::shared_ptr<RouteRule>> Rules;
QJsonArray get_route_rules();
QJsonArray get_default_route_rules();
};
} // namespace NekoGui

View File

@ -1714,57 +1714,6 @@ void MainWindow::RegisterHotkey(bool unregister) {}
void MainWindow::HotkeyEvent(const QString &key) {}
#endif
// VPN Launcher
bool MainWindow::StartVPNProcess() {
//
if (vpn_pid != 0) {
return true;
}
//
auto protectPath = QDir::currentPath() + "/protect";
auto configPath = NekoGui::WriteVPNSingBoxConfig();
auto scriptPath = NekoGui::WriteVPNLinuxScript(protectPath, configPath);
//
#ifdef Q_OS_WIN
runOnNewThread([=] {
vpn_pid = 1; // TODO get pid?
WinCommander::runProcessElevated(QApplication::applicationDirPath() + "/nekobox_core.exe",
{"--disable-color", "run", "-c", configPath}, "",
NekoGui::dataStore->vpn_hide_console ? WinCommander::SW_HIDE : WinCommander::SW_SHOWMINIMIZED); // blocking
vpn_pid = 0;
runOnUiThread([=] { neko_set_spmode_vpn(false); });
});
#else
QFile::remove(protectPath);
if (QFile::exists(protectPath)) {
MessageBoxWarning("Error", "protect cannot be removed");
return false;
}
//
auto vpn_process = new QProcess;
QProcess::connect(vpn_process, &QProcess::stateChanged, this, [=](QProcess::ProcessState state) {
if (state == QProcess::NotRunning) {
vpn_pid = 0;
vpn_process->deleteLater();
GetMainWindow()->neko_set_spmode_vpn(false);
}
});
//
vpn_process->setProcessChannelMode(QProcess::ForwardedChannels);
#ifdef Q_OS_MACOS
vpn_process->start("osascript", {"-e", QString("do shell script \"%1\" with administrator privileges")
.arg("bash " + scriptPath)});
#else
vpn_process->start("pkexec", {"bash", scriptPath});
#endif
vpn_process->waitForStarted();
vpn_pid = vpn_process->processId(); // actually it's pkexec or bash PID
#endif
return true;
}
bool MainWindow::StopVPNProcess(bool unconditional) {
if (unconditional || vpn_pid != 0) {
bool ok;

View File

@ -179,8 +179,6 @@ private:
void HotkeyEvent(const QString &key);
bool StartVPNProcess();
// grpc and ...
static void setup_grpc();