diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index 5d05a1782..cc58436d8 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -192,10 +192,10 @@ data class V2rayConfig( } data class GrpcSettingsBean(var serviceName: String = "", - val multiMode: Boolean? = null) + var multiMode: Boolean? = null) fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?, - quicSecurity: String?, key: String?): String { + quicSecurity: String?, key: String?, mode: String?, serviceName: String?): String { var sni = "" network = transport when (network) { @@ -250,7 +250,8 @@ data class V2rayConfig( } "grpc" -> { val grpcSetting = GrpcSettingsBean() - grpcSetting.serviceName = path ?: "" + grpcSetting.multiMode = mode == "multi" + grpcSetting.serviceName = serviceName ?: "" sni = host ?: "" grpcSettings = grpcSetting } @@ -357,7 +358,7 @@ data class V2rayConfig( } "grpc" -> { val grpcSetting = streamSettings?.grpcSettings ?: return null - listOf("", + listOf(if (grpcSetting.multiMode == true) "multi" else "gun", "", grpcSetting.serviceName) } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt index e237ec8e0..7a068474a 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt @@ -5,6 +5,9 @@ import android.support.v7.app.AlertDialog import android.text.TextUtils import android.view.Menu import android.view.MenuItem +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter import com.tencent.mmkv.MMKV import com.v2ray.ang.R import com.v2ray.ang.dto.EConfigType @@ -28,6 +31,7 @@ import kotlinx.android.synthetic.main.activity_server_vmess.et_remarks import kotlinx.android.synthetic.main.activity_server_vmess.et_request_host import kotlinx.android.synthetic.main.activity_server_vmess.sp_allow_insecure import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type +import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type_title import kotlinx.android.synthetic.main.activity_server_vmess.sp_network import kotlinx.android.synthetic.main.activity_server_vmess.sp_stream_security @@ -56,8 +60,14 @@ class ServerActivity : BaseActivity() { private val networks: Array by lazy { resources.getStringArray(R.array.networks) } - private val headertypes: Array by lazy { - resources.getStringArray(R.array.headertypes) + private val tcpTypes: Array by lazy { + resources.getStringArray(R.array.header_type_tcp) + } + private val kcpAndQuicTypes: Array by lazy { + resources.getStringArray(R.array.header_type_kcp_and_quic) + } + private val grpcModes: Array by lazy { + resources.getStringArray(R.array.mode_type_grpc) } private val streamSecuritys: Array by lazy { resources.getStringArray(R.array.streamsecurityxs) @@ -79,6 +89,26 @@ class ServerActivity : BaseActivity() { // EConfigType.VLESS -> setContentView(R.layout.activity_server_vless) // EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan) } + sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val types = transportTypes(networks[position]) + sp_header_type?.isEnabled = types.size > 1 + val adapter = ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + sp_header_type?.adapter = adapter + sp_header_type_title?.text = if (networks[position] == "grpc") + getString(R.string.server_lab_mode_type) else + getString(R.string.server_lab_head_type) + config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails -> + sp_header_type.setSelection(Utils.arrayFind(types, transportDetails[0])) + et_request_host.text = Utils.getEditable(transportDetails[1]) + et_path.text = Utils.getEditable(transportDetails[2]) + } + } + override fun onNothingSelected(parent: AdapterView<*>?) { + // do nothing + } + } if (config != null) { bindingServer(config) } else { @@ -129,11 +159,6 @@ class ServerActivity : BaseActivity() { if (network >= 0) { sp_network?.setSelection(network) } - outbound.getTransportSettingDetails()?.let { transportDetails -> - sp_header_type.setSelection(Utils.arrayFind(headertypes, transportDetails[0])) - et_request_host.text = Utils.getEditable(transportDetails[1]) - et_path.text = Utils.getEditable(transportDetails[2]) - } return true } @@ -246,16 +271,20 @@ class ServerActivity : BaseActivity() { } private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, config: ServerConfig) { + val network = if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK + val type = if (sp_header_type != null) transportTypes(network)[sp_header_type.selectedItemPosition] else ""; val requestHost = if (et_request_host != null) et_request_host.text.toString().trim() else "" val path = if (et_path != null) et_path.text.toString().trim() else "" var sni = streamSetting.populateTransportSettings( - if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK, - if (sp_header_type != null) headertypes[sp_header_type.selectedItemPosition] else "", - requestHost, - path, - path, - requestHost, - path + transport = network, + headerType = type, + host = requestHost, + path = path, + seed = path, + quicSecurity = requestHost, + key = path, + mode = type, + serviceName = path ) val allowInsecure = if (sp_allow_insecure == null || allowinsecures[sp_allow_insecure.selectedItemPosition].isBlank()) { false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false @@ -270,6 +299,18 @@ class ServerActivity : BaseActivity() { ) } + private fun transportTypes(network: String?): Array { + return if (network == "tcp") { + tcpTypes + } else if (network == "kcp" || network == "quic") { + kcpAndQuicTypes + } else if (network == "grpc") { + grpcModes + } else { + arrayOf("---") + } + } + /** * save server config */ diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index d03686b4c..f5279f44e 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -131,7 +131,8 @@ object AngConfigManager { } config.outboundBean?.streamSettings?.let { streamSetting -> val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType, - vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path) + vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path, + vmessBean.headerType, vmessBean.path) // val allowInsecure = if (vmessBean.allowInsecure.isBlank()) { // settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false // } else { @@ -209,7 +210,7 @@ object AngConfigManager { vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid) } val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host, - vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path) + vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path, vmessQRCode.type, vmessQRCode.path) streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure, if (vmessQRCode.sni.isNotBlank()) vmessQRCode.sni else sni) } @@ -312,7 +313,8 @@ object AngConfigManager { } val sni = streamSetting.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"], - queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"]) + queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], + queryParam["mode"], queryParam["serviceName"]) streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure, queryParam["sni"] ?: sni) } if (config == null){ @@ -337,7 +339,7 @@ object AngConfigManager { val uri = URI(uriString) check(uri.scheme == "vmess") val (_, protocol, tlsStr, uuid, alterId) = - Regex("(tcp|http|ws|kcp|quic)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})-([0-9]+)") + Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})-([0-9]+)") .matchEntire(uri.userInfo)?.groupValues ?: error("parse user info fail.") val tls = tlsStr.isNotBlank() @@ -357,8 +359,8 @@ object AngConfigManager { val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"], queryParam["host"]?.split("|")?.get(0) ?: "", - queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", - queryParam["seed"], queryParam["security"], queryParam["key"]) + queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", queryParam["seed"], queryParam["security"], + queryParam["key"], queryParam["mode"], queryParam["serviceName"]) streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni) true }.getOrElse { false } @@ -501,6 +503,10 @@ object AngConfigManager { dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1]) dicQuery["key"] = Utils.urlEncode(transportDetails[2]) } + "grpc" -> { + dicQuery["mode"] = transportDetails[0] + dicQuery["serviceName"] = transportDetails[2] + } } } val query = "?" + dicQuery.toList().joinToString( diff --git a/V2rayNG/app/src/main/res/layout/activity_server_vmess.xml b/V2rayNG/app/src/main/res/layout/activity_server_vmess.xml index 593cf3348..69fb9b586 100644 --- a/V2rayNG/app/src/main/res/layout/activity_server_vmess.xml +++ b/V2rayNG/app/src/main/res/layout/activity_server_vmess.xml @@ -174,6 +174,7 @@ android:orientation="vertical"> @@ -181,8 +182,7 @@ + android:layout_height="@dimen/edit_height" /> 传输协议(network) 功能设置(不清楚则保持默认值) 伪装类型(type) + gRPC 传输模式 (mode) 伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式 - path(ws path/h2 path)/QUIC 加密密钥/kcp seed/grpc serviceName + path(ws path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName 底层传输安全(tls) 跳过证书验证(allowInsecure) 服务器地址 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 416dbf7f6..dd6ac7117 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -43,8 +43,9 @@ 網路 更多功能 標頭類型 + gRPC 傳輸模式 (mode) 要求主機(host)(host/ws host/h2 host)/QUIC加密方式 - path(ws path/h2 path)/QUIC加密密鑰/kcp seed/grpc serviceName + path(ws path/h2 path)/QUIC加密密鑰/kcp seed/gRPC serviceName 傳輸層安全性(tls) 跳過證書驗證(allowInsecure) 伺服器位址 diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 3a690f7be..98f2f609c 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -27,9 +27,13 @@ grpc - + none http + + + + none srtp utp wechat-video @@ -37,18 +41,10 @@ wireguard - - none - http - - - - none - srtp - utp - wechat-video - dtls - wireguard + + gun + multi + diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 06782022b..334aecae9 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -40,11 +40,12 @@ id alterId security - network + Network more function head type + gRPC mode request host(host/ws host/h2 host)/QUIC security - path(ws path/h2 path)/QUIC key/kcp seed/grpc serviceName + path(ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName tls allowInsecure address