Unverified Commit 6d1f9310 authored by Mygod's avatar Mygod Committed by GitHub

Merge pull request #3 from Mygod/dns-server-no-dnsgw

DNS over UDP and various other improvements
parents 5425b068 c9ba87da
...@@ -33,11 +33,10 @@ import java.io.FileNotFoundException ...@@ -33,11 +33,10 @@ import java.io.FileNotFoundException
object Executable { object Executable {
const val REDSOCKS = "libredsocks.so" const val REDSOCKS = "libredsocks.so"
const val SS_LOCAL = "libss-local.so" const val SS_LOCAL = "libss-local.so"
const val SS_TUNNEL = "libss-tunnel.so"
const val TUN2SOCKS = "libtun2socks.so" const val TUN2SOCKS = "libtun2socks.so"
const val OVERTURE = "liboverture.so" const val OVERTURE = "liboverture.so"
private val EXECUTABLES = setOf(SS_LOCAL, SS_TUNNEL, REDSOCKS, TUN2SOCKS, OVERTURE) private val EXECUTABLES = setOf(SS_LOCAL, REDSOCKS, TUN2SOCKS, OVERTURE)
fun killAll() { fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) { for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
......
...@@ -28,6 +28,7 @@ import com.github.shadowsocks.net.Socks5Endpoint ...@@ -28,6 +28,7 @@ import com.github.shadowsocks.net.Socks5Endpoint
import com.github.shadowsocks.net.Subnet import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.URI
import java.util.* import java.util.*
object LocalDnsService { object LocalDnsService {
...@@ -43,11 +44,12 @@ object LocalDnsService { ...@@ -43,11 +44,12 @@ object LocalDnsService {
interface Interface : BaseService.Interface { interface Interface : BaseService.Interface {
override suspend fun startProcesses() { override suspend fun startProcesses() {
super.startProcesses() super.startProcesses()
val data = data
val profile = data.proxy!!.profile val profile = data.proxy!!.profile
if (!profile.udpdns) servers[this] = LocalDnsServer(this::resolver, val dns = URI("dns://${profile.remoteDns}")
Socks5Endpoint(profile.remoteDns.split(",").first(), 53), servers[this] = LocalDnsServer(this::resolver,
Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port),
DataStore.proxyAddress).apply { DataStore.proxyAddress).apply {
tcp = !profile.udpdns
when (profile.route) { when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> { Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> {
remoteDomainMatcher = googleApisTester remoteDomainMatcher = googleApisTester
......
...@@ -47,7 +47,7 @@ import java.security.MessageDigest ...@@ -47,7 +47,7 @@ import java.security.MessageDigest
* This class sets up environment for ss-local. * This class sets up environment for ss-local.
*/ */
class ProxyInstance(val profile: Profile, private val route: String = profile.route) : AutoCloseable { class ProxyInstance(val profile: Profile, private val route: String = profile.route) : AutoCloseable {
var configFile: File? = null private var configFile: File? = null
var trafficMonitor: TrafficMonitor? = null var trafficMonitor: TrafficMonitor? = null
private val plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions private val plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions
val pluginPath by lazy { PluginManager.init(plugin) } val pluginPath by lazy { PluginManager.init(plugin) }
...@@ -115,7 +115,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -115,7 +115,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
} }
// for UDP profile, it's only going to operate in UDP relay mode-only so this flag has no effect // for UDP profile, it's only going to operate in UDP relay mode-only so this flag has no effect
if (profile.udpdns) cmd += "-D" if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_LAN) cmd += "-D"
if (DataStore.tcpFastOpen) cmd += "--fast-open" if (DataStore.tcpFastOpen) cmd += "--fast-open"
......
...@@ -36,20 +36,6 @@ class TransproxyService : Service(), LocalDnsService.Interface { ...@@ -36,20 +36,6 @@ class TransproxyService : Service(), LocalDnsService.Interface {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId) super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
private fun startDNSTunnel() {
val proxy = data.proxy!!
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.SS_TUNNEL).absolutePath,
"-t", "10",
"-b", DataStore.listenAddress,
"-u",
"-l", DataStore.portLocalDns.toString(), // ss-tunnel listens on the same port as overture
"-L", proxy.profile.remoteDns.split(",").first().trim() + ":53",
// config is already built by BaseService.Interface
"-c", (data.udpFallback ?: proxy).configFile!!.absolutePath)
if (DataStore.tcpFastOpen) cmd += "--fast-open"
data.processes!!.start(cmd)
}
private fun startRedsocksDaemon() { private fun startRedsocksDaemon() {
File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base { File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base {
log_debug = off; log_debug = off;
...@@ -73,7 +59,6 @@ redsocks { ...@@ -73,7 +59,6 @@ redsocks {
override suspend fun startProcesses() { override suspend fun startProcesses() {
startRedsocksDaemon() startRedsocksDaemon()
super.startProcesses() super.startProcesses()
if (data.proxy!!.profile.udpdns) startDNSTunnel()
} }
override fun onDestroy() { override fun onDestroy() {
......
...@@ -39,7 +39,6 @@ import com.github.shadowsocks.net.DefaultNetworkListener ...@@ -39,7 +39,6 @@ import com.github.shadowsocks.net.DefaultNetworkListener
import com.github.shadowsocks.net.Subnet import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import java.io.Closeable import java.io.Closeable
...@@ -52,8 +51,10 @@ import android.net.VpnService as BaseVpnService ...@@ -52,8 +51,10 @@ import android.net.VpnService as BaseVpnService
class VpnService : BaseVpnService(), LocalDnsService.Interface { class VpnService : BaseVpnService(), LocalDnsService.Interface {
companion object { companion object {
private const val VPN_MTU = 1500 private const val VPN_MTU = 1500
private const val PRIVATE_VLAN = "172.19.0.%s" private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1"
private const val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s" private const val PRIVATE_VLAN4_ROUTER = "172.19.0.2"
private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1"
private const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2"
/** /**
* https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466 * https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466
...@@ -136,9 +137,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -136,9 +137,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override suspend fun startProcesses() { override suspend fun startProcesses() {
worker = ProtectWorker().apply { start() } worker = ProtectWorker().apply { start() }
super.startProcesses() super.startProcesses()
sendFd(startVpn()) sendFd(startVpn())
} }
...@@ -153,12 +152,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -153,12 +152,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
.setConfigureIntent(Core.configureIntent(this)) .setConfigureIntent(Core.configureIntent(this))
.setSession(profile.formattedName) .setSession(profile.formattedName)
.setMtu(VPN_MTU) .setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN.format(Locale.ENGLISH, "1"), 24) .addAddress(PRIVATE_VLAN4_CLIENT, 30)
.addDnsServer(PRIVATE_VLAN4_ROUTER)
profile.remoteDns.split(",").forEach { builder.addDnsServer(it.trim()) }
if (profile.ipv6) { if (profile.ipv6) {
builder.addAddress(PRIVATE_VLAN6.format(Locale.ENGLISH, "1"), 126) builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
builder.addRoute("::", 0) builder.addRoute("::", 0)
} }
...@@ -179,13 +177,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -179,13 +177,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
when (profile.route) { when (profile.route) {
Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0) Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0)
else -> { else -> resources.getStringArray(R.array.bypass_private_route).forEach {
resources.getStringArray(R.array.bypass_private_route).forEach { val subnet = Subnet.fromString(it)!!
val subnet = Subnet.fromString(it)!! builder.addRoute(subnet.address.hostAddress, subnet.prefixSize)
builder.addRoute(subnet.address.hostAddress, subnet.prefixSize)
}
profile.remoteDns.split(",").mapNotNull { it.trim().parseNumericAddress() }
.forEach { builder.addRoute(it, it.address.size shl 3) }
} }
} }
...@@ -197,22 +191,19 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -197,22 +191,19 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
val fd = conn.fd val fd = conn.fd
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath, val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"), "--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
"--netif-netmask", "255.255.255.0", "--netif-netmask", "255.255.255.0",
"--socks-server-addr", "${DataStore.listenAddress}:${DataStore.portProxy}", "--socks-server-addr", "${DataStore.listenAddress}:${DataStore.portProxy}",
"--tunfd", fd.toString(), "--tunfd", fd.toString(),
"--tunmtu", VPN_MTU.toString(), "--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path", "--sock-path", "sock_path",
"--dnsgw", "127.0.0.1:${DataStore.portLocalDns}",
"--loglevel", "3") "--loglevel", "3")
if (profile.ipv6) { if (profile.ipv6) {
cmd += "--netif-ip6addr" cmd += "--netif-ip6addr"
cmd += PRIVATE_VLAN6.format(Locale.ENGLISH, "2") cmd += PRIVATE_VLAN6_ROUTER
} }
cmd += "--enable-udprelay" cmd += "--enable-udprelay"
if (!profile.udpdns) {
cmd += "--dnsgw"
cmd += "127.0.0.1:${DataStore.portLocalDns}"
}
data.processes!!.start(cmd, onRestartCallback = { data.processes!!.start(cmd, onRestartCallback = {
try { try {
sendFd(conn.fileDescriptor) sendFd(conn.fileDescriptor)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.net package com.github.shadowsocks.net
import android.os.Build
import android.os.SystemClock import android.os.SystemClock
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
...@@ -29,12 +30,12 @@ import com.github.shadowsocks.acl.Acl ...@@ -29,12 +30,12 @@ import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.responseLength
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL
import java.net.URLConnection
/** /**
* Based on: https://android.googlesource.com/platform/frameworks/base/+/b19a838/services/core/java/com/android/server/connectivity/NetworkMonitor.java#1071 * Based on: https://android.googlesource.com/platform/frameworks/base/+/b19a838/services/core/java/com/android/server/connectivity/NetworkMonitor.java#1071
...@@ -116,4 +117,7 @@ class HttpsTest : ViewModel() { ...@@ -116,4 +117,7 @@ class HttpsTest : ViewModel() {
cancelTest() cancelTest()
status.value = Status.Idle status.value = Status.Idle
} }
private val URLConnection.responseLength: Long
get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong()
} }
package com.github.shadowsocks.net package com.github.shadowsocks.net
import com.github.shadowsocks.utils.shutdown import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.IOException
abstract class SocketListener(name: String) : Thread(name), AutoCloseable { abstract class SocketListener(name: String) : Thread(name), AutoCloseable {
protected abstract val fileDescriptor: FileDescriptor protected abstract val fileDescriptor: FileDescriptor
@Volatile @Volatile
protected var running = true protected var running = true
private fun FileDescriptor.shutdown() {
// see also: https://issuetracker.google.com/issues/36945762#comment15
if (valid()) try {
Os.shutdown(this, OsConstants.SHUT_RDWR)
} catch (e: ErrnoException) {
// suppress fd inactive or already closed
if (e.errno != OsConstants.EBADF && e.errno != OsConstants.ENOTCONN) throw IOException(e)
}
}
override fun close() { override fun close() {
running = false running = false
fileDescriptor.shutdown() fileDescriptor.shutdown()
......
...@@ -25,13 +25,12 @@ import androidx.preference.PreferenceDataStore ...@@ -25,13 +25,12 @@ import androidx.preference.PreferenceDataStore
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.database.PrivateDatabase import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase import com.github.shadowsocks.database.PublicDatabase
import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.Proxy
import java.net.SocketException import java.net.SocketException
object DataStore : OnPreferenceDataStoreChangeListener { object DataStore : OnPreferenceDataStoreChangeListener {
......
...@@ -30,17 +30,13 @@ import android.graphics.BitmapFactory ...@@ -30,17 +30,13 @@ import android.graphics.BitmapFactory
import android.graphics.ImageDecoder import android.graphics.ImageDecoder
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.system.ErrnoException
import android.system.Os import android.system.Os
import android.system.OsConstants import android.system.OsConstants
import android.util.TypedValue import android.util.TypedValue
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.preference.Preference import androidx.preference.Preference
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import java.io.FileDescriptor
import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.URLConnection
private val parseNumericAddress by lazy { private val parseNumericAddress by lazy {
InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply { InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply {
...@@ -55,16 +51,6 @@ private val parseNumericAddress by lazy { ...@@ -55,16 +51,6 @@ private val parseNumericAddress by lazy {
fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this) fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress } ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress }
fun FileDescriptor.shutdown() {
// see also: https://issuetracker.google.com/issues/36945762#comment15
if (valid()) try {
Os.shutdown(this, OsConstants.SHUT_RDWR)
} catch (e: ErrnoException) {
// suppress fd inactive or already closed
if (e.errno != OsConstants.EBADF && e.errno != OsConstants.ENOTCONN) throw IOException(e)
}
}
fun parsePort(str: String?, default: Int, min: Int = 1025): Int { fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
val value = str?.toIntOrNull() ?: default val value = str?.toIntOrNull() ?: default
return if (value < min || value > 65535) default else value return if (value < min || value > 65535) default else value
...@@ -74,9 +60,6 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = ...@@ -74,9 +60,6 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =
override fun onReceive(context: Context, intent: Intent) = callback(context, intent) override fun onReceive(context: Context, intent: Intent) = callback(context, intent)
} }
val URLConnection.responseLength: Long
get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong()
fun ContentResolver.openBitmap(uri: Uri) = fun ContentResolver.openBitmap(uri: Uri) =
if (Build.VERSION.SDK_INT >= 28) ImageDecoder.decodeBitmap(ImageDecoder.createSource(this, uri)) if (Build.VERSION.SDK_INT >= 28) ImageDecoder.decodeBitmap(ImageDecoder.createSource(this, uri))
else BitmapFactory.decodeStream(openInputStream(uri)) else BitmapFactory.decodeStream(openInputStream(uri))
......
...@@ -278,39 +278,6 @@ LOCAL_LDLIBS := -llog ...@@ -278,39 +278,6 @@ LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_EXECUTABLE) include $(BUILD_SHARED_EXECUTABLE)
########################################################
## shadowsocks-libev tunnel
########################################################
include $(CLEAR_VARS)
SHADOWSOCKS_SOURCES := tunnel.c \
cache.c udprelay.c utils.c netutils.c json.c jconf.c \
crypto.c aead.c stream.c base64.c \
plugin.c ppbloom.c \
android.c
LOCAL_MODULE := ss-tunnel
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
LOCAL_CFLAGS := -Wall -fno-strict-aliasing -DMODULE_TUNNEL \
-DUSE_CRYPTO_MBEDTLS -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
-DCONNECT_IN_PROGRESS=EINPROGRESS \
-I$(LOCAL_PATH)/libancillary \
-I$(LOCAL_PATH)/include \
-I$(LOCAL_PATH)/libsodium/src/libsodium/include \
-I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \
-I$(LOCAL_PATH)/mbedtls/include \
-I$(LOCAL_PATH)/libev \
-I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \
-I$(LOCAL_PATH)/shadowsocks-libev/libbloom \
-I$(LOCAL_PATH)/include/shadowsocks-libev
LOCAL_STATIC_LIBRARIES := libev libmbedtls libsodium libcork libbloom libancillary
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_EXECUTABLE)
######################################################## ########################################################
## tun2socks ## tun2socks
######################################################## ########################################################
...@@ -358,7 +325,6 @@ TUN2SOCKS_SOURCES := \ ...@@ -358,7 +325,6 @@ TUN2SOCKS_SOURCES := \
flow/PacketProtoDecoder.c \ flow/PacketProtoDecoder.c \
socksclient/BSocksClient.c \ socksclient/BSocksClient.c \
tuntap/BTap.c \ tuntap/BTap.c \
lwip/src/core/timers.c \
lwip/src/core/udp.c \ lwip/src/core/udp.c \
lwip/src/core/memp.c \ lwip/src/core/memp.c \
lwip/src/core/init.c \ lwip/src/core/init.c \
...@@ -367,14 +333,16 @@ TUN2SOCKS_SOURCES := \ ...@@ -367,14 +333,16 @@ TUN2SOCKS_SOURCES := \
lwip/src/core/tcp_out.c \ lwip/src/core/tcp_out.c \
lwip/src/core/netif.c \ lwip/src/core/netif.c \
lwip/src/core/def.c \ lwip/src/core/def.c \
lwip/src/core/ip.c \
lwip/src/core/mem.c \ lwip/src/core/mem.c \
lwip/src/core/tcp_in.c \ lwip/src/core/tcp_in.c \
lwip/src/core/stats.c \ lwip/src/core/stats.c \
lwip/src/core/inet_chksum.c \ lwip/src/core/inet_chksum.c \
lwip/src/core/timeouts.c \
lwip/src/core/ipv4/icmp.c \ lwip/src/core/ipv4/icmp.c \
lwip/src/core/ipv4/igmp.c \ lwip/src/core/ipv4/igmp.c \
lwip/src/core/ipv4/ip4_addr.c \ lwip/src/core/ipv4/ip4_addr.c \
lwip/src/core/ipv4/ip_frag.c \ lwip/src/core/ipv4/ip4_frag.c \
lwip/src/core/ipv4/ip4.c \ lwip/src/core/ipv4/ip4.c \
lwip/src/core/ipv4/autoip.c \ lwip/src/core/ipv4/autoip.c \
lwip/src/core/ipv6/ethip6.c \ lwip/src/core/ipv6/ethip6.c \
......
Subproject commit 4c0050878618423da53e7e335fbb55ec2af6be06 Subproject commit ba4c45d6b4d30fd6b2557e92a770ccae12ec9257
...@@ -57,8 +57,8 @@ ...@@ -57,8 +57,8 @@
<string name="tcp_fastopen_summary">Toggling might require ROOT permission</string> <string name="tcp_fastopen_summary">Toggling might require ROOT permission</string>
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string> <string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="tcp_fastopen_failure">Toggle failed</string> <string name="tcp_fastopen_failure">Toggle failed</string>
<string name="udp_dns">DNS Forwarding</string> <string name="udp_dns">Send DNS over UDP</string>
<string name="udp_dns_summary">Forward all DNS requests to remote</string> <string name="udp_dns_summary">Requires UDP forwarding on server side</string>
<string name="udp_fallback">UDP Fallback</string> <string name="udp_fallback">UDP Fallback</string>
<!-- notification category --> <!-- notification category -->
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment