From 9b49ffd20e709be7793548f215430f22f0f4087c Mon Sep 17 00:00:00 2001 From: HystericalDragon <138737572+HystericalDragon@users.noreply.github.com> Date: Sun, 13 Aug 2023 20:33:35 +0800 Subject: [PATCH] feat: add sharelink for TUIC --- .../sagernet/database/ProxyEntity.kt | 3 +- .../nekohasekai/sagernet/fmt/tuic/TuicFmt.kt | 54 ++++++++++++++++++- .../io/nekohasekai/sagernet/ktx/Formats.kt | 8 +++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt index 9aa1a2fe..8c3fc74f 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -24,6 +24,7 @@ import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig import io.nekohasekai.sagernet.fmt.trojan_go.toUri import io.nekohasekai.sagernet.fmt.tuic.TuicBean +import io.nekohasekai.sagernet.fmt.tuic.toUri import io.nekohasekai.sagernet.fmt.tuic.buildTuicConfig import io.nekohasekai.sagernet.fmt.v2ray.* import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean @@ -225,7 +226,6 @@ data class ProxyEntity( fun haveStandardLink(): Boolean { return when (requireBean()) { - is TuicBean -> false is SSHBean -> false is WireGuardBean -> false is ShadowTLSBean -> false @@ -245,6 +245,7 @@ data class ProxyEntity( is TrojanGoBean -> toUri() is NaiveBean -> toUri() is HysteriaBean -> toUri() + is TuicBean -> toUri() is NekoBean -> shareLink() else -> toUniversalLink() } diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt index 38ecfec5..99d85b5a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt @@ -1,7 +1,7 @@ package io.nekohasekai.sagernet.fmt.tuic import io.nekohasekai.sagernet.fmt.LOCALHOST -import io.nekohasekai.sagernet.ktx.isIpAddress +import io.nekohasekai.sagernet.ktx.* import moe.matsuri.nb4a.SingBoxOptions import moe.matsuri.nb4a.utils.JavaUtil import moe.matsuri.nb4a.utils.Util @@ -9,6 +9,58 @@ import moe.matsuri.nb4a.utils.listByLineOrComma import org.json.JSONArray import org.json.JSONObject import java.io.File +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + +fun parseTuic(url: String): TuicBean { + // https://github.com/daeuniverse/dae/discussions/182 + var link = url.replace("tuic://", "https://").toHttpUrlOrNull() ?: error( + "invalid tuic link $url" + ) + return TuicBean().apply { + protocolVersion = 5 + + name = link.fragment + uuid = link.username + token = link.password + serverAddress = link.host + serverPort = link.port + + link.queryParameter("sni")?.let { + sni = it + } + link.queryParameter("congestion_control")?.let { + congestionController = it + } + link.queryParameter("udp_relay_mode")?.let { + udpRelayMode = it + } + link.queryParameter("alpn")?.let { + alpn = it + } + link.queryParameter("allow_insecure")?.let { + if (it == "1") allowInsecure = true + } + link.queryParameter("disable_sni")?.let { + if (it == "1") disableSNI =true + } + } +} + +fun TuicBean.toUri(): String { + val builder = linkBuilder().username(uuid).password(token).host(serverAddress).port(serverPort) + + builder.addQueryParameter("congestion_control", congestionController) + builder.addQueryParameter("udp_relay_mode", udpRelayMode) + + if (sni.isNotBlank()) builder.addQueryParameter("sni", sni) + if (alpn.isNotBlank()) builder.addQueryParameter("alpn", alpn) + if (allowInsecure) builder.addQueryParameter("allow_insecure", "1") + if (disableSNI) builder.addQueryParameter("disable_sni", "1") + if (name.isNotBlank()) builder.encodedFragment(name.urlSafe()) + + return builder.toLink("tuic") +} fun buildSingBoxOutboundTuicBean(bean: TuicBean): SingBoxOptions.Outbound_TUICOptions { return SingBoxOptions.Outbound_TUICOptions().apply { diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt index aa28fb8e..ce1c179a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt @@ -10,6 +10,7 @@ import io.nekohasekai.sagernet.fmt.parseUniversal import io.nekohasekai.sagernet.fmt.shadowsocks.parseShadowsocks import io.nekohasekai.sagernet.fmt.socks.parseSOCKS import io.nekohasekai.sagernet.fmt.trojan.parseTrojan +import io.nekohasekai.sagernet.fmt.tuic.parseTuic import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo import io.nekohasekai.sagernet.fmt.v2ray.parseV2Ray import moe.matsuri.nb4a.plugin.NekoPluginManager @@ -187,6 +188,13 @@ suspend fun parseProxies(text: String): List { }.onFailure { Logs.w(it) } + } else if (startsWith("tuic://")) { + Logs.d("Try parse TUIC link: $this") + runCatching { + entities.add(parseTuic(this)) + }.onFailure { + Logs.w(it) + } } else { // Neko Plugins NekoPluginManager.getProtocols().forEach { obj -> obj.protocolConfig.optJSONArray("links")?.forEach { _, any ->