mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2026-01-06 21:36:02 +08:00
feat: Add dns hijack feature
This commit is contained in:
parent
6d7ce2868c
commit
41648c0db7
@ -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"},
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 = "";
|
||||
|
||||
@ -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"};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -132,6 +132,122 @@ also if the connection cannot be established with the current address family (ip
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="title">
|
||||
<string>Hijack</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>DNS Server</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="dnshijack_enable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="dnshijack_what">
|
||||
<property name="text">
|
||||
<string>How does it work?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="dnshijack_listenaddr_l">
|
||||
<property name="text">
|
||||
<string>Listen Address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="dnshijack_listenaddr"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="dnshijack_listenport_l">
|
||||
<property name="text">
|
||||
<string>Listen Port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="dnshijack_listenport"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="dnshijack_rules_l">
|
||||
<property name="text">
|
||||
<string>Rules</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QTextEdit" name="dnshijack_rules"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="dnshijack_v4resp"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="dnshijack_v4resp_l">
|
||||
<property name="text">
|
||||
<string>IPv4 Response</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="dnshijack_v6resp"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="dnshijack_v6resp_l">
|
||||
<property name="text">
|
||||
<string>IPv6 Response</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Redirect Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="redirect_listenport"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="redirect_listenport_l">
|
||||
<property name="text">
|
||||
<string>Listen Port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="redirect_enable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="redirect_listenaddr"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="redirect_listenaddr_l">
|
||||
<property name="text">
|
||||
<string>Listen Address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>DNS</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user