add tls fragment support

This commit is contained in:
Nova 2025-08-10 12:48:17 +03:30
parent de1b1385c3
commit c41a61e2a9
8 changed files with 312 additions and 153 deletions

View File

@ -20,6 +20,9 @@ namespace Configs {
QString alpn = "";
QString certificate = "";
QString utlsFingerprint = "";
bool enable_tls_fragment = false;
QString tls_fragment_fallback_delay;
bool enable_tls_record_fragment = false;
bool allow_insecure = false;
// ws early data
QString ws_early_data_name = "";
@ -44,6 +47,9 @@ namespace Configs {
_add(new configItem("ed_name", &ws_early_data_name, itemType::string));
_add(new configItem("ed_len", &ws_early_data_length, itemType::integer));
_add(new configItem("utls", &utlsFingerprint, itemType::string));
_add(new configItem("tls_frag", &enable_tls_fragment, itemType::boolean));
_add(new configItem("tls_frag_fall_delay", &tls_fragment_fallback_delay, itemType::string));
_add(new configItem("tls_record_frag", &enable_tls_record_fragment, itemType::boolean));
_add(new configItem("pbk", &reality_pbk, itemType::string));
_add(new configItem("sid", &reality_sid, itemType::string));
}

View File

@ -16,6 +16,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Edit</string>
</property>
@ -285,7 +291,7 @@
<item>
<widget class="QWidget" name="right_all_w" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -293,7 +299,7 @@
<property name="minimumSize">
<size>
<width>400</width>
<height>613</height>
<height>500</height>
</size>
</property>
<property name="toolTip">
@ -632,52 +638,154 @@
</item>
<item>
<widget class="QGroupBox" name="tls_camouflage_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>TLS Camouflage Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="MyLineEdit" name="reality_pbk"/>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="utlsFingerprint">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="reality_pbk_l">
<property name="toolTip">
<string>Reality public key. If not empty, turn TLS into REALITY.</string>
</property>
<property name="text">
<string notr="true">Reality Pbk</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="reality_sid_l">
<property name="text">
<string>Reality SID</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinimumSize</enum>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Fingerprint</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinimumSize</enum>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="tls_frag">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;May degrade performance, try record fragment first&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable TLS fragment</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;time format, like 500ms, 10ms etc&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Fallback Delay</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tls_frag_fall_delay"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="tls_rec_frag">
<property name="text">
<string>Enable TLS Record Fragment</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="reality_sid"/>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>7</number>
</property>
<property name="verticalSpacing">
<number>7</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Fingerprint</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="utlsFingerprint">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="reality_pbk_l">
<property name="toolTip">
<string>Reality public key. If not empty, turn TLS into REALITY.</string>
</property>
<property name="text">
<string notr="true">Reality Pbk</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="MyLineEdit" name="reality_pbk"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="reality_sid_l">
<property name="text">
<string>Reality SID</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="reality_sid"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
@ -714,8 +822,6 @@
<tabstop>certificate_edit</tabstop>
<tabstop>sni</tabstop>
<tabstop>alpn</tabstop>
<tabstop>utlsFingerprint</tabstop>
<tabstop>reality_pbk</tabstop>
</tabstops>
<resources/>
<connections>

View File

@ -6,115 +6,121 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>657</height>
<width>640</width>
<height>727</height>
</rect>
</property>
<property name="windowTitle">
<string>EditWireguard</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="private_key_l">
<property name="text">
<string>Private Key</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="private_key_l">
<property name="text">
<string>Private Key</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="private_key"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="public_key_l">
<property name="text">
<string>Public Key</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="public_key"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="preshared_key_l">
<property name="text">
<string>Pre Shared Key</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="preshared_key"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="reserved_l">
<property name="text">
<string>Reserved</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="reserved"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;persistent_keepalive_interval in seconds&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Persistent Keepalive</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="persistent_keepalive"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="local_addr_l">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;comma seperated list of subnets&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Local Address</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="local_addr"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mtu_l">
<property name="text">
<string>MTU</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="mtu">
<property name="text">
<string>1420</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="workers_l">
<property name="text">
<string>Workers</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="workers"/>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="sys_ifc">
<property name="text">
<string>Use System Interface</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="private_key"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="public_key_l">
<property name="text">
<string>Public Key</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="public_key"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="preshared_key_l">
<property name="text">
<string>Pre Shared Key</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="preshared_key"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="reserved_l">
<property name="text">
<string>Reserved</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="reserved"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;persistent_keepalive_interval in seconds&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Persistent Keepalive</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="persistent_keepalive"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="local_addr_l">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;comma seperated list of subnets&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Local Address</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="local_addr"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mtu_l">
<property name="text">
<string>MTU</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="mtu">
<property name="text">
<string>1420</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="workers_l">
<property name="text">
<string>Workers</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="workers"/>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="sys_ifc">
<property name="text">
<string>Use System Interface</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Amenzia Settings</string>
@ -127,9 +133,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="junk_packet_count"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@ -137,8 +140,8 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="junk_packet_min_size"/>
<item row="1" column="1">
<widget class="QLineEdit" name="junk_packet_count"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
@ -147,6 +150,9 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="junk_packet_min_size"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">

View File

@ -12,8 +12,7 @@ namespace Configs {
auto pathWithoutEd = SubStrBefore(path, "?ed=");
if (!pathWithoutEd.isEmpty()) transport["path"] = pathWithoutEd;
if (pathWithoutEd != path) {
auto ed = SubStrAfter(path, "?ed=").toInt();
if (ed > 0) {
if (auto ed = SubStrAfter(path, "?ed=").toInt(); ed > 0) {
transport["max_early_data"] = ed;
transport["early_data_header_name"] = "Sec-WebSocket-Protocol";
}
@ -74,13 +73,18 @@ namespace Configs {
if (!alpn.trimmed().isEmpty()) {
tls["alpn"] = QListStr2QJsonArray(alpn.split(","));
}
QString fp = utlsFingerprint;
if (!fp.isEmpty()) {
if (QString fp = utlsFingerprint; !fp.isEmpty()) {
tls["utls"] = QJsonObject{
{"enabled", true},
{"fingerprint", fp},
};
}
if (enable_tls_fragment)
{
tls["fragment"] = enable_tls_fragment;
if (!tls_fragment_fallback_delay.isEmpty()) tls["fragment_fallback_delay"] = tls_fragment_fallback_delay;
}
if (enable_tls_record_fragment) tls["record_fragment"] = enable_tls_record_fragment;
outbound->insert("tls", tls);
} else if (security == "reality") {
QJsonObject tls{{"enabled", true}};
@ -105,6 +109,12 @@ namespace Configs {
{"fingerprint", fp},
};
}
if (enable_tls_fragment)
{
tls["fragment"] = enable_tls_fragment;
if (!tls_fragment_fallback_delay.isEmpty()) tls["fragment_fallback_delay"] = tls_fragment_fallback_delay;
}
if (enable_tls_record_fragment) tls["record_fragment"] = enable_tls_record_fragment;
outbound->insert("tls", tls);
}
@ -277,7 +287,7 @@ namespace Configs {
auto tun_name = "throne-wg";
#ifdef Q_OS_MACOS
tun_name = "uwg9";
tun_name = "";
#endif
QJsonObject peer{

View File

@ -39,6 +39,9 @@ namespace Configs {
if (!stream->alpn.isEmpty()) query.addQueryItem("alpn", stream->alpn);
if (stream->allow_insecure) query.addQueryItem("allowInsecure", "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);
@ -145,6 +148,9 @@ namespace Configs {
} else {
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);

View File

@ -141,6 +141,9 @@ namespace Configs
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();
@ -183,6 +186,9 @@ namespace Configs
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();

View File

@ -71,8 +71,11 @@ namespace Configs {
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 = Configs::dataStore->utlsFingerprint;
stream->utlsFingerprint = dataStore->utlsFingerprint;
}
if (stream->security.isEmpty()) {
if (!sni1.isEmpty() || !sni2.isEmpty()) stream->security = "tls";
@ -224,6 +227,9 @@ namespace Configs {
if (stream->utlsFingerprint.isEmpty()) {
stream->utlsFingerprint = Configs::dataStore->utlsFingerprint;
}
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;
// mux
auto mux_str = GetQueryValue(query, "mux", "");

View File

@ -134,6 +134,12 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
});
emit ui->security->currentTextChanged(ui->security->currentText());
// for fragment
connect(ui->tls_frag, &QCheckBox::checkStateChanged, this, [=](bool state)
{
ui->tls_frag_fall_delay->setEnabled(state);
});
// mux setting changed
connect(ui->multiplex, &QComboBox::currentTextChanged, this, [=](const QString &txt) {
if (txt == "Off") {
@ -287,6 +293,10 @@ void DialogEditProfile::typeSelected(const QString &newType) {
} else {
ui->utlsFingerprint->setCurrentText(stream->utlsFingerprint);
}
ui->tls_frag->setChecked(stream->enable_tls_fragment);
ui->tls_frag_fall_delay->setEnabled(stream->enable_tls_fragment);
ui->tls_frag_fall_delay->setText(stream->tls_fragment_fallback_delay);
ui->tls_rec_frag->setChecked(stream->enable_tls_record_fragment);
ui->insecure->setChecked(stream->allow_insecure);
ui->header_type->setCurrentText(stream->header_type);
ui->headers->setText(stream->headers);
@ -423,6 +433,9 @@ bool DialogEditProfile::onEnd() {
stream->sni = ui->sni->text();
stream->alpn = ui->alpn->text();
stream->utlsFingerprint = ui->utlsFingerprint->currentText();
stream->enable_tls_fragment = ui->tls_frag->isChecked();
stream->tls_fragment_fallback_delay = ui->tls_frag_fall_delay->text();
stream->enable_tls_record_fragment = ui->tls_rec_frag->isChecked();
stream->allow_insecure = ui->insecure->isChecked();
stream->headers = ui->headers->text();
stream->header_type = ui->header_type->currentText();