Compare commits

...

24 commits

Author SHA1 Message Date
2dust
3bf911da9c up 1.10.8
Some checks are pending
Build APK / build (push) Waiting to run
Validate Fastlane metadata / go (push) Waiting to run
2025-06-29 10:27:57 +08:00
2dust
3f778a1ea2 Optimize the source of tls sni
Some checks are pending
Build APK / build (push) Waiting to run
Validate Fastlane metadata / go (push) Waiting to run
2025-06-28 10:02:25 +08:00
Hossein Abaspanah
8e03de8055
Update strings.xml (#4698)
Add "title_core_settings" string
2025-06-28 08:45:00 +08:00
Hossein Abaspanah
1f42d7fc07
Update strings.xml (#4696)
Some checks are pending
Build APK / build (push) Waiting to run
Validate Fastlane metadata / go (push) Waiting to run
2025-06-27 20:39:05 +08:00
Hossein Abaspanah
0700e834f1
Update Luri Bakhtiari translation (#4695) 2025-06-27 20:38:31 +08:00
2dust
777190e861 Added setting option for Outbound domain pre-resolve method
Some checks are pending
Build APK / build (push) Waiting to run
Validate Fastlane metadata / go (push) Waiting to run
https://github.com/2dust/v2rayNG/issues/4679
2025-06-27 17:48:31 +08:00
2dust
33572477fc Adjustment setting items 2025-06-27 16:39:06 +08:00
2dust
2fb6e62e13 Added setting option for VPN interface address
https://github.com/2dust/v2rayNG/issues/4641
2025-06-27 16:09:03 +08:00
2dust
94cc72d2b9 up 1.10.7 2025-06-19 14:40:47 +08:00
2dust
f68c353715 Update AndroidLibXrayLite 2025-06-19 14:40:11 +08:00
2dust
e077c18108 Improved update checking and prompts in case of abnormality 2025-06-19 14:40:07 +08:00
Ural Khamitov
1a5e105212
Fix blinking QSTile when QS panel is opening (#4676) 2025-06-18 16:17:28 +08:00
DHR60
e0881caab4
Fix missing sockopt.domainStrategy (#4673)
* Fix missing sockopt.domainStrategy

* Fix
2025-06-17 13:43:03 +08:00
DHR60
7219425258
Cloudflare DNS Hosts (#4661) 2025-06-15 09:46:30 +08:00
2dust
51eabe5440 up 1.10.6 2025-06-14 14:27:09 +08:00
2dust
6f0b3ce990 Update AndroidLibXrayLite 2025-06-14 14:26:37 +08:00
2dust
69e27ed3bb Fix log for plugin 2025-06-14 14:26:33 +08:00
patterniha
fff6ab30e6
Xray-core default FakeIPv6 Pool should not bypass and should route (#4649)
* Update V2RayVpnService.kt

* Update V2RayVpnService.kt

* Update AppConfig.kt
2025-06-14 13:59:59 +08:00
2dust
fdb67a86f4 up 1.10.5 2025-06-08 09:26:36 +08:00
2dust
ea088376ac Update AndroidLibXrayLite 2025-06-08 09:25:46 +08:00
2dust
52332d960e Update libs.versions.toml 2025-06-07 11:20:41 +08:00
2dust
3ead542e2b VPN bypass LAN By default 2025-06-07 11:20:37 +08:00
2dust
9d1f98ff34 Fix non-English domain
https://github.com/2dust/v2rayNG/issues/4626
f305e26a39
2025-05-31 14:03:52 +08:00
2dust
f305e26a39 Fix the parsing problem of non-English domain
https://github.com/2dust/v2rayNG/issues/4626
2025-05-31 11:12:57 +08:00
37 changed files with 378 additions and 102 deletions

@ -1 +1 @@
Subproject commit ddcaecad0ae2f0816b33b8a56d1e36fce4efcae4
Subproject commit 8ad3e1ddf165d8d67e488346b2faa9153d3e33a4

View file

@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 654
versionName = "1.10.4"
versionCode = 658
versionName = "1.10.8"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')

View file

@ -26,6 +26,7 @@ object AppConfig {
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan"
const val PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX = "pref_vpn_interface_address_config_index"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
const val PREF_MUX_ENABLED = "pref_mux_enabled"
@ -55,6 +56,7 @@ object AppConfig {
const val PREF_DNS_HOSTS = "pref_dns_hosts"
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD = "pref_outbound_domain_resolve_method"
const val PREF_MODE = "pref_mode"
const val PREF_IS_BOOTED = "pref_is_booted"
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
@ -168,7 +170,9 @@ object AppConfig {
// Android Private DNS constants
const val DNS_DNSPOD_DOMAIN = "dot.pub"
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
const val DNS_CLOUDFLARE_DOMAIN = "one.one.one.one"
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
const val DNS_GOOGLE_DOMAIN = "dns.google"
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
@ -182,14 +186,16 @@ object AppConfig {
const val HEADER_TYPE_HTTP = "http"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5")
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
//minimum list https://serverfault.com/a/304791
val BYPASS_PRIVATE_IP_LIST = arrayListOf(
val ROUTED_IP_LIST = arrayListOf(
"0.0.0.0/5",
"8.0.0.0/7",
"11.0.0.0/8",

View file

@ -0,0 +1,39 @@
package com.v2ray.ang.dto
/**
* VPN interface address configuration enum class
* Defines predefined IPv4 and IPv6 address pairs for VPN TUN interface configuration.
* Each option provides client and router addresses to establish point-to-point VPN tunnels.
*/
enum class VpnInterfaceAddressConfig(
val displayName: String,
val ipv4Client: String,
val ipv4Router: String,
val ipv6Client: String,
val ipv6Router: String
) {
OPTION_1("10.10.14.x", "10.10.14.1", "10.10.14.2", "fc00::10:10:14:1", "fc00::10:10:14:2"),
OPTION_2("10.1.0.x", "10.1.0.1", "10.1.0.2", "fc00::10:1:0:1", "fc00::10:1:0:2"),
OPTION_3("10.0.0.x", "10.0.0.1", "10.0.0.2", "fc00::10:0:0:1", "fc00::10:0:0:2"),
OPTION_4("172.31.0.x", "172.31.0.1", "172.31.0.2", "fc00::172:31:0:1", "fc00::172:31:0:2"),
OPTION_5("172.20.0.x", "172.20.0.1", "172.20.0.2", "fc00::172:20:0:1", "fc00::172:20:0:2"),
OPTION_6("172.16.0.x", "172.16.0.1", "172.16.0.2", "fc00::172:16:0:1", "fc00::172:16:0:2"),
OPTION_7("192.168.100.x", "192.168.100.1", "192.168.100.2", "fc00::192:168:100:1", "fc00::192:168:100:2");
companion object {
/**
* Retrieves the VPN interface address configuration based on the specified index.
*
* @param index The configuration index (0-based) corresponding to user selection
* @return The VpnInterfaceAddressConfig instance at the specified index,
* or OPTION_1 (default) if the index is out of bounds
*/
fun getConfigByIndex(index: Int): VpnInterfaceAddressConfig {
return if (index in values().indices) {
values()[index]
} else {
OPTION_1 // Default to the first configuration
}
}
}
}

View file

@ -4,6 +4,8 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.Utils
import java.net.URI
@ -26,7 +28,7 @@ open class FmtBase {
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(config.server),
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
config.serverPort
)
@ -149,6 +151,20 @@ open class FmtBase {
return dicQuery
}
fun getServerAddress(profileItem: ProfileItem): String {
if (Utils.isPureIpAddress(profileItem.server.orEmpty())) {
return profileItem.server.orEmpty()
}
val domain = HttpUtil.toIdnDomain(profileItem.server.orEmpty())
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") != "2") {
return domain
}
//Resolve and replace domain
val resolvedIps = HttpUtil.resolveHostToIP(domain, MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
if (resolvedIps.isNullOrEmpty()) {
return domain
}
return resolvedIps.first()
}
}

View file

@ -17,7 +17,7 @@ object HttpFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HTTP)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()

View file

@ -135,7 +135,7 @@ object ShadowsocksFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SHADOWSOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.method = profileItem.method

View file

@ -64,7 +64,7 @@ object SocksFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()

View file

@ -64,7 +64,7 @@ object TrojanFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.TROJAN)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.flow = profileItem.flow

View file

@ -60,7 +60,7 @@ object VlessFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VLESS)
outboundBean?.settings?.vnext?.first()?.let { vnext ->
vnext.address = profileItem.server.orEmpty()
vnext.address = getServerAddress(profileItem)
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].encryption = profileItem.method

View file

@ -172,7 +172,7 @@ object VmessFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VMESS)
outboundBean?.settings?.vnext?.first()?.let { vnext ->
vnext.address = profileItem.server.orEmpty()
vnext.address = getServerAddress(profileItem)
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].security = profileItem.method

View file

@ -415,7 +415,7 @@ object AngConfigManager {
if (!it.second.enabled) {
return 0
}
val url = HttpUtil.idnToASCII(it.second.url)
val url = HttpUtil.toIdnUrl(it.second.url)
if (!Utils.isValidUrl(url)) {
return 0
}

View file

@ -16,6 +16,7 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RoutingType
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.VpnInterfaceAddressConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerList
import com.v2ray.ang.util.JsonUtil
@ -159,7 +160,7 @@ object SettingsManager {
* @return True if bypassing LAN, false otherwise.
*/
fun routingRulesetsBypassLan(): Boolean {
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "0"
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "1"
if (vpnBypassLan == "1") {
return true
} else if (vpnBypassLan == "2") {
@ -356,4 +357,17 @@ object SettingsManager {
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
/**
* Retrieves the currently selected VPN interface address configuration.
* This method reads the user's preference for VPN interface addressing and returns
* the corresponding configuration containing IPv4 and IPv6 addresses.
*
* @return The selected VpnInterfaceAddressConfig instance, or the default configuration
* if no valid selection is found or if the stored index is invalid.
*/
fun getCurrentVpnInterfaceAddressConfig(): VpnInterfaceAddressConfig {
val selectedIndex = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX, "0")?.toInt()
return VpnInterfaceAddressConfig.getConfigByIndex(selectedIndex ?: 0)
}
}

View file

@ -17,7 +17,6 @@ import java.io.FileOutputStream
object UpdateCheckerManager {
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
try {
val url = if (includePreRelease) {
AppConfig.APP_API_URL
} else {
@ -53,10 +52,6 @@ object UpdateCheckerManager {
} else {
CheckUpdateResult(hasUpdate = false)
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
return@withContext CheckUpdateResult(hasUpdate = false, error = e.message)
}
}
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {

View file

@ -16,7 +16,6 @@ import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.fmt.HttpFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
import com.v2ray.ang.fmt.SocksFmt
import com.v2ray.ang.fmt.TrojanFmt
@ -98,7 +97,7 @@ object V2rayConfigManager {
val result = ConfigResult(false)
val address = config.server ?: return result
if (!Utils.isIpAddress(address)) {
if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result
@ -132,7 +131,10 @@ object V2rayConfigManager {
v2rayConfig.policy = null
}
resolveOutboundDomainsToHosts(v2rayConfig)
//Resolve and add to DNS Hosts
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
resolveOutboundDomainsToHosts(v2rayConfig)
}
result.status = true
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
@ -152,7 +154,7 @@ object V2rayConfigManager {
val result = ConfigResult(false)
val address = config.server ?: return result
if (!Utils.isIpAddress(address)) {
if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result
@ -479,6 +481,25 @@ object V2rayConfigManager {
)
}
//block dns
val blkDomain = getUserRule2Domain(AppConfig.TAG_BLOCKED)
if (blkDomain.isNotEmpty()) {
hosts.putAll(blkDomain.map { it to AppConfig.LOOPBACK })
}
// hardcode googleapi rule to fix play store problems
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
// hardcode popular Android Private DNS rule to fix localhost DNS problem
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
//User DNS hosts
try {
val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
@ -493,24 +514,6 @@ object V2rayConfigManager {
Log.e(AppConfig.TAG, "Failed to configure user DNS hosts", e)
}
//block dns
val blkDomain = getUserRule2Domain(AppConfig.TAG_BLOCKED)
if (blkDomain.isNotEmpty()) {
hosts.putAll(blkDomain.map { it to AppConfig.LOOPBACK })
}
// hardcode googleapi rule to fix play store problems
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
// hardcode popular Android Private DNS rule to fix localhost DNS problem
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
// DNS dns
v2rayConfig.dns = V2rayConfig.DnsBean(
servers = servers,
@ -828,7 +831,11 @@ object V2rayConfigManager {
for (item in proxyOutboundList) {
val domain = item.getServerAddress()
if (domain.isNullOrEmpty()) continue
if (newHosts.containsKey(domain)) continue
if (newHosts.containsKey(domain)) {
item.ensureSockopt().domainStrategy = if (preferIpv6) "UseIPv6v4" else "UseIPv4v6"
continue
}
val resolvedIps = HttpUtil.resolveHostToIP(domain, preferIpv6)
if (resolvedIps.isNullOrEmpty()) continue
@ -1045,7 +1052,15 @@ object V2rayConfigManager {
fun populateTlsSettings(streamSettings: StreamSettingsBean, profileItem: ProfileItem, sniExt: String?) {
val streamSecurity = profileItem.security.orEmpty()
val allowInsecure = profileItem.insecure == true
val sni = if (profileItem.sni.isNullOrEmpty()) sniExt else profileItem.sni
val sni = if (profileItem.sni.isNullOrEmpty()) {
when {
sniExt.isNotNullEmpty() && Utils.isDomainName(sniExt) -> sniExt
profileItem.server.isNotNullEmpty() && Utils.isDomainName(profileItem.server) -> profileItem.server
else -> sniExt
}
} else {
profileItem.sni
}
val fingerprint = profileItem.fingerPrint
val alpns = profileItem.alpn
val publicKey = profileItem.publicKey

View file

@ -25,14 +25,13 @@ class QSTileService : TileService() {
* @param state The state to set.
*/
fun setState(state: Int) {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
if (state == Tile.STATE_INACTIVE) {
qsTile?.state = Tile.STATE_INACTIVE
qsTile?.label = getString(R.string.app_name)
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
} else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = V2RayServiceManager.getRunningServerName()
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
}
qsTile?.updateTile()
@ -45,7 +44,11 @@ class QSTileService : TileService() {
override fun onStartListening() {
super.onStartListening()
setState(Tile.STATE_INACTIVE)
if (V2RayServiceManager.isRunning()) {
setState(Tile.STATE_ACTIVE)
} else {
setState(Tile.STATE_INACTIVE)
}
mMsgReceive = ReceiveMessageHandler(this)
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())

View file

@ -102,7 +102,7 @@ object V2RayServiceManager {
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (config.configType != EConfigType.CUSTOM
&& !Utils.isValidUrl(config.server)
&& !Utils.isIpAddress(config.server)
&& !Utils.isPureIpAddress(config.server.orEmpty())
) return
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
// if (!result.status) return

View file

@ -33,12 +33,7 @@ import java.lang.ref.SoftReference
class V2RayVpnService : VpnService(), ServiceControl {
companion object {
private const val VPN_MTU = 1500
private const val PRIVATE_VLAN4_CLIENT = "10.10.14.1"
private const val PRIVATE_VLAN4_ROUTER = "10.10.14.2"
private const val PRIVATE_VLAN6_CLIENT = "fc00::10:10:14:1"
private const val PRIVATE_VLAN6_ROUTER = "fc00::10:10:14:2"
private const val TUN2SOCKS = "libtun2socks.so"
}
private lateinit var mInterface: ParcelFileDescriptor
@ -160,14 +155,15 @@ class V2RayVpnService : VpnService(), ServiceControl {
// If the old interface has exactly the same parameters, use it!
// Configure a builder while parsing the parameters.
val builder = Builder()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
builder.setMtu(VPN_MTU)
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
builder.addAddress(vpnConfig.ipv4Client, 30)
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
val bypassLan = SettingsManager.routingRulesetsBypassLan()
if (bypassLan) {
AppConfig.BYPASS_PRIVATE_IP_LIST.forEach {
AppConfig.ROUTED_IP_LIST.forEach {
val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}
@ -176,9 +172,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
builder.addAddress(vpnConfig.ipv6Client, 126)
if (bypassLan) {
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
builder.addRoute("fc00::", 18) //Xray-core default FakeIPv6 Pool
} else {
builder.addRoute("::", 0)
}
@ -259,9 +256,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
private fun runTun2socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS")
val socksPort = SettingsManager.getSocksPort()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
val cmd = arrayListOf(
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
"--netif-ipaddr", vpnConfig.ipv4Router,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "$LOOPBACK:${socksPort}",
"--tunmtu", VPN_MTU.toString(),
@ -272,7 +270,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
cmd.add("--netif-ip6addr")
cmd.add(PRIVATE_VLAN6_ROUTER)
cmd.add(vpnConfig.ipv6Router)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())

View file

@ -1,6 +1,7 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
@ -9,6 +10,7 @@ import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityCheckUpdateBinding
import com.v2ray.ang.dto.CheckUpdateResult
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SpeedtestManager
@ -46,11 +48,16 @@ class CheckUpdateActivity : BaseActivity() {
toast(R.string.update_checking_for_update)
lifecycleScope.launch {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toastSuccess(R.string.update_already_latest_version)
try {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toastSuccess(R.string.update_already_latest_version)
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
toastError(e.message ?: getString(R.string.toast_failure))
}
}
}

View file

@ -44,6 +44,7 @@ class SettingsActivity : BaseActivity() {
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
private val vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) }
private val vpnInterfaceAddress by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX) }
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
@ -249,12 +250,14 @@ class SettingsActivity : BaseActivity() {
listOf(
AppConfig.PREF_VPN_BYPASS_LAN,
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_MUX_XUDP_QUIC,
AppConfig.PREF_FRAGMENT_PACKETS,
AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_MODE
).forEach { key ->
if (MmkvManager.decodeSettingsString(key) != null) {
@ -273,7 +276,7 @@ class SettingsActivity : BaseActivity() {
localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn
vpnBypassLan?.isEnabled = vpn
vpn
vpnInterfaceAddress?.isEnabled = vpn
if (vpn) {
updateLocalDns(
MmkvManager.decodeSettingsBool(

View file

@ -18,12 +18,14 @@ import java.net.URL
object HttpUtil {
/**
* Converts a URL string to its ASCII representation.
* Converts the domain part of a URL string to its IDN (Punycode, ASCII Compatible Encoding) format.
*
* @param str The URL string to convert.
* @return The ASCII representation of the URL.
* For example, a URL like "https://例子.中国/path" will be converted to "https://xn--fsqu00a.xn--fiqs8s/path".
*
* @param str The URL string to convert (can contain non-ASCII characters in the domain).
* @return The URL string with the domain part converted to ASCII-compatible (Punycode) format.
*/
fun idnToASCII(str: String): String {
fun toIdnUrl(str: String): String {
val url = URL(str)
val host = url.host
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
@ -34,6 +36,28 @@ object HttpUtil {
}
}
/**
* Converts a Unicode domain name to its IDN (Punycode, ASCII Compatible Encoding) format.
* If the input is an IP address or already an ASCII domain, returns the original string.
*
* @param domain The domain string to convert (can include non-ASCII internationalized characters).
* @return The domain in ASCII-compatible (Punycode) format, or the original string if input is an IP or already ASCII.
*/
fun toIdnDomain(domain: String): String {
// Return as is if it's a pure IP address (IPv4 or IPv6)
if (Utils.isPureIpAddress(domain)) {
return domain
}
// Return as is if already ASCII (English domain or already punycode)
if (domain.all { it.code < 128 }) {
return domain
}
// Otherwise, convert to ASCII using IDN
return IDN.toASCII(domain, IDN.ALLOW_UNASSIGNED)
}
/**
* Resolves a hostname to an IP address, returns original input if it's already an IP
*

View file

@ -28,13 +28,17 @@ object PluginUtil {
fun runPlugin(context: Context, config: ProfileItem?, socksPort: Int?) {
Log.i(AppConfig.TAG, "Starting plugin execution")
if (config == null || socksPort == null) {
if (config == null) {
Log.w(AppConfig.TAG, "Cannot run plugin: config is null")
return
}
try {
if (config.configType == EConfigType.HYSTERIA2) {
if (socksPort == null) {
Log.w(AppConfig.TAG, "Cannot run plugin: socksPort is null")
return
}
Log.i(AppConfig.TAG, "Running Hysteria2 plugin")
val configFile = genConfigHy2(context, config, socksPort) ?: return
val cmd = genCmdHy2(context, configFile)

View file

@ -198,6 +198,21 @@ object Utils {
return isIpv4Address(value) || isIpv6Address(value)
}
/**
* Check if a string is a valid domain name.
*
* A valid domain name must not be an IP address and must be a valid URL format.
*
* @param input The string to check.
* @return True if the string is a valid domain name, false otherwise.
*/
fun isDomainName(input: String?): Boolean {
if (input.isNullOrEmpty()) return false
// Must not be an IP address and must be a valid URL format
return !isPureIpAddress(input) && isValidUrl(input)
}
/**
* Check if a string is a valid IPv4 address.
*

View file

@ -41,6 +41,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_MODE,
AppConfig.PREF_VPN_DNS,
AppConfig.PREF_VPN_BYPASS_LAN,
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_DNS_HOSTS,
@ -48,6 +49,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_LOCAL_DNS_PORT,
AppConfig.PREF_SOCKS_PORT,
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,

View file

@ -141,6 +141,7 @@
<!-- Preferences -->
<string name="title_settings">الإعدادات</string>
<string name="title_advanced">إعدادات متقدمة</string>
<string name="title_core_settings">إعدادات النواة</string>
<string name="title_vpn_settings">إعدادات VPN</string>
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
@ -181,6 +182,8 @@
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -238,6 +241,7 @@
<string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</string>
<string name="title_core_loglevel">مستوى السجل</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">الوضع</string>
<string name="title_mode_help">انقر هنا للحصول على مزيد من المساعدة</string>
<string name="title_language">اللغة</string>
@ -354,4 +358,10 @@
<item>Not Bypass</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
</resources>

View file

@ -139,6 +139,7 @@
<!-- Preferences -->
<string name="title_settings">সেটিংস</string>
<string name="title_advanced">এডভান্সড সেটিংস</string>
<string name="title_core_settings">কোর সেটিংস</string>
<string name="title_vpn_settings">VPN সেটিংস</string>
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
@ -181,6 +182,8 @@
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -238,6 +241,7 @@
<string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</string>
<string name="title_core_loglevel">লগ স্তর</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">মোড</string>
<string name="title_mode_help">আরো সাহায্যের জন্য ক্লিক করুন</string>
<string name="title_language">ভাষা</string>
@ -359,4 +363,10 @@
<item>Not Bypass</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
</resources>

View file

@ -140,6 +140,7 @@
<!-- Preferences -->
<string name="title_settings">سامووا</string>
<string name="title_advanced">سامووا پؽش رئڌه</string>
<string name="title_core_settings">سامووا هسته</string>
<string name="title_vpn_settings">سامووا VPN</string>
<string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string>
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه واجۊری بیڌه پروکسی هڌ، منپیز موستقیم بؽ نشووه هڌ. هالت دور زیڌن: برنومه نشووک ناڌه موستقیمن منپیز هڌ، پروکسی نشووک زیڌه نؽڌ. گۊزینه پسند خوتکار برنومه پروکسی من نومگه</string>
@ -167,7 +168,7 @@
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی مۉرد نزرن و عونوان نشۊوی IP ووردارین.</string>
<string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string>
<string name="summary_pref_local_dns_enabled">DNS پردازشت وابیڌه و دس هسته ماژول DNS (پؽشنهاڌ ابۊ، ٱر نیاز هڌ ک جوستن تور وو ولات ٱسلین دور زنی)</string>
<string name="summary_pref_local_dns_enabled">درخاستا DNS و هسته و من ایان وو و دست ماژول DNS پردازشت ابۊن (پؽشنهاڌ ابۊ ٱر لنگ تور جوستن سی دور زیڌن نشۊویا LAN وو وولات ٱسلی هڌین فعال بۊ)</string>
<string name="title_pref_fake_dns_enabled">ر وندن DNS جئلی</string>
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
@ -175,16 +176,18 @@
<string name="title_pref_prefer_ipv6">ترجی IPv6</string>
<string name="summary_pref_prefer_ipv6">تورا IPv6 ن فعال کۊنین وو نشۊویا IPv6 ن ترجی بڌین</string>
<string name="title_pref_remote_dns">DNS ز ر دیر (اختیاری) (udp/tcp/https/quic) (اختیاری)</string>
<string name="title_pref_remote_dns">ز ر دیر (اختیاری) DNS (udp/tcp/https/quic) (اختیاری)</string>
<string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string>
<string name="title_pref_vpn_bypass_lan">ز شبکه مهلی اگوڌرته؟ VPN</string>
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="title_pref_vpn_interface_address">نشۊوی رابت VPN</string>
<string name="title_pref_domestic_dns">منی (اختیاری) DNS</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS هاست موستقیم (قالوو: دامنه: نشۊوی،...)</string>
<string name="title_pref_dns_hosts">هاست موستقیم (قالوو: دامنه: نشۊوی،...) DNS</string>
<string name="summary_pref_dns_hosts">دامنه:نشۊوی،...</string>
<string name="title_pref_delay_test_url">نشۊوی اینترنتی آزمایش تئخیر واقعی (http/https)</string>
@ -204,20 +207,20 @@
<string name="summary_pref_local_dns_port">پورت DNS مهلی</string>
<string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string>
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ</string>
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ.</string>
<string name="title_pref_start_scan_immediate">زی اسکنن ر ون</string>
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، اندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، ٱندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن وا برنومه ن ز نۊ ر ونین.</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن، وا برنومه ن ز نۊ ر ونین.</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">فشناڌن منشڌ</string>
<string name="summary_pref_feedback">فشناڌن منشڌ یا داسوو موشکلا من Github</string>
<string name="summary_pref_feedback">فشناڌن منشڌ یا داسووݩ موشکلا من Github</string>
<string name="summary_pref_tg_group">ٱووڌن من جرگه تلگرام</string>
<string name="toast_tg_app_not_found">برنومه تلگرامن نجوست</string>
<string name="title_privacy_policy">هریم سیخومی</string>
@ -238,6 +241,7 @@
<string name="title_pref_auto_update_interval">فاسله ورۊ کردن خوتکار (اقلن وا 15 دؽقه بۊ)</string>
<string name="title_core_loglevel">سئت داسووا</string>
<string name="title_outbound_domain_resolve_method">بارت پؽش هل دامنه دری</string>
<string name="title_mode">هالت</string>
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
<string name="title_language">زۉݩ</string>
@ -369,4 +373,10 @@
<item>دور زیڌه نبۊ</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>هل وو فسل مکۊنین</item>
<item>هل وو ٱووردن و میزبووݩ یل دامنه DNS</item>
<item>هل وو جایونی دامنه</item>
</string-array>
</resources>

View file

@ -137,6 +137,7 @@
<!-- Preferences -->
<string name="title_settings">تنظیمات</string>
<string name="title_advanced">تنظیمات پیشرفته</string>
<string name="title_core_settings">تنظیمات هسته</string>
<string name="title_vpn_settings">تنظیمات VPN</string>
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه انتخاب شده از طریق یک پروکسی متصل می شود، برنامه انتخاب نشده مستقیماً متصل می شود. \nحالت دور زدن: برنامه انتخاب شده مستقیماً متصل می شود، برنامه انتخاب نشده از طریق یک پروکسی متصل می شود. \nانتخاب خودکار برنامه های پراکسی در منو امکان پذیر است.</string>
@ -179,6 +180,8 @@
<string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -235,6 +238,7 @@
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string>
<string name="title_pref_auto_update_interval">فاصله به‌ روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string>
<string name="title_core_loglevel">سطح گزارشات</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">حالت</string>
<string name="title_mode_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string>
<string name="title_language">زبان</string>
@ -368,4 +372,10 @@
<item>دور زده نشود</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
</resources>

View file

@ -139,6 +139,7 @@
<!-- Preferences -->
<string name="title_settings">Настройки</string>
<string name="title_advanced">Расширенные настройки</string>
<string name="title_core_settings">Настройки ядра</string>
<string name="title_vpn_settings">Настройки VPN</string>
<string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string>
<string name="summary_pref_per_app_proxy">Основной: выбранное приложение соединяется через прокси, не выбранное — напрямую;\nРежим обхода: выбранное приложение соединяется напрямую, не выбранное — через прокси.\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string>
@ -180,6 +181,8 @@
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN пропускает LAN</string>
<string name="title_pref_vpn_interface_address">VPN частный IP</string>
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -237,6 +240,7 @@
<string name="title_pref_auto_update_interval">Интервал автообновления (минут, не менее 15)</string>
<string name="title_core_loglevel">Подробность ведения журнала</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">Режим</string>
<string name="title_mode_help">Нажмите для получения дополнительной информации</string>
<string name="title_language">Язык</string>
@ -368,4 +372,10 @@
<item>Не пропускает</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
</resources>

View file

@ -138,6 +138,7 @@
<!-- Preferences -->
<string name="title_settings">Cài đặt</string>
<string name="title_advanced">Cài đặt nâng cao</string>
<string name="title_core_settings">Cài đặt lõi</string>
<string name="title_vpn_settings">Cài đặt VPN</string>
<string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string>
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
@ -181,6 +182,8 @@
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -238,6 +241,7 @@
<string name="title_pref_auto_update_interval">Thời gian cập nhật tự động (Phút, giá trị tối thiểu là 15)</string>
<string name="title_core_loglevel">Cấp độ nhật ký</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">Chế độ kết nối</string>
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
<string name="title_language">Ngôn ngữ</string>
@ -356,4 +360,10 @@
<item>Not Bypass</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
</resources>

View file

@ -137,6 +137,7 @@
<!-- Preferences -->
<string name="title_settings">设置</string>
<string name="title_advanced">进阶设置</string>
<string name="title_core_settings">核心设置</string>
<string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用</string>
<string name="summary_pref_per_app_proxy">常规: 勾选的 App 被代理, 未勾选的直连;\n绕行模式: 勾选的 App 直连, 未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
@ -178,6 +179,8 @@
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN 是否绕过局域网</string>
<string name="title_pref_vpn_interface_address">VPN 接口地址</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -235,6 +238,7 @@
<string name="title_pref_auto_update_interval">自动更新间隔(分钟,最小值 15</string>
<string name="title_core_loglevel">日志级别</string>
<string name="title_outbound_domain_resolve_method">Outbound 域名预解析方式</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">点此查看更多帮助</string>
<string name="title_language">语言</string>
@ -360,4 +364,10 @@
<item>不绕过</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>不解析</item>
<item>解析后添加至 DNS Hosts</item>
<item>解析后替换原域名</item>
</string-array>
</resources>

View file

@ -138,6 +138,7 @@
<!-- Preferences -->
<string name="title_settings">設定</string>
<string name="title_advanced">進階</string>
<string name="title_core_settings">核心設定</string>
<string name="title_vpn_settings">VPN 設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
<string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy未勾選的直接連線\n繞行模式勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string>
@ -180,6 +181,8 @@
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN 是否繞過區域網</string>
<string name="title_pref_vpn_interface_address">VPN 介面位址</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
@ -236,6 +239,7 @@
<string name="title_pref_auto_update_interval">自動更新間隔(分鐘,最小值 15</string>
<string name="title_core_loglevel">記錄層級</string>
<string name="title_outbound_domain_resolve_method">Outbound 網域預解析方式</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">輕觸以檢視說明</string>
<string name="title_language">語言</string>
@ -360,4 +364,10 @@
<item>不繞過</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>不解析</item>
<item>解析後加入 DNS Hosts</item>
<item>解析後替換原網域名稱</item>
</string-array>
</resources>

View file

@ -182,4 +182,30 @@
<item>2</item>
</string-array>
<string-array name="vpn_interface_address_value" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</string-array>
<string-array name="vpn_interface_address">
<item>10.10.14.x</item>
<item>10.1.0.x</item>
<item>10.0.0.x</item>
<item>172.31.0.x</item>
<item>172.20.0.x</item>
<item>172.16.0.x</item>
<item>192.168.100.x</item>
</string-array>
<string-array name="outbound_domain_resolve_method_value" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>

View file

@ -140,6 +140,7 @@
<!-- Preferences -->
<string name="title_settings">Settings</string>
<string name="title_advanced">Advanced Settings</string>
<string name="title_core_settings">Core Settings</string>
<string name="title_vpn_settings">VPN Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string>
<string name="summary_pref_per_app_proxy">General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string>
@ -182,6 +183,8 @@
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@ -239,6 +242,7 @@
<string name="title_pref_auto_update_interval">Auto Update Interval (Minutes, Min value 15)</string>
<string name="title_core_loglevel">Log Level</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">Mode</string>
<string name="title_mode_help">Click me for more help</string>
<string name="title_language">Language</string>
@ -370,4 +374,10 @@
<item>Not Bypass</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
</resources>

View file

@ -20,9 +20,9 @@
<PreferenceCategory android:title="@string/title_vpn_settings">
<CheckBoxPreference
android:key="pref_prefer_ipv6"
android:summary="@string/summary_pref_prefer_ipv6"
android:title="@string/title_pref_prefer_ipv6" />
android:key="pref_prefer_ipv6"
android:summary="@string/summary_pref_prefer_ipv6"
android:title="@string/title_pref_prefer_ipv6" />
<CheckBoxPreference
android:key="pref_per_app_proxy"
@ -56,12 +56,20 @@
android:title="@string/title_pref_vpn_dns" />
<ListPreference
android:defaultValue="0"
android:defaultValue="1"
android:entries="@array/vpn_bypass_lan"
android:entryValues="@array/vpn_bypass_lan_value"
android:key="pref_vpn_bypass_lan"
android:summary="%s"
android:title="@string/title_pref_vpn_bypass_lan" />
<ListPreference
android:defaultValue="0"
android:entries="@array/vpn_interface_address"
android:entryValues="@array/vpn_interface_address_value"
android:key="pref_vpn_interface_address_config_index"
android:summary="%s"
android:title="@string/title_pref_vpn_interface_address" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/title_ui_settings">
@ -171,9 +179,7 @@
android:title="@string/title_pref_auto_update_interval" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/title_advanced"
app:initialExpandedChildrenCount="0">
<PreferenceCategory android:title="@string/title_core_settings">
<CheckBoxPreference
android:defaultValue="false"
@ -208,11 +214,6 @@
android:summary="@string/summary_pref_dns_hosts"
android:title="@string/title_pref_dns_hosts" />
<EditTextPreference
android:key="pref_delay_test_url"
android:summary="@string/summary_pref_delay_test_url"
android:title="@string/title_pref_delay_test_url" />
<ListPreference
android:defaultValue="warning"
android:entries="@array/core_loglevel"
@ -221,6 +222,23 @@
android:summary="%s"
android:title="@string/title_core_loglevel" />
<ListPreference
android:defaultValue="1"
android:entries="@array/outbound_domain_resolve_method"
android:entryValues="@array/outbound_domain_resolve_method_value"
android:key="pref_outbound_domain_resolve_method"
android:summary="%s"
android:title="@string/title_outbound_domain_resolve_method" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/title_advanced">
<EditTextPreference
android:key="pref_delay_test_url"
android:summary="@string/summary_pref_delay_test_url"
android:title="@string/title_pref_delay_test_url" />
<ListPreference
android:defaultValue="VPN"
android:entries="@array/mode_entries"
@ -228,6 +246,7 @@
android:key="pref_mode"
android:summary="%s"
android:title="@string/title_mode" />
</PreferenceCategory>
</PreferenceScreen>

View file

@ -10,31 +10,31 @@ class HttpUtilTest {
fun testIdnToASCII() {
// Regular URL remains unchanged
val regularUrl = "https://example.com/path"
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
assertEquals(regularUrl, HttpUtil.toIdnUrl(regularUrl))
// Non-ASCII URL converts to ASCII (Punycode)
val nonAsciiUrl = "https://例子.测试/path"
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl))
assertEquals(expectedNonAscii, HttpUtil.toIdnUrl(nonAsciiUrl))
// Mixed URL only converts the host part
val mixedUrl = "https://例子.com/测试"
val expectedMixed = "https://xn--fsqu00a.com/测试"
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
assertEquals(expectedMixed, HttpUtil.toIdnUrl(mixedUrl))
// URL with Basic Authentication using regular domain
val basicAuthUrl = "https://user:password@example.com/path"
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl))
assertEquals(basicAuthUrl, HttpUtil.toIdnUrl(basicAuthUrl))
// URL with Basic Authentication using non-ASCII domain
val basicAuthNonAscii = "https://user:password@例子.测试/path"
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii))
assertEquals(expectedBasicAuthNonAscii, HttpUtil.toIdnUrl(basicAuthNonAscii))
// URL with non-ASCII username and password
val nonAsciiAuth = "https://用户:密码@example.com/path"
// Basic auth credentials should remain unchanged as they're percent-encoded separately
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
assertEquals(nonAsciiAuth, HttpUtil.toIdnUrl(nonAsciiAuth))
}

View file

@ -1,5 +1,5 @@
[versions]
agp = "8.10.0"
agp = "8.10.1"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.21"
@ -7,7 +7,7 @@ coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
appcompat = "1.7.1"
material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
@ -20,8 +20,8 @@ swiperefreshlayout = "1.1.0"
toasty = "1.5.2"
editorkit = "2.9.0"
core = "3.5.3"
workRuntimeKtx = "2.10.1"
lifecycleViewmodelKtx = "2.9.0"
workRuntimeKtx = "2.10.2"
lifecycleViewmodelKtx = "2.9.1"
multidex = "2.0.1"
mockitoMockitoInline = "5.2.0"
flexbox = "3.0.0"