AdGuardHome/internal/querylog/decode.go
Stanislav Chzhen d96e65cb0c Pull request 2304: AGDNS-2374-slog-querylog
Squashed commit of the following:

commit e98e5efaaf5388551322933321df0707ad7b2a9c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Nov 21 13:15:51 2024 +0300

    all: imp code

commit fbe728c9aa
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Nov 18 20:57:15 2024 +0300

    all: imp code

commit ef715c58cb
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Nov 18 16:39:35 2024 +0300

    all: imp code

commit cbb993f7ae
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Nov 18 14:03:42 2024 +0300

    all: imp code

commit 8d88d79930
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Nov 15 15:57:07 2024 +0300

    all: slog querylog
2024-11-21 20:19:39 +03:00

767 lines
16 KiB
Go

package querylog
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net"
"net/netip"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)
// logEntryHandler represents a handler for decoding json token to the logEntry
// struct.
type logEntryHandler func(t json.Token, ent *logEntry) error
// logEntryHandlers is the map of log entry decode handlers for various keys.
var logEntryHandlers = map[string]logEntryHandler{
"CID": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.ClientID = v
return nil
},
"IP": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
if ent.IP == nil {
ent.IP = net.ParseIP(v)
}
return nil
},
"T": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
var err error
ent.Time, err = time.Parse(time.RFC3339, v)
return err
},
"QH": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.QHost = v
return nil
},
"QT": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.QType = v
return nil
},
"QC": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.QClass = v
return nil
},
"CP": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
var err error
ent.ClientProto, err = NewClientProto(v)
return err
},
"Answer": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
var err error
ent.Answer, err = base64.StdEncoding.DecodeString(v)
return err
},
"OrigAnswer": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
var err error
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
return err
},
"ECS": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.ReqECS = v
return nil
},
"Cached": func(t json.Token, ent *logEntry) error {
v, ok := t.(bool)
if !ok {
return nil
}
ent.Cached = v
return nil
},
"AD": func(t json.Token, ent *logEntry) error {
v, ok := t.(bool)
if !ok {
return nil
}
ent.AuthenticatedData = v
return nil
},
"Upstream": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.Upstream = v
return nil
},
"Elapsed": func(t json.Token, ent *logEntry) error {
v, ok := t.(json.Number)
if !ok {
return nil
}
i, err := v.Int64()
if err != nil {
return err
}
ent.Elapsed = time.Duration(i)
return nil
},
}
// decodeResultRuleKey decodes the token of "Rules" type to logEntry struct.
func (l *queryLog) decodeResultRuleKey(
ctx context.Context,
key string,
i int,
dec *json.Decoder,
ent *logEntry,
) {
var vToken json.Token
switch key {
case "FilterListID":
ent.Result.Rules, vToken = l.decodeVTokenAndAddRule(ctx, key, i, dec, ent.Result.Rules)
if n, ok := vToken.(json.Number); ok {
id, _ := n.Int64()
ent.Result.Rules[i].FilterListID = rulelist.URLFilterID(id)
}
case "IP":
ent.Result.Rules, vToken = l.decodeVTokenAndAddRule(ctx, key, i, dec, ent.Result.Rules)
if ipStr, ok := vToken.(string); ok {
if ip, err := netip.ParseAddr(ipStr); err == nil {
ent.Result.Rules[i].IP = ip
} else {
l.logger.DebugContext(ctx, "decoding ip", "value", ipStr, slogutil.KeyError, err)
}
}
case "Text":
ent.Result.Rules, vToken = l.decodeVTokenAndAddRule(ctx, key, i, dec, ent.Result.Rules)
if s, ok := vToken.(string); ok {
ent.Result.Rules[i].Text = s
}
default:
// Go on.
}
}
// decodeVTokenAndAddRule decodes the "Rules" toke as [filtering.ResultRule]
// and then adds the decoded object to the slice of result rules.
func (l *queryLog) decodeVTokenAndAddRule(
ctx context.Context,
key string,
i int,
dec *json.Decoder,
rules []*filtering.ResultRule,
) (newRules []*filtering.ResultRule, vToken json.Token) {
newRules = rules
vToken, err := dec.Token()
if err != nil {
if err != io.EOF {
l.logger.DebugContext(
ctx,
"decoding result rule key",
"key", key,
slogutil.KeyError, err,
)
}
return newRules, nil
}
if len(rules) < i+1 {
newRules = append(newRules, &filtering.ResultRule{})
}
return newRules, vToken
}
// decodeResultRules parses the dec's tokens into logEntry ent interpreting it
// as a slice of the result rules.
func (l *queryLog) decodeResultRules(ctx context.Context, dec *json.Decoder, ent *logEntry) {
const msgPrefix = "decoding result rules"
for {
delimToken, err := dec.Token()
if err != nil {
if err != io.EOF {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
if d, ok := delimToken.(json.Delim); !ok {
return
} else if d != '[' {
l.logger.DebugContext(
ctx,
msgPrefix,
slogutil.KeyError, newUnexpectedDelimiterError(d),
)
}
err = l.decodeResultRuleToken(ctx, dec, ent)
if err != nil {
if err != io.EOF && !errors.Is(err, ErrEndOfToken) {
l.logger.DebugContext(ctx, msgPrefix+"; rule token", slogutil.KeyError, err)
}
return
}
}
}
// decodeResultRuleToken decodes the tokens of "Rules" type to the logEntry ent.
func (l *queryLog) decodeResultRuleToken(
ctx context.Context,
dec *json.Decoder,
ent *logEntry,
) (err error) {
i := 0
for {
var keyToken json.Token
keyToken, err = dec.Token()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
if d, ok := keyToken.(json.Delim); ok {
switch d {
case '}':
i++
case ']':
return ErrEndOfToken
default:
// Go on.
}
continue
}
key, ok := keyToken.(string)
if !ok {
return fmt.Errorf("keyToken is %T (%[1]v) and not string", keyToken)
}
l.decodeResultRuleKey(ctx, key, i, dec, ent)
}
}
// decodeResultReverseHosts parses the dec's tokens into ent interpreting it as
// the result of hosts container's $dnsrewrite rule. It assumes there are no
// other occurrences of DNSRewriteResult in the entry since hosts container's
// rewrites currently has the highest priority along the entire filtering
// pipeline.
func (l *queryLog) decodeResultReverseHosts(ctx context.Context, dec *json.Decoder, ent *logEntry) {
const msgPrefix = "decoding result reverse hosts"
for {
itemToken, err := dec.Token()
if err != nil {
if err != io.EOF {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
switch v := itemToken.(type) {
case json.Delim:
if v == '[' {
continue
} else if v == ']' {
return
}
l.logger.DebugContext(
ctx,
msgPrefix,
slogutil.KeyError, newUnexpectedDelimiterError(v),
)
return
case string:
v = dns.Fqdn(v)
if res := &ent.Result; res.DNSRewriteResult == nil {
res.DNSRewriteResult = &filtering.DNSRewriteResult{
RCode: dns.RcodeSuccess,
Response: filtering.DNSRewriteResultResponse{
dns.TypePTR: []rules.RRValue{v},
},
}
continue
} else {
res.DNSRewriteResult.RCode = dns.RcodeSuccess
}
if rres := ent.Result.DNSRewriteResult; rres.Response == nil {
rres.Response = filtering.DNSRewriteResultResponse{dns.TypePTR: []rules.RRValue{v}}
} else {
rres.Response[dns.TypePTR] = append(rres.Response[dns.TypePTR], v)
}
default:
continue
}
}
}
// decodeResultIPList parses the dec's tokens into logEntry ent interpreting it
// as the result IP addresses list.
func (l *queryLog) decodeResultIPList(ctx context.Context, dec *json.Decoder, ent *logEntry) {
const msgPrefix = "decoding result ip list"
for {
itemToken, err := dec.Token()
if err != nil {
if err != io.EOF {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
switch v := itemToken.(type) {
case json.Delim:
if v == '[' {
continue
} else if v == ']' {
return
}
l.logger.DebugContext(
ctx,
msgPrefix,
slogutil.KeyError, newUnexpectedDelimiterError(v),
)
return
case string:
var ip netip.Addr
ip, err = netip.ParseAddr(v)
if err == nil {
ent.Result.IPList = append(ent.Result.IPList, ip)
}
default:
continue
}
}
}
// decodeResultDNSRewriteResultKey decodes the token of "DNSRewriteResult" type
// to the logEntry struct.
func (l *queryLog) decodeResultDNSRewriteResultKey(
ctx context.Context,
key string,
dec *json.Decoder,
ent *logEntry,
) {
const msgPrefix = "decoding result dns rewrite result key"
var err error
switch key {
case "RCode":
var vToken json.Token
vToken, err = dec.Token()
if err != nil {
if err != io.EOF {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
if ent.Result.DNSRewriteResult == nil {
ent.Result.DNSRewriteResult = &filtering.DNSRewriteResult{}
}
if n, ok := vToken.(json.Number); ok {
rcode64, _ := n.Int64()
ent.Result.DNSRewriteResult.RCode = rules.RCode(rcode64)
}
case "Response":
if ent.Result.DNSRewriteResult == nil {
ent.Result.DNSRewriteResult = &filtering.DNSRewriteResult{}
}
if ent.Result.DNSRewriteResult.Response == nil {
ent.Result.DNSRewriteResult.Response = filtering.DNSRewriteResultResponse{}
}
// TODO(a.garipov): I give up. This whole file is a mess. Luckily, we
// can assume that this field is relatively rare and just use the normal
// decoding and correct the values.
err = dec.Decode(&ent.Result.DNSRewriteResult.Response)
if err != nil {
l.logger.DebugContext(ctx, msgPrefix+"; response", slogutil.KeyError, err)
}
ent.parseDNSRewriteResultIPs()
default:
// Go on.
}
}
// decodeResultDNSRewriteResult parses the dec's tokens into logEntry ent
// interpreting it as the result DNSRewriteResult.
func (l *queryLog) decodeResultDNSRewriteResult(
ctx context.Context,
dec *json.Decoder,
ent *logEntry,
) {
const msgPrefix = "decoding result dns rewrite result"
for {
key, err := parseKeyToken(dec)
if err != nil {
if err != io.EOF && !errors.Is(err, ErrEndOfToken) {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
if key == "" {
continue
}
l.decodeResultDNSRewriteResultKey(ctx, key, dec, ent)
}
}
// translateResult converts some fields of the ent.Result to the format
// consistent with current implementation.
func translateResult(ent *logEntry) {
res := &ent.Result
if res.Reason != filtering.RewrittenAutoHosts || len(res.IPList) == 0 {
return
}
if res.DNSRewriteResult == nil {
res.DNSRewriteResult = &filtering.DNSRewriteResult{
RCode: dns.RcodeSuccess,
}
}
if res.DNSRewriteResult.Response == nil {
res.DNSRewriteResult.Response = filtering.DNSRewriteResultResponse{}
}
resp := res.DNSRewriteResult.Response
for _, ip := range res.IPList {
qType := dns.TypeAAAA
if ip.Is4() {
qType = dns.TypeA
}
resp[qType] = append(resp[qType], ip)
}
res.IPList = nil
}
// ErrEndOfToken is an error returned by parse key token when the closing
// bracket is found.
const ErrEndOfToken errors.Error = "end of token"
// parseKeyToken parses the dec's token key.
func parseKeyToken(dec *json.Decoder) (key string, err error) {
keyToken, err := dec.Token()
if err != nil {
return "", err
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
return "", ErrEndOfToken
}
return "", nil
}
key, ok := keyToken.(string)
if !ok {
return "", fmt.Errorf("keyToken is %T (%[1]v) and not string", keyToken)
}
return key, nil
}
// decodeResult decodes a token of "Result" type to logEntry struct.
func (l *queryLog) decodeResult(ctx context.Context, dec *json.Decoder, ent *logEntry) {
const msgPrefix = "decoding result"
defer translateResult(ent)
for {
key, err := parseKeyToken(dec)
if err != nil {
if err != io.EOF && !errors.Is(err, ErrEndOfToken) {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
if key == "" {
continue
}
ok := l.resultDecHandler(ctx, key, dec, ent)
if ok {
continue
}
handler, ok := resultHandlers[key]
if !ok {
continue
}
val, err := dec.Token()
if err != nil {
return
}
if err = handler(val, ent); err != nil {
l.logger.DebugContext(ctx, msgPrefix+"; handler", slogutil.KeyError, err)
return
}
}
}
// resultHandlers is the map of log entry decode handlers for various keys.
var resultHandlers = map[string]logEntryHandler{
"IsFiltered": func(t json.Token, ent *logEntry) error {
v, ok := t.(bool)
if !ok {
return nil
}
ent.Result.IsFiltered = v
return nil
},
"Rule": func(t json.Token, ent *logEntry) error {
s, ok := t.(string)
if !ok {
return nil
}
l := len(ent.Result.Rules)
if l == 0 {
ent.Result.Rules = []*filtering.ResultRule{{}}
l++
}
ent.Result.Rules[l-1].Text = s
return nil
},
"FilterID": func(t json.Token, ent *logEntry) error {
n, ok := t.(json.Number)
if !ok {
return nil
}
id, err := n.Int64()
if err != nil {
return err
}
l := len(ent.Result.Rules)
if l == 0 {
ent.Result.Rules = []*filtering.ResultRule{{}}
l++
}
ent.Result.Rules[l-1].FilterListID = rulelist.URLFilterID(id)
return nil
},
"Reason": func(t json.Token, ent *logEntry) error {
v, ok := t.(json.Number)
if !ok {
return nil
}
i, err := v.Int64()
if err != nil {
return err
}
ent.Result.Reason = filtering.Reason(i)
return nil
},
"ServiceName": func(t json.Token, ent *logEntry) error {
s, ok := t.(string)
if !ok {
return nil
}
ent.Result.ServiceName = s
return nil
},
"CanonName": func(t json.Token, ent *logEntry) error {
s, ok := t.(string)
if !ok {
return nil
}
ent.Result.CanonName = s
return nil
},
}
// resultDecHandlers calls a decode handler for key if there is one.
func (l *queryLog) resultDecHandler(
ctx context.Context,
name string,
dec *json.Decoder,
ent *logEntry,
) (ok bool) {
ok = true
switch name {
case "ReverseHosts":
l.decodeResultReverseHosts(ctx, dec, ent)
case "IPList":
l.decodeResultIPList(ctx, dec, ent)
case "Rules":
l.decodeResultRules(ctx, dec, ent)
case "DNSRewriteResult":
l.decodeResultDNSRewriteResult(ctx, dec, ent)
default:
ok = false
}
return ok
}
// decodeLogEntry decodes string str to logEntry ent.
func (l *queryLog) decodeLogEntry(ctx context.Context, ent *logEntry, str string) {
const msgPrefix = "decoding log entry"
dec := json.NewDecoder(strings.NewReader(str))
dec.UseNumber()
for {
keyToken, err := dec.Token()
if err != nil {
if err != io.EOF {
l.logger.DebugContext(ctx, msgPrefix+"; token", slogutil.KeyError, err)
}
return
}
if _, ok := keyToken.(json.Delim); ok {
continue
}
key, ok := keyToken.(string)
if !ok {
err = fmt.Errorf("%s: keyToken is %T (%[2]v) and not string", msgPrefix, keyToken)
l.logger.DebugContext(ctx, msgPrefix, slogutil.KeyError, err)
return
}
if key == "Result" {
l.decodeResult(ctx, dec, ent)
continue
}
handler, ok := logEntryHandlers[key]
if !ok {
continue
}
val, err := dec.Token()
if err != nil {
return
}
if err = handler(val, ent); err != nil {
l.logger.DebugContext(ctx, msgPrefix+"; handler", slogutil.KeyError, err)
return
}
}
}
// newUnexpectedDelimiterError is a helper for creating informative errors.
func newUnexpectedDelimiterError(d json.Delim) (err error) {
return fmt.Errorf("unexpected delimiter: %q", d)
}