From c56685fd404366472e296c50d37262dc539189ea Mon Sep 17 00:00:00 2001 From: asdlokj1qpi23 Date: Fri, 12 Jul 2024 21:56:29 +0800 Subject: [PATCH] Add TUIC protocol support to Clash and Singbox.(#23) --- src/generator/config/subexport.cpp | 81 +++++++++++++++++--- src/parser/config/proxy.h | 13 +++- src/parser/subparser.cpp | 119 ++++++++++++++++++++++++++++- src/parser/subparser.h | 7 +- 4 files changed, 203 insertions(+), 17 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 1660658..7218292 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -486,6 +486,36 @@ proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGroupCo if (!x.Ports.empty()) singleproxy["ports"] = x.Ports; break; + case ProxyType::TUIC: + singleproxy["type"] = "tuic"; + if (!x.Password.empty()) { + singleproxy["password"] = x.Password; + } + if (!x.UserId.empty()) { + singleproxy["uuid"] = x.UserId; + } + if (!x.token.empty()) { + singleproxy["token"] = x.token; + } + if (!x.ServerName.empty()) { + singleproxy["sni"] = x.ServerName; + } + if (!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + if (!x.Alpn.empty()) + singleproxy["alpn"].push_back(x.Alpn); + singleproxy["disable-sni"] = x.DisableSni.get(); + singleproxy["reduce-rtt"] = x.ReduceRtt.get(); + singleproxy["request-timeout"] = x.RequestTimeout; + if (!x.UdpRelayMode.empty()) { + if (x.UdpRelayMode == "native" || x.UdpRelayMode == "quic") { + singleproxy["udp-relay-mode"] = x.UdpRelayMode; + } + } + if (!x.CongestionControl.empty()) { + singleproxy["congestion-controller"] = x.CongestionControl; + } + break; case ProxyType::VLESS: singleproxy["type"] = "vless"; singleproxy["uuid"] = x.UserId; @@ -559,7 +589,7 @@ proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGroupCo // UDP is not supported yet in clash using snell // sees in https://dreamacro.github.io/clash/configuration/outbound.html#snell - if (udp && x.Type != ProxyType::Snell) + if (udp && x.Type != ProxyType::Snell && x.Type != ProxyType::TUIC) singleproxy["udp"] = true; if (block) singleproxy.SetStyle(YAML::EmitterStyle::Block); @@ -1536,7 +1566,8 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector break; case ProxyType::Hysteria2: proxy = "Hysteria2," + hostname + "," + port + ",\"" + password + "\""; - if(!x.ServerName.empty()){ - proxy += ",sni="+x.ServerName; + if (!x.ServerName.empty()) { + proxy += ",sni=" + x.ServerName; } - if(!x.UpMbps.empty()){ + if (!x.UpMbps.empty()) { std::string search = " Mbps"; size_t pos = x.UpMbps.find(search); if (pos != std::string::npos) { x.UpMbps.replace(pos, search.length(), ""); - } else{ + } else { search = "Mbps"; pos = x.UpMbps.find(search); if (pos != std::string::npos) { x.UpMbps.replace(pos, search.length(), ""); } } - proxy += ",download-bandwidth="+x.UpMbps; - }else{ + proxy += ",download-bandwidth=" + x.UpMbps; + } else { proxy += ",download-bandwidth=100"; } if (!scv.is_undef()) @@ -2012,17 +2043,17 @@ proxyToLoon(std::vector &nodes, const std::string &base_conf, std::vector continue; } - if (ext.tfo){ + if (ext.tfo) { proxy += ",fast-open=true"; } else { - if (x.Type == ProxyType::Hysteria2){ + if (x.Type == ProxyType::Hysteria2) { proxy += ",fast-open=false"; } } - if (ext.udp){ + if (ext.udp) { proxy += ",udp=true"; } else { - if (x.Type == ProxyType::Hysteria2){ + if (x.Type == ProxyType::Hysteria2) { proxy += ",udp=true"; } } @@ -2470,6 +2501,32 @@ proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector } break; } + case ProxyType::TUIC: { + addSingBoxCommonMembers(proxy, x, "tuic", allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + proxy.AddMember("uuid", rapidjson::StringRef(x.UserId.c_str()), allocator); + if (!x.TLSSecure && !x.Alpn.empty()) { + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enabled", true, allocator); + if (!x.ServerName.empty()) + tls.AddMember("server_name", rapidjson::StringRef(""), allocator); + if (!x.Alpn.empty()) { + auto alpns = stringArrayToJsonArray(x.Alpn, ",", allocator); + tls.AddMember("alpn", alpns, allocator); + } + proxy.AddMember("tls", tls, allocator); + } + if (!x.CongestionControl.empty()){ + proxy.AddMember("congestion_control", rapidjson::StringRef(x.CongestionControl.c_str()), allocator); + } + if (!x.UdpRelayMode.empty()){ + proxy.AddMember("udp_relay_mode", rapidjson::StringRef(x.UdpRelayMode.c_str()), allocator); + } + if (!x.ReduceRtt.is_undef()){ + proxy.AddMember("zero_rtt_handshake", buildBooleanValue(x.ReduceRtt), allocator); + } + break; + } default: continue; } diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h index e53e6d7..bec2b48 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -22,7 +22,8 @@ enum class ProxyType { WireGuard, VLESS, 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"; } @@ -64,7 +67,7 @@ struct Proxy { String Remark; String Hostname; uint16_t Port = 0; - + String CongestionControl; String Username; String Password; String EncryptMethod; @@ -122,6 +125,11 @@ struct Proxy { String ShortId; String Flow; bool FlowShow = false; + tribool DisableSni; + tribool ReduceRtt; + String UdpRelayMode = "native"; + uint16_t RequestTimeout = 15000; + String token; }; #define SS_DEFAULT_GROUP "SSProvider" @@ -135,5 +143,6 @@ struct Proxy { #define XRAY_DEFAULT_GROUP "XRayProvider" #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 c171d24..7170efa 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -239,6 +239,25 @@ void hysteria2Construct(Proxy &node, const std::string &group, const std::string node.Ports = ports; } +void tuicConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, + const std::string &port, const std::string &password, const std::string &congestion_control, + const std::string &alpn, + const std::string &sni, const std::string &uuid, const std::string &udpRelayMode,const std::string &token, + tribool udp, tribool tfo, + tribool scv, tribool reduceRtt, tribool disableSni) { + commonConstruct(node, ProxyType::TUIC, group, remarks, add, port, udp, tfo, scv, tribool()); + node.Password = password; + node.Alpn = alpn; + node.ServerName = sni; + node.CongestionControl = congestion_control; + node.ReduceRtt = reduceRtt; + node.DisableSni = disableSni; + node.UserId = uuid; + node.UdpRelayMode = udpRelayMode; + node.token = token; +} + + void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls, sni; Document jsondata; @@ -1072,8 +1091,10 @@ void explodeClash(Node yamlnode, std::vector &nodes) { std::string ip, ipv6, private_key, public_key, mtu; //wireguard std::string auth, up, down, obfsParam, insecure, alpn;//hysteria std::string obfsPassword;//hysteria2 + std::string congestion_control, udp_relay_mode,token;// tuic string_array dns_server; tribool udp, tfo, scv; + bool reduceRtt, disableSni;//tuic Proxy node; singleproxy = yamlnode[section][i]; singleproxy["type"] >>= proxytype; @@ -1361,6 +1382,24 @@ void explodeClash(Node yamlnode, std::vector &nodes) { hysteria2Construct(node, group, ps, server, port, password, host, up, down, alpn, obfsParam, obfsPassword, sni, public_key, ports, udp, tfo, scv); break; + case "tuic"_hash: + group = TUIC_DEFAULT_GROUP; + singleproxy["password"] >>= password; + singleproxy["uuid"] >>= password; + singleproxy["congestion-controller"] >>= congestion_control; + singleproxy["sni"] >>= sni; + if (!singleproxy["alpn"].IsNull()) { + singleproxy["alpn"][0] >>= alpn; + } + singleproxy["disable-sni"] >>= disableSni; + singleproxy["reduce-rtt"] >>= reduceRtt; + singleproxy["token"] >>= token; + tuicConstruct(node, TUIC_DEFAULT_GROUP, ps, server, port, password, congestion_control, alpn, sni, id, + udp_relay_mode,token, + tribool(), + tribool(), scv, reduceRtt, disableSni); + + break; default: continue; } @@ -1447,7 +1486,7 @@ void explodeStdHysteria(std::string hysteria, Proxy &node) { } void explodeStdHysteria2(std::string hysteria2, Proxy &node) { - std::string add, port, password, host, insecure, up, down, alpn, obfsParam, obfsPassword, remarks, sni ,ports; + std::string add, port, password, host, insecure, up, down, alpn, obfsParam, obfsPassword, remarks, sni, ports; std::string addition; tribool scv; hysteria2 = hysteria2.substr(12); @@ -2593,7 +2632,8 @@ void explodeSingbox(rapidjson::Value &outbounds, std::vector &nodes) { std::string auth, up, down, obfsParam, insecure, alpn;//hysteria std::string obfsPassword;//hysteria2 string_array dns_server; - tribool udp, tfo, scv; + std::string congestion_control,udp_relay_mode;//quic + tribool udp, tfo, scv,rrt; rapidjson::Value singboxNode = outbounds[i].GetObject(); if (singboxNode.HasMember("type") && singboxNode["type"].IsString()) { Proxy node; @@ -2762,6 +2802,19 @@ void explodeSingbox(rapidjson::Value &outbounds, std::vector &nodes) { hysteria2Construct(node, group, ps, server, port, password, host, up, down, alpn, obfsParam, obfsPassword, sni, public_key, "", udp, tfo, scv); break; + case "tuic"_hash: + group = TUIC_DEFAULT_GROUP; + password = GetMember(singboxNode, "password"); + id = GetMember(singboxNode, "uuid"); + congestion_control = GetMember(singboxNode, "congestion_control"); + if (singboxNode.HasMember("zero_rtt_handshake") && singboxNode["zero_rtt_handshake"].IsBool()) { + rrt = singboxNode["zero_rtt_handshake"].GetBool(); + } + udp_relay_mode = GetMember(singboxNode, "udp_relay_mode"); + tuicConstruct(node, TUIC_DEFAULT_GROUP, ps, server, port, password, congestion_control, alpn, sni, id, udp_relay_mode, "", + tribool(), + tribool(), scv,rrt); + break; default: continue; } @@ -2773,6 +2826,66 @@ void explodeSingbox(rapidjson::Value &outbounds, std::vector &nodes) { } } +void explodeTuic(const std::string &tuic, Proxy &node) { + std::string add, port, password, host, insecure, alpn, remarks, sni, ports, congestion_control; + std::string addition; + tribool scv; + std::string link = tuic.substr(7); + string_size pos; + + pos = link.rfind("#"); + if (pos != std::string::npos) { + remarks = urlDecode(link.substr(pos + 1)); + link.erase(pos); + } + + pos = link.rfind("?"); + if (pos != std::string::npos) { + addition = link.substr(pos + 1); + link.erase(pos); + } + + std::string uuid; + pos = link.find(":"); + if (pos != std::string::npos) { + uuid = link.substr(0, pos); + link = link.substr(pos + 1); + if (strFind(link, "@")) { + pos = link.find("@"); + if (pos != std::string::npos) { + password = link.substr(0, pos); + link = link.substr(pos + 1); + } + } + } + + pos = link.find(":"); + if (pos != std::string::npos) { + add = link.substr(0, pos); + link = link.substr(pos + 1); + pos = link.find("?"); + if (pos != std::string::npos) { + port = link.substr(0, pos); + addition = link.substr(pos + 1); + } else { + port = link; + } + } + + + scv = getUrlArg(addition, "insecure"); + alpn = getUrlArg(addition, "alpn"); + sni = getUrlArg(addition, "sni"); + congestion_control = getUrlArg(addition, "congestion_control"); + if (remarks.empty()) + remarks = add + ":" + port; + tuicConstruct(node, TUIC_DEFAULT_GROUP, remarks, add, port, password, congestion_control, alpn, sni, uuid, "native", "", + tribool(), + tribool(), scv); + + return; +} + void explode(const std::string &link, Proxy &node) { if (startsWith(link, "ssr://")) explodeSSR(link, node); @@ -2792,6 +2905,8 @@ void explode(const std::string &link, Proxy &node) { explodeVless(link, node); else if (strFind(link, "hysteria://") || strFind(link, "hy://")) explodeHysteria(link, node); + else if (strFind(link, "tuic://")) + explodeTuic(link, node); else if (strFind(link, "hysteria2://") || strFind(link, "hy2://")) explodeHysteria2(link, node); else if (isLink(link)) diff --git a/src/parser/subparser.h b/src/parser/subparser.h index a552115..00da5a4 100644 --- a/src/parser/subparser.h +++ b/src/parser/subparser.h @@ -78,7 +78,12 @@ void snellConstruct(Proxy &node, const std::string &group, const std::string &re 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()); - +void tuicConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, + const std::string &port, const std::string &password, const std::string &congestion_control, + const std::string &alpn, + const std::string &sni,const std::string & uuid,const std::string &udpRelayMode,const std::string &token, + tribool udp = tribool(), tribool tfo = tribool(), + tribool scv = tribool(),tribool reduceRtt = tribool(),tribool disableSni = tribool()); void explodeVmess(std::string vmess, Proxy &node); void explodeSSR(std::string ssr, Proxy &node);