AdGuardHome/internal/querylog/querylog.go
Stanislav Chzhen 4479b32ad4 Pull request 2020: AG-26236-ring-buffer
Squashed commit of the following:

commit 4b9cc9ddf52739fc5f918babedc99ac7ac0e2415
Merge: a6259ed57 39aeaf891
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 20:39:58 2023 +0300

    Merge branch 'master' into AG-26236-ring-buffer

commit a6259ed5758156e4110ee3ea6a49760d2880ade3
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 20:30:20 2023 +0300

    querylog: imp code

commit 40f9f7cd5a1cff22bcb858020f2cfa9be8399671
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 20:11:49 2023 +0300

    querylog: fix typo

commit 1aabbadcb5fcbe6a95945c5bd1455b956b85a8d0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 20:05:22 2023 +0300

    querylog: imp err msg

commit 02913d35b43e190e42765823ccfcdd332839e984
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 19:37:47 2023 +0300

    aghalg: imp tests

commit 98a7909088d2a65b78afa9fa3113545c94429e65
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 19:23:10 2023 +0300

    all: imp tests

commit e147804eeafe89e5020cd917784eef3ff2b310d0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 18:49:49 2023 +0300

    all: imp code

commit 5f21f2f63b7bce89f2bc79c97c7350cd693c956b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 3 14:39:43 2023 +0300

    all: add tests

commit 35a45c7dc5d5961f6987da7c69249c56e54e97f5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Oct 2 17:43:09 2023 +0300

    all: imp code

commit 21e51fcbe411258eaf830825df9d05b7ddcc187a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Sep 29 18:21:00 2023 +0300

    all: add ring buffer
2023-10-03 20:50:43 +03:00

170 lines
4.1 KiB
Go

package querylog
import (
"fmt"
"net"
"path/filepath"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/errors"
"github.com/miekg/dns"
)
// QueryLog - main interface
type QueryLog interface {
Start()
// Close query log object
Close()
// Add a log entry
Add(params *AddParams)
// WriteDiskConfig - write configuration
WriteDiskConfig(c *Config)
// ShouldLog returns true if request for the host should be logged.
ShouldLog(host string, qType, qClass uint16, ids []string) bool
}
// Config is the query log configuration structure.
//
// Do not alter any fields of this structure after using it.
type Config struct {
// Ignored contains the list of host names, which should not be written to
// log, and matches them.
Ignored *aghnet.IgnoreEngine
// Anonymizer processes the IP addresses to anonymize those if needed.
Anonymizer *aghnet.IPMut
// ConfigModified is called when the configuration is changed, for example
// by HTTP requests.
ConfigModified func()
// HTTPRegister registers an HTTP handler.
HTTPRegister aghhttp.RegisterFunc
// FindClient returns client information by their IDs.
FindClient func(ids []string) (c *Client, err error)
// BaseDir is the base directory for log files.
BaseDir string
// RotationIvl is the interval for log rotation. After that period, the old
// log file will be renamed, NOT deleted, so the actual log retention time
// is twice the interval.
RotationIvl time.Duration
// MemSize is the number of entries kept in a memory buffer before they are
// flushed to disk.
MemSize int
// Enabled tells if the query log is enabled.
Enabled bool
// FileEnabled tells if the query log writes logs to files.
FileEnabled bool
// AnonymizeClientIP tells if the query log should anonymize clients' IP
// addresses.
AnonymizeClientIP bool
}
// AddParams is the parameters for adding an entry.
type AddParams struct {
Question *dns.Msg
// ReqECS is the IP network extracted from EDNS Client-Subnet option of a
// request.
ReqECS *net.IPNet
// Answer is the response which is sent to the client, if any.
Answer *dns.Msg
// OrigAnswer is the response from an upstream server. It's only set if the
// answer has been modified by filtering.
OrigAnswer *dns.Msg
// Result is the filtering result (optional).
Result *filtering.Result
ClientID string
// Upstream is the URL of the upstream DNS server.
Upstream string
ClientProto ClientProto
ClientIP net.IP
// Elapsed is the time spent for processing the request.
Elapsed time.Duration
// Cached indicates if the response is served from cache.
Cached bool
// AuthenticatedData shows if the response had the AD bit set.
AuthenticatedData bool
}
// validate returns an error if the parameters aren't valid.
func (p *AddParams) validate() (err error) {
switch {
case p.Question == nil:
return errors.Error("question is nil")
case len(p.Question.Question) != 1:
return errors.Error("more than one question")
case len(p.Question.Question[0].Name) == 0:
return errors.Error("no host in question")
case p.ClientIP == nil:
return errors.Error("no client ip")
default:
return nil
}
}
// New creates a new instance of the query log.
func New(conf Config) (ql QueryLog, err error) {
return newQueryLog(conf)
}
// newQueryLog crates a new queryLog.
func newQueryLog(conf Config) (l *queryLog, err error) {
findClient := conf.FindClient
if findClient == nil {
findClient = func(_ []string) (_ *Client, _ error) {
return nil, nil
}
}
if conf.MemSize < 0 {
return nil, errors.Error("memory size must be greater or equal to zero")
}
l = &queryLog{
findClient: findClient,
buffer: aghalg.NewRingBuffer[*logEntry](conf.MemSize),
conf: &Config{},
confMu: &sync.RWMutex{},
logFile: filepath.Join(conf.BaseDir, queryLogFileName),
anonymizer: conf.Anonymizer,
}
*l.conf = conf
err = validateIvl(conf.RotationIvl)
if err != nil {
return nil, fmt.Errorf("unsupported interval: %w", err)
}
return l, nil
}