Commit 25a2546c authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Merge remote-tracking branch 'origin/master'

parents 09a75acf 1c41c300
......@@ -25,8 +25,8 @@ android {
applicationId "com.frostnerd.smokescreen"
minSdkVersion 21
targetSdkVersion 29
versionCode 52
versionName "1.0.3"
versionCode 53
versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("Boolean", "FROM_CI", String.valueOf(getSystemVariableOrDefault("CI_COMMIT_SHORT_SHA", "") != ""))
......@@ -85,7 +85,6 @@ android {
adblocker {
dimension "version"
versionNameSuffix "-adblock"
versionCode 52
}
normal {
dimension "version"
......@@ -125,7 +124,7 @@ dependencies {
implementation 'com.frostnerd.utilskt:preferences:1.5.17' // https://git.frostnerd.com/AndroidUtils/preferenceskt
implementation 'com.frostnerd.utilskt:navigationdraweractivity:1.3.29' // https://git.frostnerd.com/AndroidUtils/navigationdraweractivity
implementation 'com.frostnerd.utilskt:encrypteddnstunnelproxy:1.5.169' // https://git.frostnerd.com/AndroidUtils/encrypteddnstunnelproxy
implementation 'com.frostnerd.utilskt:encrypteddnstunnelproxy:1.5.171' // https://git.frostnerd.com/AndroidUtils/encrypteddnstunnelproxy
implementation 'com.frostnerd.utilskt:general:1.0.19' // https://git.frostnerd.com/AndroidUtils/generalkt
implementation 'com.frostnerd.utilskt:adapters:1.1.6' // https://git.frostnerd.com/AndroidUtils/Adapters
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import leakcanary.LeakSentry
import java.net.InetAddress
import java.util.*
import kotlin.system.exitProcess
......@@ -96,59 +97,62 @@ class SmokeScreen : Application() {
fun initSentry(forceStatus: Status = Status.NONE) {
if (!BuildConfig.DEBUG && BuildConfig.SENTRY_DSN != "dummy") {
val enabledType = getPreferences().crashreportingType
if (forceStatus != Status.DATASAVING && (enabledType == Crashreporting.FULL || forceStatus == Status.ENABLED)) {
// Enable Sentry in full mode
// This passes some device-related data, but nothing which allows user actions to be tracked across the app
// Info: Some data is attached by the AndroidEventBuilderHelper class, which is present by default
GlobalScope.launch(Dispatchers.IO) {
Sentry.init(
BuildConfig.SENTRY_DSN,
AndroidSentryClientFactory(this@SmokeScreen)
)
Sentry.getContext().user =
User(getPreferences().crashReportingUUID, null, null, null)
Sentry.getStoredClient().apply {
addTag("user.language", Locale.getDefault().displayLanguage)
addTag("app.database_version", AppDatabase.currentVersion.toString())
addTag("app.dns_server_name", getPreferences().dnsServerConfig.name)
addTag(
"app.dns_server_primary",
getPreferences().dnsServerConfig.servers[0].address.formatToString()
)
addTag(
"app.dns_server_secondary",
getPreferences().dnsServerConfig.servers.getOrNull(1)?.address?.formatToString()
GlobalScope.launch(Dispatchers.IO) {
val hostName = InetAddress.getLocalHost().hostName
if(!hostName.startsWith("mars-sandbox", true)) {
val enabledType = getPreferences().crashreportingType
if (forceStatus != Status.DATASAVING && (enabledType == Crashreporting.FULL || forceStatus == Status.ENABLED)) {
// Enable Sentry in full mode
// This passes some device-related data, but nothing which allows user actions to be tracked across the app
// Info: Some data is attached by the AndroidEventBuilderHelper class, which is present by default
Sentry.init(
BuildConfig.SENTRY_DSN,
AndroidSentryClientFactory(this@SmokeScreen)
)
addTag(
"app.installer_package",
packageManager.getInstallerPackageName(packageName)
Sentry.getContext().user =
User(getPreferences().crashReportingUUID, null, null, null)
Sentry.getStoredClient().apply {
addTag("user.language", Locale.getDefault().displayLanguage)
addTag("app.database_version", AppDatabase.currentVersion.toString())
addTag("app.dns_server_name", getPreferences().dnsServerConfig.name)
addTag(
"app.dns_server_primary",
getPreferences().dnsServerConfig.servers[0].address.formatToString()
)
addTag(
"app.dns_server_secondary",
getPreferences().dnsServerConfig.servers.getOrNull(1)?.address?.formatToString()
)
addTag(
"app.installer_package",
packageManager.getInstallerPackageName(packageName)
)
addTag("richdata", "true")
addTag("app.fromCi", BuildConfig.FROM_CI.toString())
addTag("app.commit", BuildConfig.COMMIT_HASH)
}
} else if (enabledType == Crashreporting.MINIMAL || forceStatus == Status.DATASAVING) {
// Inits Sentry in datasaving mode
// Only data absolutely necessary is transmitted (Android version, app version).
// Only crashes will be reported, no regular events.
Sentry.init(
BuildConfig.SENTRY_DSN,
AndroidSentryClientFactory(this@SmokeScreen)
)
addTag("richdata", "true")
addTag("app.fromCi", BuildConfig.FROM_CI.toString())
addTag("app.commit", BuildConfig.COMMIT_HASH)
}
}
} else if(enabledType == Crashreporting.MINIMAL || forceStatus == Status.DATASAVING){
// Inits Sentry in datasaving mode
// Only data absolutely necessary is transmitted (Android version, app version).
// Only crashes will be reported, no regular events.
GlobalScope.launch(Dispatchers.IO) {
Sentry.init(
BuildConfig.SENTRY_DSN,
AndroidSentryClientFactory(this@SmokeScreen)
)
Sentry.getContext().user = User("anon-" + BuildConfig.VERSION_CODE, null, null, null)
Sentry.getStoredClient().apply {
addTag("richdata", "false")
addTag("dist", BuildConfig.VERSION_CODE.toString())
addTag("app.commit", BuildConfig.COMMIT_HASH)
addTag("app.fromCi", BuildConfig.FROM_CI.toString())
addExtra("dist", BuildConfig.VERSION_CODE)
this.builderHelpers.forEach {
this.removeBuilderHelper(it)
Sentry.getContext().user =
User("anon-" + BuildConfig.VERSION_CODE, null, null, null)
Sentry.getStoredClient().apply {
addTag("richdata", "false")
addTag("dist", BuildConfig.VERSION_CODE.toString())
addTag("app.commit", BuildConfig.COMMIT_HASH)
addTag("app.fromCi", BuildConfig.FROM_CI.toString())
addExtra("dist", BuildConfig.VERSION_CODE)
this.builderHelpers.forEach {
this.removeBuilderHelper(it)
}
this.addBuilderHelper(DatasavingSentryEventHelper())
}
this.addBuilderHelper(DatasavingSentryEventHelper())
}
}
}
......
......@@ -41,20 +41,25 @@ import kotlinx.coroutines.launch
*/
class ServerChoosalDialog(
context: AppCompatActivity,
selectedServer:DnsServerInformation<*>?,
showTls:Boolean = selectedServer?.hasTlsServer() ?: true,
onEntrySelected: (config: DnsServerInformation<*>) -> Unit
) :
BaseDialog(context, context.getPreferences().theme.dialogStyle) {
private var populationJob: Job? = null
private var currentSelectedServer: DnsServerInformation<*>
private var currentSelectedServer: DnsServerInformation<*>?
private lateinit var defaultConfig: List<DnsServerInformation<*>>
private lateinit var userConfig: List<UserServerConfiguration>
constructor(context: AppCompatActivity,
onEntrySelected: (config: DnsServerInformation<*>) -> Unit):this(context, context.getPreferences().dnsServerConfig, onEntrySelected=onEntrySelected)
init {
val view = layoutInflater.inflate(R.layout.dialog_server_configuration, null, false)
setTitle(R.string.dialog_serverconfiguration_title)
setView(view)
currentSelectedServer = context.getPreferences().dnsServerConfig
currentSelectedServer = selectedServer
setButton(
DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel)
......@@ -62,10 +67,9 @@ class ServerChoosalDialog(
setButton(
DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok)
) { _, _ ->
onEntrySelected.invoke(currentSelectedServer)
currentSelectedServer?.apply(onEntrySelected)
}
val isCurrentServerTls = currentSelectedServer.hasTlsServer()
loadServerData(isCurrentServerTls)
loadServerData(showTls)
val spinnerAdapter = ArrayAdapter<String>(
context, android.R.layout.simple_spinner_item,
......@@ -77,7 +81,7 @@ class ServerChoosalDialog(
spinnerAdapter.setDropDownViewResource(R.layout.item_tasker_action_spinner_dropdown_item)
val spinner = view.findViewById<Spinner>(R.id.spinner)
spinner.adapter = spinnerAdapter
if(isCurrentServerTls) spinner.setSelection(1)
if(showTls) spinner.setSelection(1)
view.findViewById<RadioGroup>(R.id.knownServersGroup).setOnCheckedChangeListener { group, _ ->
val button = view.findViewById(group.checkedRadioButtonId) as RadioButton
val payload = button.tag
......@@ -174,6 +178,7 @@ class ServerChoosalDialog(
}
private fun markCurrentSelectedServer() {
val currentSelectedServer = this.currentSelectedServer ?: return
for (id in 0 until knownServersGroup.childCount) {
val child = knownServersGroup.getChildAt(id) as RadioButton
val payload = child.tag
......@@ -299,7 +304,7 @@ class ServerChoosalDialog(
currentSelectedServer =
if (userConfiguration.isHttpsServer()) AbstractHttpsDNSHandle.KNOWN_DNS_SERVERS.minBy { it.key }!!.value else AbstractTLSDnsHandle.KNOWN_DNS_SERVERS.minBy { it.key }!!.value
markCurrentSelectedServer()
context.getPreferences().dnsServerConfig = currentSelectedServer
context.getPreferences().dnsServerConfig = currentSelectedServer!!
}
knownServersGroup.removeView(button)
}.show()
......@@ -348,7 +353,7 @@ class ServerChoosalDialog(
currentSelectedServer =
if (isHttps) AbstractHttpsDNSHandle.KNOWN_DNS_SERVERS.minBy { it.key }!!.value else AbstractTLSDnsHandle.KNOWN_DNS_SERVERS.minBy { it.key }!!.value
markCurrentSelectedServer()
context.getPreferences().dnsServerConfig = currentSelectedServer
context.getPreferences().dnsServerConfig = currentSelectedServer!!
}
knownServersGroup.removeView(button)
}.show()
......
......@@ -12,12 +12,10 @@ import android.widget.ArrayAdapter
import com.frostnerd.dnstunnelproxy.DnsServerInformation
import com.frostnerd.encrypteddnstunnelproxy.HttpsDnsServerInformation
import com.frostnerd.lifecyclemanagement.BaseActivity
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.*
import com.frostnerd.smokescreen.activity.BackgroundVpnConfigureActivity
import com.frostnerd.smokescreen.dialog.NewServerDialog
import com.frostnerd.smokescreen.fromServerUrls
import com.frostnerd.smokescreen.hasHttpsServer
import com.frostnerd.smokescreen.tlsServerFromHosts
import com.frostnerd.smokescreen.dialog.ServerChoosalDialog
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.activity_tasker_configure.*
......@@ -74,11 +72,11 @@ class ConfigureActivity : BaseActivity() {
"start" -> {
actionType.setSelection(0)
startIfRunning.isChecked = settings.getBoolean(TaskerHelper.DATA_KEY_STARTIFRUNNING, true)
useServersFromConfig.isChecked =
!settings.containsKey(TaskerHelper.DATA_KEY_PRIMARYSERVER) && !settings.containsKey(
useCustomServer.isChecked =
settings.containsKey(TaskerHelper.DATA_KEY_PRIMARYSERVER) || settings.containsKey(
BackgroundVpnConfigureActivity.extraKeyServerConfig
)
if (!useServersFromConfig.isChecked) {
if (useCustomServer.isChecked) {
if (settings.containsKey(TaskerHelper.DATA_KEY_PRIMARYSERVER)) {
serverType.setSelection(0)
primaryServer.setText(settings.getString(TaskerHelper.DATA_KEY_PRIMARYSERVER))
......@@ -110,8 +108,8 @@ class ConfigureActivity : BaseActivity() {
}
private fun createLayout() {
useServersFromConfig.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
useCustomServer.setOnCheckedChangeListener { _, isChecked ->
if (!isChecked) {
serverConfigWrap.visibility = View.GONE
serverType.visibility = View.GONE
} else {
......@@ -157,6 +155,15 @@ class ConfigureActivity : BaseActivity() {
setHints()
}
}
selectServer.setOnClickListener {
ServerChoosalDialog(this@ConfigureActivity, null, serverType.selectedItemPosition == 1) {
val typePosition = if(it.hasTlsServer()) 1 else 0
if(serverType.selectedItemPosition != typePosition) serverType.setSelection(typePosition)
primaryServer.setText(it.servers.first().address.formatToString())
if(it.servers.size > 1) secondaryServer.setText(it.servers.last().address.formatToString())
else secondaryServer.setText("")
}.show()
}
addUrlTextWatcher(primaryServerWrap, primaryServer, false)
addUrlTextWatcher(secondaryServerWrap, secondaryServer, true)
setHints()
......@@ -213,7 +220,7 @@ class ConfigureActivity : BaseActivity() {
)
if (action == "start") {
settings.putBoolean(TaskerHelper.DATA_KEY_STARTIFRUNNING, startIfRunning.isChecked)
if (!useServersFromConfig.isChecked) {
if (useCustomServer.isChecked) {
if (primaryServerWrap.error == null &&
secondaryServerWrap.error == null
) {
......
......@@ -79,86 +79,81 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
})
}
override suspend fun canResolve(question: Question): Boolean {
return if ((ruleCount == 0 || (ruleCount != null && ruleCount == whitelistCount)) || (question.type != Record.TYPE.A && question.type != Record.TYPE.AAAA)) {
false
} else {
val uniformQuestion = question.name.toString().replace(wwwRegex, "").toLowerCase(Locale.ROOT)
val hostHash = hashHost(uniformQuestion, question.type)
val wildcardHostHash = hashHost(uniformQuestion, Record.TYPE.ANY)
private fun findRuleTarget(question: String, type:Record.TYPE):String? {
val uniformQuestion = question.replace(wwwRegex, "").toLowerCase(Locale.ROOT)
val hostHash = hashHost(uniformQuestion, type)
val wildcardHostHash = hashHost(uniformQuestion, Record.TYPE.ANY)
if(cachedNonIncluded.size != 0 && cachedNonIncluded.contains(hostHash)) return false
if(whitelistCount != 0) {
if(cachedNonWildcardWhitelisted.size != 0 && cachedNonWildcardWhitelisted.contains(wildcardHostHash)) return false
else if(cachedWildcardWhitelisted.size != 0 && cachedWildcardWhitelisted.contains(wildcardHostHash)) return false
}
if(nonWildcardCount != 0 && cachedResolved.size != 0) {
val res = cachedResolved[hostHash]
if(res != null) {
resolveResults[question.hashCode()] = res
return true
}
if(cachedNonIncluded.size != 0 && cachedNonIncluded.contains(hostHash)) return null
if(whitelistCount != 0) {
if(cachedNonWildcardWhitelisted.size != 0 && cachedNonWildcardWhitelisted.contains(wildcardHostHash)) return null
else if(cachedWildcardWhitelisted.size != 0 && cachedWildcardWhitelisted.contains(wildcardHostHash)) return null
}
if(nonWildcardCount != 0 && cachedResolved.size != 0) {
val res = cachedResolved[hostHash]
if(res != null) {
return res
}
if(wildcardCount != 0 && cachedWildcardResolved.size != 0) {
val res = cachedWildcardResolved[hostHash]
if(res != null) {
resolveResults[question.hashCode()] = res
return true
}
}
if(wildcardCount != 0 && cachedWildcardResolved.size != 0) {
val res = cachedWildcardResolved[hostHash]
if(res != null) {
return res
}
}
val whitelistEntry: DnsRule? = if (whitelistCount != 0) {
val normal = if(nonWildcardWhitelistCount != 0 && (nonWildcardWhitelistCount == null || nonWildcardWhitelistCount != whitelistCount)) dao.findNonWildcardWhitelistEntry(
uniformQuestion,
useUserRules
).firstOrNull() else null
normal ?: if(wildcardWhitelistCount != 0) dao.findPossibleWildcardRuleTarget(
uniformQuestion,
question.type,
useUserRules,
true,
false
).firstOrNull {
DnsRuleDialog.databaseHostToMatcher(it.host).reset(uniformQuestion)
.matches()
} else null
val whitelistEntry: DnsRule? = if (whitelistCount != 0) {
val normal = if(nonWildcardWhitelistCount != 0 && (nonWildcardWhitelistCount == null || nonWildcardWhitelistCount != whitelistCount)) dao.findNonWildcardWhitelistEntry(
uniformQuestion,
useUserRules
).firstOrNull() else null
normal ?: if(wildcardWhitelistCount != 0) dao.findPossibleWildcardRuleTarget(
uniformQuestion,
type,
useUserRules,
true,
false
).firstOrNull {
DnsRuleDialog.databaseHostToMatcher(it.host).reset(uniformQuestion)
.matches()
} else null
} else null
if (whitelistEntry != null) {
if(whitelistEntry.isWildcard) cachedWildcardWhitelisted.add(wildcardHostHash)
else cachedNonWildcardWhitelisted.add(wildcardHostHash)
if(cachedWildcardWhitelisted.size >= maxWhitelistCacheSize*2) cachedWildcardWhitelisted.clear()
if(cachedNonWildcardWhitelisted.size >= maxWhitelistCacheSize) cachedNonWildcardWhitelisted.clear()
if (whitelistEntry != null) {
if(whitelistEntry.isWildcard) cachedWildcardWhitelisted.add(wildcardHostHash)
else cachedNonWildcardWhitelisted.add(wildcardHostHash)
false
}
else {
val resolveResult = if(nonWildcardCount != 0) {
if(nonWildcardCount == cachedResolved.size) {
null // We would have hit cache otherwise
} else {
dao.findRuleTarget(uniformQuestion, question.type, useUserRules)
?.let {
when (it) {
"0" -> "0.0.0.0"
"1" -> {
if (question.type == Record.TYPE.AAAA) "::1"
else "127.0.0.1"
}
else -> it
if(cachedWildcardWhitelisted.size >= maxWhitelistCacheSize*2) cachedWildcardWhitelisted.clear()
if(cachedNonWildcardWhitelisted.size >= maxWhitelistCacheSize) cachedNonWildcardWhitelisted.clear()
return null
}
else {
val resolveResult = if(nonWildcardCount != 0) {
if(nonWildcardCount == cachedResolved.size) {
null // We would have hit cache otherwise
} else {
dao.findRuleTarget(uniformQuestion, type, useUserRules)
?.let {
when (it) {
"0" -> "0.0.0.0"
"1" -> {
if (type == Record.TYPE.AAAA) "::1"
else "127.0.0.1"
}
else -> it
}
}
} else null
if (resolveResult != null) {
}
}
} else null
when {
resolveResult != null -> {
cachedResolved[hostHash] = resolveResult
resolveResults[question.hashCode()] = resolveResult
true
} else if (wildcardCount != 0) {
return resolveResult
}
wildcardCount != 0 -> {
val wildcardResolveResult = dao.findPossibleWildcardRuleTarget(
uniformQuestion,
question.type,
type,
useUserRules,
false,
true
......@@ -166,37 +161,49 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
DnsRuleDialog.databaseHostToMatcher(it.host)
.reset(uniformQuestion).matches()
}?.let {
if (question.type == Record.TYPE.AAAA) it.ipv6Target
if (type == Record.TYPE.AAAA) it.ipv6Target
?: it.target
else it.target
}?.let {
when (it) {
"0" -> "0.0.0.0"
"1" -> {
if (question.type == Record.TYPE.AAAA) "::1"
if (type == Record.TYPE.AAAA) "::1"
else "127.0.0.1"
}
else -> it
}
}
if (wildcardResolveResult != null) {
return if (wildcardResolveResult != null) {
cachedWildcardResolved[hostHash] = wildcardResolveResult
resolveResults[question.hashCode()] = wildcardResolveResult
true
wildcardResolveResult
} else {
if(cachedNonIncluded.size >= maxWhitelistCacheSize) cachedNonIncluded.clear()
cachedNonIncluded.add(hostHash)
false
null
}
} else {
}
else -> {
if(cachedNonIncluded.size >= maxWhitelistCacheSize) cachedNonIncluded.clear()
cachedNonIncluded.add(hostHash)
false
return null
}
}
}
}
override suspend fun canResolve(question: Question): Boolean {
return if ((ruleCount == 0 || (ruleCount != null && ruleCount == whitelistCount)) || (question.type != Record.TYPE.A && question.type != Record.TYPE.AAAA)) {
false
} else {
val res = findRuleTarget(question.name.toString(), question.type)
return if(res != null) {
resolveResults[question.hashCode()] = res
true
} else false
}
}
// A fast hashing function with high(er) collision rate
// As only a few hosts are stored at the same time the collision rate is not important.
// The effective room is 2^31
......@@ -233,7 +240,10 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
// Handle CNAME Cloaking
// Does not need to handle whitelist as the query has already been forwarded
override suspend fun mapResponse(message: DnsMessage): DnsMessage {
if(ruleCount == 0 || (ruleCount != null && ruleCount == whitelistCount)) return message // No rules or only whitelist rules present
if(ruleCount == 0 || (ruleCount != null && ruleCount == whitelistCount) || message.questions.size == 0) return message // No rules or only whitelist rules present
else if(whitelistCount != 0 && hashHost(message.question.name.toString().replace(wwwRegex, "").toLowerCase(Locale.ROOT), message.question.type).let {
cachedWildcardWhitelisted.contains(it) || cachedNonWildcardWhitelisted.contains(it)
}) return message
else if(!message.answerSection.any {
it.type == Record.TYPE.CNAME
}) return message
......@@ -290,46 +300,7 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
}
private fun resolveForCname(host:String, type:Record.TYPE): Data? {
val uniformQuestion = host.replace(wwwRegex, "").toLowerCase(Locale.ROOT)
var entry = if(nonWildcardCount != 0) {
dao.findRuleTarget(uniformQuestion, type, useUserRules)
?.let {
when (it) {
"0" -> "0.0.0.0"
"1" -> {
if (type == Record.TYPE.AAAA) "::1"
else "127.0.0.1"
}
else -> it
}
}
} else null
if(entry == null && wildcardCount != 0) {
entry = dao.findPossibleWildcardRuleTarget(
uniformQuestion,
type,
useUserRules,
false,
true
).firstOrNull {
DnsRuleDialog.databaseHostToMatcher(it.host)
.reset(uniformQuestion).matches()
}?.let {
if (type == Record.TYPE.AAAA) it.ipv6Target
?: it.target
else it.target
}?.let {
when (it) {
"0" -> "0.0.0.0"
"1" -> {
if (type == Record.TYPE.AAAA) "::1"
else "127.0.0.1"
}
else -> it
}
}
}
val entry = findRuleTarget(host, type)
return if(entry != null) {
if(type == Record.TYPE.A) A(entry)
......