Commit 2180a51f authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Merge branch 'master' of...

Merge branch 'master' of https://git.frostnerd.com/PublicAndroidApps/smokescreen into 185-request-advanced-backup-and-restore
parents 9f2c6766 22924ff9
......@@ -13,6 +13,7 @@ import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import java.io.StringReader
import java.io.StringWriter
import java.util.*
import kotlin.reflect.KProperty
/*
......@@ -53,7 +54,7 @@ class UserServerConfigurationPreference(key: String, defaultValue: (String) -> S
var id = 0
var info: DnsServerInformation<*>? = null
while (reader.peek() != JsonToken.END_OBJECT) {
when (reader.nextName().toLowerCase()) {
when (reader.nextName().toLowerCase(Locale.ROOT)) {
"id" -> id = reader.nextInt()
"server_https", "server" -> info = httpsTypeAdapter.read(reader)!!
"server_tls" -> info = tlsTypeAdapter.read(reader)!!
......
......@@ -7,6 +7,7 @@ import com.frostnerd.smokescreen.database.getDatabase
import com.frostnerd.smokescreen.dialog.DnsRuleDialog
import com.frostnerd.smokescreen.getPreferences
import com.frostnerd.smokescreen.util.MaxSizeMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
......@@ -49,7 +50,7 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
fun refreshRuleCount() {
previousRefreshJob?.cancel()
previousRefreshJob = GlobalScope.launch {
previousRefreshJob = GlobalScope.launch(Dispatchers.IO) {
val previousRuleCount = ruleCount
ruleCount = dao.getActiveCount().toInt()
wildcardCount = dao.getActiveWildcardCount().toInt()
......@@ -73,9 +74,9 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
}
}
private suspend fun preloadWhitelistEntries() {
private fun preloadWhitelistEntries() {
cachedNonWildcardWhitelisted.addAll(dao.getRandomNonWildcardWhitelistEntries(100).map {
hashHost(it.toLowerCase(), Record.TYPE.ANY)
hashHost(it.toLowerCase(Locale.ROOT), Record.TYPE.ANY)
})
}
......@@ -111,8 +112,8 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
uniformQuestion,
type,
useUserRules,
true,
false
includeWhitelistEntries = true,
includeNonWhitelistEntries = false
).firstOrNull {
DnsRuleDialog.databaseHostToMatcher(it.host).reset(uniformQuestion)
.matches()
......@@ -155,8 +156,8 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
uniformQuestion,
type,
useUserRules,
false,
true
includeWhitelistEntries = false,
includeNonWhitelistEntries = true
).firstOrNull {
DnsRuleDialog.databaseHostToMatcher(it.host)
.reset(uniformQuestion).matches()
......@@ -235,7 +236,13 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
} ?: throw IllegalStateException()
}
override fun cleanup() {}
override fun cleanup() {
cachedWildcardWhitelisted.clear()
cachedNonWildcardWhitelisted.clear()
cachedResolved.clear()
cachedWildcardResolved.clear()
cachedNonIncluded.clear()
}
// Handle CNAME Cloaking
// Does not need to handle whitelist as the query has already been forwarded
......
......@@ -2,6 +2,7 @@ package com.frostnerd.smokescreen.util.proxy
import com.frostnerd.dnstunnelproxy.AbstractUDPDnsHandle
import com.frostnerd.dnstunnelproxy.IPPacket
import com.frostnerd.dnstunnelproxy.Packet
import com.frostnerd.dnstunnelproxy.UpstreamAddress
import com.frostnerd.vpntunnelproxy.DeviceWriteToken
import com.frostnerd.vpntunnelproxy.FutureAnswer
......@@ -51,7 +52,7 @@ class ProxyBypassHandler(private val searchDomains:List<String>, private val des
override suspend fun forwardDnsQuestion(
deviceWriteToken: DeviceWriteToken,
dnsMessage: DnsMessage,
originalEnvelope: IPPacket,
originalEnvelope: Packet,
realDestination: UpstreamAddress
) {
val bytes = dnsMessage.toArray()
......
......@@ -27,15 +27,15 @@ import java.net.InetAddress
* You can contact the developer at daniel.wolf@frostnerd.com.
*/
class ProxyHttpsHandler(
private val ownAddresses:List<String>,
serverConfigurations: List<ServerConfiguration>,
connectTimeout: Long,
val queryCountCallback: ((queryCount: Int) -> Unit)? = null,
val queryCountCallback: (() -> Unit)? = null,
val mapQueryRefusedToHostBlock:Boolean
) :
AbstractHttpsDNSHandle(serverConfigurations, connectTimeout) {
override val handlesSpecificRequests: Boolean = ProxyBypassHandler.knownSearchDomains.isNotEmpty()
private val dummyUpstreamAddress = UpstreamAddress(AddressCreator.fromHostAddress("0.0.0.0"), 1)
private var queryCount = 0
override fun name(): String {
return "ProxyHttpsHandler"
......@@ -50,12 +50,6 @@ class ProxyHttpsHandler(
} else true
}
constructor(
serverConfiguration: ServerConfiguration,
connectTimeout: Long,
mapQueryRefusedToHostBlock:Boolean
) : this(listOf(serverConfiguration), connectTimeout, mapQueryRefusedToHostBlock = mapQueryRefusedToHostBlock)
override suspend fun modifyUpstreamResponse(dnsMessage: DnsMessage): DnsMessage {
return if(dnsMessage.responseCode == DnsMessage.RESPONSE_CODE.REFUSED) {
if(dnsMessage.questions.isNotEmpty()) {
......@@ -76,11 +70,11 @@ class ProxyHttpsHandler(
}
override suspend fun remapDestination(destinationAddress: InetAddress, port: Int): UpstreamAddress {
queryCountCallback?.invoke(++queryCount)
queryCountCallback?.invoke()
return dummyUpstreamAddress
}
override suspend fun shouldHandleDestination(destinationAddress: InetAddress, port: Int): Boolean = true
override suspend fun shouldHandleDestination(destinationAddress: InetAddress, port: Int): Boolean = ownAddresses.any { it.equals(destinationAddress.hostAddress, true) }
override suspend fun shouldModifyUpstreamResponse(answer: ReceivedAnswer, receivedPayload: ByteArray): Boolean =
mapQueryRefusedToHostBlock
......
package com.frostnerd.smokescreen.util.proxy
import com.frostnerd.dnstunnelproxy.DnsPacketProxy
import com.frostnerd.dnstunnelproxy.IPPacket
import com.frostnerd.dnstunnelproxy.Packet
import com.frostnerd.dnstunnelproxy.UpstreamAddress
import com.frostnerd.encrypteddnstunnelproxy.tls.AbstractTLSDnsHandle
import com.frostnerd.encrypteddnstunnelproxy.tls.TLSUpstreamAddress
......@@ -39,20 +41,20 @@ import javax.net.ssl.SSLSession
*/
class ProxyTlsHandler(
private val ownAddresses:List<String>,
private val upstreamAddresses: List<TLSUpstreamAddress>,
connectTimeout: Int,
val queryCountCallback: ((queryCount: Int) -> Unit)? = null,
val queryCountCallback: (() -> Unit)? = null,
val mapQueryRefusedToHostBlock:Boolean
):AbstractTLSDnsHandle(connectTimeout) {
override val handlesSpecificRequests: Boolean =
ProxyBypassHandler.knownSearchDomains.isNotEmpty()
private val hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
private var queryCount = 0
override suspend fun forwardDnsQuestion(
deviceWriteToken: DeviceWriteToken,
dnsMessage: DnsMessage,
originalEnvelope: IPPacket,
originalEnvelope: Packet,
realDestination: UpstreamAddress
) {
val destination = selectAddressOrNull(realDestination)
......@@ -90,13 +92,11 @@ class ProxyTlsHandler(
}
override suspend fun remapDestination(destinationAddress: InetAddress, port: Int): TLSUpstreamAddress {
queryCountCallback?.invoke(++queryCount)
queryCountCallback?.invoke()
return upstreamAddresses[0]
}
override suspend fun shouldHandleDestination(destinationAddress: InetAddress, port: Int): Boolean {
return true
}
override suspend fun shouldHandleDestination(destinationAddress: InetAddress, port: Int): Boolean = ownAddresses.any { it.equals(destinationAddress.hostAddress, true) }
override suspend fun shouldHandleRequest(dnsMessage: DnsMessage): Boolean {
return if(dnsMessage.questions.size > 0) {
......
......@@ -111,10 +111,9 @@ class QueryListener(private val context: Context) : QueryListener {
} ?: return
val wasInserted = queryLogState.remove(responseMessage.id)!! != 0 // Update if already inserted (0=insert)
query.responseTime = System.currentTimeMillis()
query.responses = mutableListOf()
for (answer in responseMessage.answerSection) {
query.addResponse(answer)
}
query.responses = responseMessage.answerSection.map {
query.encodeResponse(it)
}.toMutableList()
query.responseSource = source
doneQueries[query] = wasInserted
}
......
......@@ -2,6 +2,7 @@ package com.frostnerd.smokescreen.util.proxy
import com.frostnerd.dnstunnelproxy.*
import com.frostnerd.dnstunnelproxy.QueryListener
import java.net.InetAddress
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
......@@ -38,4 +39,26 @@ class SmokeProxy(
cache,
queryListener = queryListener,
localResolver = localResolver
)
class NonIPSmokeProxy(
dnsHandle: DnsHandle,
proxyBypassHandles: List<DnsHandle>,
val cache: SimpleDnsCache?,
queryListener: QueryListener?,
localResolver: LocalResolver?,
bindAddress:InetAddress,
bindPort:Int
) :
NonIPDnsPacketProxy(
proxyBypassHandles.toMutableList().let {
it.add(dnsHandle)
it
}.toList(),
null,
cache,
queryListener = queryListener,
localResolver = localResolver,
localAddress = bindAddress,
localPort = bindPort
)
\ No newline at end of file
package com.frostnerd.smokescreen.util.speedtest
import androidx.annotation.IntRange
import cn.danielw.fop.ObjectFactory
import cn.danielw.fop.ObjectPool
import cn.danielw.fop.PoolConfig
import cn.danielw.fop.Poolable
import com.frostnerd.dnstunnelproxy.DnsServerInformation
import com.frostnerd.dnstunnelproxy.UpstreamAddress
import com.frostnerd.encrypteddnstunnelproxy.HttpsDnsServerInformation
import com.frostnerd.encrypteddnstunnelproxy.ServerConfiguration
import com.frostnerd.encrypteddnstunnelproxy.tls.TLSUpstreamAddress
import okhttp3.Dns
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.internal.closeQuietly
import org.minidns.dnsmessage.DnsMessage
import org.minidns.dnsmessage.Question
import org.minidns.record.Record
import java.io.DataInputStream
import java.io.DataOutputStream
import java.lang.IllegalArgumentException
import java.net.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLSocketFactory
import kotlin.random.Random
......@@ -56,6 +61,29 @@ class DnsSpeedTest(val server: DnsServerInformation<*>,
it.urlCreator.address
})
}
private val connectionPool = ConcurrentHashMap<TLSUpstreamAddress, ObjectPool<Socket>>()
private var poolConfig:PoolConfig = PoolConfig().apply {
this.maxSize = 1
this.minSize = 1
this.partitionSize = 1
this.maxIdleMilliseconds = 60*1000*5
}
private val poolFactory = object: ObjectFactory<Socket> {
private val sslSocketFactory = SSLSocketFactory.getDefault()
override fun validate(t: Socket): Boolean {
return t.isClosed || !t.isConnected
}
override fun destroy(t: Socket) {
t.closeQuietly()
}
override fun create(): Socket {
return sslSocketFactory.createSocket()
}
}
companion object {
val testDomains = listOf("google.com", "frostnerd.com", "amazon.com", "youtube.com", "github.com",
"stackoverflow.com", "stackexchange.com", "spotify.com", "material.io", "reddit.com", "android.com")
......@@ -67,6 +95,7 @@ class DnsSpeedTest(val server: DnsServerInformation<*>,
*/
fun runTest(@IntRange(from = 1) passes: Int): Int? {
var ttl = 0
for (i in 0 until passes) {
if (server is HttpsDnsServerInformation) {
server.serverConfigurations.values.forEach {
......@@ -86,7 +115,14 @@ class DnsSpeedTest(val server: DnsServerInformation<*>,
private fun testHttps(config: ServerConfiguration): Int? {
val msg = createTestDnsPacket()
val url: URL = config.urlCreator.createUrl(msg, config.urlCreator.address)
log("Using URL: $url")
try {
url.toString().toHttpUrl()
log("Using URL: $url")
} catch (ignored:IllegalArgumentException) {
log("Invalid URL: $url")
return null
}
val requestBuilder = Request.Builder().url(url)
if (config.requestHasBody) {
val body = config.bodyCreator!!.createBody(msg, config.urlCreator.address)
......@@ -129,18 +165,30 @@ class DnsSpeedTest(val server: DnsServerInformation<*>,
}
}
private fun obtainTlsSocket(address: TLSUpstreamAddress): Poolable<Socket>? {
return try {
connectionPool.getOrPut(address) {
ObjectPool(poolConfig, poolFactory)
}.borrowObject()
} catch (e: RuntimeException) {
null
}
}
private fun testTls(address: TLSUpstreamAddress): Int? {
val addr =
address.addressCreator.resolveOrGetResultOrNull(retryIfError = true, runResolveNow = true) ?: run {
log("DoT test failed once for ${server.name}: Address failed to resolve ($address)")
return null
}
var socket: Socket? = null
var socketPooled: Poolable<Socket>? = null
var socket:Socket? = null
try {
socket = SSLSocketFactory.getDefault().createSocket()
socketPooled = obtainTlsSocket(address)
socket = socketPooled?.`object` ?: SSLSocketFactory.getDefault().createSocket()
val msg = createTestDnsPacket()
val start = System.currentTimeMillis()
socket.connect(InetSocketAddress(addr[0], address.port), connectTimeout)
socket!!.connect(InetSocketAddress(addr[0], address.port), connectTimeout)
socket.soTimeout = readTimeout
val data: ByteArray = msg.toArray()
val outputStream = DataOutputStream(socket.getOutputStream())
......@@ -155,7 +203,6 @@ class DnsSpeedTest(val server: DnsServerInformation<*>,
inStream.read(readData)
val time = (System.currentTimeMillis() - start).toInt()
socket.close()
socket = null
if(!testResponse(DnsMessage(readData))) {
log("DoT test failed once for ${server.name}: Testing the response for valid dns message failed")
......@@ -167,6 +214,7 @@ class DnsSpeedTest(val server: DnsServerInformation<*>,
return null
} finally {
socket?.close()
socketPooled?.returnObject()
}
}
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:paddingLeft="@dimen/dialog_horizontal_margin"
......@@ -52,7 +53,8 @@
android:imeOptions="actionNext"
android:text="12"
android:maxLines="1"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
<Spinner
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:paddingLeft="@dimen/dialog_horizontal_margin"
android:paddingRight="@dimen/dialog_horizontal_margin"
android:paddingTop="@dimen/dialog_vertical_margin"
android:paddingBottom="@dimen/dialog_vertical_margin"
android:layout_width="match_parent"
tools:ignore="HardcodedText"
android:layout_height="match_parent">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
android:orientation="vertical"
android:paddingLeft="@dimen/dialog_horizontal_margin"
android:paddingTop="@dimen/dialog_vertical_margin"
android:paddingRight="@dimen/dialog_horizontal_margin"
android:paddingBottom="@dimen/dialog_vertical_margin"
tools:ignore="HardcodedText">
<EditText
android:id="@+id/iterations"
android:layout_width="match_parent"
android:hint="Iterations"
android:layout_height="wrap_content"
android:digits="0123456789"
android:hint="Iterations"
android:importantForAutofill="no"
android:inputType="number"
android:id="@+id/iterations"
android:layout_height="wrap_content"/>
android:inputType="number" />
<EditText
android:id="@+id/delay"
android:layout_width="match_parent"
android:hint="Base delay time"
android:layout_height="wrap_content"
android:digits="0123456789"
android:hint="Base delay time"
android:importantForAutofill="no"
android:inputType="number"
android:id="@+id/delay"
android:text="20000"
android:layout_height="wrap_content"/>
android:text="20000" />
<CheckBox
android:id="@+id/baseDomains"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColor"
android:layout_height="wrap_content"
android:text="Open base domains"
android:id="@+id/baseDomains"
android:layout_height="wrap_content"/>
android:textColor="?android:attr/textColor" />
<CheckBox
android:id="@+id/deepurls"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColor"
android:layout_height="wrap_content"
android:text="Open deep urls"
android:id="@+id/deepurls"
android:layout_height="wrap_content"/>
android:textColor="?android:attr/textColor" />
<CheckBox
android:layout_width="wrap_content"
android:id="@+id/randomTimeout"
android:textColor="?android:attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add random delay"
android:layout_height="wrap_content"/>
android:textColor="?android:attr/textColor" />
<CheckBox
android:id="@+id/useChrome"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColor"
android:layout_height="wrap_content"
android:text="Use chrome"
android:id="@+id/useChrome"
android:layout_height="wrap_content"/>
android:textColor="?android:attr/textColor" />
<CheckBox
android:layout_width="wrap_content"
android:textColor="?android:attr/textColor"
android:text="Restart vpn inbetween sites"
android:id="@+id/restartVpn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:layout_height="wrap_content"/>
android:text="Restart vpn inbetween sites"
android:textColor="?android:attr/textColor" />
</LinearLayout>
\ No newline at end of file
......@@ -54,6 +54,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="8dp"
android:visibility="gone"
android:drawableStart="@drawable/ic_caret_right"
android:drawablePadding="8dp"
android:gravity="center"
......
......@@ -203,6 +203,28 @@
android:layout_centerVertical="true"
android:layout_height="32dp"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:id="@+id/nonVpnMode"
android:textStyle="bold"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:text="@string/preference_category_nonvpnmode"
android:layout_centerVertical="true"
android:layout_height="wrap_content"/>
<ImageView
android:layout_width="32dp"
android:src="@drawable/ic_chevron_right"
android:tint="?android:attr/textColor"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_height="32dp"/>
</RelativeLayout>
</LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="dialog_logexport_loading_title">Preparant els registres</string>
<string name="dialog_serverimportfailed_text_exception" tools:ignore="PluralsCandidate">Els servidors no han pogut ser importats.
\n(%1s)</string>
<string name="dialog_serverimportfailed_text">El servidor(s) no ha(n) pogut ser importat(s).</string>
<string name="dialog_serverimportfailed_title">Importació fallida</string>
<string name="dialog_serverimport_text" tools:ignore="PluralsCandidate">Si us plau, seleccioneu els servidors que voleu importar.
\n
\nSi seleccioneu servidors que ja estiguin importats, es duplicaran.</string>
<string name="dialog_serverimport_servertype_dot">Tipus: DNS-over-TLS (DoT)</string>
<string name="dialog_serverimport_servertype_doh">Tipus: DNS-over-HTTPS (DoH)</string>
<string name="dialog_serverimport_experimental">Experimental</string>
<string name="dialog_serverimport_priority">Prioritat %1d</string>
<string name="dialog_serverimport_done">Fet</string>
<plurals name="dialog_serverimport_title">
<item quantity="one">Importació de %1d servidor</item>
<item quantity="other">Importació de %1d servidors</item>
</plurals>
<plurals name="dialog_excludedapps_infotext">
<item quantity="one">L\'aplicació %1d està exclosa de manera predeterminada i no es pot seleccionar</item>
<item quantity="other">Les aplicacions %1d estan excloses de manera predeterminada i no es poden seleccionar</item>
</plurals>
<string name="dialog_appchoosal_whitelist">Només fes servir DoH/DoT en les aplicacions seleccionades</string>
<string name="dialog_appchoosal_showsystemapps">Mostrar aplicacions del sistema</string>
<string name="dialog_deleteconfig_text">Vols esborrar la configuració %1$s\?</string>
<string name="dialog_deleteconfig_title">Esborrar configuració\?</string>
<string name="dialog_newserver_secondaryserver">Servidor secundari (opcional)</string>
<string name="dialog_newserver_primaryserver">Servidor primari</string>
<string name="dialog_newserver_name_hint">Exemple</string>
<string name="dialog_newserver_name">Nom del servidor</string>
<string name="dialog_newserver_title_tls">Afegir un servidor (DoT)</string>
<string name="dialog_newserver_title_https">Afegir un servidor (DoH)</string>
<string name="dialog_serverconfiguration_addserver">Afegir un servidor</string>
<string name="dialog_serverconfiguration_title">Configuració del servidor</string>
<string name="dialog_error_title">Informe de fallada</string>
<string name="dialog_logexport_general">Tria aplicació</string>