diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index d2fa870..6ae8d42 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -127,7 +127,8 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & {ProxyType::SOCKS5, "SOCKS5"}, {ProxyType::WireGuard, "WIREGUARD"}, {ProxyType::Hysteria, "HYSTERIA"}, - {ProxyType::Hysteria2, "HYSTERIA2"} + {ProxyType::Hysteria2, "HYSTERIA2"}, + {ProxyType::TUIC, "TUIC"} }; if(startsWith(rule, "!!GROUP=")) { @@ -557,6 +558,38 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr if (x.HopInterval) singleproxy["hop-interval"] = x.HopInterval; break; + case ProxyType::TUIC: + singleproxy["type"] = "tuic"; + if (!x.UUID.empty()) + singleproxy["uuid"] = x.UUID; + if (!x.Password.empty()) + singleproxy["password"] = x.Password; + if (!x.HeartbeatInterval.empty()) + singleproxy["heartbeat-interval"] = x.HeartbeatInterval; + if (!x.Alpn.empty()) + singleproxy["alpn"] = x.Alpn; + if (!x.FastOpen.is_undef()) + singleproxy["fast-open"] = x.FastOpen.get(); + if (!x.UdpRelayMode.empty()) + singleproxy["udp-relay-mode"] = x.UdpRelayMode; + if (!x.CongestionController.empty()) + singleproxy["congestion-controller"] = x.CongestionController; + if (!x.SNI.empty()) + singleproxy["sni"] = x.SNI; + if (!x.DisableSNI.is_undef()) + singleproxy["disable-sni"] = x.DisableSNI.get(); + if (!x.ReduceRTT.is_undef()) + singleproxy["reduce-rtt"] = x.ReduceRTT.get(); + if (x.RequestTimeout != 0) + singleproxy["request-timeout"] = x.RequestTimeout; + if (x.MaxUdpRelayPacketSize != 0) + singleproxy["max-udp-relay-packet-size"] = x.MaxUdpRelayPacketSize; + if (x.MaxOpenStreams != 0) + singleproxy["max-open-streams"] = x.MaxOpenStreams; + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; + default: continue; } @@ -947,7 +980,6 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf proxy = "hysteria, " + hostname + ", " + port + ", password=" + password; if(x.DownSpeed) proxy += ", download-bandwidth=" + x.DownSpeed; - if(!scv.is_undef()) proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); if(!x.Fingerprint.empty()) @@ -955,6 +987,15 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf if(!x.SNI.empty()) proxy += ",sni=" + x.SNI; break; + case ProxyType::TUIC: + if(surge_ver < 4) + continue; + proxy = "tuic-v5, " + hostname + ", " + port + ", password=" + password; + if(!x.UUID.empty()) + proxy += ",uuid=" + x.UUID; + if(!x.SNI.empty()) + proxy += ",sni=" + x.SNI; + break; default: continue; } @@ -2452,6 +2493,49 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("tls", tls, allocator); break; } + case ProxyType::TUIC: + { + addSingBoxCommonMembers(proxy, x, "tuic", allocator); + + if (!x.UUID.empty()) + proxy.AddMember("uuid", rapidjson::StringRef(x.UUID.c_str()), allocator); + + if (!x.Password.empty()) + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + + if (!x.HeartbeatInterval.empty()) + proxy.AddMember("heartbeat", rapidjson::StringRef(x.HeartbeatInterval.c_str()), allocator); + + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enabled", true, allocator); + if (!scv.is_undef()) + tls.AddMember("insecure", scv.get(), allocator); + if (!x.Alpn.empty()) + { + rapidjson::Value alpn(rapidjson::kArrayType); + alpn.PushBack(rapidjson::StringRef(x.Alpn[0].c_str()), allocator); + tls.AddMember("alpn", alpn, allocator); + } + + if (!x.UdpRelayMode.empty()) + proxy.AddMember("udp_relay_mode", rapidjson::StringRef(x.UdpRelayMode.c_str()), allocator); + + if (!x.CongestionController.empty()) + proxy.AddMember("congestion_controller", rapidjson::StringRef(x.CongestionController.c_str()), allocator); + + if (!x.SNI.empty()) + proxy.AddMember("sni", rapidjson::StringRef(x.SNI.c_str()), allocator); + + if (!scv.is_undef()) + { + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enabled", true, allocator); + tls.AddMember("insecure", scv.get(), 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 80b2ee0..87805c9 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -22,7 +22,8 @@ enum class ProxyType SOCKS5, WireGuard, Hysteria, - Hysteria2 + Hysteria2, + TUIC }; inline String getProxyTypeName(ProxyType type) @@ -51,6 +52,8 @@ inline String getProxyTypeName(ProxyType type) return "Hysteria"; case ProxyType::Hysteria2: return "Hysteria2"; + case ProxyType::TUIC: + return "TUIC"; default: return "Unknown"; } @@ -127,6 +130,18 @@ struct Proxy StringArray Alpn; uint32_t CWND = 0; + + String UUID; + String IP; + String HeartbeatInterval; + tribool DisableSNI; + tribool ReduceRTT; + uint32_t RequestTimeout; + String UdpRelayMode; + String CongestionController; + uint32_t MaxUdpRelayPacketSize; + tribool FastOpen; + uint32_t MaxOpenStreams; }; #define SS_DEFAULT_GROUP "SSProvider" @@ -139,5 +154,6 @@ struct Proxy #define WG_DEFAULT_GROUP "WireGuardProvider" #define HYSTERIA_DEFAULT_GROUP "HysteriaProvider" #define HYSTERIA2_DEFAULT_GROUP "Hysteria2Provider" +#define TUIC_DEFAULT_GROUP "TUICProvider" #endif // PROXY_H_INCLUDED diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 10a8ad3..381e8fe 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -224,7 +224,7 @@ void hysteria2Construct( const std::string &fingerprint, const std::string &alpn, const std::string &ca, - const std::string &caStr, + const std::string &ca_str, const std::string &cwnd, const std::string &hop_interval, tribool tfo, @@ -245,11 +245,55 @@ void hysteria2Construct( node.Alpn = StringArray {alpn}; } node.Ca = ca; - node.CaStr = caStr; + node.CaStr = ca_str; node.CWND = to_int(cwnd); node.HopInterval = to_int(hop_interval); } +void tuicConstruct( + 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 &password, + const std::string &ip, + const std::string &heartbeat_interval, + const std::string &alpn, + const std::string &disable_sni, + const std::string &reduce_rtt, + const std::string &request_timeout, + const std::string &udp_relay_mode, + const std::string &congestion_controller, + const std::string &max_udp_relay_packet_size, + const std::string &max_open_streams, + const std::string &sni, + const std::string &fast_open, + tribool tfo, + tribool scv, + const std::string &underlying_proxy +){ + commonConstruct(node, ProxyType::TUIC, group, remarks, server, port, tribool(), tfo, scv, tribool(), underlying_proxy); + node.Password = password; + node.UUID = uuid; + node.IP = ip; + node.HeartbeatInterval = heartbeat_interval; + if (!alpn.empty()) + { + node.Alpn = StringArray {alpn}; + } + node.DisableSNI= disable_sni; + node.ReduceRTT = reduce_rtt; + node.RequestTimeout = to_int(request_timeout); + node.UdpRelayMode = udp_relay_mode; + node.CongestionController = congestion_controller; + node.MaxUdpRelayPacketSize = to_int(max_udp_relay_packet_size); + node.MaxOpenStreams = to_int(max_open_streams); + node.SNI = sni; + node.FastOpen = tribool(fast_open); +} + void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls, sni; @@ -1101,6 +1145,7 @@ void explodeClash(Node yamlnode, std::vector &nodes) std::string ip, ipv6, private_key, public_key, mtu; //wireguard std::string ports, obfs_protocol, up, up_speed, down, down_speed, auth, auth_str,/* obfs, sni,*/ fingerprint, ca, ca_str, recv_window_conn, recv_window, disable_mtu_discovery, hop_interval, alpn; //hysteria 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 string_array dns_server; tribool udp, tfo, scv; Node singleproxy; @@ -1366,9 +1411,27 @@ void explodeClash(Node yamlnode, std::vector &nodes) singleproxy["cwnd"] >>= cwnd; singleproxy["hop-interval"] >>= hop_interval; - hysteria2Construct(node, group, ps, server, port, ports, up, down, password, obfs, obfs_password, sni, fingerprint, ca, ca_str, cwnd, alpn, hop_interval, tfo, scv, underlying_proxy); + hysteria2Construct(node, group, ps, server, port, ports, up, down, password, obfs, obfs_password, sni, fingerprint, alpn, ca, ca_str, cwnd, hop_interval, tfo, scv, underlying_proxy); break; - + case "tuic"_hash: + group = TUIC_DEFAULT_GROUP; + singleproxy["uuid"] >>= uuid; + singleproxy["ip"] >>= ip; + singleproxy["password"] >>= password; + singleproxy["heartbeat_interval"] >>= heartbeat_interval; + if (singleproxy["alpn"].IsSequence()) + singleproxy["alpn"][0] >>= alpn; + else + singleproxy["alpn"] >>= alpn; + singleproxy["disable-sni"] >>= disable_sni; + singleproxy["reduce-rtt"] >>= reduce_rtt; + singleproxy["request-timeout"] >>= request_timeout; + singleproxy["udp-relay-mode"] >>= udp_relay_mode; + singleproxy["congestion-controller"] >>= congestion_controller; + singleproxy["max-udp-relay-packet-size"] >>= max_udp_relay_packet_size; + singleproxy["max-open-streams"] >>= max_open_streams; + singleproxy["fast-open"] >>= fast_open; + tuicConstruct(node, group, ps, server, port, uuid, password, ip, heartbeat_interval, alpn, disable_sni, reduce_rtt, request_timeout, udp_relay_mode, congestion_controller, max_udp_relay_packet_size, max_open_streams, sni, fast_open, tfo, scv, underlying_proxy); default: continue; } @@ -1547,6 +1610,7 @@ void explodeStdHysteria2(std::string hysteria2, Proxy &node) { obfs = getUrlArg(addition, "obfs"); obfs_password = getUrlArg(addition, "obfs-password"); sni = getUrlArg(addition, "sni"); + fingerprint = getUrlArg(addition, "pinSHA256"); if (remarks.empty()) remarks = add + ":" + port; @@ -1566,6 +1630,78 @@ void explodeHysteria2(std::string hysteria2, Proxy &node) { } } +void explodeStdTuic(std::string tuic, Proxy &node) { + std::string add, port,uuid, ip, password, token, heartbeat_interval, disable_sni, reduce_rtt, request_timeout; + std::string udp_relay_mode, congestion_controller, max_udp_relay_packet_size, max_open_streams; + std::string alpn, sni, fast_open, remarks, addition; + tribool tfo, scv; + + tuic = tuic.substr(7); // 去除 tuic:// + string_size pos; + + pos = tuic.rfind("#"); + if (pos != tuic.npos) { + remarks = urlDecode(tuic.substr(pos + 1)); + tuic.erase(pos); + } + + pos = tuic.rfind("?"); + if (pos != tuic.npos) { + addition = tuic.substr(pos + 1); + tuic.erase(pos); + } + + // 支持 user:pass@host:port + if (strFind(tuic, "@")) { + if (regGetMatch(tuic, R"(^(.*?):(.*?)@(.*?):(\d+)$)", 5, 0, &uuid, &password, &add, &port)) + return; + } else { + uuid = getUrlArg(addition, "uuid"); + password = getUrlArg(addition, "password"); + if (uuid.empty() || password.empty()) + return; + + if (!strFind(tuic, ":")) + return; + + if (regGetMatch(tuic, R"(^(.*?):(\d+)$)", 3, 0, &ip, &port)) + return; + } + + // 其他参数 + token = getUrlArg(addition, "token"); + heartbeat_interval = getUrlArg(addition, "heartbeat_interval"); + disable_sni = getUrlArg(addition, "disable_sni"); + reduce_rtt = getUrlArg(addition, "reduce_rtt"); + request_timeout = getUrlArg(addition, "request_timeout"); + udp_relay_mode = getUrlArg(addition, "udp_relay_mode"); + congestion_controller = getUrlArg(addition, "congestion_control"); + max_udp_relay_packet_size = getUrlArg(addition, "max_udp_relay_packet_size"); + max_open_streams = getUrlArg(addition, "max_open_streams"); + alpn = getUrlArg(addition, "alpn"); + sni = getUrlArg(addition, "sni"); + fast_open = getUrlArg(addition, "fast_open"); + scv = getUrlArg(addition, "insecure"); + + if (remarks.empty()) + remarks = add + ":" + port; + + tuicConstruct(node, TUIC_DEFAULT_GROUP, remarks, add, port, uuid, password, ip, heartbeat_interval, alpn, + disable_sni, reduce_rtt, request_timeout, udp_relay_mode, congestion_controller, + max_udp_relay_packet_size, max_open_streams, sni, fast_open, tfo, scv, ""); +} + +void explodeTUIC(std::string tuic, Proxy &node) { + tuic = regReplace(tuic, "(tuic)://", "tuic://"); + + // replace /? with ? + tuic = regReplace(tuic, "/\\?", "?", true, false); + if (regMatch(tuic, "tuic://(.*?)[:](.*)")) { + explodeStdTuic(tuic, node); + return; + } +} + // 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) { @@ -2463,6 +2599,8 @@ 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://")) + explodeTUIC(link, node); else if(isLink(link)) explodeHTTPSub(link, node); } diff --git a/src/parser/subparser.h b/src/parser/subparser.h index 018ef67..55ff89f 100644 --- a/src/parser/subparser.h +++ b/src/parser/subparser.h @@ -28,6 +28,7 @@ void httpConstruct(Proxy &node, const std::string &group, const std::string &rem void trojanConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &network, const std::string &host, const std::string &path, bool tlssecure, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool(), const std::string &underlying_proxy = ""); void snellConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &obfs, const std::string &host, uint16_t version = 0, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), const std::string &underlying_proxy = ""); + void hysteriaConstruct( Proxy &node, const std::string &group, @@ -52,8 +53,8 @@ void hysteriaConstruct( const std::string &recv_window, const std::string &disable_mtu_discovery, const std::string &hop_interval, - const std::string &alpn, - tribool tfo, + const std::string &alpn, + tribool tfo, tribool scv, const std::string &underlying_proxy = "" ); @@ -82,6 +83,31 @@ void hysteria2Construct( const std::string &underlying_proxy = "" ); +void tuicConstruct( + 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 &password, + const std::string &ip, + const std::string &heartbeat_interval, + const std::string &alpn, + const std::string &disable_sni, + const std::string &reduce_rtt, + const std::string &request_timeout, + const std::string &udp_relay_mode, + const std::string &congestion_controller, + const std::string &max_udp_relay_packet_size, + const std::string &max_open_streams, + const std::string &sni, + const std::string &fast_open, + 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); @@ -91,6 +117,7 @@ void explodeStdVMess(std::string vmess, Proxy &node); void explodeShadowrocket(std::string kit, Proxy &node); void explodeKitsunebi(std::string kit, Proxy &node); void explodeHysteria2(std::string hysteria2, Proxy &node); +void explodeTUIC(std::string tuic, Proxy &node); /// Parse a link void explode(const std::string &link, Proxy &node);