diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 63628fd..c8e246b 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -325,6 +325,7 @@ namespace NekoRay { bool muxApplied = false; QString pastTag; + int pastExternalStat = 0; int index = 0; for (const auto &ent: ents) { @@ -338,13 +339,15 @@ namespace NekoRay { bool needGlobal = false; // first profile set as global - if (index == ents.length() - 1) { + auto isFirstProfile = index == ents.length() - 1; + if (isFirstProfile) { needGlobal = true; tagOut = "g-" + Int2String(ent->id); } // last profile set as "proxy" if (chainId == 0 && index == 0) { + needGlobal = false; tagOut = "proxy"; } @@ -357,7 +360,7 @@ namespace NekoRay { if (index > 0) { // chain rules: past - if (!ents[index - 1]->bean->NeedExternal()) { + if (pastExternalStat == 0) { auto replaced = status->outbounds.last().toObject(); if (IS_NEKO_BOX) { replaced["detour"] = tagOut; @@ -385,7 +388,11 @@ namespace NekoRay { // chain rules: this auto mapping_port = MkPort(); - if (ent->bean->NeedExternal()) { + auto thisExternalStat = ent->bean->NeedExternal(isFirstProfile, + dataStore->running_spmode == SystemProxyMode::VPN); + if (thisExternalStat == 2) dataStore->need_keep_vpn_off = true; + if (thisExternalStat == 1) { + // mapping if (IS_NEKO_BOX) { status->inbounds += QJsonObject{{"type", "direct"}, {"tag", tagOut + "-mapping"}, @@ -404,7 +411,7 @@ namespace NekoRay { {"network", "tcp,udp"},}},}; } // no chain rule and not outbound, so need to set to direct - if (index == ents.length() - 1) { + if (isFirstProfile) { if (IS_NEKO_BOX) { status->routingRules += QJsonObject{{"inbound", QJsonArray{tagOut + "-mapping"}}, {"outbound", "direct"},}; @@ -422,9 +429,9 @@ namespace NekoRay { fmt::CoreObjOutboundBuildResult coreR; fmt::ExternalBuildResult extR; - if (ent->bean->NeedExternal()) { + if (thisExternalStat > 0) { auto ext_socks_port = MkPort(); - extR = ent->bean->BuildExternal(mapping_port, ext_socks_port); + extR = ent->bean->BuildExternal(mapping_port, ext_socks_port, thisExternalStat); if (extR.program.isEmpty()) { status->result->error = QObject::tr("Core not found: %1").arg(ent->bean->DisplayType()); return {}; @@ -512,7 +519,7 @@ namespace NekoRay { } // Bypass Lookup for the first profile - if (index == ents.length() - 1 && !IsIpAddress(ent->bean->serverAddress)) { + if (isFirstProfile && !IsIpAddress(ent->bean->serverAddress)) { if (dataStore->enhance_resolve_server_domain && !IS_NEKO_BOX) { status->result->tryDomains += ent->bean->serverAddress; } else { @@ -522,6 +529,7 @@ namespace NekoRay { status->outbounds += outbound; pastTag = tagOut; + pastExternalStat = thisExternalStat; index++; } diff --git a/fmt/AbstractBean.hpp b/fmt/AbstractBean.hpp index e40172f..dddf3ca 100644 --- a/fmt/AbstractBean.hpp +++ b/fmt/AbstractBean.hpp @@ -47,13 +47,13 @@ namespace NekoRay::fmt { // - virtual bool NeedExternal() { return false; }; + virtual int NeedExternal(bool isFirstProfile, bool isVPN) { return 0; }; virtual CoreObjOutboundBuildResult BuildCoreObjV2Ray() { return {}; }; virtual CoreObjOutboundBuildResult BuildCoreObjSingBox() { return {}; }; - virtual ExternalBuildResult BuildExternal(int mapping_port, int socks_port) { return {}; }; + virtual ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) { return {}; }; virtual QString ToShareLink() { return {}; }; diff --git a/fmt/Bean2External.cpp b/fmt/Bean2External.cpp index d140093..11485f8 100644 --- a/fmt/Bean2External.cpp +++ b/fmt/Bean2External.cpp @@ -1,5 +1,7 @@ #include "db/ProxyEntity.hpp" #include "fmt/includes.h" +#include "NaiveBean.hpp" + #include #include @@ -19,13 +21,37 @@ f.close(); \ auto TempFile = QFileInfo(f).absoluteFilePath(); namespace NekoRay::fmt { - ExternalBuildResult NaiveBean::BuildExternal(int mapping_port, int socks_port) { + // 0: no external + // 1: Mapping External + // 2: Direct External + + int NaiveBean::NeedExternal(bool isFirstProfile, bool isVPN) { + if (isFirstProfile && !isVPN) { + return 2; + } + return 1; + } + + int CustomBean::NeedExternal(bool isFirstProfile, bool isVPN) { + if (core == "internal") return 0; + if (IS_NEKO_BOX && core == "hysteria") return 0; + if (core == "hysteria") { + if (isFirstProfile && !isVPN) { + return 2; + } + } + return 1; + } + + ExternalBuildResult NaiveBean::BuildExternal(int mapping_port, int socks_port, int external_stat) { ExternalBuildResult result{dataStore->extraCore->Get("naive")}; - auto is_export = mapping_port == 114514; + auto is_direct = external_stat == 2; auto domain_address = sni.isEmpty() ? serverAddress : sni; - auto connect_address = is_export ? serverAddress : "127.0.0.1"; - auto connect_port = is_export ? serverPort : mapping_port; + auto connect_address = is_direct ? serverAddress : "127.0.0.1"; + auto connect_port = is_direct ? serverPort : mapping_port; + domain_address = WrapIPV6Host(domain_address); + connect_address = WrapIPV6Host(connect_address); result.arguments += "--log"; result.arguments += "--listen=socks://127.0.0.1:" + Int2String(socks_port); @@ -47,7 +73,7 @@ namespace NekoRay::fmt { return result; } - ExternalBuildResult CustomBean::BuildExternal(int mapping_port, int socks_port) { + ExternalBuildResult CustomBean::BuildExternal(int mapping_port, int socks_port, int external_stat) { ExternalBuildResult result{dataStore->extraCore->Get(core)}; result.arguments = command; // TODO split? @@ -75,6 +101,15 @@ namespace NekoRay::fmt { suffix = ".json"; } + // known core direct out + if (external_stat == 2) { + if (core == "hysteria") { + config = config.replace(QString("\"127.0.0.1:%1\"").arg(mapping_port), + "\"" + DisplayAddress() + "\""); + } + } + + // write config WriteTempFile("custom_" + GetRandomString(10) + suffix, config.toUtf8()); for (int i = 0; i < result.arguments.count(); i++) { result.arguments[i] = result.arguments[i].replace("%config%", TempFile); diff --git a/fmt/CustomBean.hpp b/fmt/CustomBean.hpp index b721ef7..2d62a1d 100644 --- a/fmt/CustomBean.hpp +++ b/fmt/CustomBean.hpp @@ -25,7 +25,7 @@ namespace NekoRay::fmt { return core; }; - QString DisplayCoreType() override { return NeedExternal() ? core : software_core_name; }; + QString DisplayCoreType() override { return NeedExternal(false, false) ? core : software_core_name; }; QString DisplayAddress() override { if (core == "internal") { @@ -39,13 +39,9 @@ namespace NekoRay::fmt { return AbstractBean::DisplayAddress(); }; - bool NeedExternal() override { - if (core == "internal") return false; - if (IS_NEKO_BOX && core == "hysteria") return false; - return true; - }; + int NeedExternal(bool isFirstProfile, bool isVPN) override; - ExternalBuildResult BuildExternal(int mapping_port, int socks_port) override; + ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; diff --git a/fmt/NaiveBean.hpp b/fmt/NaiveBean.hpp index f6b72c6..455ee24 100644 --- a/fmt/NaiveBean.hpp +++ b/fmt/NaiveBean.hpp @@ -27,9 +27,9 @@ namespace NekoRay::fmt { QString DisplayType() override { return "Naive"; }; - bool NeedExternal() override { return true; }; + int NeedExternal(bool isFirstProfile, bool isVPN) override; - ExternalBuildResult BuildExternal(int mapping_port, int socks_port) override; + ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override; bool TryParseLink(const QString &link); diff --git a/go/cmd/nekoray_core/grpc_ray.go b/go/cmd/nekoray_core/grpc_ray.go index 015e2a3..1cd12ca 100644 --- a/go/cmd/nekoray_core/grpc_ray.go +++ b/go/cmd/nekoray_core/grpc_ray.go @@ -35,7 +35,7 @@ func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.Err }() if neko_common.Debug { - logrus.Println("Start:", in) + logrus.Println("Start:", in.CoreConfig, in.TryDomains) } if instance != nil { diff --git a/main/NekoRay.cpp b/main/NekoRay.cpp index aeb06b8..916ea63 100644 --- a/main/NekoRay.cpp +++ b/main/NekoRay.cpp @@ -41,7 +41,7 @@ namespace NekoRay { _add(new configItem("remember_enable", &remember_enable, itemType::boolean)); _add(new configItem("start_minimal", &start_minimal, itemType::boolean)); _add(new configItem("language", &language, itemType::integer)); - _add(new configItem("spmode", &system_proxy_mode, itemType::integer)); + _add(new configItem("spmode", &remember_spmode, itemType::integer)); _add(new configItem("insecure_hint", &insecure_hint, itemType::boolean)); _add(new configItem("skip_cert", &skip_cert, itemType::boolean)); _add(new configItem("hk_mw", &hotkey_mainwindow, itemType::string)); diff --git a/main/NekoRay_DataStore.hpp b/main/NekoRay_DataStore.hpp index 10c67e0..5524eb8 100644 --- a/main/NekoRay_DataStore.hpp +++ b/main/NekoRay_DataStore.hpp @@ -45,6 +45,8 @@ namespace NekoRay { int started_id = -1919; bool core_running = false; bool core_prepare_exit = false; + int running_spmode = NekoRay::SystemProxyMode::DISABLE; + bool need_keep_vpn_off = false; Routing *routing = new Routing; int imported_count = 0; @@ -82,7 +84,7 @@ namespace NekoRay { bool skip_cert = false; // Remember - int system_proxy_mode = NekoRay::SystemProxyMode::DISABLE; + int remember_spmode = NekoRay::SystemProxyMode::DISABLE; int remember_id = -1919; bool remember_enable = false; bool start_minimal = false; diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index f94873d..f57a554 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -1185,6 +1185,10 @@ End: %2 Not Running 未启动 + + Current server is incompatible with VPN. Please stop the server first, enable VPN mode, and then restart. + 当前服务器与 VPN 不兼容。请先停止服务器,打开 VPN 模式后再启动。 + ProxyItem diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index d393a26..6bf0ee4 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -338,9 +338,10 @@ MainWindow::MainWindow(QWidget *parent) neko_set_spmode(checked ? NekoRay::SystemProxyMode::SYSTEM_PROXY : NekoRay::SystemProxyMode::DISABLE); }); connect(ui->menu_spmode, &QMenu::aboutToShow, this, [=]() { - ui->menu_spmode_disabled->setChecked(title_spmode == NekoRay::SystemProxyMode::DISABLE); - ui->menu_spmode_system_proxy->setChecked(title_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY); - ui->menu_spmode_vpn->setChecked(title_spmode == NekoRay::SystemProxyMode::VPN); + ui->menu_spmode_disabled->setChecked(NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::DISABLE); + ui->menu_spmode_system_proxy->setChecked( + NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY); + ui->menu_spmode_vpn->setChecked(NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN); }); connect(ui->menu_spmode_system_proxy, &QAction::triggered, this, [=]() { neko_set_spmode(NekoRay::SystemProxyMode::SYSTEM_PROXY); }); @@ -392,8 +393,8 @@ MainWindow::MainWindow(QWidget *parent) // Start last if (NekoRay::dataStore->remember_enable) { - if (NekoRay::dataStore->system_proxy_mode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { - neko_set_spmode(NekoRay::dataStore->system_proxy_mode, false); + if (NekoRay::dataStore->remember_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { + neko_set_spmode(NekoRay::dataStore->remember_spmode, false); } if (NekoRay::dataStore->remember_id >= 0) { runOnUiThread([=] { neko_start(NekoRay::dataStore->remember_id); }); @@ -465,7 +466,7 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info) auto changed = NekoRay::dataStore->Save(); if (info.contains("RouteChanged")) changed = true; refresh_proxy_list(); - if (info.contains("VPNChanged") && title_spmode == NekoRay::SystemProxyMode::VPN) { + if (info.contains("VPNChanged") && NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN) { MessageBoxWarning(tr("VPN settings changed"), tr("Restart VPN to take effect.")); } else if (changed && NekoRay::dataStore->started_id >= 0 && QMessageBox::question(GetMessageBoxParent(), tr("Confirmation"), @@ -554,7 +555,7 @@ void MainWindow::on_commitDataRequest() { void MainWindow::on_menu_exit_triggered() { neko_set_spmode(NekoRay::SystemProxyMode::DISABLE, false); - if (title_spmode == NekoRay::SystemProxyMode::VPN) return; + if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN) return; RegisterHotkey(true); // on_commitDataRequest(); @@ -574,16 +575,17 @@ void MainWindow::on_menu_exit_triggered() { qApp->quit(); } +#define neko_set_spmode_FAILED refresh_status(); return; + void MainWindow::neko_set_spmode(int mode, bool save) { - if (mode != title_spmode) { + if (mode != NekoRay::dataStore->running_spmode) { // DISABLE - if (title_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { + if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { ClearSystemProxy(); - } else if (title_spmode == NekoRay::SystemProxyMode::VPN) { + } else if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN) { if (!StopVPNProcess()) { - refresh_status(); - return; + neko_set_spmode_FAILED } } @@ -609,19 +611,22 @@ void MainWindow::neko_set_spmode(int mode, bool save) { } SetSystemProxy(http_port, socks_port); } else if (mode == NekoRay::SystemProxyMode::VPN) { + if (NekoRay::dataStore->need_keep_vpn_off) { + MessageBoxWarning(software_name, tr("Current server is incompatible with VPN. Please stop the server first, enable VPN mode, and then restart.")); + neko_set_spmode_FAILED + } if (!StartVPNProcess()) { - refresh_status(); - return; + neko_set_spmode_FAILED } } } if (save) { - NekoRay::dataStore->system_proxy_mode = mode; + NekoRay::dataStore->remember_spmode = mode; NekoRay::dataStore->Save(); } - title_spmode = mode; + NekoRay::dataStore->running_spmode = mode; refresh_status(); } @@ -654,17 +659,17 @@ void MainWindow::refresh_status(const QString &traffic_update) { if (IS_NEKO_BOX) inbound_txt = QString("Mixed: %1").arg(display_socks); ui->label_inbound->setText(inbound_txt); // - ui->checkBox_VPN->setChecked(title_spmode == NekoRay::SystemProxyMode::VPN); - ui->checkBox_SystemProxy->setChecked(title_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY); + ui->checkBox_VPN->setChecked(NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN); + ui->checkBox_SystemProxy->setChecked(NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY); if (select_mode) ui->label_running->setText("[" + tr("Select") + "]"); auto make_title = [=](bool isTray) { QStringList tt; if (select_mode) tt << "[" + tr("Select") + "]"; if (!title_error.isEmpty()) tt << "[" + title_error + "]"; - if (title_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { + if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { tt << "[" + tr("System Proxy") + "]"; - } else if (title_spmode == NekoRay::SystemProxyMode::VPN) { + } else if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN) { tt << "[" + tr("VPN Mode") + "]"; } tt << software_name; @@ -677,9 +682,9 @@ void MainWindow::refresh_status(const QString &traffic_update) { }; auto icon_status_new = TrayIcon::NONE; - if (title_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { + if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) { icon_status_new = TrayIcon::SYSTEM_PROXY; - } else if (title_spmode == NekoRay::SystemProxyMode::VPN) { + } else if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN) { icon_status_new = TrayIcon::VPN; } else if (!running.isNull()) { icon_status_new = TrayIcon::RUNNING; diff --git a/ui/mainwindow.h b/ui/mainwindow.h index de03c23..fb23003 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -139,7 +139,6 @@ private: QTextDocument *qvLogDocument = new QTextDocument(this); // QString title_error; - int title_spmode = NekoRay::SystemProxyMode::DISABLE; int icon_status = -1; QSharedPointer running; QString traffic_update_cache; diff --git a/ui/mainwindow_grpc.cpp b/ui/mainwindow_grpc.cpp index 1497804..4cc8fa3 100644 --- a/ui/mainwindow_grpc.cpp +++ b/ui/mainwindow_grpc.cpp @@ -292,6 +292,7 @@ void MainWindow::neko_stop(bool crash) { #endif NekoRay::dataStore->UpdateStartedId(-1919); + NekoRay::dataStore->need_keep_vpn_off = false; running = nullptr; refresh_status(); refresh_proxy_list(id);