support vmess header

This commit is contained in:
arm64v8a
2022-08-29 17:12:18 +08:00
parent 3a053b5ea6
commit a0e42ec28e
7 changed files with 129 additions and 35 deletions

View File

@@ -17,18 +17,23 @@ namespace NekoRay::fmt {
if (network == "ws") { if (network == "ws") {
QJsonObject ws; QJsonObject ws;
if (!path.trimmed().isEmpty()) ws["path"] = path; if (!path.isEmpty()) ws["path"] = path;
if (!host.trimmed().isEmpty()) ws["headers"] = QJsonObject{{"Host", host}}; if (!host.isEmpty()) ws["headers"] = QJsonObject{{"Host", host}};
streamSettings["wsSettings"] = ws; streamSettings["wsSettings"] = ws;
} else if (network == "h2") { } else if (network == "h2") {
QJsonObject h2; QJsonObject h2;
if (!path.trimmed().isEmpty()) h2["path"] = path; if (!path.isEmpty()) h2["path"] = path;
if (!host.trimmed().isEmpty()) h2["host"] = QList2QJsonArray(host.split(",")); if (!host.isEmpty()) h2["host"] = QList2QJsonArray(host.split(","));
streamSettings["httpSettings"] = h2; streamSettings["httpSettings"] = h2;
} else if (network == "grpc") { } else if (network == "grpc") {
QJsonObject grpc; QJsonObject grpc;
if (!path.trimmed().isEmpty()) grpc["serviceName"] = path; if (!path.isEmpty()) grpc["serviceName"] = path;
streamSettings["grpcSettings"] = grpc; streamSettings["grpcSettings"] = grpc;
} else if (network == "quic") {
QJsonObject quic;
if (!path.isEmpty()) quic["key"] = path;
if (!host.isEmpty()) quic["security"] = host;
streamSettings["quicSettings"] = quic;
} }
if (security == "tls") { if (security == "tls") {
@@ -48,6 +53,18 @@ namespace NekoRay::fmt {
streamSettings["tlsSettings"] = tls; streamSettings["tlsSettings"] = tls;
} }
if (!header_type.isEmpty()) {
QJsonObject header{{"type", header_type}};
if (header_type == "http") {
QJsonObject request{
{"path", QList2QJsonArray(path.split(","))},
{"headers", QJsonObject{{"Host", QList2QJsonArray(host.split(","))}}},
};
header["request"] = request;
}
streamSettings["header"] = header;
}
return streamSettings; return streamSettings;
} }

View File

