mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2025-12-19 05:30:06 +08:00
feat: add anytls support
This commit is contained in:
parent
6febb17e64
commit
be51ed3e6f
@ -141,6 +141,9 @@ set(PROJECT_SOURCES
|
||||
include/ui/profile/edit_trojan_vless.h
|
||||
src/ui/profile/edit_trojan_vless.cpp
|
||||
include/ui/profile/edit_trojan_vless.ui
|
||||
include/ui/profile/edit_anytls.h
|
||||
src/ui/profile/edit_anytls.cpp
|
||||
include/ui/profile/edit_anytls.ui
|
||||
|
||||
include/ui/profile/edit_quic.h
|
||||
src/ui/profile/edit_quic.cpp
|
||||
|
||||
@ -32,6 +32,7 @@ Apple platforms have a very strict security policy and since Throne does not hav
|
||||
- TUIC
|
||||
- Hysteria
|
||||
- Hysteria2
|
||||
- AnyTls
|
||||
- Wireguard
|
||||
- SSH
|
||||
- Custom Outbound
|
||||
|
||||
35
include/configs/proxy/AnyTlsBean.hpp
Normal file
35
include/configs/proxy/AnyTlsBean.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractBean.hpp"
|
||||
#include "V2RayStreamSettings.hpp"
|
||||
#include "Preset.hpp"
|
||||
|
||||
namespace Configs {
|
||||
class AnyTlsBean : public AbstractBean {
|
||||
public:
|
||||
QString password = "";
|
||||
int idle_session_check_interval = 30;
|
||||
int idle_session_timeout = 30;
|
||||
int min_idle_session = 0;
|
||||
|
||||
std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();
|
||||
|
||||
AnyTlsBean() : AbstractBean(0) {
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
_add(new configItem("idle_session_check_interval", &idle_session_check_interval, itemType::integer));
|
||||
_add(new configItem("idle_session_timeout", &idle_session_timeout, itemType::integer));
|
||||
_add(new configItem("min_idle_session", &min_idle_session, itemType::integer));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return "AnyTls"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
bool TryParseJson(const QJsonObject &obj);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace Configs
|
||||
@ -6,6 +6,7 @@
|
||||
#include "VMessBean.hpp"
|
||||
#include "TrojanVLESSBean.hpp"
|
||||
#include "QUICBean.hpp"
|
||||
#include "AnyTlsBean.hpp"
|
||||
#include "WireguardBean.h"
|
||||
#include "SSHBean.h"
|
||||
#include "CustomBean.hpp"
|
||||
|
||||
@ -18,6 +18,8 @@ namespace Configs {
|
||||
|
||||
class QUICBean;
|
||||
|
||||
class AnyTlsBean;
|
||||
|
||||
class WireguardBean;
|
||||
|
||||
class SSHBean;
|
||||
@ -76,6 +78,10 @@ namespace Configs {
|
||||
return (Configs::QUICBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] Configs::AnyTlsBean *AnyTlsBean() const {
|
||||
return (Configs::AnyTlsBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] Configs::WireguardBean *WireguardBean() const {
|
||||
return (Configs::WireguardBean *) bean.get();
|
||||
};
|
||||
|
||||
28
include/ui/profile/edit_anytls.h
Normal file
28
include/ui/profile/edit_anytls.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include "profile_editor.h"
|
||||
#include "ui_edit_anytls.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class EditAnyTls;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class EditAnyTls : public QWidget, public ProfileEditor {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EditAnyTls(QWidget *parent = nullptr);
|
||||
|
||||
~EditAnyTls() override;
|
||||
|
||||
void onStart(std::shared_ptr<Configs::ProxyEntity> _ent) override;
|
||||
|
||||
bool onEnd() override;
|
||||
|
||||
private:
|
||||
Ui::EditAnyTls *ui;
|
||||
std::shared_ptr<Configs::ProxyEntity> ent;
|
||||
};
|
||||
71
include/ui/profile/edit_anytls.ui
Normal file
71
include/ui/profile/edit_anytls.ui
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>EditAnyTls</class>
|
||||
<widget class="QWidget" name="EditAnyTls">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">EditAnyTls</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="MyLineEdit" name="password"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Idle Session Check Interval</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="interval"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Idle Session Timeout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="timeout"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Min Idle Session</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="min"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MyLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>include/ui/utils/MyLineEdit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>password</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@ -374,7 +374,7 @@ namespace Configs {
|
||||
|
||||
auto stream = GetStreamSettings(ent->bean.get());
|
||||
if (stream != nullptr) {
|
||||
if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) {
|
||||
if (stream->network == "grpc" || stream->network == "quic" || stream->network == "anytls" || (stream->network == "http" && stream->security == "tls")) {
|
||||
needMux = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,6 +171,24 @@ namespace Configs {
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult AnyTlsBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound{
|
||||
{"type", "anytls"},
|
||||
{"server", serverAddress},
|
||||
{"server_port", serverPort},
|
||||
{"password", password},
|
||||
{"idle_session_check_interval", Int2String(idle_session_check_interval)+"s"},
|
||||
{"idle_session_timeout", Int2String(idle_session_timeout)+"s"},
|
||||
{"min_idle_session", min_idle_session},
|
||||
};
|
||||
|
||||
stream->BuildStreamSettingsSingBox(&outbound);
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult VMessBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
|
||||
@ -23,6 +23,35 @@ namespace Configs {
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QString AnyTlsBean::ToShareLink() {
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
url.setScheme("anytls");
|
||||
url.setUserName(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
|
||||
// security
|
||||
query.addQueryItem("security", stream->security == "" ? "none" : stream->security);
|
||||
|
||||
if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni);
|
||||
if (!stream->alpn.isEmpty()) query.addQueryItem("alpn", stream->alpn);
|
||||
if (stream->allow_insecure) query.addQueryItem("insecure", "1");
|
||||
if (!stream->utlsFingerprint.isEmpty()) query.addQueryItem("fp", stream->utlsFingerprint);
|
||||
if (stream->enable_tls_fragment) query.addQueryItem("fragment", "1");
|
||||
if (!stream->tls_fragment_fallback_delay.isEmpty()) query.addQueryItem("fragment_fallback_delay", stream->tls_fragment_fallback_delay);
|
||||
if (stream->enable_tls_record_fragment) query.addQueryItem("record_fragment", "1");
|
||||
|
||||
if (stream->security == "reality") {
|
||||
query.addQueryItem("pbk", stream->reality_pbk);
|
||||
if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid);
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QString TrojanVLESSBean::ToShareLink() {
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <include/configs/proxy/SocksHttpBean.hpp>
|
||||
#include <include/configs/proxy/TrojanVLESSBean.hpp>
|
||||
#include <include/configs/proxy/VMessBean.hpp>
|
||||
#include <include/configs/proxy/AnyTlsBean.hpp>
|
||||
#include <include/configs/proxy/WireguardBean.h>
|
||||
|
||||
#include "include/configs/proxy/ExtraCore.h"
|
||||
@ -213,6 +214,32 @@ namespace Configs
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnyTlsBean::TryParseJson(const QJsonObject& obj)
|
||||
{
|
||||
name = obj["tag"].toString();
|
||||
serverAddress = obj["server"].toString();
|
||||
serverPort = obj["server_port"].toInt();
|
||||
password = obj["password"].toString();
|
||||
idle_session_check_interval = obj["idle_session_check_interval"].toInt();
|
||||
idle_session_timeout = obj["idle_session_timeout"].toInt();
|
||||
min_idle_session = obj["min_idle_session"].toInt();
|
||||
stream->security = obj["tls"].isObject() ? "tls" : "";
|
||||
if (obj["tls"].toObject()["reality"].toObject()["enabled"].toBool())
|
||||
{
|
||||
stream->security = "reality";
|
||||
}
|
||||
stream->reality_pbk = obj["tls"].toObject()["reality"].toObject()["public_key"].toString();
|
||||
stream->reality_sid = obj["tls"].toObject()["reality"].toObject()["short_id"].toString();
|
||||
stream->utlsFingerprint = obj["tls"].toObject()["utls"].toObject()["fingerprint"].toString();
|
||||
stream->enable_tls_fragment = obj["tls"].toObject()["fragment"].toBool();
|
||||
stream->tls_fragment_fallback_delay = obj["tls"].toObject()["fragment_fallback_delay"].toString();
|
||||
stream->enable_tls_record_fragment = obj["tls"].toObject()["record_fragment"].toBool();
|
||||
stream->sni = obj["tls"].toObject()["server_name"].toString();
|
||||
stream->alpn = obj["tls"].toObject()["alpn"].isArray() ? QJsonArray2QListString(obj["tls"].toObject()["alpn"].toArray()).join(",") : obj["tls"].toObject()["alpn"].toString();
|
||||
stream->allow_insecure = obj["tls"].toObject()["insecure"].toBool();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardBean::TryParseJson(const QJsonObject& obj)
|
||||
{
|
||||
name = obj["tag"].toString();
|
||||
|
||||
@ -42,6 +42,43 @@ namespace Configs {
|
||||
return !serverAddress.isEmpty();
|
||||
}
|
||||
|
||||
bool AnyTlsBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
auto query = GetQuery(url);
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
password = url.userName();
|
||||
if (serverPort == -1) serverPort = 443;
|
||||
|
||||
// security
|
||||
|
||||
stream->security = GetQueryValue(query, "security", "").replace("none", "");
|
||||
auto sni1 = GetQueryValue(query, "sni");
|
||||
auto sni2 = GetQueryValue(query, "peer");
|
||||
if (!sni1.isEmpty()) stream->sni = sni1;
|
||||
if (!sni2.isEmpty()) stream->sni = sni2;
|
||||
stream->alpn = GetQueryValue(query, "alpn");
|
||||
stream->allow_insecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure"));
|
||||
stream->reality_pbk = GetQueryValue(query, "pbk", "");
|
||||
stream->reality_sid = GetQueryValue(query, "sid", "");
|
||||
stream->utlsFingerprint = GetQueryValue(query, "fp", "");
|
||||
if (query.queryItemValue("fragment") == "1") stream->enable_tls_fragment = true;
|
||||
stream->tls_fragment_fallback_delay = query.queryItemValue("fragment_fallback_delay");
|
||||
if (query.queryItemValue("record_fragment") == "1") stream->enable_tls_record_fragment = true;
|
||||
if (stream->utlsFingerprint.isEmpty()) {
|
||||
stream->utlsFingerprint = dataStore->utlsFingerprint;
|
||||
}
|
||||
if (stream->security.isEmpty()) {
|
||||
if (!sni1.isEmpty() || !sni2.isEmpty()) stream->security = "tls";
|
||||
if (!stream->reality_pbk.isEmpty()) stream->security = "reality";
|
||||
}
|
||||
|
||||
return !(password.isEmpty() || serverAddress.isEmpty());
|
||||
}
|
||||
|
||||
bool TrojanVLESSBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
|
||||
@ -172,6 +172,13 @@ namespace Subscription {
|
||||
if (!ok) return;
|
||||
}
|
||||
|
||||
// AnyTls
|
||||
if (str.startsWith("anytls://")) {
|
||||
ent = Configs::ProfileManager::NewProxyEntity("anytls");
|
||||
auto ok = ent->AnyTlsBean()->TryParseLink(str);
|
||||
if (!ok) return;
|
||||
}
|
||||
|
||||
// Hysteria1
|
||||
if (str.startsWith("hysteria://")) {
|
||||
needFix = false;
|
||||
@ -290,6 +297,13 @@ namespace Subscription {
|
||||
if (!ok) continue;
|
||||
}
|
||||
|
||||
// AnyTls
|
||||
if (out["type"] == "anytls") {
|
||||
ent = Configs::ProfileManager::NewProxyEntity("anytls");
|
||||
auto ok = ent->AnyTlsBean()->TryParseJson(out);
|
||||
if (!ok) continue;
|
||||
}
|
||||
|
||||
// Hysteria1
|
||||
if (out["type"] == "hysteria") {
|
||||
ent = Configs::ProfileManager::NewProxyEntity("hysteria");
|
||||
@ -526,7 +540,6 @@ namespace Subscription {
|
||||
if (Node2Bool(proxy["tls"])) bean->stream->security = "tls";
|
||||
if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true;
|
||||
bean->stream->utlsFingerprint = Node2QString(proxy["client-fingerprint"]);
|
||||
bean->stream->utlsFingerprint = Node2QString(proxy["client-fingerprint"]);
|
||||
if (bean->stream->utlsFingerprint.isEmpty()) {
|
||||
bean->stream->utlsFingerprint = Configs::dataStore->utlsFingerprint;
|
||||
}
|
||||
@ -593,6 +606,24 @@ namespace Subscription {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (type == "anytls") {
|
||||
needFix = true;
|
||||
auto bean = ent->AnyTlsBean();
|
||||
bean->password = Node2QString(proxy["password"]);
|
||||
if (Node2Bool(proxy["tls"])) bean->stream->security = "tls";
|
||||
if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true;
|
||||
bean->stream->sni = FIRST_OR_SECOND(Node2QString(proxy["sni"]), Node2QString(proxy["servername"]));
|
||||
bean->stream->alpn = Node2QStringList(proxy["alpn"]).join(",");
|
||||
bean->stream->utlsFingerprint = Node2QString(proxy["client-fingerprint"]);
|
||||
if (bean->stream->utlsFingerprint.isEmpty()) {
|
||||
bean->stream->utlsFingerprint = Configs::dataStore->utlsFingerprint;
|
||||
}
|
||||
|
||||
auto reality = NodeChild(proxy, {"reality-opts"});
|
||||
if (reality.is_mapping()) {
|
||||
bean->stream->reality_pbk = Node2QString(reality["public-key"]);
|
||||
bean->stream->reality_sid = Node2QString(reality["short-id"]);
|
||||
}
|
||||
} else if (type == "hysteria") {
|
||||
auto bean = ent->QUICBean();
|
||||
|
||||
|
||||
@ -176,6 +176,8 @@ namespace Configs {
|
||||
bean = new Configs::QUICBean(Configs::QUICBean::proxy_Hysteria2);
|
||||
} else if (type == "tuic") {
|
||||
bean = new Configs::QUICBean(Configs::QUICBean::proxy_TUIC);
|
||||
} else if (type == "anytls") {
|
||||
bean = new Configs::AnyTlsBean();
|
||||
} else if (type == "wireguard") {
|
||||
bean = new Configs::WireguardBean(Configs::WireguardBean());
|
||||
} else if (type == "ssh") {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "include/ui/profile/edit_vmess.h"
|
||||
#include "include/ui/profile/edit_trojan_vless.h"
|
||||
#include "include/ui/profile/edit_quic.h"
|
||||
#include "include/ui/profile/edit_anytls.h"
|
||||
#include "include/ui/profile/edit_wireguard.h"
|
||||
#include "include/ui/profile/edit_ssh.h"
|
||||
#include "include/ui/profile/edit_custom.h"
|
||||
@ -166,6 +167,7 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
|
||||
LOAD_TYPE("hysteria")
|
||||
LOAD_TYPE("hysteria2")
|
||||
LOAD_TYPE("tuic")
|
||||
LOAD_TYPE("anytls")
|
||||
LOAD_TYPE("wireguard")
|
||||
LOAD_TYPE("ssh")
|
||||
ui->type->addItem(tr("Custom (%1 outbound)").arg(software_core_name), "internal");
|
||||
@ -233,6 +235,10 @@ void DialogEditProfile::typeSelected(const QString &newType) {
|
||||
auto _innerWidget = new EditQUIC(this);
|
||||
innerWidget = _innerWidget;
|
||||
innerEditor = _innerWidget;
|
||||
} else if (type == "anytls") {
|
||||
auto _innerWidget = new EditAnyTls(this);
|
||||
innerWidget = _innerWidget;
|
||||
innerEditor = _innerWidget;
|
||||
} else if (type == "wireguard") {
|
||||
auto _innerWidget = new EditWireguard(this);
|
||||
innerWidget = _innerWidget;
|
||||
@ -375,7 +381,7 @@ void DialogEditProfile::typeSelected(const QString &newType) {
|
||||
ui->network->setVisible(false);
|
||||
ui->network_box->setVisible(false);
|
||||
}
|
||||
if (type == "vmess" || type == "vless" || type == "trojan" || type == "http") {
|
||||
if (type == "vmess" || type == "vless" || type == "trojan" || type == "http" || type == "anytls") {
|
||||
ui->security->setVisible(true);
|
||||
ui->security_l->setVisible(true);
|
||||
} else {
|
||||
|
||||
39
src/ui/profile/edit_anytls.cpp
Normal file
39
src/ui/profile/edit_anytls.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include "include/ui/profile/edit_anytls.h"
|
||||
|
||||
#include "include/configs/proxy/AnyTlsBean.hpp"
|
||||
|
||||
#include <QUuid>
|
||||
#include <QRegularExpressionValidator>
|
||||
#include "include/global/GuiUtils.hpp"
|
||||
|
||||
EditAnyTls::EditAnyTls(QWidget *parent) : QWidget(parent), ui(new Ui::EditAnyTls) {
|
||||
ui->setupUi(this);
|
||||
ui->interval->setValidator(QRegExpValidator_Number);
|
||||
ui->timeout->setValidator(QRegExpValidator_Number);
|
||||
ui->min->setValidator(QRegExpValidator_Number);
|
||||
}
|
||||
|
||||
EditAnyTls::~EditAnyTls() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void EditAnyTls::onStart(std::shared_ptr<Configs::ProxyEntity> _ent) {
|
||||
this->ent = _ent;
|
||||
auto bean = this->ent->AnyTlsBean();
|
||||
|
||||
ui->password->setText(bean->password);
|
||||
ui->interval->setText(Int2String(bean->idle_session_check_interval));
|
||||
ui->timeout->setText(Int2String(bean->idle_session_timeout));
|
||||
ui->min->setText(Int2String(bean->min_idle_session));
|
||||
}
|
||||
|
||||
bool EditAnyTls::onEnd() {
|
||||
auto bean = this->ent->AnyTlsBean();
|
||||
|
||||
bean->password = ui->password->text();
|
||||
bean->idle_session_check_interval = ui->interval->text().toInt();
|
||||
bean->idle_session_timeout = ui->timeout->text().toInt();
|
||||
bean->min_idle_session = ui->min->text().toInt();
|
||||
|
||||
return true;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user