diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index fde411a9..d28889b3 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -550,7 +550,7 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error { s.RLock() // Synchronize access to s.queryLog and s.stats so they won't be suddenly uninitialized while in use. // This can happen after proxy server has been stopped, but its workers haven't yet exited. - if s.conf.QueryLogEnabled && shouldLog && s.queryLog != nil { + if shouldLog && s.queryLog != nil { upstreamAddr := "" if d.Upstream != nil { upstreamAddr = d.Upstream.Address() diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go index 92a1e01b..94e72d67 100644 --- a/dnsforward/dnsforward_test.go +++ b/dnsforward/dnsforward_test.go @@ -380,7 +380,6 @@ func createTestServer(t *testing.T) *Server { s.conf.UDPListenAddr = &net.UDPAddr{Port: 0} s.conf.TCPListenAddr = &net.TCPAddr{Port: 0} - s.conf.QueryLogEnabled = true s.conf.FilteringConfig.FilteringEnabled = true s.conf.FilteringConfig.ProtectionEnabled = true s.conf.FilteringConfig.SafeBrowsingEnabled = true diff --git a/home/config.go b/home/config.go index 68e5fc00..726eda90 100644 --- a/home/config.go +++ b/home/config.go @@ -278,10 +278,6 @@ func parseConfig() error { config.DNS.FiltersUpdateIntervalHours = 24 } - if !checkQueryLogInterval(config.DNS.QueryLogInterval) { - config.DNS.QueryLogInterval = 1 - } - for _, cy := range config.Clients { cli := Client{ Name: cy.Name, @@ -364,6 +360,13 @@ func (c *configuration) write() error { config.DNS.StatsInterval = sdc.Interval } + if config.queryLog != nil { + dc := querylog.DiskConfig{} + config.queryLog.WriteDiskConfig(&dc) + config.DNS.QueryLogEnabled = dc.Enabled + config.DNS.QueryLogInterval = dc.Interval + } + configFile := config.getConfigFilename() log.Debug("Writing YAML file: %s", configFile) yamlText, err := yaml.Marshal(&config) diff --git a/home/control.go b/home/control.go index 7d3d3f21..d18c2a85 100644 --- a/home/control.go +++ b/home/control.go @@ -111,7 +111,6 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { "http_port": config.BindPort, "dns_port": config.DNS.Port, "protection_enabled": config.DNS.ProtectionEnabled, - "querylog_enabled": config.DNS.QueryLogEnabled, "running": isRunning(), "bootstrap_dns": config.DNS.BootstrapDNS, "upstream_dns": config.DNS.UpstreamDNS, @@ -568,7 +567,6 @@ func registerControlHandlers() { RegisterClientsHandlers() registerRewritesHandlers() RegisterBlockedServicesHandlers() - RegisterQueryLogHandlers() RegisterAuthHandlers() http.HandleFunc("/dns-query", postInstall(handleDOH)) diff --git a/home/control_querylog.go b/home/control_querylog.go deleted file mode 100644 index 737b9f12..00000000 --- a/home/control_querylog.go +++ /dev/null @@ -1,160 +0,0 @@ -package home - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/AdguardTeam/AdGuardHome/querylog" - "github.com/miekg/dns" -) - -type qlogFilterJSON struct { - Domain string `json:"domain"` - Client string `json:"client"` - QuestionType string `json:"question_type"` - ResponseStatus string `json:"response_status"` -} - -type queryLogRequest struct { - OlderThan string `json:"older_than"` - Filter qlogFilterJSON `json:"filter"` -} - -// "value" -> value, return TRUE -func getDoubleQuotesEnclosedValue(s *string) bool { - t := *s - if len(t) >= 2 && t[0] == '"' && t[len(t)-1] == '"' { - *s = t[1 : len(t)-1] - return true - } - return false -} - -func handleQueryLog(w http.ResponseWriter, r *http.Request) { - req := queryLogRequest{} - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - httpError(w, http.StatusBadRequest, "json decode: %s", err) - return - } - - params := querylog.GetDataParams{ - Domain: req.Filter.Domain, - Client: req.Filter.Client, - } - if len(req.OlderThan) != 0 { - params.OlderThan, err = time.Parse(time.RFC3339Nano, req.OlderThan) - if err != nil { - httpError(w, http.StatusBadRequest, "invalid time stamp: %s", err) - return - } - } - - if getDoubleQuotesEnclosedValue(¶ms.Domain) { - params.StrictMatchDomain = true - } - if getDoubleQuotesEnclosedValue(¶ms.Client) { - params.StrictMatchClient = true - } - - if len(req.Filter.QuestionType) != 0 { - qtype, ok := dns.StringToType[req.Filter.QuestionType] - if !ok { - httpError(w, http.StatusBadRequest, "invalid question_type") - return - } - params.QuestionType = qtype - } - - if len(req.Filter.ResponseStatus) != 0 { - switch req.Filter.ResponseStatus { - case "filtered": - params.ResponseStatus = querylog.ResponseStatusFiltered - default: - httpError(w, http.StatusBadRequest, "invalid response_status") - return - } - } - - data := config.queryLog.GetData(params) - - jsonVal, err := json.Marshal(data) - if err != nil { - httpError(w, http.StatusInternalServerError, "Couldn't marshal data into json: %s", err) - return - } - - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) - if err != nil { - httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err) - } -} - -func handleQueryLogClear(w http.ResponseWriter, r *http.Request) { - config.queryLog.Clear() - returnOK(w) -} - -type qlogConfig struct { - Enabled bool `json:"enabled"` - Interval uint32 `json:"interval"` -} - -// Get configuration -func handleQueryLogInfo(w http.ResponseWriter, r *http.Request) { - resp := qlogConfig{} - resp.Enabled = config.DNS.QueryLogEnabled - resp.Interval = config.DNS.QueryLogInterval - - jsonVal, err := json.Marshal(resp) - if err != nil { - httpError(w, http.StatusInternalServerError, "json encode: %s", err) - return - } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) - if err != nil { - httpError(w, http.StatusInternalServerError, "http write: %s", err) - } -} - -// Set configuration -func handleQueryLogConfig(w http.ResponseWriter, r *http.Request) { - - reqData := qlogConfig{} - err := json.NewDecoder(r.Body).Decode(&reqData) - if err != nil { - httpError(w, http.StatusBadRequest, "json decode: %s", err) - return - } - - if !checkQueryLogInterval(reqData.Interval) { - httpError(w, http.StatusBadRequest, "Unsupported interval") - return - } - - config.DNS.QueryLogEnabled = reqData.Enabled - config.DNS.QueryLogInterval = reqData.Interval - _ = config.write() - - conf := querylog.Config{ - Interval: config.DNS.QueryLogInterval * 24, - } - config.queryLog.Configure(conf) - - returnOK(w) -} - -func checkQueryLogInterval(i uint32) bool { - return i == 1 || i == 7 || i == 30 || i == 90 -} - -// RegisterQueryLogHandlers - register handlers -func RegisterQueryLogHandlers() { - httpRegister("POST", "/control/querylog", handleQueryLog) - httpRegister(http.MethodGet, "/control/querylog_info", handleQueryLogInfo) - httpRegister(http.MethodPost, "/control/querylog_clear", handleQueryLogClear) - httpRegister(http.MethodPost, "/control/querylog_config", handleQueryLogConfig) -} diff --git a/home/dns.go b/home/dns.go index 3bf96185..64c9efe4 100644 --- a/home/dns.go +++ b/home/dns.go @@ -48,8 +48,11 @@ func initDNSServer() { log.Fatal("Couldn't initialize statistics module") } conf := querylog.Config{ - BaseDir: baseDir, - Interval: config.DNS.QueryLogInterval * 24, + Enabled: config.DNS.QueryLogEnabled, + BaseDir: baseDir, + Interval: config.DNS.QueryLogInterval, + ConfigModified: onConfigModified, + HTTPRegister: httpRegister, } config.queryLog = querylog.New(conf) config.dnsServer = dnsforward.NewServer(config.stats, config.queryLog) diff --git a/querylog/qlog.go b/querylog/qlog.go index 5465b7c6..65eefbd9 100644 --- a/querylog/qlog.go +++ b/querylog/qlog.go @@ -16,7 +16,7 @@ import ( ) const ( - logBufferCap = 5000 // maximum capacity of logBuffer before it's flushed to disk + logBufferCap = 5000 // maximum capacity of buffer before it's flushed to disk queryLogFileName = "querylog.json" // .gz added during compression getDataLimit = 500 // GetData(): maximum log entries to return @@ -29,10 +29,11 @@ type queryLog struct { conf Config logFile string // path to the log file - logBufferLock sync.RWMutex - logBuffer []*logEntry + bufferLock sync.RWMutex + buffer []*logEntry fileFlushLock sync.Mutex // synchronize a file-flushing goroutine and main thread flushPending bool // don't start another goroutine while the previous one is still running + fileWriteLock sync.Mutex } // create a new instance of the query log @@ -40,7 +41,13 @@ func newQueryLog(conf Config) *queryLog { l := queryLog{} l.logFile = filepath.Join(conf.BaseDir, queryLogFileName) l.conf = conf - go l.periodicQueryLogRotate() + if !checkInterval(l.conf.Interval) { + l.conf.Interval = 1 + } + if l.conf.HTTPRegister != nil { + l.initWeb() + } + go l.periodicRotate() return &l } @@ -48,18 +55,30 @@ func (l *queryLog) Close() { _ = l.flushLogBuffer(true) } -func (l *queryLog) Configure(conf Config) { - l.conf = conf +func checkInterval(days uint32) bool { + return days == 1 || days == 7 || days == 30 || days == 90 } -func (l *queryLog) Clear() { +// Set new configuration at runtime +func (l *queryLog) configure(conf Config) { + l.conf.Enabled = conf.Enabled + l.conf.Interval = conf.Interval +} + +func (l *queryLog) WriteDiskConfig(dc *DiskConfig) { + dc.Enabled = l.conf.Enabled + dc.Interval = l.conf.Interval +} + +// Clear memory buffer and remove log files +func (l *queryLog) clear() { l.fileFlushLock.Lock() defer l.fileFlushLock.Unlock() - l.logBufferLock.Lock() - l.logBuffer = nil + l.bufferLock.Lock() + l.buffer = nil l.flushPending = false - l.logBufferLock.Unlock() + l.bufferLock.Unlock() err := os.Remove(l.logFile + ".1") if err != nil && !os.IsNotExist(err) { @@ -96,6 +115,10 @@ func getIPString(addr net.Addr) string { } func (l *queryLog) Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Result, elapsed time.Duration, addr net.Addr, upstream string) { + if !l.conf.Enabled { + return + } + var q []byte var a []byte var err error @@ -132,16 +155,16 @@ func (l *queryLog) Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Res Upstream: upstream, } - l.logBufferLock.Lock() - l.logBuffer = append(l.logBuffer, &entry) + l.bufferLock.Lock() + l.buffer = append(l.buffer, &entry) needFlush := false if !l.flushPending { - needFlush = len(l.logBuffer) >= logBufferCap + needFlush = len(l.buffer) >= logBufferCap if needFlush { l.flushPending = true } } - l.logBufferLock.Unlock() + l.bufferLock.Unlock() // if buffer needs to be flushed to disk, do it now if needFlush { @@ -152,11 +175,9 @@ func (l *queryLog) Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Res } // Return TRUE if this entry is needed -func isNeeded(entry *logEntry, params GetDataParams) bool { - if params.ResponseStatus != 0 { - if params.ResponseStatus == ResponseStatusFiltered && !entry.Result.IsFiltered { - return false - } +func isNeeded(entry *logEntry, params getDataParams) bool { + if params.ResponseStatus == responseStatusFiltered && !entry.Result.IsFiltered { + return false } if len(params.Domain) != 0 || params.QuestionType != 0 { @@ -193,7 +214,7 @@ func isNeeded(entry *logEntry, params GetDataParams) bool { return true } -func (l *queryLog) readFromFile(params GetDataParams) ([]*logEntry, int) { +func (l *queryLog) readFromFile(params getDataParams) ([]*logEntry, int) { entries := []*logEntry{} olderThan := params.OlderThan totalChunks := 0 @@ -247,7 +268,28 @@ func (l *queryLog) readFromFile(params GetDataParams) ([]*logEntry, int) { return entries, total } -func (l *queryLog) GetData(params GetDataParams) []map[string]interface{} { +// Parameters for getData() +type getDataParams struct { + OlderThan time.Time // return entries that are older than this value + Domain string // filter by domain name in question + Client string // filter by client IP + QuestionType uint16 // filter by question type + ResponseStatus responseStatusType // filter by response status + StrictMatchDomain bool // if Domain value must be matched strictly + StrictMatchClient bool // if Client value must be matched strictly +} + +// Response status +type responseStatusType int32 + +// Response status constants +const ( + responseStatusAll responseStatusType = iota + 1 + responseStatusFiltered +) + +// Get log entries +func (l *queryLog) getData(params getDataParams) []map[string]interface{} { var data = []map[string]interface{}{} if len(params.Domain) != 0 && params.StrictMatchDomain { @@ -266,9 +308,9 @@ func (l *queryLog) GetData(params GetDataParams) []map[string]interface{} { } // add from memory buffer - l.logBufferLock.Lock() - total += len(l.logBuffer) - for _, entry := range l.logBuffer { + l.bufferLock.Lock() + total += len(l.buffer) + for _, entry := range l.buffer { if !isNeeded(entry, params) { continue @@ -283,7 +325,7 @@ func (l *queryLog) GetData(params GetDataParams) []map[string]interface{} { } entries = append(entries, entry) } - l.logBufferLock.Unlock() + l.bufferLock.Unlock() // process the elements from latest to oldest for i := len(entries) - 1; i >= 0; i-- { diff --git a/querylog/qlog_http.go b/querylog/qlog_http.go new file mode 100644 index 00000000..1b06abc9 --- /dev/null +++ b/querylog/qlog_http.go @@ -0,0 +1,162 @@ +package querylog + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/AdguardTeam/golibs/log" + "github.com/miekg/dns" +) + +func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) { + text := fmt.Sprintf(format, args...) + + log.Info("QueryLog: %s %s: %s", r.Method, r.URL, text) + + http.Error(w, text, code) +} + +type filterJSON struct { + Domain string `json:"domain"` + Client string `json:"client"` + QuestionType string `json:"question_type"` + ResponseStatus string `json:"response_status"` +} + +type request struct { + OlderThan string `json:"older_than"` + Filter filterJSON `json:"filter"` +} + +// "value" -> value, return TRUE +func getDoubleQuotesEnclosedValue(s *string) bool { + t := *s + if len(t) >= 2 && t[0] == '"' && t[len(t)-1] == '"' { + *s = t[1 : len(t)-1] + return true + } + return false +} + +func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) { + req := request{} + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + httpError(r, w, http.StatusBadRequest, "json decode: %s", err) + return + } + + params := getDataParams{ + Domain: req.Filter.Domain, + Client: req.Filter.Client, + ResponseStatus: responseStatusAll, + } + if len(req.OlderThan) != 0 { + params.OlderThan, err = time.Parse(time.RFC3339Nano, req.OlderThan) + if err != nil { + httpError(r, w, http.StatusBadRequest, "invalid time stamp: %s", err) + return + } + } + + if getDoubleQuotesEnclosedValue(¶ms.Domain) { + params.StrictMatchDomain = true + } + if getDoubleQuotesEnclosedValue(¶ms.Client) { + params.StrictMatchClient = true + } + + if len(req.Filter.QuestionType) != 0 { + qtype, ok := dns.StringToType[req.Filter.QuestionType] + if !ok { + httpError(r, w, http.StatusBadRequest, "invalid question_type") + return + } + params.QuestionType = qtype + } + + if len(req.Filter.ResponseStatus) != 0 { + switch req.Filter.ResponseStatus { + case "filtered": + params.ResponseStatus = responseStatusFiltered + default: + httpError(r, w, http.StatusBadRequest, "invalid response_status") + return + } + } + + data := l.getData(params) + + jsonVal, err := json.Marshal(data) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Couldn't marshal data into json: %s", err) + return + } + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonVal) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err) + } +} + +func (l *queryLog) handleQueryLogClear(w http.ResponseWriter, r *http.Request) { + l.clear() +} + +type qlogConfig struct { + Enabled bool `json:"enabled"` + Interval uint32 `json:"interval"` +} + +// Get configuration +func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) { + resp := qlogConfig{} + resp.Enabled = l.conf.Enabled + resp.Interval = l.conf.Interval + + jsonVal, err := json.Marshal(resp) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "json encode: %s", err) + return + } + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonVal) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "http write: %s", err) + } +} + +// Set configuration +func (l *queryLog) handleQueryLogConfig(w http.ResponseWriter, r *http.Request) { + + reqData := qlogConfig{} + err := json.NewDecoder(r.Body).Decode(&reqData) + if err != nil { + httpError(r, w, http.StatusBadRequest, "json decode: %s", err) + return + } + + if !checkInterval(reqData.Interval) { + httpError(r, w, http.StatusBadRequest, "Unsupported interval") + return + } + + conf := Config{ + Enabled: reqData.Enabled, + Interval: reqData.Interval, + } + l.configure(conf) + + l.conf.ConfigModified() +} + +// Register web handlers +func (l *queryLog) initWeb() { + l.conf.HTTPRegister("POST", "/control/querylog", l.handleQueryLog) + l.conf.HTTPRegister("GET", "/control/querylog_info", l.handleQueryLogInfo) + l.conf.HTTPRegister("POST", "/control/querylog_clear", l.handleQueryLogClear) + l.conf.HTTPRegister("POST", "/control/querylog_config", l.handleQueryLogConfig) +} diff --git a/querylog/querylog.go b/querylog/querylog.go index 308830a9..26bd55a0 100644 --- a/querylog/querylog.go +++ b/querylog/querylog.go @@ -2,58 +2,45 @@ package querylog import ( "net" + "net/http" "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/miekg/dns" ) +// DiskConfig - configuration settings that are stored on disk +type DiskConfig struct { + Enabled bool + Interval uint32 +} + // QueryLog - main interface type QueryLog interface { // Close query log object Close() - // Set new configuration at runtime - // Currently only 'Interval' field is supported. - Configure(conf Config) - // Add a log entry Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Result, elapsed time.Duration, addr net.Addr, upstream string) - // Get log entries - GetData(params GetDataParams) []map[string]interface{} - - // Clear memory buffer and remove log files - Clear() + // WriteDiskConfig - write configuration + WriteDiskConfig(dc *DiskConfig) } // Config - configuration object type Config struct { + Enabled bool BaseDir string // directory where log file is stored - Interval uint32 // interval to rotate logs (in hours) + Interval uint32 // interval to rotate logs (in days) + + // Called when the configuration is changed by HTTP request + ConfigModified func() + + // Register an HTTP handler + HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) } // New - create a new instance of the query log func New(conf Config) QueryLog { return newQueryLog(conf) } - -// GetDataParams - parameters for GetData() -type GetDataParams struct { - OlderThan time.Time // return entries that are older than this value - Domain string // filter by domain name in question - Client string // filter by client IP - QuestionType uint16 // filter by question type - ResponseStatus ResponseStatusType // filter by response status - StrictMatchDomain bool // if Domain value must be matched strictly - StrictMatchClient bool // if Client value must be matched strictly -} - -// ResponseStatusType - response status -type ResponseStatusType int32 - -// Response status constants -const ( - ResponseStatusAll ResponseStatusType = iota + 1 - ResponseStatusFiltered -) diff --git a/querylog/querylog_file.go b/querylog/querylog_file.go index 8f613012..1a6c4481 100644 --- a/querylog/querylog_file.go +++ b/querylog/querylog_file.go @@ -7,17 +7,12 @@ import ( "fmt" "io" "os" - "sync" "time" "github.com/AdguardTeam/golibs/log" "github.com/go-test/deep" ) -var ( - fileWriteLock sync.Mutex -) - const enableGzip = false const maxEntrySize = 1000 @@ -27,16 +22,16 @@ func (l *queryLog) flushLogBuffer(fullFlush bool) error { defer l.fileFlushLock.Unlock() // flush remainder to file - l.logBufferLock.Lock() - needFlush := len(l.logBuffer) >= logBufferCap + l.bufferLock.Lock() + needFlush := len(l.buffer) >= logBufferCap if !needFlush && !fullFlush { - l.logBufferLock.Unlock() + l.bufferLock.Unlock() return nil } - flushBuffer := l.logBuffer - l.logBuffer = nil + flushBuffer := l.buffer + l.buffer = nil l.flushPending = false - l.logBufferLock.Unlock() + l.bufferLock.Unlock() err := l.flushToFile(flushBuffer) if err != nil { log.Error("Saving querylog to file failed: %s", err) @@ -98,8 +93,8 @@ func (l *queryLog) flushToFile(buffer []*logEntry) error { zb = b } - fileWriteLock.Lock() - defer fileWriteLock.Unlock() + l.fileWriteLock.Lock() + defer l.fileWriteLock.Unlock() f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Error("failed to create file \"%s\": %s", filename, err) @@ -146,7 +141,7 @@ func checkBuffer(buffer []*logEntry, b bytes.Buffer) error { return nil } -func (l *queryLog) rotateQueryLog() error { +func (l *queryLog) rotate() error { from := l.logFile to := l.logFile + ".1" @@ -171,9 +166,9 @@ func (l *queryLog) rotateQueryLog() error { return nil } -func (l *queryLog) periodicQueryLogRotate() { - for range time.Tick(time.Duration(l.conf.Interval) * time.Hour) { - err := l.rotateQueryLog() +func (l *queryLog) periodicRotate() { + for range time.Tick(time.Duration(l.conf.Interval) * 24 * time.Hour) { + err := l.rotate() if err != nil { log.Error("Failed to rotate querylog: %s", err) // do nothing, continue rotating @@ -219,7 +214,7 @@ func (l *queryLog) OpenReader() *Reader { r := Reader{} r.ql = l r.now = time.Now() - r.validFrom = r.now.Unix() - int64(l.conf.Interval*60*60) + r.validFrom = r.now.Unix() - int64(l.conf.Interval*24*60*60) r.validFrom *= 1000000000 r.files = []string{ r.ql.logFile, diff --git a/querylog/querylog_test.go b/querylog/querylog_test.go index d533fe4c..e300cba1 100644 --- a/querylog/querylog_test.go +++ b/querylog/querylog_test.go @@ -12,9 +12,10 @@ import ( func TestQueryLog(t *testing.T) { conf := Config{ + Enabled: true, Interval: 1, } - l := New(conf) + l := newQueryLog(conf) q := dns.Msg{} q.Question = append(q.Question, dns.Question{ @@ -37,10 +38,10 @@ func TestQueryLog(t *testing.T) { res := dnsfilter.Result{} l.Add(&q, &a, &res, 0, nil, "upstream") - params := GetDataParams{ + params := getDataParams{ OlderThan: time.Now(), } - d := l.GetData(params) + d := l.getData(params) m := d[0] mq := m["question"].(map[string]interface{}) assert.True(t, mq["host"].(string) == "example.org")