Commit 76729c27 authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Track the response source for DnsQuery, not only whether it was answered from cache or not

parent 05dc9692
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "fb5d5231e14e3e9fbd2d47ee90178b0f",
"entities": [
{
"tableName": "CachedResponse",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`dnsName` TEXT NOT NULL, `type` INTEGER NOT NULL, `records` TEXT NOT NULL, PRIMARY KEY(`dnsName`, `type`))",
"fields": [
{
"fieldPath": "dnsName",
"columnName": "dnsName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "records",
"columnName": "records",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"dnsName",
"type"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DnsQuery",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `askedServer` TEXT, `responseSource` TEXT, `questionTime` INTEGER NOT NULL, `responseTime` INTEGER NOT NULL, `responses` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "askedServer",
"columnName": "askedServer",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "responseSource",
"columnName": "responseSource",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "questionTime",
"columnName": "questionTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "responseTime",
"columnName": "responseTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "responses",
"columnName": "responses",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DnsRule",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stagingType` INTEGER NOT NULL, `type` INTEGER NOT NULL, `host` TEXT NOT NULL, `target` TEXT NOT NULL, `ipv6Target` TEXT, `importedFrom` INTEGER, `isWildcard` INTEGER NOT NULL, FOREIGN KEY(`importedFrom`) REFERENCES `HostSource`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "stagingType",
"columnName": "stagingType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "target",
"columnName": "target",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ipv6Target",
"columnName": "ipv6Target",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "importedFrom",
"columnName": "importedFrom",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isWildcard",
"columnName": "isWildcard",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_DnsRule_importedFrom",
"unique": false,
"columnNames": [
"importedFrom"
],
"createSql": "CREATE INDEX `index_DnsRule_importedFrom` ON `${TABLE_NAME}` (`importedFrom`)"
},
{
"name": "index_DnsRule_host_type_stagingType",
"unique": true,
"columnNames": [
"host",
"type",
"stagingType"
],
"createSql": "CREATE UNIQUE INDEX `index_DnsRule_host_type_stagingType` ON `${TABLE_NAME}` (`host`, `type`, `stagingType`)"
}
],
"foreignKeys": [
{
"table": "HostSource",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"importedFrom"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "HostSource",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `enabled` INTEGER NOT NULL, `ruleCount` INTEGER, `checksum` TEXT, `name` TEXT NOT NULL, `source` TEXT NOT NULL, `whitelistSource` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleCount",
"columnName": "ruleCount",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "checksum",
"columnName": "checksum",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "whitelistSource",
"columnName": "whitelistSource",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fb5d5231e14e3e9fbd2d47ee90178b0f')"
]
}
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ package com.frostnerd.smokescreen.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.frostnerd.dnstunnelproxy.QueryListener
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.database.converters.StringListConverter
import com.frostnerd.smokescreen.database.entities.DnsQuery
......@@ -55,11 +56,18 @@ class QueryImportActivity: AppCompatActivity() {
val converter = StringListConverter()
for(line in iterator) {
val split = line.split(",")
val source = if(split[5].equals("false", true) || split[5].equals("true", true)) {
if (split[5].toBoolean()) QueryListener.Source.CACHE
else QueryListener.Source.UPSTREAM
} else if(split[5].isBlank()) null
else QueryListener.Source.values().find {
it.name.equals(split[5], true)
}
queries.add(DnsQuery(
name=split[0],
type = Record.TYPE.getType(split[3].toInt()),
askedServer = split[4],
fromCache = split[5].toBoolean(),
responseSource = source,
questionTime = split[6].toLong(),
responseTime = split[7].toLong(),
responses = converter.stringToList(split[8].replaceFirst("\"", "").replace(Regex("\"$"), ""))
......
......@@ -37,7 +37,7 @@ import com.frostnerd.smokescreen.database.repository.HostSourceRepository
@Database(entities = [CachedResponse::class, DnsQuery::class, DnsRule::class, HostSource::class], version = AppDatabase.currentVersion)
abstract class AppDatabase : RoomDatabase() {
companion object {
const val currentVersion:Int = 10
const val currentVersion:Int = 11
}
abstract fun cachedResponseDao(): CachedResponseDao
......
......@@ -97,12 +97,21 @@ val MIGRATION_9_10 = migration(9, 10) {
Logger.logIfOpen("DB_MIGRATION", "Migration from 9 to 10 completed")
}
val MIGRATION_10_11 = migration(10, 11) {
Logger.logIfOpen("DB_MIGRATION", "Migrating from 10 to 11")
it.execSQL("CREATE TABLE `DnsQuery_tmp` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `askedServer` TEXT, `responseSource` TEXT, `questionTime` INTEGER NOT NULL, `responseTime` INTEGER NOT NULL, `responses` TEXT NOT NULL)")
it.execSQL("INSERT INTO `DnsQuery_tmp`(id, type, name, askedServer, questionTime, responseTime, responses, responseSource) SELECT id, type, name, askedServer, questionTime, responseTime, responses, CASE WHEN fromCache=1 THEN 'CACHE' else 'UPSTREAM' END as `responseSource` FROM DnsQuery")
it.execSQL("DROP TABLE `DnsQuery`")
it.execSQL("ALTER TABLE `DnsQuery_tmp` RENAME TO `DnsQuery`")
Logger.logIfOpen("DB_MIGRATION", "Migration from 10 to 11 completed")
}
fun Context.getDatabase(): AppDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "data")
.allowMainThreadQueries()
.addMigrations(MIGRATION_2_X, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10)
.addMigrations(MIGRATION_2_X, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11)
.build()
}
return INSTANCE!!
......
package com.frostnerd.smokescreen.database.converters
import androidx.room.TypeConverter
import com.frostnerd.dnstunnelproxy.QueryListener
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can contact the developer at daniel.wolf@frostnerd.com.
*/
class QuerySourceConverter {
@TypeConverter
fun sourceFromString(value: String): QueryListener.Source? {
return QueryListener.Source.values().find {
it.name.equals(value, true)
}
}
@TypeConverter
fun sourceToString(source:QueryListener.Source?): String {
return source?.name ?: ""
}
}
\ No newline at end of file
......@@ -5,7 +5,9 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.frostnerd.dnstunnelproxy.QueryListener
import com.frostnerd.smokescreen.database.converters.DnsTypeConverter
import com.frostnerd.smokescreen.database.converters.QuerySourceConverter
import com.frostnerd.smokescreen.database.converters.StringListConverter
import com.frostnerd.smokescreen.database.recordFromBase64
import org.minidns.record.Record
......@@ -29,13 +31,13 @@ import org.minidns.record.Record
* You can contact the developer at daniel.wolf@frostnerd.com.
*/
@Entity(tableName = "DnsQuery")
@TypeConverters(DnsTypeConverter::class, StringListConverter::class)
@TypeConverters(DnsTypeConverter::class, StringListConverter::class, QuerySourceConverter::class)
data class DnsQuery(
@PrimaryKey(autoGenerate = true) var id: Long = 0,
val type: Record.TYPE,
val name: String,
var askedServer: String?,
var fromCache:Boolean,
var responseSource:QueryListener.Source? = null,
val questionTime: Long,
var responseTime: Long = 0,
var responses: MutableList<String>
......
......@@ -86,7 +86,7 @@ class DnsQueryRepository(private val dnsQueryDao: DnsQueryDao) {
builder.append(query.type.name).append(",")
builder.append(query.type.value).append(",")
builder.append(query.askedServer).append(",")
builder.append(query.fromCache).append(",")
builder.append(query.responseSource?.name ?: "").append(",")
builder.append(query.questionTime).append(",")
builder.append(query.responseTime).append(",")
builder.append("\"").append(responseConverter.someObjectListToString(query.responses).replace(",", ";").replace("\"", "'")).append("\"")
......
......@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.frostnerd.dnstunnelproxy.QueryListener
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.database.entities.DnsQuery
import kotlinx.android.synthetic.main.fragment_querylog_detail.*
......@@ -109,7 +110,7 @@ class QueryLogDetailFragment : Fragment() {
query.askedServer!!.startsWith("https") -> getString(R.string.fragment_querydetail_mode_doh)
else -> getString(R.string.fragment_querydetail_mode_dot)
}
if(query.fromCache) {
if(query.responseSource == QueryListener.Source.CACHE) {
resolvedBy.text = "Cache"
} else {
resolvedBy.text = query.askedServer?.replace("tls::", "")?.replace("https::", "") ?: "-"
......
......@@ -67,8 +67,7 @@ class QueryListener(private val context: Context) : QueryListener {
name = questionMessage.question.name.toString(),
askedServer = null,
questionTime = System.currentTimeMillis(),
responses = mutableListOf(),
fromCache = false
responses = mutableListOf()
)
val dao = context.getDatabase().dnsQueryDao()
dao.insert(query)
......@@ -91,7 +90,7 @@ class QueryListener(private val context: Context) : QueryListener {
for (answer in responseMessage.answerSection) {
query.addResponse(answer)
}
query.fromCache = (source == QueryListener.Source.CACHE || source == QueryListener.Source.CACHE_AND_LOCALRESOLVER)
query.responseSource = source
context.getDatabase().dnsQueryRepository().updateAsync(query)
synchronized(waitingQueryLogs) {
waitingQueryLogs.remove(responseMessage.id)
......
Supports Markdown
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