From d42e169ed0c5065380f0c6d27f6c4761d8aba5e7 Mon Sep 17 00:00:00 2001 From: riolurs Date: Mon, 9 Jun 2025 17:23:38 +0800 Subject: [PATCH] Add VLESS reality support --- src/generator/config/subexport.cpp | 68 +++++++++++++++ src/parser/config/proxy.h | 10 +++ src/parser/subparser.cpp | 136 ++++++++++++++++++++++++++++- src/parser/subparser.h | 20 +++++ 4 files changed, 231 insertions(+), 3 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index b3b3d45..0a5222a 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -609,6 +609,28 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr if (!scv.is_undef()) singleproxy["skip-cert-verify"] = scv.get(); break; + case ProxyType::VLESS: + singleproxy["type"] = "vless"; + singleproxy["packet-encoding"] = "xudp"; + if (!x.UUID.empty()) + singleproxy["uuid"] = x.UUID; + if (!x.SNI.empty()) + singleproxy["servername"] = x.SNI; + if (!x.Alpn.empty()) + singleproxy["alpn"] = x.Alpn; + if (!x.Fingerprint.empty()) + singleproxy["fingerprint"] = x.Fingerprint; + if (!x.Flow.empty()) + singleproxy["flow"] = x.Flow; + if (x.XTLS == 2) { + if (!x.PublicKey.empty() && !x.ShortID.empty()) { + singleproxy["reality-opts"]["public-key"] = x.PublicKey; + singleproxy["reality-opts"]["short-id"] = x.ShortID; + } + } + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; default: continue; } @@ -2590,6 +2612,52 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v break; } + + case ProxyType::VLESS: + { + addSingBoxCommonMembers(proxy, x, "vless", allocator); + proxy.AddMember("packet_encoding", "xudp", allocator); + + if (!x.UUID.empty()) + proxy.AddMember("uuid", rapidjson::StringRef(x.UUID.c_str()), allocator); + + if (!x.SNI.empty()) + proxy.AddMember("sni", rapidjson::StringRef(x.SNI.c_str()), allocator); + + if (!x.Flow.empty()) + proxy.AddMember("flow", rapidjson::StringRef(x.Flow.c_str()), allocator); + + // TLS 配置 + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enabled", true, allocator); + + if (!scv.is_undef()) + tls.AddMember("insecure", scv.get(), allocator); + if (!x.Fingerprint.empty()) + tls.AddMember("fingerprint", rapidjson::StringRef(x.Fingerprint.c_str()), allocator); + if (!x.Alpn.empty()) { + rapidjson::Value alpn(rapidjson::kArrayType); + for (const auto& item : x.Alpn) + alpn.PushBack(rapidjson::StringRef(item.c_str()), allocator); + tls.AddMember("alpn", alpn, allocator); + } + if (x.XTLS == 2){ + if (!x.PublicKey.empty() && !x.ShortID.empty()) { + rapidjson::Value reality(rapidjson::kObjectType); + reality.AddMember("enabled", true, allocator); + if (!x.PublicKey.empty()) + reality.AddMember("public_key", rapidjson::StringRef(x.PublicKey.c_str()), allocator); + if (!x.ShortID.empty()) + reality.AddMember("short_id", rapidjson::StringRef(x.ShortID.c_str()), allocator); + tls.AddMember("reality", reality, allocator); + } + } + + proxy.AddMember("tls", tls, allocator); + + break; + } + case ProxyType::HTTP: case ProxyType::HTTPS: { diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h index 19d43f8..0f26893 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -15,6 +15,7 @@ enum class ProxyType Shadowsocks, ShadowsocksR, VMess, + VLESS, Trojan, Snell, HTTP, @@ -37,6 +38,8 @@ inline String getProxyTypeName(ProxyType type) return "SSR"; case ProxyType::VMess: return "VMess"; + case ProxyType::VLESS: + return "VLESS"; case ProxyType::Trojan: return "Trojan"; case ProxyType::Snell: @@ -149,11 +152,18 @@ struct Proxy uint32_t IdleSessionCheckInterval; uint32_t IdleSessionTimeout; uint32_t MinIdleSession; + + String Flow; + uint32_t XTLS; + String PacketEncoding; + String ShortID; + }; #define SS_DEFAULT_GROUP "SSProvider" #define SSR_DEFAULT_GROUP "SSRProvider" #define V2RAY_DEFAULT_GROUP "V2RayProvider" +#define VLESS_DEFAULT_GROUP "VLESSProvider" #define SOCKS_DEFAULT_GROUP "SocksProvider" #define HTTP_DEFAULT_GROUP "HTTPProvider" #define TROJAN_DEFAULT_GROUP "TrojanProvider" diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index b312474..ae2cf9d 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -323,6 +323,37 @@ void anytlsConstruct( node.MinIdleSession = to_int(min_idle_session); } +void vlessConstruct( + Proxy &node, + const std::string &group, + const std::string &remarks, + const std::string &server, + const std::string &port, + const std::string &uuid, + const std::string &sni, + const std::string &alpn, + const std::string &fingerprint, + const std::string &flow, + const std::string &xtls, + const std::string &public_key, + const std::string &short_id, + tribool tfo, + tribool scv, + const std::string &underlying_proxy +) { + commonConstruct(node, ProxyType::VLESS, group, remarks, server, port, tribool(), tfo, scv, tribool(), underlying_proxy); + node.UUID = uuid; + node.SNI = sni; + if (!alpn.empty()) { + node.Alpn = StringArray{alpn}; + } + node.Fingerprint = fingerprint; + node.Flow = flow; + node.XTLS = to_int(xtls); + node.PublicKey = public_key; + node.ShortID = short_id; +} + void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls, sni; @@ -1176,6 +1207,7 @@ void explodeClash(Node yamlnode, std::vector &nodes) std::string obfs_password, cwnd; //hysteria2 std::string uuid,/*ip , password*/ heartbeat_interval, disable_sni, reduce_rtt, request_timeout, udp_relay_mode, congestion_controller, max_udp_relay_packet_size, max_open_streams, fast_open; //TUIC std::string idle_session_check_interval, idle_session_timeout, min_idle_session; + std::string flow, xtls, short_id; string_array dns_server; tribool udp, tfo, scv; @@ -1242,7 +1274,24 @@ void explodeClash(Node yamlnode, std::vector &nodes) vmessConstruct(node, group, ps, server, port, "", id, aid, net, cipher, path, host, edge, tls, sni, udp, tfo, scv, tribool(), underlying_proxy); break; - case "ss"_hash: + case "vless"_hash: { + group = VLESS_DEFAULT_GROUP; + singleproxy["uuid"] >>= uuid; + singleproxy["servername"] >>= sni; + if (singleproxy["alpn"].IsSequence()) + singleproxy["alpn"][0] >>= alpn; + else + singleproxy["alpn"] >>= alpn; + singleproxy["fingerprint"] >>= fingerprint; + singleproxy["flow"] >>= flow; + if (singleproxy["reality-opts"].IsDefined()) { + singleproxy["reality-opts"]["public-key"] >>= public_key; + singleproxy["reality-opts"]["short-id"] >>= short_id; + } + vlessConstruct(node, group, ps, server, port, uuid, sni, alpn, fingerprint, flow, xtls, public_key, short_id, tfo, scv, underlying_proxy); + break; + } + case "ss"_hash: group = SS_DEFAULT_GROUP; singleproxy["cipher"] >>= cipher; @@ -1801,6 +1850,85 @@ void explodeAnyTLS(std::string anytls, Proxy &node) { } } +void explodeStdVLESS(std::string vless, Proxy &node) { + std::string add, port, uuid, sni, alpn, fingerprint, remarks, addition, flow, xtls, public_key, short_id; + tribool tfo, scv; + std::string decoded, userinfo, hostinfo; + string_array user_parts; + + vless = vless.substr(8); + string_size pos; + + pos = vless.rfind("#"); + if (pos != vless.npos) { + remarks = urlDecode(vless.substr(pos + 1)); + vless.erase(pos); + } + + pos = vless.rfind("?"); + if (pos != vless.npos) { + addition = vless.substr(pos + 1); + vless.erase(pos); + } + + decoded = urlSafeBase64Decode(vless); + + // 尝试从URL参数中获取uuid + uuid = getUrlArg(addition, "uuid"); + + // 如果URL参数中没有uuid,尝试从decoded中解析 + if (uuid.empty() && strFind(decoded, "@") && strFind(decoded, ":")) { + userinfo = decoded.substr(0, decoded.find('@')); + hostinfo = decoded.substr(decoded.find('@') + 1); + + if (strFind(userinfo, ":")) { + user_parts = split(userinfo, ":"); + if (user_parts.size() >= 2) { + uuid = user_parts[1]; + } + } else { + uuid = userinfo; + } + + if (regGetMatch(hostinfo, R"(^(.*?):(\d+)$)", 3, 0, &add, &port) != 0) + return; + } else if (regGetMatch(vless, R"(^(.*?):(\d+)$)", 3, 0, &add, &port) != 0) { + return; + } + + if (uuid.empty()) return; + + if (!addition.empty()) { + sni = getUrlArg(addition, "peer"); + alpn = getUrlArg(addition, "alpn"); + fingerprint = getUrlArg(addition, "hpkp"); + flow = getUrlArg(addition, "flow"); + xtls = getUrlArg(addition, "xtls"); + public_key = getUrlArg(addition, "pbk"); + short_id = getUrlArg(addition, "sid"); + tfo = tribool(getUrlArg(addition, "tfo")); + scv = tribool(getUrlArg(addition, "insecure")); + + if (remarks.empty()) { + remarks = urlDecode(getUrlArg(addition, "remark")); + if (remarks.empty()) + remarks = urlDecode(getUrlArg(addition, "remarks")); + } + } + + if (remarks.empty()) + remarks = add + ":" + port; + + vlessConstruct(node, VLESS_DEFAULT_GROUP, remarks, add, port, uuid, sni, alpn, fingerprint, flow, xtls, public_key, short_id, tfo, scv, ""); +} + +void explodeVLESS(std::string vless, Proxy &node) { + vless = regReplace(vless, "(vless)://", "vless://"); + // replace /? with ? + vless = regReplace(vless, "/\\?", "?", true, false); + explodeStdVLESS(vless, node); +} + // peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) void parsePeers(Proxy &node, const std::string &data) { @@ -2698,10 +2826,12 @@ void explode(const std::string &link, Proxy &node) explodeTrojan(link, node); else if (strFind(link, "hysteria2://") || strFind(link, "hy2://")) explodeHysteria2(link, node); - else if (strFind(link, "tuic://") || strFind(link, "tuic://")) + else if (strFind(link, "tuic://")) explodeTUIC(link, node); - else if (strFind(link, "anytls://") || strFind(link, "anytls://")) + else if (strFind(link, "anytls://")) explodeAnyTLS(link, node); + else if (strFind(link, "vless://")) + explodeVLESS(link, node); else if(isLink(link)) explodeHTTPSub(link, node); } diff --git a/src/parser/subparser.h b/src/parser/subparser.h index 4550470..bdb755e 100644 --- a/src/parser/subparser.h +++ b/src/parser/subparser.h @@ -126,6 +126,25 @@ void anytlsConstruct( const std::string &underlying_proxy = "" ); +void vlessConstruct( + Proxy &node, + const std::string &group, + const std::string &remarks, + const std::string &server, + const std::string &port, + const std::string &uuid, + const std::string &sni, + const std::string &alpn, + const std::string &fingerprint, + const std::string &flow, + const std::string &xtls, + const std::string &public_key, + const std::string &short_id, + tribool tfo, + tribool scv, + const std::string &underlying_proxy = "" +); + void explodeVmess(std::string vmess, Proxy &node); void explodeSSR(std::string ssr, Proxy &node); void explodeSS(std::string ss, Proxy &node); @@ -137,6 +156,7 @@ void explodeKitsunebi(std::string kit, Proxy &node); void explodeHysteria2(std::string hysteria2, Proxy &node); void explodeTUIC(std::string tuic, Proxy &node); void explodeAnyTLS(std::string anytls, Proxy &node); +void explodeVLESS(std::string vless, Proxy &node); /// Parse a link void explode(const std::string &link, Proxy &node);