@@ -62,18 +62,17 @@ namespace NekoRay::fmt {
QString VMessBean::ToShareLink() { QString VMessBean::ToShareLink() {
QJsonObject N{ QJsonObject N{
{"v", 2}, {"v", "2"},
{"ps", name}, {"ps", name},
{"add", serverAddress}, {"add", serverAddress},
{"port", serverPort}, {"port", Int2String(serverPort)},
{"id", uuid}, {"id", uuid},
{"aid", aid}, {"aid", Int2String(aid)},
{"net", stream->network}, {"net", stream->network},
{"host", stream->host}, {"host", stream->host},
{"path", stream->path}, {"path", stream->path},
{"type", stream->header_type}, {"type", stream->header_type},
{"scy", security}, {"scy", security},
// TODO header type
{"tls", stream->security == "tls" ? "tls" : ""}, {"tls", stream->security == "tls" ? "tls" : ""},
{"sni", stream->sni}, {"sni", stream->sni},
}; };

View File

@@ -113,11 +113,11 @@ namespace NekoRay::fmt {
serverPort = objN["port"].toVariant().toInt(); serverPort = objN["port"].toVariant().toInt();
// OPTIONAL // OPTIONAL
name = objN["ps"].toString(); name = objN["ps"].toString();
auto aid_ = objN["aid"]; aid = objN["aid"].toVariant().toInt();
aid = aid_.isString() ? aid_.toString().toInt() : aid_.toInt();
stream->host = objN["host"].toString(); stream->host = objN["host"].toString();
stream->path = objN["path"].toString(); stream->path = objN["path"].toString();
stream->sni = objN["sni"].toString(); stream->sni = objN["sni"].toString();
stream->header_type = objN["type"].toString();
auto net = objN["net"].toString().replace("http", "h2"); auto net = objN["net"].toString().replace("http", "h2");
if (!net.isEmpty()) stream->network = net; if (!net.isEmpty()) stream->network = net;
auto scy = objN["scy"].toString(); auto scy = objN["scy"].toString();

View File

@@ -8,10 +8,10 @@ namespace NekoRay::fmt {
QString network = "tcp"; QString network = "tcp";
QString security = "none"; QString security = "none";
QString packet_encoding = ""; QString packet_encoding = "";
// ws/h2/grpc // ws/h2/grpc/tcp-http
QString path = ""; QString path = "";
QString host = ""; QString host = "";
// QUIC & KCP // kcp/quic/tcp-http
QString header_type = ""; QString header_type = "";
// tls // tls
QString sni = ""; QString sni = "";

View File

@@ -164,6 +164,15 @@ namespace NekoRay::sub {
} }
} }
// NodeChild returns the first defined children or Null Node
YAML::Node NodeChild(const YAML::Node &n, const std::list<std::string> &keys) {
for (const auto &key: keys) {
auto child = n[key];
if (child.IsDefined()) return child;
}
return {};
}
#endif #endif
// https://github.com/Dreamacro/clash/wiki/configuration // https://github.com/Dreamacro/clash/wiki/configuration
@@ -223,7 +232,7 @@ namespace NekoRay::sub {
if (Node2Bool(proxy["tls"])) bean->stream->security = "tls"; if (Node2Bool(proxy["tls"])) bean->stream->security = "tls";
if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true; if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true;
auto ws = proxy["ws-opts"]; auto ws = NodeChild(proxy, {"ws-opts", "ws-opt"});
if (ws.IsMap()) { if (ws.IsMap()) {
auto headers = ws["headers"]; auto headers = ws["headers"];
for (auto header: headers) { for (auto header: headers) {
@@ -234,12 +243,12 @@ namespace NekoRay::sub {
bean->stream->path = Node2QString(ws["path"]); bean->stream->path = Node2QString(ws["path"]);
} }
auto grpc = proxy["grpc-opts"]; auto grpc = NodeChild(proxy, {"grpc-opts", "grpc-opt"});
if (grpc.IsMap()) { if (grpc.IsMap()) {
bean->stream->path = Node2QString(grpc["grpc-service-name"]); bean->stream->path = Node2QString(grpc["grpc-service-name"]);
} }
auto h2 = proxy["h2-opts"]; auto h2 = NodeChild(proxy, {"h2-opts", "h2-opt"});
if (h2.IsMap()) { if (h2.IsMap()) {
auto hosts = ws["host"]; auto hosts = ws["host"];
for (auto host: hosts) { for (auto host: hosts) {
@@ -248,6 +257,24 @@ namespace NekoRay::sub {
} }
bean->stream->path = Node2QString(h2["path"]); bean->stream->path = Node2QString(h2["path"]);
} }
auto tcp_http = NodeChild(proxy, {"http-opts", "http-opt"});
if (tcp_http.IsMap()) {
bean->stream->network = "tcp";
bean->stream->header_type = "http";
auto headers = tcp_http["headers"];
for (auto header: headers) {
if (Node2QString(header.first).toLower() == "host") {
bean->stream->host = Node2QString(header.second[0]);
}
break;
}
auto paths = tcp_http["path"];
for (auto path: paths) {
bean->stream->path = Node2QString(path);
break;
}
}
} else { } else {
continue; continue;
} }

View File

@@ -31,18 +31,35 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
// network changed // network changed
network_title_base = ui->network_box->title(); network_title_base = ui->network_box->title();
connect(ui->network, &QComboBox::currentTextChanged, this, [=](const QString &txt) { connect(ui->network, &QComboBox::currentTextChanged, this, [=](const QString &txt) {
ui->network_box->setTitle(network_title_base.arg(txt));
if (txt == "tcp" || txt == "quic") { if (txt == "tcp" || txt == "quic") {
ui->network_box->setVisible(false); ui->header_type->setVisible(true);
ui->header_type_l->setVisible(true);
ui->path->setVisible(true);
ui->path_l->setVisible(true);
ui->host->setVisible(true);
ui->host_l->setVisible(true);
} else if (txt == "grpc") {
ui->header_type->setVisible(false);
ui->header_type_l->setVisible(false);
ui->path->setVisible(true);
ui->path_l->setVisible(true);
ui->host->setVisible(false);
ui->host_l->setVisible(false);
} else if (txt == "ws" || txt == "h2") {
ui->header_type->setVisible(false);
ui->header_type_l->setVisible(false);
ui->path->setVisible(true);
ui->path_l->setVisible(true);
ui->host->setVisible(true);
ui->host_l->setVisible(true);
} else { } else {
ui->network_box->setVisible(true); ui->header_type->setVisible(false);
ui->network_box->setTitle(network_title_base.arg(txt)); ui->header_type_l->setVisible(false);
if (txt == "grpc") { ui->path->setVisible(false);
ui->host->setVisible(false); ui->path_l->setVisible(false);
ui->host_l->setVisible(false); ui->host->setVisible(false);
} else { ui->host_l->setVisible(false);
ui->host->setVisible(true);
ui->host_l->setVisible(true);
}
} }
ADJUST_SIZE ADJUST_SIZE
}); });
@@ -167,6 +184,7 @@ void DialogEditProfile::typeSelected(const QString &newType) {
ui->sni->setText(stream->sni); ui->sni->setText(stream->sni);
ui->alpn->setText(stream->alpn); ui->alpn->setText(stream->alpn);
ui->insecure->setChecked(stream->allow_insecure); ui->insecure->setChecked(stream->allow_insecure);
ui->header_type->setCurrentText(stream->header_type);
CACHE.certificate = stream->certificate; CACHE.certificate = stream->certificate;
} else { } else {
ui->right_all_w->setVisible(false); ui->right_all_w->setVisible(false);
@@ -241,6 +259,7 @@ void DialogEditProfile::accept() {
stream->sni = ui->sni->text(); stream->sni = ui->sni->text();
stream->alpn = ui->alpn->text(); stream->alpn = ui->alpn->text();
stream->allow_insecure = ui->insecure->isChecked(); stream->allow_insecure = ui->insecure->isChecked();
stream->header_type = ui->header_type->currentText();
stream->certificate = CACHE.certificate; stream->certificate = CACHE.certificate;
} }
auto custom_item = ent->bean->_get("custom"); auto custom_item = ent->bean->_get("custom");

View File

@@ -315,31 +315,63 @@
<string>Network Settings (%1)</string> <string>Network Settings (%1)</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="1" column="1">
<widget class="QLabel" name="label_7"> <widget class="MyLineEdit" name="path"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="path_l">
<property name="toolTip"> <property name="toolTip">
<string notr="true">http path (ws/http) 或 serviceName (gRPC)</string> <string notr="true">http path (ws/h2/伪装http)
serviceName (gRPC)
key (QUIC)</string>
</property> </property>
<property name="text"> <property name="text">
<string>Path</string> <string>Path</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="2" column="1">
<widget class="MyLineEdit" name="path"/> <widget class="MyLineEdit" name="host"/>
</item> </item>
<item row="1" column="0"> <item row="2" column="0">
<widget class="QLabel" name="host_l"> <widget class="QLabel" name="host_l">
<property name="toolTip"> <property name="toolTip">
<string notr="true">http host</string> <string notr="true">http host (ws/h2/伪装http)
security (QUIC)</string>
</property> </property>
<property name="text"> <property name="text">
<string>Host</string> <string>Host</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="0" column="0">
<widget class="MyLineEdit" name="host"/> <widget class="QLabel" name="header_type_l">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string notr="true">伪装头部 (tcp/quic)</string>
</property>
<property name="text">
<string notr="true">headerType</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="header_type">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>