QueryLogDetailFragment.kt 6.56 KB
Newer Older
1
2
package com.frostnerd.smokescreen.fragment.querylogfragment

3
import android.os.Build
4
5
6
7
8
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
9
import com.frostnerd.dnstunnelproxy.QueryListener
10
11
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.database.entities.DnsQuery
12
13
14
15
16
import com.frostnerd.smokescreen.database.entities.DnsRule
import com.frostnerd.smokescreen.database.getDatabase
import com.frostnerd.smokescreen.dialog.DnsRuleDialog
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_dns_rules.*
17
import kotlinx.android.synthetic.main.fragment_querylog_detail.*
18
import org.minidns.record.A
19
import org.minidns.record.Record
20
21
import java.text.DateFormat
import java.util.*
22
23


Daniel Wolf's avatar
Daniel Wolf committed
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
 * 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.
41
42
43
44
 */
class QueryLogDetailFragment : Fragment() {
    var currentQuery: DnsQuery? = null
        private set
45
46
    private lateinit var timeFormatSameDay: DateFormat
    private lateinit var timeFormatDifferentDay: DateFormat
Daniel Wolf's avatar
Daniel Wolf committed
47
    private fun formatTimeStamp(timestamp:Long): String {
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
        return if(isTimeStampToday(timestamp)) timeFormatSameDay.format(timestamp)
        else timeFormatDifferentDay.format(timestamp)
    }

    private fun isTimeStampToday(timestamp:Long):Boolean {
        return timestamp >= getStartOfDay()
    }

    private fun getStartOfDay():Long {
        val calendar = Calendar.getInstance()
        calendar.set(Calendar.HOUR_OF_DAY, 0)
        calendar.set(Calendar.MINUTE, 0)
        calendar.set(Calendar.SECOND, 0)
        calendar.set(Calendar.MILLISECOND, 0)
        return calendar.timeInMillis
    }
Daniel Wolf's avatar
Daniel Wolf committed
64

65
66
67
68
69
70
71
72
    private fun getLocale(): Locale {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
            resources.configuration.locales.get(0)!!
        } else{
            @Suppress("DEPRECATION")
            resources.configuration.locale!!
        }
    }
Daniel Wolf's avatar
Daniel Wolf committed
73

74
75
76
77
78
    private fun setupTimeFormat() {
        val locale = getLocale()
        timeFormatSameDay = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale)
        timeFormatDifferentDay = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale)
    }
79
80
81
    private var viewCreated = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Daniel Wolf's avatar
Daniel Wolf committed
82
        setupTimeFormat()
83
84
85
86
87
88
        return inflater.inflate(R.layout.fragment_querylog_detail, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewCreated = true
        updateUi()
89
90
91
        createDnsRule.setOnClickListener {
            val query = currentQuery
            if(query != null) {
92
93
94
95
                val answerIP = query.getParsedResponses().firstOrNull {
                    it.type == query.type
                }?.payload?.toString() ?: if(query.type == Record.TYPE.A) "0.0.0.0" else "::1"
                DnsRuleDialog(context!!, DnsRule(query.type, query.name, target = answerIP), onRuleCreated = { newRule ->
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
                    val id = if (newRule.isWhitelistRule()) {
                        getDatabase().dnsRuleDao().insertWhitelist(newRule)
                    } else getDatabase().dnsRuleDao().insertIgnore(newRule)
                    if (id != -1L) {
                        Snackbar.make(
                            activity!!.findViewById(android.R.id.content),
                            R.string.windows_querylogging_dnsrule_created,
                            Snackbar.LENGTH_LONG
                        ).show()
                    } else {
                        Snackbar.make(
                            activity!!.findViewById(android.R.id.content),
                            R.string.window_dnsrules_hostalreadyexists,
                            Snackbar.LENGTH_LONG
                        ).show()
                    }
                }).show()
            }
        }
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    }

    fun isShowingQuery(): Boolean {
        return currentQuery != null
    }

    fun showQuery(query: DnsQuery) {
        val wasUpdated = query != currentQuery
        currentQuery = query

        if (wasUpdated) {
            updateUi()
        }
    }

    private fun updateUi() {
        val query = currentQuery
        if(query != null && viewCreated) {
133
            queryTime.text = formatTimeStamp(query.questionTime)
134
135
136
137
138
139
140
            if(query.responseTime >= query.questionTime) {
                latency.text = (query.responseTime - query.questionTime).toString() + " ms"
            } else {
                latency.text = "-"
            }
            longName.text = query.name
            type.text = query.type.name
141
            protocol.text = when {
142
143
144
                query.responseSource == QueryListener.Source.CACHE -> getString(R.string.windows_querylogging_usedserver_cache)
                query.responseSource == QueryListener.Source.LOCALRESOLVER -> getString(R.string.windows_querylogging_usedserver_dnsrules)
                query.askedServer == null -> "-"
145
146
147
                query.askedServer!!.startsWith("https") -> getString(R.string.fragment_querydetail_mode_doh)
                else -> getString(R.string.fragment_querydetail_mode_dot)
            }
148
149
150
151
            resolvedBy.text = when {
                query.responseSource == QueryListener.Source.CACHE -> getString(R.string.windows_querylogging_usedserver_cache)
                query.responseSource == QueryListener.Source.LOCALRESOLVER -> getString(R.string.windows_querylogging_usedserver_dnsrules)
                else -> query.askedServer?.replace("tls::", "")?.replace("https::", "") ?: "-"
152
            }
153
154
155
156
157
158
159
160
161
            responses.text = query.getParsedResponses().filter {
                it.type == query.type
            }.joinToString {
                val payload = it.payload
                payload.toString()
            }.let {
                if(it.isBlank()) "-"
                else it
            }
162
163
164
165
        }
    }

}