Skip to content

Commit

Permalink
Merge: + new query logs API
Browse files Browse the repository at this point in the history
* commit '59c4a2886a97143e3a36912ec895dc1a06be88cc':
  openapi
  If there are no more older entries, `"oldest":""` is returned.
  fix search by "whitelisted", "rewritten"
  doc
  fix whois test
  + "dot"
  openapi
  * minor
  + client_proto
  * openapi
  + new query logs API
  • Loading branch information
ameshkov committed Jun 17, 2020
2 parents 4870da7 + 59c4a28 commit b1c951f
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 80 deletions.
33 changes: 25 additions & 8 deletions AGHTechDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -1198,8 +1198,9 @@ When a new DNS request is received and processed, we store information about thi
"QH":"...", // target host name without the last dot
"QT":"...", // question type
"QC":"...", // question class
"Answer":"...",
"OrigAnswer":"...",
"CP":"" | "doh", // client connection protocol
"Answer":"base64 data",
"OrigAnswer":"base64 data",
"Result":{
"IsFiltered":true,
"Reason":3,
Expand Down Expand Up @@ -1232,16 +1233,28 @@ Request:

GET /control/querylog
?older_than=2006-01-02T15:04:05.999999999Z07:00
&filter_domain=...
&filter_client=...
&filter_question_type=A | AAAA
&filter_response_status= | filtered
&search=...
&response_status="..."

`older_than` setting is used for paging. UI uses an empty value for `older_than` on the first request and gets the latest log entries. To get the older entries, UI sets `older_than` to the `oldest` value from the server's response.

If "filter" settings are set, server returns only entries that match the specified request.
If search settings are set, server returns only entries that match the specified request.

For `filter.domain` and `filter.client` the server matches substrings by default: `adguard.com` matches `www.adguard.com`. Strict matching can be enabled by enclosing the value in double quotes: `"adguard.com"` matches `adguard.com` but doesn't match `www.adguard.com`.
`search`:
match by domain name or client IP address.
The server matches substrings by default: e.g. `adguard.com` matches `www.adguard.com`.
Strict matching can be enabled by enclosing the value in double quotes: e.g. `"adguard.com"` matches `adguard.com` but doesn't match `www.adguard.com`.

`response_status`:
* all
* filtered - all kinds of filtering
* blocked - blocked or blocked service
* blocked_safebrowsing - blocked by safebrowsing
* blocked_parental - blocked by parental control
* whitelisted - whitelisted
* rewritten - all kinds of rewrites
* safe_search - enforced safe search
* processed - not blocked, not white-listed entries

Response:

Expand All @@ -1264,8 +1277,10 @@ Response:
}
...
],
"upstream":"...", // Upstream URL starting with tcp://, tls://, https://, or with an IP address
"answer_dnssec": true,
"client":"127.0.0.1",
"client_proto": "" (plain) | "doh" | "dot",
"elapsedMs":"0.098403",
"filterId":1,
"question":{
Expand All @@ -1285,6 +1300,8 @@ Response:

The most recent entries are at the top of list.

If there are no more older entries, `"oldest":""` is returned.


### API: Set querylog parameters

Expand Down
5 changes: 5 additions & 0 deletions dnsforward/dnsforward_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ func checkDNS(input string, bootstrap []string) error {
return nil
}

// Control flow:
// web
// -> dnsforward.handleDOH -> dnsforward.ServeHTTP
// -> proxy.ServeHTTP -> proxy.handleDNSRequest
// -> dnsforward.handleDNSRequest
func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) {
if !s.conf.TLSAllowUnencryptedDOH && r.TLS == nil {
httpError(r, w, http.StatusNotFound, "Not Found")
Expand Down
7 changes: 7 additions & 0 deletions dnsforward/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func processQueryLogsAndStats(ctx *dnsContext) int {
Elapsed: elapsed,
ClientIP: getIP(d.Addr),
}

if d.Proto == "https" {
p.ClientProto = "doh"
} else if d.Proto == "tls" {
p.ClientProto = "dot"
}

if d.Upstream != nil {
p.Upstream = d.Upstream.Address()
}
Expand Down
8 changes: 4 additions & 4 deletions home/whois_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ func TestWhois(t *testing.T) {

w := Whois{timeoutMsec: 5000}
resp, err := w.queryAll("8.8.8.8")
assert.True(t, err == nil)
assert.Nil(t, err)
m := whoisParse(resp)
assert.True(t, m["orgname"] == "Google LLC")
assert.True(t, m["country"] == "US")
assert.True(t, m["city"] == "Mountain View")
assert.Equal(t, "Google LLC", m["orgname"])
assert.Equal(t, "US", m["country"])
assert.Equal(t, "Mountain View", m["city"])
}
33 changes: 19 additions & 14 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,26 @@ paths:
description: Limit the number of records to be returned
schema:
type: integer
- name: filter_domain
- name: search
in: query
description: Filter by domain name
description: Filter by domain name or client IP
schema:
type: string
- name: filter_client
in: query
description: Filter by client
schema:
type: string
- name: filter_question_type
in: query
description: Filter by question type
schema:
type: string
- name: filter_response_status
- name: response_status
in: query
description: Filter by response status
schema:
type: string
enum:
- null
- all
- filtered
- blocked
- blocked_safebrowsing
- blocked_parental
- whitelisted
- rewritten
- safe_search
- processed
responses:
"200":
description: OK
Expand Down Expand Up @@ -1420,11 +1417,19 @@ components:
description: Answer from upstream server (optional)
items:
$ref: "#/components/schemas/DnsAnswer"
upstream:
type: string
description: Upstream URL starting with tcp://, tls://, https://, or with an IP address
answer_dnssec:
type: boolean
client:
type: string
example: 192.168.0.1
client_proto:
enum:
- dot
- doh
- ""
elapsedMs:
type: string
example: "54.023928"
Expand Down
3 changes: 3 additions & 0 deletions querylog/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ func decodeLogEntry(ent *logEntry, str string) {
case "QC":
ent.QClass = v

case "CP":
ent.ClientProto = v

case "Answer":
ent.Answer, err = base64.StdEncoding.DecodeString(v)
case "OrigAnswer":
Expand Down
11 changes: 7 additions & 4 deletions querylog/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
}

jsonEntry := map[string]interface{}{
"reason": entry.Result.Reason.String(),
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
"time": entry.Time.Format(time.RFC3339Nano),
"client": l.getClientIP(entry.IP),
"reason": entry.Result.Reason.String(),
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
"time": entry.Time.Format(time.RFC3339Nano),
"client": l.getClientIP(entry.IP),
"client_proto": entry.ClientProto,
}
jsonEntry["question"] = map[string]interface{}{
"host": entry.QHost,
Expand Down Expand Up @@ -112,6 +113,8 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
}
}

jsonEntry["upstream"] = entry.Upstream

return jsonEntry
}

