Merge pull request #7 in DNS/adguard-dns from metrics to master
* commit '1f0fdef8d6b2ce324e7009bb3f95626d87438d61': Fix invalid element order for historical stats -- in API declaration values are from oldest to newest, not other way around. Rewrite how historical stats are stored and calculated. coredns plugin -- convert logic into switch, logging unexpected non-covered cases After filters were redownloaded and deemed to be fresh, tell coredns server to reload coredns plugin -- on server reload, metrics disappeared, therefore they must be registered on each reload instead of once coredns plugin -- give feedback how many rules were in rulefile dnsfilter -- Update tests to check for expected filter/nofilter reason as well. Remove debug logging during checks if coredns is alive Be more atomic during writing of files -- this prevents other processes from seeing empty or impartial files Start coredns on launch before we serve HTTP -- this checks if port is available Move starting of coredns server into separate function sometimes answer can be empty, therefore question could be lost -- pass both to querylog Reduce binary size of coredns by 60% by removing orchestration plugins like kubernetes, route53, trace, etcd and federation Fix registration of metrics if querylog is enabled
This commit is contained in:
commit
74275bebdc
1
Makefile
1
Makefile
|
@ -24,6 +24,7 @@ AdguardDNS: $(STATIC) *.go
|
||||||
coredns: coredns_plugin/*.go dnsfilter/*.go
|
coredns: coredns_plugin/*.go dnsfilter/*.go
|
||||||
echo mkfile_dir = $(mkfile_dir)
|
echo mkfile_dir = $(mkfile_dir)
|
||||||
go get -v -d github.com/coredns/coredns
|
go get -v -d github.com/coredns/coredns
|
||||||
|
cd $(GOPATH)/src/github.com/coredns/coredns && perl -p -i.bak -e 's/^(trace|route53|federation|kubernetes|etcd):.*//' plugin.cfg
|
||||||
cd $(GOPATH)/src/github.com/coredns/coredns && grep -q '^dnsfilter:' plugin.cfg || perl -p -i.bak -e 's|^log:log|log:log\ndnsfilter:github.com/AdguardTeam/AdguardDNS/coredns_plugin|' plugin.cfg
|
cd $(GOPATH)/src/github.com/coredns/coredns && grep -q '^dnsfilter:' plugin.cfg || perl -p -i.bak -e 's|^log:log|log:log\ndnsfilter:github.com/AdguardTeam/AdguardDNS/coredns_plugin|' plugin.cfg
|
||||||
grep '^dnsfilter:' $(GOPATH)/src/github.com/coredns/coredns/plugin.cfg ## used to check that plugin.cfg was successfully edited by sed
|
grep '^dnsfilter:' $(GOPATH)/src/github.com/coredns/coredns/plugin.cfg ## used to check that plugin.cfg was successfully edited by sed
|
||||||
cd $(GOPATH)/src/github.com/coredns/coredns && GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go generate
|
cd $(GOPATH)/src/github.com/coredns/coredns && GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go generate
|
||||||
|
|
5
app.go
5
app.go
|
@ -122,6 +122,11 @@ func main() {
|
||||||
http.Handle("/", http.FileServer(box))
|
http.Handle("/", http.FileServer(box))
|
||||||
registerControlHandlers()
|
registerControlHandlers()
|
||||||
|
|
||||||
|
err = startDNSServer()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
URL := fmt.Sprintf("http://%s", address)
|
URL := fmt.Sprintf("http://%s", address)
|
||||||
log.Println("Go to " + URL)
|
log.Println("Go to " + URL)
|
||||||
log.Fatal(http.ListenAndServe(address, nil))
|
log.Fatal(http.ListenAndServe(address, nil))
|
||||||
|
|
13
config.go
13
config.go
|
@ -108,11 +108,16 @@ func writeConfig() error {
|
||||||
log.Printf("Couldn't generate YAML file: %s", err)
|
log.Printf("Couldn't generate YAML file: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(configfile, yamlText, 0644)
|
err = ioutil.WriteFile(configfile+".tmp", yamlText, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't write YAML config: %s", err)
|
log.Printf("Couldn't write YAML config: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = os.Rename(configfile+".tmp", configfile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't rename YAML config: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,10 +132,14 @@ func writeCoreDNSConfig() error {
|
||||||
log.Printf("Couldn't generate DNS config: %s", err)
|
log.Printf("Couldn't generate DNS config: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(corefile, []byte(configtext), 0644)
|
err = ioutil.WriteFile(corefile+".tmp", []byte(configtext), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't write DNS config: %s", err)
|
log.Printf("Couldn't write DNS config: %s", err)
|
||||||
}
|
}
|
||||||
|
err = os.Rename(corefile+".tmp", corefile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't rename DNS config: %s", err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
135
control.go
135
control.go
|
@ -44,7 +44,6 @@ func tellCoreDNSToReload() {
|
||||||
log.Printf("os.FindProcess(%d) returned err: %v\n", pid, err)
|
log.Printf("os.FindProcess(%d) returned err: %v\n", pid, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("os.FindProcess(%d) returned: %v, %v\n", pid, process, err)
|
|
||||||
err = process.Signal(syscall.SIGUSR1)
|
err = process.Signal(syscall.SIGUSR1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("process.Signal on pid %d returned: %v\n", pid, err)
|
log.Printf("process.Signal on pid %d returned: %v\n", pid, err)
|
||||||
|
@ -69,9 +68,10 @@ func isRunning() bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("os.FindProcess(%d) returned err: %v\n", pid, err)
|
log.Printf("os.FindProcess(%d) returned err: %v\n", pid, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("os.FindProcess(%d) returned: %v, %v\n", pid, process, err)
|
|
||||||
err := process.Signal(syscall.Signal(0))
|
err := process.Signal(syscall.Signal(0))
|
||||||
log.Printf("process.Signal on pid %d returned: %v\n", pid, err)
|
if err != nil {
|
||||||
|
log.Printf("process.Signal on pid %d returned: %v\n", pid, err)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -79,24 +79,22 @@ func isRunning() bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func handleStart(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
func startDNSServer() error {
|
||||||
if isRunning() {
|
if isRunning() {
|
||||||
http.Error(w, fmt.Sprintf("Unable to start coreDNS: Already running"), 400)
|
return fmt.Errorf("Unable to start coreDNS: Already running")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err := writeCoreDNSConfig()
|
err := writeCoreDNSConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Unable to write coredns config: %s", err)
|
errortext := fmt.Errorf("Unable to write coredns config: %s", err)
|
||||||
log.Println(errortext)
|
log.Println(errortext)
|
||||||
http.Error(w, errortext, http.StatusInternalServerError)
|
return errortext
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = writeFilterFile()
|
err = writeFilterFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Couldn't write filter file: %s", err)
|
errortext := fmt.Errorf("Couldn't write filter file: %s", err)
|
||||||
log.Println(errortext)
|
log.Println(errortext)
|
||||||
http.Error(w, errortext, http.StatusInternalServerError)
|
return errortext
|
||||||
return
|
|
||||||
}
|
}
|
||||||
binarypath := filepath.Join(config.ourBinaryDir, config.CoreDNS.binaryFile)
|
binarypath := filepath.Join(config.ourBinaryDir, config.CoreDNS.binaryFile)
|
||||||
configpath := filepath.Join(config.ourBinaryDir, config.CoreDNS.coreFile)
|
configpath := filepath.Join(config.ourBinaryDir, config.CoreDNS.coreFile)
|
||||||
|
@ -105,14 +103,27 @@ func handleStart(w http.ResponseWriter, r *http.Request) {
|
||||||
coreDNSCommand.Stderr = os.Stderr
|
coreDNSCommand.Stderr = os.Stderr
|
||||||
err = coreDNSCommand.Start()
|
err = coreDNSCommand.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Unable to start coreDNS: %s", err)
|
errortext := fmt.Errorf("Unable to start coreDNS: %s", err)
|
||||||
log.Println(errortext)
|
log.Println(errortext)
|
||||||
http.Error(w, errortext, http.StatusInternalServerError)
|
return errortext
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Printf("coredns PID: %v\n", coreDNSCommand.Process.Pid)
|
log.Printf("coredns PID: %v\n", coreDNSCommand.Process.Pid)
|
||||||
fmt.Fprintf(w, "OK, PID %d\n", coreDNSCommand.Process.Pid)
|
|
||||||
go childwaiter()
|
go childwaiter()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStart(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if isRunning() {
|
||||||
|
http.Error(w, fmt.Sprintf("Unable to start coreDNS: Already running"), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := startDNSServer()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "OK, PID %d\n", coreDNSCommand.Process.Pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func childwaiter() {
|
func childwaiter() {
|
||||||
|
@ -188,21 +199,23 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
// stats
|
// stats
|
||||||
// -----
|
// -----
|
||||||
func handleStats(w http.ResponseWriter, r *http.Request) {
|
func handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
snap := &statistics.lastsnap
|
histrical := generateMapFromStats(&statistics.perMinute, 0, 2)
|
||||||
|
// sum them up
|
||||||
|
summed := map[string]interface{}{}
|
||||||
|
for key, values := range histrical {
|
||||||
|
summedValue := 0.0
|
||||||
|
floats, ok := values.([]float64)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, v := range floats {
|
||||||
|
summedValue += v
|
||||||
|
}
|
||||||
|
summed[key] = summedValue
|
||||||
|
}
|
||||||
|
summed["stats_period"] = "3 minutes"
|
||||||
|
|
||||||
// generate from last 3 minutes
|
json, err := json.Marshal(summed)
|
||||||
var last3mins statsSnapshot
|
|
||||||
last3mins.filteredTotal = snap.filteredTotal - statistics.perMinute.filteredTotal[2]
|
|
||||||
last3mins.filteredLists = snap.filteredLists - statistics.perMinute.filteredLists[2]
|
|
||||||
last3mins.filteredSafebrowsing = snap.filteredSafebrowsing - statistics.perMinute.filteredSafebrowsing[2]
|
|
||||||
last3mins.filteredParental = snap.filteredParental - statistics.perMinute.filteredParental[2]
|
|
||||||
last3mins.totalRequests = snap.totalRequests - statistics.perMinute.totalRequests[2]
|
|
||||||
last3mins.processingTimeSum = snap.processingTimeSum - statistics.perMinute.processingTimeSum[2]
|
|
||||||
last3mins.processingTimeCount = snap.processingTimeCount - statistics.perMinute.processingTimeCount[2]
|
|
||||||
// rate := computeRate(append([]float64(snap.totalRequests}, statistics.perMinute.totalRequests[0:2])
|
|
||||||
|
|
||||||
data := generateMapFromSnap(last3mins)
|
|
||||||
json, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Unable to marshal status json: %s", err)
|
errortext := fmt.Sprintf("Unable to marshal status json: %s", err)
|
||||||
log.Println(errortext)
|
log.Println(errortext)
|
||||||
|
@ -221,28 +234,29 @@ func handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func handleStatsHistory(w http.ResponseWriter, r *http.Request) {
|
func handleStatsHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
// handle time unit and prepare our time window size
|
// handle time unit and prepare our time window size
|
||||||
limitTime := time.Now()
|
now := time.Now()
|
||||||
timeUnit := r.URL.Query().Get("time_unit")
|
timeUnitString := r.URL.Query().Get("time_unit")
|
||||||
var stats *periodicStats
|
var stats *periodicStats
|
||||||
switch timeUnit {
|
var timeUnit time.Duration
|
||||||
|
switch timeUnitString {
|
||||||
case "seconds":
|
case "seconds":
|
||||||
limitTime = limitTime.Add(statsHistoryElements * -1 * time.Second)
|
timeUnit = time.Second
|
||||||
stats = &statistics.perSecond
|
stats = &statistics.perSecond
|
||||||
case "minutes":
|
case "minutes":
|
||||||
limitTime = limitTime.Add(statsHistoryElements * -1 * time.Minute)
|
timeUnit = time.Minute
|
||||||
stats = &statistics.perMinute
|
stats = &statistics.perMinute
|
||||||
case "hours":
|
case "hours":
|
||||||
limitTime = limitTime.Add(statsHistoryElements * -1 * time.Hour)
|
timeUnit = time.Hour
|
||||||
stats = &statistics.perHour
|
stats = &statistics.perHour
|
||||||
case "days":
|
case "days":
|
||||||
limitTime = limitTime.Add(statsHistoryElements * -1 * time.Hour * 24)
|
timeUnit = time.Hour * 24
|
||||||
stats = &statistics.perDay
|
stats = &statistics.perDay
|
||||||
default:
|
default:
|
||||||
http.Error(w, "Must specify valid time_unit parameter", 400)
|
http.Error(w, "Must specify valid time_unit parameter", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if start time is within supported time range
|
// parse start and end time
|
||||||
startTime, err := time.Parse(time.RFC3339, r.URL.Query().Get("start_time"))
|
startTime, err := time.Parse(time.RFC3339, r.URL.Query().Get("start_time"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Must specify valid start_time parameter: %s", err)
|
errortext := fmt.Sprintf("Must specify valid start_time parameter: %s", err)
|
||||||
|
@ -250,12 +264,6 @@ func handleStatsHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, errortext, 400)
|
http.Error(w, errortext, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if startTime.Before(limitTime) {
|
|
||||||
http.Error(w, "start_time parameter is outside of supported range", 501)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if end time is within supported time range
|
|
||||||
endTime, err := time.Parse(time.RFC3339, r.URL.Query().Get("end_time"))
|
endTime, err := time.Parse(time.RFC3339, r.URL.Query().Get("end_time"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Must specify valid end_time parameter: %s", err)
|
errortext := fmt.Sprintf("Must specify valid end_time parameter: %s", err)
|
||||||
|
@ -263,28 +271,22 @@ func handleStatsHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, errortext, 400)
|
http.Error(w, errortext, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if endTime.Before(limitTime) {
|
|
||||||
|
// check if start and time times are within supported time range
|
||||||
|
timeRange := timeUnit * statsHistoryElements
|
||||||
|
if startTime.Add(timeRange).Before(now) {
|
||||||
|
http.Error(w, "start_time parameter is outside of supported range", 501)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if endTime.Add(timeRange).Before(now) {
|
||||||
http.Error(w, "end_time parameter is outside of supported range", 501)
|
http.Error(w, "end_time parameter is outside of supported range", 501)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate how which slice range we need to provide
|
// calculate start and end of our array
|
||||||
var start int
|
// basically it's how many hours/minutes/etc have passed since now
|
||||||
var end int
|
start := int(now.Sub(endTime) / timeUnit)
|
||||||
switch timeUnit {
|
end := int(now.Sub(startTime) / timeUnit)
|
||||||
case "seconds":
|
|
||||||
start = int(startTime.Sub(limitTime).Seconds())
|
|
||||||
end = int(endTime.Sub(limitTime).Seconds())
|
|
||||||
case "minutes":
|
|
||||||
start = int(startTime.Sub(limitTime).Minutes())
|
|
||||||
end = int(endTime.Sub(limitTime).Minutes())
|
|
||||||
case "hours":
|
|
||||||
start = int(startTime.Sub(limitTime).Hours())
|
|
||||||
end = int(endTime.Sub(limitTime).Hours())
|
|
||||||
case "days":
|
|
||||||
start = int(startTime.Sub(limitTime).Hours() / 24.0)
|
|
||||||
end = int(endTime.Sub(limitTime).Hours() / 24.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap them around if they're inverted
|
// swap them around if they're inverted
|
||||||
if start > end {
|
if start > end {
|
||||||
|
@ -840,6 +842,7 @@ func refreshFiltersIfNeccessary() int {
|
||||||
errortext := fmt.Sprintf("Couldn't write filter file: %s", err)
|
errortext := fmt.Sprintf("Couldn't write filter file: %s", err)
|
||||||
log.Println(errortext)
|
log.Println(errortext)
|
||||||
}
|
}
|
||||||
|
tellCoreDNSToReload()
|
||||||
}
|
}
|
||||||
return updateCount
|
return updateCount
|
||||||
}
|
}
|
||||||
|
@ -918,11 +921,17 @@ func writeFilterFile() error {
|
||||||
data = append(data, []byte(rule)...)
|
data = append(data, []byte(rule)...)
|
||||||
data = append(data, '\n')
|
data = append(data, '\n')
|
||||||
}
|
}
|
||||||
err := ioutil.WriteFile(filterpath, data, 0644)
|
err := ioutil.WriteFile(filterpath+".tmp", data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't write filter file: %s", err)
|
log.Printf("Couldn't write filter file: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = os.Rename(filterpath+".tmp", filterpath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't rename filter file: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
|
||||||
}
|
}
|
||||||
case "querylog":
|
case "querylog":
|
||||||
d.QueryLogEnabled = true
|
d.QueryLogEnabled = true
|
||||||
once.Do(func() {
|
onceQueryLog.Do(func() {
|
||||||
go startQueryLogServer() // TODO: how to handle errors?
|
go startQueryLogServer() // TODO: how to handle errors?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,7 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
count := 0
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
text := scanner.Text()
|
text := scanner.Text()
|
||||||
|
@ -158,7 +159,9 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
|
log.Printf("Added %d rules from %s", count, filterFileName)
|
||||||
|
|
||||||
if err = scanner.Err(); err != nil {
|
if err = scanner.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -184,23 +187,21 @@ func setup(c *caddy.Controller) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
c.OnStartup(func() error {
|
c.OnStartup(func() error {
|
||||||
once.Do(func() {
|
m := dnsserver.GetConfig(c).Handler("prometheus")
|
||||||
m := dnsserver.GetConfig(c).Handler("prometheus")
|
if m == nil {
|
||||||
if m == nil {
|
return nil
|
||||||
return
|
}
|
||||||
}
|
if x, ok := m.(*metrics.Metrics); ok {
|
||||||
if x, ok := m.(*metrics.Metrics); ok {
|
x.MustRegister(requests)
|
||||||
x.MustRegister(requests)
|
x.MustRegister(filtered)
|
||||||
x.MustRegister(filtered)
|
x.MustRegister(filteredLists)
|
||||||
x.MustRegister(filteredLists)
|
x.MustRegister(filteredSafebrowsing)
|
||||||
x.MustRegister(filteredSafebrowsing)
|
x.MustRegister(filteredParental)
|
||||||
x.MustRegister(filteredParental)
|
x.MustRegister(whitelisted)
|
||||||
x.MustRegister(whitelisted)
|
x.MustRegister(safesearch)
|
||||||
x.MustRegister(safesearch)
|
x.MustRegister(errorsTotal)
|
||||||
x.MustRegister(errorsTotal)
|
x.MustRegister(d)
|
||||||
x.MustRegister(d)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
c.OnShutdown(d.OnShutdown)
|
c.OnShutdown(d.OnShutdown)
|
||||||
|
@ -410,39 +411,44 @@ func (d *Plugin) serveDNSInternal(ctx context.Context, w dns.ResponseWriter, r *
|
||||||
return dns.RcodeServerFailure, fmt.Errorf("plugin/dnsfilter: %s", err), dnsfilter.Result{}
|
return dns.RcodeServerFailure, fmt.Errorf("plugin/dnsfilter: %s", err), dnsfilter.Result{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// safebrowsing
|
if result.IsFiltered {
|
||||||
if result.IsFiltered == true && result.Reason == dnsfilter.FilteredSafeBrowsing {
|
switch result.Reason {
|
||||||
// return cname safebrowsing.block.dns.adguard.com
|
case dnsfilter.FilteredSafeBrowsing:
|
||||||
val := d.SafeBrowsingBlockHost
|
// return cname safebrowsing.block.dns.adguard.com
|
||||||
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question)
|
val := d.SafeBrowsingBlockHost
|
||||||
if err != nil {
|
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question)
|
||||||
return rcode, err, dnsfilter.Result{}
|
if err != nil {
|
||||||
|
return rcode, err, dnsfilter.Result{}
|
||||||
|
}
|
||||||
|
return rcode, err, result
|
||||||
|
case dnsfilter.FilteredParental:
|
||||||
|
// return cname family.block.dns.adguard.com
|
||||||
|
val := d.ParentalBlockHost
|
||||||
|
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question)
|
||||||
|
if err != nil {
|
||||||
|
return rcode, err, dnsfilter.Result{}
|
||||||
|
}
|
||||||
|
return rcode, err, result
|
||||||
|
case dnsfilter.FilteredBlackList:
|
||||||
|
// return NXdomain
|
||||||
|
rcode, err := writeNXdomain(ctx, w, r)
|
||||||
|
if err != nil {
|
||||||
|
return rcode, err, dnsfilter.Result{}
|
||||||
|
}
|
||||||
|
return rcode, err, result
|
||||||
|
default:
|
||||||
|
log.Printf("SHOULD NOT HAPPEN -- got unknown reason for filtering: %T %v %s", result.Reason, result.Reason, result.Reason.String())
|
||||||
}
|
}
|
||||||
return rcode, err, result
|
} else {
|
||||||
}
|
switch result.Reason {
|
||||||
|
case dnsfilter.NotFilteredWhiteList:
|
||||||
// parental
|
rcode, err := plugin.NextOrFailure(d.Name(), d.Next, ctx, w, r)
|
||||||
if result.IsFiltered == true && result.Reason == dnsfilter.FilteredParental {
|
return rcode, err, result
|
||||||
// return cname
|
case dnsfilter.NotFilteredNotFound:
|
||||||
val := d.ParentalBlockHost
|
// do nothing, pass through to lower code
|
||||||
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question)
|
default:
|
||||||
if err != nil {
|
log.Printf("SHOULD NOT HAPPEN -- got unknown reason for not filtering: %T %v %s", result.Reason, result.Reason, result.Reason.String())
|
||||||
return rcode, err, dnsfilter.Result{}
|
|
||||||
}
|
}
|
||||||
return rcode, err, result
|
|
||||||
}
|
|
||||||
|
|
||||||
// blacklist
|
|
||||||
if result.IsFiltered == true && result.Reason == dnsfilter.FilteredBlackList {
|
|
||||||
rcode, err := writeNXdomain(ctx, w, r)
|
|
||||||
if err != nil {
|
|
||||||
return rcode, err, dnsfilter.Result{}
|
|
||||||
}
|
|
||||||
return rcode, err, result
|
|
||||||
}
|
|
||||||
if result.IsFiltered == false && result.Reason == dnsfilter.NotFilteredWhiteList {
|
|
||||||
rcode, err := plugin.NextOrFailure(d.Name(), d.Next, ctx, w, r)
|
|
||||||
return rcode, err, result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rcode, err := plugin.NextOrFailure(d.Name(), d.Next, ctx, w, r)
|
rcode, err := plugin.NextOrFailure(d.Name(), d.Next, ctx, w, r)
|
||||||
|
@ -498,11 +504,11 @@ func (d *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
|
|
||||||
// log
|
// log
|
||||||
if d.QueryLogEnabled {
|
if d.QueryLogEnabled {
|
||||||
logRequest(rrw.Msg, result, time.Since(start), ip)
|
logRequest(r, rrw.Msg, result, time.Since(start), ip)
|
||||||
}
|
}
|
||||||
return rcode, err
|
return rcode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Plugin) Name() string { return "dnsfilter" }
|
func (d *Plugin) Name() string { return "dnsfilter" }
|
||||||
|
|
||||||
var once sync.Once
|
var onceQueryLog sync.Once
|
||||||
|
|
|
@ -19,24 +19,26 @@ import (
|
||||||
var logBuffer = ring.Ring{}
|
var logBuffer = ring.Ring{}
|
||||||
|
|
||||||
type logEntry struct {
|
type logEntry struct {
|
||||||
R *dns.Msg
|
Question *dns.Msg
|
||||||
Result dnsfilter.Result
|
Answer *dns.Msg
|
||||||
Time time.Time
|
Result dnsfilter.Result
|
||||||
Elapsed time.Duration
|
Time time.Time
|
||||||
IP string
|
Elapsed time.Duration
|
||||||
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logBuffer.SetCapacity(1000)
|
logBuffer.SetCapacity(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func logRequest(r *dns.Msg, result dnsfilter.Result, elapsed time.Duration, ip string) {
|
func logRequest(question *dns.Msg, answer *dns.Msg, result dnsfilter.Result, elapsed time.Duration, ip string) {
|
||||||
entry := logEntry{
|
entry := logEntry{
|
||||||
R: r,
|
Question: question,
|
||||||
Result: result,
|
Answer: answer,
|
||||||
Time: time.Now(),
|
Result: result,
|
||||||
Elapsed: elapsed,
|
Time: time.Now(),
|
||||||
IP: ip,
|
Elapsed: elapsed,
|
||||||
|
IP: ip,
|
||||||
}
|
}
|
||||||
logBuffer.Enqueue(entry)
|
logBuffer.Enqueue(entry)
|
||||||
}
|
}
|
||||||
|
@ -57,21 +59,21 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
"client": entry.IP,
|
"client": entry.IP,
|
||||||
}
|
}
|
||||||
question := map[string]interface{}{
|
question := map[string]interface{}{
|
||||||
"host": strings.ToLower(strings.TrimSuffix(entry.R.Question[0].Name, ".")),
|
"host": strings.ToLower(strings.TrimSuffix(entry.Question.Question[0].Name, ".")),
|
||||||
"type": dns.Type(entry.R.Question[0].Qtype).String(),
|
"type": dns.Type(entry.Question.Question[0].Qtype).String(),
|
||||||
"class": dns.Class(entry.R.Question[0].Qclass).String(),
|
"class": dns.Class(entry.Question.Question[0].Qclass).String(),
|
||||||
}
|
}
|
||||||
jsonentry["question"] = question
|
jsonentry["question"] = question
|
||||||
|
|
||||||
status, _ := response.Typify(entry.R, time.Now().UTC())
|
status, _ := response.Typify(entry.Answer, time.Now().UTC())
|
||||||
jsonentry["status"] = status.String()
|
jsonentry["status"] = status.String()
|
||||||
if len(entry.Result.Rule) > 0 {
|
if len(entry.Result.Rule) > 0 {
|
||||||
jsonentry["rule"] = entry.Result.Rule
|
jsonentry["rule"] = entry.Result.Rule
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entry.R.Answer) > 0 {
|
if entry.Answer != nil && len(entry.Answer.Answer) > 0 {
|
||||||
var answers = []map[string]interface{}{}
|
var answers = []map[string]interface{}{}
|
||||||
for _, k := range entry.R.Answer {
|
for _, k := range entry.Answer.Answer {
|
||||||
header := k.Header()
|
header := k.Header()
|
||||||
answer := map[string]interface{}{
|
answer := map[string]interface{}{
|
||||||
"type": dns.TypeToString[header.Rrtype],
|
"type": dns.TypeToString[header.Rrtype],
|
||||||
|
|
|
@ -385,43 +385,44 @@ var regexRules = []string{"/example\\.org/", "@@||test.example.org^"}
|
||||||
var maskRules = []string{"test*.example.org^", "exam*.com"}
|
var maskRules = []string{"test*.example.org^", "exam*.com"}
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
testname string
|
testname string
|
||||||
rules []string
|
rules []string
|
||||||
hostname string
|
hostname string
|
||||||
result bool
|
isFiltered bool
|
||||||
|
reason Reason
|
||||||
}{
|
}{
|
||||||
{"sanity", []string{"||doubleclick.net^"}, "www.doubleclick.net", true},
|
{"sanity", []string{"||doubleclick.net^"}, "www.doubleclick.net", true, FilteredBlackList},
|
||||||
{"sanity", []string{"||doubleclick.net^"}, "nodoubleclick.net", false},
|
{"sanity", []string{"||doubleclick.net^"}, "nodoubleclick.net", false, NotFilteredNotFound},
|
||||||
{"sanity", []string{"||doubleclick.net^"}, "doubleclick.net.ru", false},
|
{"sanity", []string{"||doubleclick.net^"}, "doubleclick.net.ru", false, NotFilteredNotFound},
|
||||||
{"sanity", []string{"||doubleclick.net^"}, "wmconvirus.narod.ru", false},
|
{"sanity", []string{"||doubleclick.net^"}, "wmconvirus.narod.ru", false, NotFilteredNotFound},
|
||||||
{"blocking", blockingRules, "example.org", true},
|
{"blocking", blockingRules, "example.org", true, FilteredBlackList},
|
||||||
{"blocking", blockingRules, "test.example.org", true},
|
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList},
|
||||||
{"blocking", blockingRules, "test.test.example.org", true},
|
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList},
|
||||||
{"blocking", blockingRules, "testexample.org", false},
|
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound},
|
||||||
{"blocking", blockingRules, "onemoreexample.org", false},
|
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound},
|
||||||
{"whitelist", whitelistRules, "example.org", true},
|
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList},
|
||||||
{"whitelist", whitelistRules, "test.example.org", false},
|
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList},
|
||||||
{"whitelist", whitelistRules, "test.test.example.org", false},
|
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList},
|
||||||
{"whitelist", whitelistRules, "testexample.org", false},
|
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound},
|
||||||
{"whitelist", whitelistRules, "onemoreexample.org", false},
|
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound},
|
||||||
{"important", importantRules, "example.org", false},
|
{"important", importantRules, "example.org", false, NotFilteredWhiteList},
|
||||||
{"important", importantRules, "test.example.org", true},
|
{"important", importantRules, "test.example.org", true, FilteredBlackList},
|
||||||
{"important", importantRules, "test.test.example.org", true},
|
{"important", importantRules, "test.test.example.org", true, FilteredBlackList},
|
||||||
{"important", importantRules, "testexample.org", false},
|
{"important", importantRules, "testexample.org", false, NotFilteredNotFound},
|
||||||
{"important", importantRules, "onemoreexample.org", false},
|
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound},
|
||||||
{"regex", regexRules, "example.org", true},
|
{"regex", regexRules, "example.org", true, FilteredBlackList},
|
||||||
{"regex", regexRules, "test.example.org", false},
|
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList},
|
||||||
{"regex", regexRules, "test.test.example.org", false},
|
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList},
|
||||||
{"regex", regexRules, "testexample.org", true},
|
{"regex", regexRules, "testexample.org", true, FilteredBlackList},
|
||||||
{"regex", regexRules, "onemoreexample.org", true},
|
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList},
|
||||||
{"mask", maskRules, "test.example.org", true},
|
{"mask", maskRules, "test.example.org", true, FilteredBlackList},
|
||||||
{"mask", maskRules, "test2.example.org", true},
|
{"mask", maskRules, "test2.example.org", true, FilteredBlackList},
|
||||||
{"mask", maskRules, "example.com", true},
|
{"mask", maskRules, "example.com", true, FilteredBlackList},
|
||||||
{"mask", maskRules, "exampleeee.com", true},
|
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList},
|
||||||
{"mask", maskRules, "onemoreexamsite.com", true},
|
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList},
|
||||||
{"mask", maskRules, "example.org", false},
|
{"mask", maskRules, "example.org", false, NotFilteredNotFound},
|
||||||
{"mask", maskRules, "testexample.org", false},
|
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound},
|
||||||
{"mask", maskRules, "example.co.uk", false},
|
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatching(t *testing.T) {
|
func TestMatching(t *testing.T) {
|
||||||
|
@ -439,8 +440,11 @@ func TestMatching(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
||||||
}
|
}
|
||||||
if ret.IsFiltered != test.result {
|
if ret.IsFiltered != test.isFiltered {
|
||||||
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret, test.result)
|
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret.IsFiltered, test.isFiltered)
|
||||||
|
}
|
||||||
|
if ret.Reason != test.reason {
|
||||||
|
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, ret.Reason.String(), test.reason.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
39
helpers.go
39
helpers.go
|
@ -51,33 +51,14 @@ func ensureDELETE(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||||
// --------------------------
|
// --------------------------
|
||||||
// helper functions for stats
|
// helper functions for stats
|
||||||
// --------------------------
|
// --------------------------
|
||||||
func computeRate(input []float64) []float64 {
|
func getReversedSlice(input [statsHistoryElements]float64, start int, end int) []float64 {
|
||||||
output := make([]float64, 0)
|
output := make([]float64, 0)
|
||||||
for i := len(input) - 2; i >= 0; i-- {
|
for i := start; i <= end; i++ {
|
||||||
value := input[i]
|
output = append([]float64{input[i]}, output...)
|
||||||
diff := value - input[i+1]
|
|
||||||
output = append([]float64{diff}, output...)
|
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateMapFromSnap(snap statsSnapshot) map[string]interface{} {
|
|
||||||
var avgProcessingTime float64
|
|
||||||
if snap.processingTimeCount > 0 {
|
|
||||||
avgProcessingTime = snap.processingTimeSum / snap.processingTimeCount
|
|
||||||
}
|
|
||||||
|
|
||||||
result := map[string]interface{}{
|
|
||||||
"dns_queries": snap.totalRequests,
|
|
||||||
"blocked_filtering": snap.filteredLists,
|
|
||||||
"replaced_safebrowsing": snap.filteredSafebrowsing,
|
|
||||||
"replaced_safesearch": snap.filteredSafesearch,
|
|
||||||
"replaced_parental": snap.filteredParental,
|
|
||||||
"avg_processing_time": avgProcessingTime,
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMapFromStats(stats *periodicStats, start int, end int) map[string]interface{} {
|
func generateMapFromStats(stats *periodicStats, start int, end int) map[string]interface{} {
|
||||||
// clamp
|
// clamp
|
||||||
start = clamp(start, 0, statsHistoryElements)
|
start = clamp(start, 0, statsHistoryElements)
|
||||||
|
@ -85,8 +66,8 @@ func generateMapFromStats(stats *periodicStats, start int, end int) map[string]i
|
||||||
|
|
||||||
avgProcessingTime := make([]float64, 0)
|
avgProcessingTime := make([]float64, 0)
|
||||||
|
|
||||||
count := computeRate(stats.processingTimeCount[start:end])
|
count := getReversedSlice(stats.entries[processingTimeCount], start, end)
|
||||||
sum := computeRate(stats.processingTimeSum[start:end])
|
sum := getReversedSlice(stats.entries[processingTimeSum], start, end)
|
||||||
for i := 0; i < len(count); i++ {
|
for i := 0; i < len(count); i++ {
|
||||||
var avg float64
|
var avg float64
|
||||||
if count[i] != 0 {
|
if count[i] != 0 {
|
||||||
|
@ -97,11 +78,11 @@ func generateMapFromStats(stats *periodicStats, start int, end int) map[string]i
|
||||||
}
|
}
|
||||||
|
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"dns_queries": computeRate(stats.totalRequests[start:end]),
|
"dns_queries": getReversedSlice(stats.entries[totalRequests], start, end),
|
||||||
"blocked_filtering": computeRate(stats.filteredLists[start:end]),
|
"blocked_filtering": getReversedSlice(stats.entries[filteredLists], start, end),
|
||||||
"replaced_safebrowsing": computeRate(stats.filteredSafebrowsing[start:end]),
|
"replaced_safebrowsing": getReversedSlice(stats.entries[filteredSafebrowsing], start, end),
|
||||||
"replaced_safesearch": computeRate(stats.filteredSafesearch[start:end]),
|
"replaced_safesearch": getReversedSlice(stats.entries[filteredSafesearch], start, end),
|
||||||
"replaced_parental": computeRate(stats.filteredParental[start:end]),
|
"replaced_parental": getReversedSlice(stats.entries[filteredParental], start, end),
|
||||||
"avg_processing_time": avgProcessingTime,
|
"avg_processing_time": avgProcessingTime,
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
200
stats.go
200
stats.go
|
@ -8,70 +8,50 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type periodicStats struct {
|
|
||||||
totalRequests []float64
|
|
||||||
|
|
||||||
filteredTotal []float64
|
|
||||||
filteredLists []float64
|
|
||||||
filteredSafebrowsing []float64
|
|
||||||
filteredSafesearch []float64
|
|
||||||
filteredParental []float64
|
|
||||||
|
|
||||||
processingTimeSum []float64
|
|
||||||
processingTimeCount []float64
|
|
||||||
|
|
||||||
lastRotate time.Time // last time this data was rotated
|
|
||||||
}
|
|
||||||
|
|
||||||
type statsSnapshot struct {
|
|
||||||
totalRequests float64
|
|
||||||
|
|
||||||
filteredTotal float64
|
|
||||||
filteredLists float64
|
|
||||||
filteredSafebrowsing float64
|
|
||||||
filteredSafesearch float64
|
|
||||||
filteredParental float64
|
|
||||||
|
|
||||||
processingTimeSum float64
|
|
||||||
processingTimeCount float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type statsCollection struct {
|
|
||||||
perSecond periodicStats
|
|
||||||
perMinute periodicStats
|
|
||||||
perHour periodicStats
|
|
||||||
perDay periodicStats
|
|
||||||
lastsnap statsSnapshot
|
|
||||||
}
|
|
||||||
|
|
||||||
var statistics statsCollection
|
|
||||||
|
|
||||||
var client = &http.Client{
|
var client = &http.Client{
|
||||||
Timeout: time.Second * 30,
|
Timeout: time.Second * 30,
|
||||||
}
|
}
|
||||||
|
|
||||||
const statsHistoryElements = 60 + 1 // +1 for calculating delta
|
// as seen over HTTP
|
||||||
|
type statsEntry map[string]float64
|
||||||
|
type statsEntries map[string][statsHistoryElements]float64
|
||||||
|
|
||||||
var requestCountTotalRegex = regexp.MustCompile(`^coredns_dns_request_count_total`)
|
const (
|
||||||
var requestDurationSecondsSum = regexp.MustCompile(`^coredns_dns_request_duration_seconds_sum`)
|
statsHistoryElements = 60 + 1 // +1 for calculating delta
|
||||||
var requestDurationSecondsCount = regexp.MustCompile(`^coredns_dns_request_duration_seconds_count`)
|
totalRequests = `coredns_dns_request_count_total`
|
||||||
|
filteredTotal = `coredns_dnsfilter_filtered_total`
|
||||||
|
filteredLists = `coredns_dnsfilter_filtered_lists_total`
|
||||||
|
filteredSafebrowsing = `coredns_dnsfilter_filtered_safebrowsing_total`
|
||||||
|
filteredSafesearch = `coredns_dnsfilter_safesearch_total`
|
||||||
|
filteredParental = `coredns_dnsfilter_filtered_parental_total`
|
||||||
|
processingTimeSum = `coredns_dns_request_duration_seconds_sum`
|
||||||
|
processingTimeCount = `coredns_dns_request_duration_seconds_count`
|
||||||
|
)
|
||||||
|
|
||||||
func initPeriodicStats(stats *periodicStats) {
|
type periodicStats struct {
|
||||||
stats.totalRequests = make([]float64, statsHistoryElements)
|
entries statsEntries
|
||||||
stats.filteredTotal = make([]float64, statsHistoryElements)
|
lastRotate time.Time // last time this data was rotated
|
||||||
stats.filteredLists = make([]float64, statsHistoryElements)
|
}
|
||||||
stats.filteredSafebrowsing = make([]float64, statsHistoryElements)
|
|
||||||
stats.filteredSafesearch = make([]float64, statsHistoryElements)
|
type stats struct {
|
||||||
stats.filteredParental = make([]float64, statsHistoryElements)
|
perSecond periodicStats
|
||||||
stats.processingTimeSum = make([]float64, statsHistoryElements)
|
perMinute periodicStats
|
||||||
stats.processingTimeCount = make([]float64, statsHistoryElements)
|
perHour periodicStats
|
||||||
|
perDay periodicStats
|
||||||
|
|
||||||
|
lastSeen statsEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
var statistics stats
|
||||||
|
|
||||||
|
func initPeriodicStats(periodic *periodicStats) {
|
||||||
|
periodic.entries = statsEntries{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -106,37 +86,22 @@ func isConnRefused(err error) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func sliceRotate(slice *[]float64) {
|
func statsRotate(periodic *periodicStats, now time.Time) {
|
||||||
a := (*slice)[:len(*slice)-1]
|
for key, values := range periodic.entries {
|
||||||
*slice = append([]float64{0}, a...)
|
newValues := [statsHistoryElements]float64{}
|
||||||
}
|
for i := 1; i < len(values); i++ {
|
||||||
|
newValues[i] = values[i-1]
|
||||||
func statsRotate(stats *periodicStats, now time.Time) {
|
}
|
||||||
sliceRotate(&stats.totalRequests)
|
periodic.entries[key] = newValues
|
||||||
sliceRotate(&stats.filteredTotal)
|
|
||||||
sliceRotate(&stats.filteredLists)
|
|
||||||
sliceRotate(&stats.filteredSafebrowsing)
|
|
||||||
sliceRotate(&stats.filteredSafesearch)
|
|
||||||
sliceRotate(&stats.filteredParental)
|
|
||||||
sliceRotate(&stats.processingTimeSum)
|
|
||||||
sliceRotate(&stats.processingTimeCount)
|
|
||||||
stats.lastRotate = now
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleValue(input string, target *float64) {
|
|
||||||
value, err := strconv.ParseFloat(input, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to parse number input:", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
*target = value
|
periodic.lastRotate = now
|
||||||
}
|
}
|
||||||
|
|
||||||
// called every second, accumulates stats for each second, minute, hour and day
|
// called every second, accumulates stats for each second, minute, hour and day
|
||||||
func collectStats() {
|
func collectStats() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
// rotate each second
|
// rotate each second
|
||||||
// NOTE: since we are called every second, always rotate, otherwise aliasing problems cause the rotation to skip
|
// NOTE: since we are called every second, always rotate perSecond, otherwise aliasing problems cause the rotation to skip
|
||||||
if true {
|
if true {
|
||||||
statsRotate(&statistics.perSecond, now)
|
statsRotate(&statistics.perSecond, now)
|
||||||
}
|
}
|
||||||
|
@ -172,6 +137,8 @@ func collectStats() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry := statsEntry{}
|
||||||
|
|
||||||
// handle body
|
// handle body
|
||||||
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
@ -181,38 +148,61 @@ func collectStats() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
splitted := strings.Split(line, " ")
|
splitted := strings.Split(line, " ")
|
||||||
switch {
|
if len(splitted) < 2 {
|
||||||
case splitted[0] == "coredns_dnsfilter_filtered_total":
|
continue
|
||||||
handleValue(splitted[1], &statistics.lastsnap.filteredTotal)
|
|
||||||
case splitted[0] == "coredns_dnsfilter_filtered_lists_total":
|
|
||||||
handleValue(splitted[1], &statistics.lastsnap.filteredLists)
|
|
||||||
case splitted[0] == "coredns_dnsfilter_filtered_safebrowsing_total":
|
|
||||||
handleValue(splitted[1], &statistics.lastsnap.filteredSafebrowsing)
|
|
||||||
case splitted[0] == "coredns_dnsfilter_filtered_parental_total":
|
|
||||||
handleValue(splitted[1], &statistics.lastsnap.filteredParental)
|
|
||||||
case requestCountTotalRegex.MatchString(splitted[0]):
|
|
||||||
handleValue(splitted[1], &statistics.lastsnap.totalRequests)
|
|
||||||
case requestDurationSecondsSum.MatchString(splitted[0]):
|
|
||||||
handleValue(splitted[1], &statistics.lastsnap.processingTimeSum)
|
|
||||||
case requestDurationSecondsCount.MatchString(splitted[0]):
|
|
||||||
handleValue(splitted[1], &statistics.lastsnap.processingTimeCount)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseFloat(splitted[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to parse number input %s: %s", splitted[1], err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := splitted[0]
|
||||||
|
index := strings.IndexByte(key, '{')
|
||||||
|
if index >= 0 {
|
||||||
|
key = key[:index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty keys are not ok
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
got, ok := entry[key]
|
||||||
|
if ok {
|
||||||
|
value += got
|
||||||
|
}
|
||||||
|
entry[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the snap into per-second, per-minute, per-hour and per-day
|
// calculate delta
|
||||||
assignSnapToStats(&statistics.perSecond)
|
delta := calcDelta(entry, statistics.lastSeen)
|
||||||
assignSnapToStats(&statistics.perMinute)
|
|
||||||
assignSnapToStats(&statistics.perHour)
|
// apply delta to second/minute/hour/day
|
||||||
assignSnapToStats(&statistics.perDay)
|
applyDelta(&statistics.perSecond, delta)
|
||||||
|
applyDelta(&statistics.perMinute, delta)
|
||||||
|
applyDelta(&statistics.perHour, delta)
|
||||||
|
applyDelta(&statistics.perDay, delta)
|
||||||
|
|
||||||
|
// save last seen
|
||||||
|
statistics.lastSeen = entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func assignSnapToStats(stats *periodicStats) {
|
func calcDelta(current, seen statsEntry) statsEntry {
|
||||||
stats.totalRequests[0] = statistics.lastsnap.totalRequests
|
delta := statsEntry{}
|
||||||
stats.filteredTotal[0] = statistics.lastsnap.filteredTotal
|
for key, currentValue := range current {
|
||||||
stats.filteredLists[0] = statistics.lastsnap.filteredLists
|
seenValue := seen[key]
|
||||||
stats.filteredSafebrowsing[0] = statistics.lastsnap.filteredSafebrowsing
|
deltaValue := currentValue - seenValue
|
||||||
stats.filteredSafesearch[0] = statistics.lastsnap.filteredSafesearch
|
delta[key] = deltaValue
|
||||||
stats.filteredParental[0] = statistics.lastsnap.filteredParental
|
}
|
||||||
stats.processingTimeSum[0] = statistics.lastsnap.processingTimeSum
|
return delta
|
||||||
stats.processingTimeCount[0] = statistics.lastsnap.processingTimeCount
|
}
|
||||||
|
|
||||||
|
func applyDelta(current *periodicStats, delta statsEntry) {
|
||||||
|
for key, deltaValue := range delta {
|
||||||
|
currentValues := current.entries[key]
|
||||||
|
currentValues[0] += deltaValue
|
||||||
|
current.entries[key] = currentValues
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue