diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 3bdba0f..3f7823e 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -531,6 +531,17 @@ namespace NekoGui { {"tag", "dns-out"}, }; + if (dataStore->enable_redirect) { + status->inbounds.prepend(QJsonObject{ + {"tag", "hijack"}, + {"type", "direct"}, + {"listen", dataStore->redirect_listen_address}, + {"listen_port", dataStore->redirect_listen_port}, + {"sniff", true}, + {"sniff_override_destination", true}, + }); + } + // custom inbound if (!status->forTest) QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray()) @@ -579,7 +590,39 @@ namespace NekoGui { status->domainListDNSDirect << neededEnt->bean->serverAddress; } } - routeObj["rules"] = routeChain->get_route_rules(false, outboundMap); + auto routeRules = routeChain->get_route_rules(false, outboundMap); + if (dataStore->enable_dns_server) routeRules.prepend(QJsonObject{ + {"inbound", "dns-in"}, + {"outbound", "dns-out"} + }); + routeObj["rules"] = routeRules; + + bool needHijackRules = false; + QJsonArray hijackDomains; + QJsonArray hijackDomainSuffix; + QJsonArray hijackDomainRegex; + QJsonArray hijackGeoAssets; + + if (dataStore->enable_dns_server) { + for (const auto& rule : dataStore->dns_server_rules) { + if (rule.startsWith("ruleset:")) { + hijackGeoAssets << rule.mid(8); + } + if (rule.startsWith("domain:")) { + hijackDomains << rule.mid(7); + } + if (rule.startsWith("suffix:")) { + hijackDomainSuffix << rule.mid(7); + } + if (rule.startsWith("regex:")) { + hijackDomainRegex << rule.mid(6); + } + needHijackRules = true; + } + } + for (auto ruleSet : hijackGeoAssets) { + if (!neededRuleSets->contains(ruleSet.toString())) neededRuleSets->append(ruleSet.toString()); + } auto ruleSetArray = QJsonArray(); for (const auto &item: *neededRuleSets) { @@ -637,6 +680,24 @@ namespace NekoGui { {"address", "rcode://success"}, }; + // Hijack + if (dataStore->enable_dns_server) { + dnsServers += QJsonObject { + {"tag", "dns-hijack"}, + {"address", "hijack://10.10.10.10"}, + {"inet4_response", dataStore->dns_v4_resp}, + {"inet6_response", dataStore->dns_v6_resp}, + }; + + status->inbounds.prepend(QJsonObject{ + {"tag", "dns-in"}, + {"type", "direct"}, + {"listen", dataStore->dns_server_listen_addr}, + {"listen_port", dataStore->dns_server_listen_port}, + {"sniff", true}, + }); + } + // Fakedns if (dataStore->fake_dns) { dnsServers += QJsonObject{ @@ -706,6 +767,17 @@ namespace NekoGui { }; } + // dns hijack rules + if (needHijackRules) { + dnsRules += QJsonObject{ + {"rule_set", hijackGeoAssets}, + {"domain", hijackDomains}, + {"domain_suffix", hijackDomainSuffix}, + {"domain_regex", hijackDomainRegex}, + {"server", "dns-hijack"}, + }; + } + // Underlying 100% Working DNS dnsServers += QJsonObject{ {"tag", "dns-local"}, diff --git a/go/cmd/nekobox_core/go.mod b/go/cmd/nekobox_core/go.mod index 35d9d0f..b837775 100644 --- a/go/cmd/nekobox_core/go.mod +++ b/go/cmd/nekobox_core/go.mod @@ -10,7 +10,9 @@ require ( grpc_server v1.0.0 ) -replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0.20240918061449-35f1c345ec90 +replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0.20240920073315-bc73938972f4 + +replace github.com/sagernet/sing-dns => github.com/Mahdi-zarei/sing-dns v0.3.0-beta.14.0.20240918175353-709eafff43d3 require ( berty.tech/go-libtor v1.0.385 // indirect diff --git a/go/cmd/nekobox_core/go.sum b/go/cmd/nekobox_core/go.sum index ef9b795..e74fcad 100644 --- a/go/cmd/nekobox_core/go.sum +++ b/go/cmd/nekobox_core/go.sum @@ -4,10 +4,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Mahdi-zarei/sing-box v1.3.5-0.20240918061449-35f1c345ec90 h1:I1ONdswCt5excwjoCcpIRRGOFkc/+TXfxynNVS95iqk= -github.com/Mahdi-zarei/sing-box v1.3.5-0.20240918061449-35f1c345ec90/go.mod h1:nRI5VekCi5MxQyJyW6AcWiXcmjjW6hX8eJMhV78T/Fo= +github.com/Mahdi-zarei/sing-box v1.3.5-0.20240920073315-bc73938972f4 h1:8joOa6IehQSy5wKnTN2YBuvwKZf0sfSvdU1dC2hCeJ8= +github.com/Mahdi-zarei/sing-box v1.3.5-0.20240920073315-bc73938972f4/go.mod h1:MSnb8j3iBcjvuf/w7SfApa1WgBC6XecxhBeLt1vzDJk= github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240918062427-6045c5730e82 h1:h09DnPUhOGfhg7I3aJJehkjo/oC2QH4APxnWjxk/wZo= github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240918062427-6045c5730e82/go.mod h1:5/Y7uNbJpofpogdnG5Ao/Yz+bjnA8cOU1nInfA+tV4s= +github.com/Mahdi-zarei/sing-dns v0.3.0-beta.14.0.20240918175353-709eafff43d3 h1:1YoZLwSYHOZ9J9DftJrI/FADEjv29kPptn5g34A3vC4= +github.com/Mahdi-zarei/sing-dns v0.3.0-beta.14.0.20240918175353-709eafff43d3/go.mod h1:rscgSr5ixOPk8XM9ZMLuMXCyldEQ1nLvdl0nfv+lp00= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= @@ -152,8 +154,6 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.5.0-beta.2 h1:V12EpwtsgYo5OLGjAiGoJobDJZeUsKv0b5y+yGAM6W0= github.com/sagernet/sing v0.5.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0-beta.14 h1:/s+fJzYKsvLaNDt/2rjpsrDcN8wmCO2JbX6OFrl8Nww= -github.com/sagernet/sing-dns v0.3.0-beta.14/go.mod h1:rscgSr5ixOPk8XM9ZMLuMXCyldEQ1nLvdl0nfv+lp00= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-quic v0.3.0-beta.3 h1:8S98VXZxtSiOqVCFbCNbMEvKDPhOF/VNBYMjVC3xMhw= diff --git a/main/NekoGui.cpp b/main/NekoGui.cpp index bd22959..6ba13d5 100644 --- a/main/NekoGui.cpp +++ b/main/NekoGui.cpp @@ -295,6 +295,15 @@ namespace NekoGui { _add(new configItem("ntp_interval", &ntp_interval, itemType::string)); _add(new configItem("geoip_download_url", &geoip_download_url, itemType::string)); _add(new configItem("geosite_download_url", &geosite_download_url, itemType::string)); + _add(new configItem("enable_dns_server", &enable_dns_server, itemType::boolean)); + _add(new configItem("dns_server_listen_addr", &dns_server_listen_addr, itemType::string)); + _add(new configItem("dns_server_listen_port", &dns_server_listen_port, itemType::integer)); + _add(new configItem("dns_v4_resp", &dns_v4_resp, itemType::string)); + _add(new configItem("dns_v6_resp", &dns_v6_resp, itemType::string)); + _add(new configItem("dns_server_rules", &dns_server_rules, itemType::stringList)); + _add(new configItem("enable_redirect", &enable_redirect, itemType::boolean)); + _add(new configItem("redirect_listen_address", &redirect_listen_address, itemType::string)); + _add(new configItem("redirect_listen_port", &redirect_listen_port, itemType::integer)); } void DataStore::UpdateStartedId(int id) { diff --git a/main/NekoGui_DataStore.hpp b/main/NekoGui_DataStore.hpp index ddd506b..6432701 100644 --- a/main/NekoGui_DataStore.hpp +++ b/main/NekoGui_DataStore.hpp @@ -134,6 +134,21 @@ namespace NekoGui { int ntp_server_port = 0; QString ntp_interval = ""; + // Hijack + bool enable_dns_server = false; + QString dns_server_listen_addr = "127.0.0.1"; +#ifdef Q_OS_LINUX + int dns_server_listen_port = 5353; +#else + int dns_server_listen_port = 53; +#endif + QString dns_v4_resp = "127.0.0.1"; + QString dns_v6_resp = "::1"; + QStringList dns_server_rules = {}; + bool enable_redirect = false; + QString redirect_listen_address = "127.0.0.1"; + int redirect_listen_port = 443; + // Hotkey QString hotkey_mainwindow = ""; QString hotkey_group = ""; diff --git a/ui/dialog_manage_routes.cpp b/ui/dialog_manage_routes.cpp index 7724ece..e6ab256 100644 --- a/ui/dialog_manage_routes.cpp +++ b/ui/dialog_manage_routes.cpp @@ -38,6 +38,22 @@ void DialogManageRoutes::reloadProfileItems() { blocker.unblock(); } +void DialogManageRoutes::set_dns_hijack_enability(const bool enable) const { + ui->dnshijack_listenaddr->setEnabled(enable); + ui->dnshijack_listenport->setEnabled(enable); + ui->dnshijack_rules->setEnabled(enable); + ui->dnshijack_v4resp->setEnabled(enable); + ui->dnshijack_v6resp->setEnabled(enable); +} + +bool DialogManageRoutes::validate_dns_rules(const QString &rawString) { + auto rules = rawString.split("\n"); + for (const auto& rule : rules) { + if (!rule.trimmed().isEmpty() && !rule.startsWith("ruleset:") && !rule.startsWith("domain:") && !rule.startsWith("suffix:") && !rule.startsWith("regex:")) return false; + } + return true; +} + DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) { ui->setupUi(this); auto profiles = NekoGui::profileManager->routes; @@ -107,6 +123,35 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne on_delete_route_clicked(); }); + // hijack + ui->dnshijack_enable->setChecked(NekoGui::dataStore->enable_dns_server); + set_dns_hijack_enability(NekoGui::dataStore->enable_dns_server); + ui->dnshijack_listenaddr->setText(NekoGui::dataStore->dns_server_listen_addr); + ui->dnshijack_listenport->setValidator(QRegExpValidator_Number); + ui->dnshijack_listenport->setText(Int2String(NekoGui::dataStore->dns_server_listen_port)); + ui->dnshijack_rules->setText(NekoGui::dataStore->dns_server_rules.join("\n")); + ui->dnshijack_v4resp->setText(NekoGui::dataStore->dns_v4_resp); + ui->dnshijack_v6resp->setText(NekoGui::dataStore->dns_v6_resp); +#ifndef Q_OS_LINUX + ui->dnshijack_listenport->setVisible(false); + ui->dnshijack_listenport_l->setVisible(false); +#endif + + ui->redirect_enable->setChecked(NekoGui::dataStore->enable_redirect); + ui->redirect_listenaddr->setEnabled(NekoGui::dataStore->enable_redirect); + ui->redirect_listenaddr->setText(NekoGui::dataStore->redirect_listen_address); + ui->redirect_listenport->setEnabled(NekoGui::dataStore->enable_redirect); + ui->redirect_listenport->setValidator(QRegExpValidator_Number); + ui->redirect_listenport->setText(Int2String(NekoGui::dataStore->redirect_listen_port)); + + connect(ui->dnshijack_enable, &QCheckBox::checkStateChanged, this, [=](bool state) { + set_dns_hijack_enability(state); + }); + connect(ui->redirect_enable, &QCheckBox::checkStateChanged, this, [=](bool state) { + ui->redirect_listenaddr->setEnabled(state); + ui->redirect_listenport->setEnabled(state); + }); + ADD_ASTERISK(this) } @@ -123,6 +168,10 @@ void DialogManageRoutes::accept() { MessageBoxInfo("Invalid settings", "Routing profile cannot be empty"); return; } + if (!validate_dns_rules(ui->dnshijack_rules->toPlainText())) { + MessageBoxInfo("Invalid settings", "DNS Rules are not valid"); + return; + } NekoGui::dataStore->routing->sniffing_mode = ui->sniffing_mode->currentIndex(); NekoGui::dataStore->routing->domain_strategy = ui->domainStrategyCombo->currentText(); @@ -140,6 +189,22 @@ void DialogManageRoutes::accept() { NekoGui::dataStore->routing->current_route_id = currentRouteProfileID; NekoGui::dataStore->routing->def_outbound = ui->default_out->currentText(); + NekoGui::dataStore->enable_dns_server = ui->dnshijack_enable->isChecked(); + NekoGui::dataStore->dns_server_listen_addr = ui->dnshijack_listenaddr->text(); + NekoGui::dataStore->dns_server_listen_port = ui->dnshijack_listenport->text().toInt(); + NekoGui::dataStore->dns_v4_resp = ui->dnshijack_v4resp->text(); + NekoGui::dataStore->dns_v6_resp = ui->dnshijack_v6resp->text(); + auto rawRules = ui->dnshijack_rules->toPlainText().split("\n"); + QStringList dnsRules; + for (const auto& rawRule : rawRules) { + if (rawRule.trimmed().isEmpty()) continue; + dnsRules.append(rawRule.trimmed()); + } + NekoGui::dataStore->dns_server_rules = dnsRules; + + NekoGui::dataStore->enable_redirect = ui->redirect_enable->isChecked(); + NekoGui::dataStore->redirect_listen_address = ui->redirect_listenaddr->text(); + NekoGui::dataStore->redirect_listen_port = ui->redirect_listenport->text().toInt(); // QStringList msg{"UpdateDataStore"}; diff --git a/ui/dialog_manage_routes.h b/ui/dialog_manage_routes.h index ca9f5d8..840c3eb 100644 --- a/ui/dialog_manage_routes.h +++ b/ui/dialog_manage_routes.h @@ -32,6 +32,10 @@ private: int currentRouteProfileID = -1; + void set_dns_hijack_enability(bool enable) const; + + static bool validate_dns_rules(const QString &rawString); + QShortcut* deleteShortcut; public slots: void accept() override; diff --git a/ui/dialog_manage_routes.ui b/ui/dialog_manage_routes.ui index daf86bf..f675206 100644 --- a/ui/dialog_manage_routes.ui +++ b/ui/dialog_manage_routes.ui @@ -132,6 +132,122 @@ also if the connection cannot be established with the current address family (ip + + + Hijack + + + + + + DNS Server + + + + + + Enable + + + + + + + How does it work? + + + + + + + Listen Address + + + + + + + + + + Listen Port + + + + + + + + + + Rules + + + + + + + + + + + + + IPv4 Response + + + + + + + + + + IPv6 Response + + + + + + + + + + Redirect Settings + + + + + + + + + Listen Port + + + + + + + Enable + + + + + + + + + + Listen Address + + + + + + + + DNS