Expand Down
9 changes: 6 additions & 3 deletions querylog/qlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type logEntry struct {
QType string `json:"QT"`
QClass string `json:"QC"`

ClientProto string `json:"CP"` // "" or "doh"

Answer []byte `json:",omitempty"` // sometimes empty answers happen like binerdunt.top or rev2.globalrootservers.net
OrigAnswer []byte `json:",omitempty"`

Expand Down Expand Up @@ -119,9 +121,10 @@ func (l *queryLog) Add(params AddParams) {
IP: l.getClientIP(params.ClientIP.String()),
Time: now,

Result: *params.Result,
Elapsed: params.Elapsed,
Upstream: params.Upstream,
Result: *params.Result,
Elapsed: params.Elapsed,
Upstream: params.Upstream,
ClientProto: params.ClientProto,
}
q := params.Question.Question[0]
entry.QHost = strings.ToLower(q.Name[:len(q.Name)-1]) // remove the last dot
Expand Down
10 changes: 2 additions & 8 deletions querylog/qlog_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,6 @@ func (l *queryLog) parseSearchCriteria(q url.Values, name string, ct criteriaTyp
c.strict = true
}

if ct == ctClient && l.conf.AnonymizeClientIP {
c.value = l.getClientIP(c.value)
}

if ct == ctFilteringStatus && !util.ContainsString(filteringStatusValues, c.value) {
return false, c, fmt.Errorf("invalid value %s", c.value)
}
Expand Down Expand Up @@ -180,10 +176,8 @@ func (l *queryLog) parseSearchParams(r *http.Request) (*searchParams, error) {
}

paramNames := map[string]criteriaType{
"filter_domain": ctDomain,
"filter_client": ctClient,
"filter_question_type": ctQuestionType,
"filter_response_status": ctFilteringStatus,
"search": ctDomainOrClient,
"response_status": ctFilteringStatus,
}

for k, v := range paramNames {
Expand Down
8 changes: 4 additions & 4 deletions querylog/qlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestQueryLog(t *testing.T) {
// search by domain (strict)
params = newSearchParams()
params.searchCriteria = append(params.searchCriteria, searchCriteria{
criteriaType: ctDomain,
criteriaType: ctDomainOrClient,
strict: true,
value: "test.example.org",
})
Expand All @@ -68,7 +68,7 @@ func TestQueryLog(t *testing.T) {
// search by domain (not strict)
params = newSearchParams()
params.searchCriteria = append(params.searchCriteria, searchCriteria{
criteriaType: ctDomain,
criteriaType: ctDomainOrClient,
strict: false,
value: "example.org",
})
Expand All @@ -81,7 +81,7 @@ func TestQueryLog(t *testing.T) {
// search by client IP (strict)
params = newSearchParams()
params.searchCriteria = append(params.searchCriteria, searchCriteria{
criteriaType: ctClient,
criteriaType: ctDomainOrClient,
strict: true,
value: "2.2.2.2",
})
Expand All @@ -92,7 +92,7 @@ func TestQueryLog(t *testing.T) {
// search by client IP (part of)
params = newSearchParams()
params.searchCriteria = append(params.searchCriteria, searchCriteria{
criteriaType: ctClient,
criteriaType: ctDomainOrClient,
strict: false,
value: "2.2.2",
})
Expand Down
15 changes: 8 additions & 7 deletions querylog/querylog.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ type Config struct {

// AddParams - parameters for Add()
type AddParams struct {
Question *dns.Msg
Answer *dns.Msg // The response we sent to the client (optional)
OrigAnswer *dns.Msg // The response from an upstream server (optional)
Result *dnsfilter.Result // Filtering result (optional)
Elapsed time.Duration // Time spent for processing the request
ClientIP net.IP
Upstream string
Question *dns.Msg
Answer *dns.Msg // The response we sent to the client (optional)
OrigAnswer *dns.Msg // The response from an upstream server (optional)
Result *dnsfilter.Result // Filtering result (optional)
Elapsed time.Duration // Time spent for processing the request
ClientIP net.IP
Upstream string // Upstream server URL
ClientProto string // Protocol for the client connection: "" (plain), "doh", "dot"
}

// New - create a new instance of the query log
Expand Down
4 changes: 3 additions & 1 deletion querylog/querylog_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ func (l *queryLog) searchFiles(params *searchParams) ([]*logEntry, time.Time, in
}
}

oldest = time.Unix(0, oldestNano)
if oldestNano != 0 {
oldest = time.Unix(0, oldestNano)
}
return entries, oldest, total
}

Expand Down
Loading

0 comments on commit b1c951f

Please sign in to comment.