Merge pull request #23 in DNS/adguard-dns from gometalinter to master

* commit '076c9de68e73bbddc63cf6f7212818c91f3e5c08':
  Fix many lint warnings found by gometalinter
This commit is contained in:
Eugene Bujak 2018-09-17 11:26:17 +03:00
commit c2aa39efe5
11 changed files with 359 additions and 241 deletions

View File

@ -29,20 +29,20 @@ type configuration struct {
} }
type coreDNSConfig struct { type coreDNSConfig struct {
Port int `yaml:"port"`
binaryFile string binaryFile string
coreFile string coreFile string
FilterFile string `yaml:"-"` FilterFile string `yaml:"-"`
Port int `yaml:"port"`
FilteringEnabled bool `yaml:"filtering_enabled"` FilteringEnabled bool `yaml:"filtering_enabled"`
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
SafeSearchEnabled bool `yaml:"safesearch_enabled"` SafeSearchEnabled bool `yaml:"safesearch_enabled"`
QueryLogEnabled bool `yaml:"querylog_enabled"`
ParentalEnabled bool `yaml:"parental_enabled"` ParentalEnabled bool `yaml:"parental_enabled"`
ParentalSensitivity int `yaml:"parental_sensitivity"` ParentalSensitivity int `yaml:"parental_sensitivity"`
QueryLogEnabled bool `yaml:"querylog_enabled"`
Pprof string `yaml:"pprof"` Pprof string `yaml:"pprof"`
UpstreamDNS []string `yaml:"upstream_dns"`
Cache string `yaml:"cache"` Cache string `yaml:"cache"`
Prometheus string `yaml:"prometheus"` Prometheus string `yaml:"prometheus"`
UpstreamDNS []string `yaml:"upstream_dns"`
} }
type filter struct { type filter struct {

View File

@ -125,7 +125,11 @@ func handleStart(w http.ResponseWriter, r *http.Request) {
return return
} }
fmt.Fprintf(w, "OK, PID %d\n", coreDNSCommand.Process.Pid) _, err = fmt.Fprintf(w, "OK, PID %d\n", coreDNSCommand.Process.Pid)
if err != nil {
log.Printf("Couldn't write body in %s(): %s", _Func(), err)
return
}
} }
func childwaiter() { func childwaiter() {
@ -162,8 +166,11 @@ func handleStop(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, 500) http.Error(w, errortext, 500)
return return
} }
// this err is ignorable, it shows exit status of coredns _, err = fmt.Fprintf(w, "OK\n%s\n", exitstatus)
fmt.Fprintf(w, "OK\n%s\n", exitstatus) if err != nil {
log.Printf("Couldn't write body in %s(): %s", _Func(), err)
return
}
} }
func handleRestart(w http.ResponseWriter, r *http.Request) { func handleRestart(w http.ResponseWriter, r *http.Request) {
@ -359,7 +366,12 @@ func handleQueryLogEnable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleQueryLogDisable(w http.ResponseWriter, r *http.Request) { func handleQueryLogDisable(w http.ResponseWriter, r *http.Request) {
@ -371,7 +383,13 @@ func handleQueryLogDisable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleStatsTop(w http.ResponseWriter, r *http.Request) { func handleStatsTop(w http.ResponseWriter, r *http.Request) {
@ -450,7 +468,10 @@ func handleStatsTop(w http.ResponseWriter, r *http.Request) {
json.WriteString("\": {\n") json.WriteString("\": {\n")
sorted := sortByValue(top) sorted := sortByValue(top)
for i, key := range sorted { for i, key := range sorted {
fmt.Fprintf(json, " \"%s\": %d", key, top[key]) json.WriteString(" \"")
json.WriteString(key)
json.WriteString("\": ")
json.WriteString(strconv.Itoa(top[key]))
if i+1 != len(sorted) { if i+1 != len(sorted) {
json.WriteByte(',') json.WriteByte(',')
} }
@ -500,7 +521,12 @@ func handleSetUpstreamDNS(w http.ResponseWriter, r *http.Request) {
return return
} }
tellCoreDNSToReload() tellCoreDNSToReload()
fmt.Fprintf(w, "OK %d servers\n", len(hosts)) _, err = fmt.Fprintf(w, "OK %d servers\n", len(hosts))
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func parseIPsOptionalPort(input string) []string { func parseIPsOptionalPort(input string) []string {
@ -533,7 +559,13 @@ func handleFilteringEnable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleFilteringDisable(w http.ResponseWriter, r *http.Request) { func handleFilteringDisable(w http.ResponseWriter, r *http.Request) {
@ -545,7 +577,13 @@ func handleFilteringDisable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleFilteringStatus(w http.ResponseWriter, r *http.Request) { func handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
@ -647,7 +685,12 @@ func handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
return return
} }
tellCoreDNSToReload() tellCoreDNSToReload()
fmt.Fprintf(w, "OK %d rules\n", filter.RulesCount) _, err = fmt.Fprintf(w, "OK %d rules\n", filter.RulesCount)
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) { func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
@ -693,7 +736,13 @@ func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
return return
} }
tellCoreDNSToReload() tellCoreDNSToReload()
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleFilteringEnableURL(w http.ResponseWriter, r *http.Request) { func handleFilteringEnableURL(w http.ResponseWriter, r *http.Request) {
@ -748,7 +797,13 @@ func handleFilteringEnableURL(w http.ResponseWriter, r *http.Request) {
return return
} }
tellCoreDNSToReload() tellCoreDNSToReload()
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleFilteringDisableURL(w http.ResponseWriter, r *http.Request) { func handleFilteringDisableURL(w http.ResponseWriter, r *http.Request) {
@ -800,7 +855,13 @@ func handleFilteringDisableURL(w http.ResponseWriter, r *http.Request) {
return return
} }
tellCoreDNSToReload() tellCoreDNSToReload()
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
// TODO: regenerate coredns config and tell coredns to reload it if it's running // TODO: regenerate coredns config and tell coredns to reload it if it's running
} }
@ -829,7 +890,13 @@ func handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
return return
} }
tellCoreDNSToReload() tellCoreDNSToReload()
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleFilteringRefresh(w http.ResponseWriter, r *http.Request) { func handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
@ -1015,7 +1082,13 @@ func handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) { func handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
@ -1027,7 +1100,13 @@ func handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) { func handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
@ -1104,7 +1183,13 @@ func handleParentalEnable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleParentalDisable(w http.ResponseWriter, r *http.Request) { func handleParentalDisable(w http.ResponseWriter, r *http.Request) {
@ -1116,7 +1201,13 @@ func handleParentalDisable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleParentalStatus(w http.ResponseWriter, r *http.Request) { func handleParentalStatus(w http.ResponseWriter, r *http.Request) {
@ -1157,7 +1248,13 @@ func handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { func handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
@ -1169,7 +1266,13 @@ func handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
http.Error(w, errortext, http.StatusInternalServerError) http.Error(w, errortext, http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "OK\n") _, err = fmt.Fprintf(w, "OK\n")
if err != nil {
errortext := fmt.Sprintf("Couldn't write body: %s", err)
log.Println(errortext)
http.Error(w, errortext, http.StatusInternalServerError)
}
} }
func handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { func handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {

View File

@ -45,7 +45,7 @@ func init() {
}) })
} }
type Plugin struct { type plug struct {
d *dnsfilter.Dnsfilter d *dnsfilter.Dnsfilter
Next plugin.Handler Next plugin.Handler
upstream upstream.Upstream upstream upstream.Upstream
@ -56,12 +56,12 @@ type Plugin struct {
QueryLogEnabled bool QueryLogEnabled bool
} }
var defaultPlugin = Plugin{ var defaultPlugin = plug{
SafeBrowsingBlockHost: "safebrowsing.block.dns.adguard.com", SafeBrowsingBlockHost: "safebrowsing.block.dns.adguard.com",
ParentalBlockHost: "family.block.dns.adguard.com", ParentalBlockHost: "family.block.dns.adguard.com",
} }
func newDnsCounter(name string, help string) prometheus.Counter { func newDNSCounter(name string, help string) prometheus.Counter {
return prometheus.NewCounter(prometheus.CounterOpts{ return prometheus.NewCounter(prometheus.CounterOpts{
Namespace: plugin.Namespace, Namespace: plugin.Namespace,
Subsystem: "dnsfilter", Subsystem: "dnsfilter",
@ -71,26 +71,26 @@ func newDnsCounter(name string, help string) prometheus.Counter {
} }
var ( var (
requests = newDnsCounter("requests_total", "Count of requests seen by dnsfilter.") requests = newDNSCounter("requests_total", "Count of requests seen by dnsfilter.")
filtered = newDnsCounter("filtered_total", "Count of requests filtered by dnsfilter.") filtered = newDNSCounter("filtered_total", "Count of requests filtered by dnsfilter.")
filteredLists = newDnsCounter("filtered_lists_total", "Count of requests filtered by dnsfilter using lists.") filteredLists = newDNSCounter("filtered_lists_total", "Count of requests filtered by dnsfilter using lists.")
filteredSafebrowsing = newDnsCounter("filtered_safebrowsing_total", "Count of requests filtered by dnsfilter using safebrowsing.") filteredSafebrowsing = newDNSCounter("filtered_safebrowsing_total", "Count of requests filtered by dnsfilter using safebrowsing.")
filteredParental = newDnsCounter("filtered_parental_total", "Count of requests filtered by dnsfilter using parental.") filteredParental = newDNSCounter("filtered_parental_total", "Count of requests filtered by dnsfilter using parental.")
filteredInvalid = newDnsCounter("filtered_invalid_total", "Count of requests filtered by dnsfilter because they were invalid.") filteredInvalid = newDNSCounter("filtered_invalid_total", "Count of requests filtered by dnsfilter because they were invalid.")
whitelisted = newDnsCounter("whitelisted_total", "Count of requests not filtered by dnsfilter because they are whitelisted.") whitelisted = newDNSCounter("whitelisted_total", "Count of requests not filtered by dnsfilter because they are whitelisted.")
safesearch = newDnsCounter("safesearch_total", "Count of requests replaced by dnsfilter safesearch.") safesearch = newDNSCounter("safesearch_total", "Count of requests replaced by dnsfilter safesearch.")
errorsTotal = newDnsCounter("errors_total", "Count of requests that dnsfilter couldn't process because of transitive errors.") errorsTotal = newDNSCounter("errors_total", "Count of requests that dnsfilter couldn't process because of transitive errors.")
) )
// //
// coredns handling functions // coredns handling functions
// //
func setupPlugin(c *caddy.Controller) (*Plugin, error) { func setupPlugin(c *caddy.Controller) (*plug, error) {
// create new Plugin and copy default values // create new Plugin and copy default values
var d = new(Plugin) var p = new(plug)
*d = defaultPlugin *p = defaultPlugin
d.d = dnsfilter.New() p.d = dnsfilter.New()
d.hosts = make(map[string]net.IP) p.hosts = make(map[string]net.IP)
var filterFileName string var filterFileName string
for c.Next() { for c.Next() {
@ -103,15 +103,15 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
for c.NextBlock() { for c.NextBlock() {
switch c.Val() { switch c.Val() {
case "safebrowsing": case "safebrowsing":
d.d.EnableSafeBrowsing() p.d.EnableSafeBrowsing()
if c.NextArg() { if c.NextArg() {
if len(c.Val()) == 0 { if len(c.Val()) == 0 {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
d.d.SetSafeBrowsingServer(c.Val()) p.d.SetSafeBrowsingServer(c.Val())
} }
case "safesearch": case "safesearch":
d.d.EnableSafeSearch() p.d.EnableSafeSearch()
case "parental": case "parental":
if !c.NextArg() { if !c.NextArg() {
return nil, c.ArgErr() return nil, c.ArgErr()
@ -120,7 +120,7 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
if err != nil { if err != nil {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
err = d.d.EnableParental(sensitivity) err = p.d.EnableParental(sensitivity)
if err != nil { if err != nil {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
@ -128,10 +128,10 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
if len(c.Val()) == 0 { if len(c.Val()) == 0 {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
d.ParentalBlockHost = c.Val() p.ParentalBlockHost = c.Val()
} }
case "querylog": case "querylog":
d.QueryLogEnabled = true p.QueryLogEnabled = true
onceQueryLog.Do(func() { onceQueryLog.Do(func() {
go startQueryLogServer() // TODO: how to handle errors? go startQueryLogServer() // TODO: how to handle errors?
}) })
@ -149,10 +149,10 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
if d.parseEtcHosts(text) { if p.parseEtcHosts(text) {
continue continue
} }
err = d.d.AddRule(text, 0) err = p.d.AddRule(text, 0)
if err == dnsfilter.ErrInvalidSyntax { if err == dnsfilter.ErrInvalidSyntax {
continue continue
} }
@ -167,23 +167,23 @@ func setupPlugin(c *caddy.Controller) (*Plugin, error) {
return nil, err return nil, err
} }
d.upstream, err = upstream.New(nil) p.upstream, err = upstream.New(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return d, nil return p, nil
} }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
d, err := setupPlugin(c) p, err := setupPlugin(c)
if err != nil { if err != nil {
return err return err
} }
config := dnsserver.GetConfig(c) config := dnsserver.GetConfig(c)
config.AddPlugin(func(next plugin.Handler) plugin.Handler { config.AddPlugin(func(next plugin.Handler) plugin.Handler {
d.Next = next p.Next = next
return d return p
}) })
c.OnStartup(func() error { c.OnStartup(func() error {
@ -200,16 +200,16 @@ func setup(c *caddy.Controller) error {
x.MustRegister(whitelisted) x.MustRegister(whitelisted)
x.MustRegister(safesearch) x.MustRegister(safesearch)
x.MustRegister(errorsTotal) x.MustRegister(errorsTotal)
x.MustRegister(d) x.MustRegister(p)
} }
return nil return nil
}) })
c.OnShutdown(d.OnShutdown) c.OnShutdown(p.onShutdown)
return nil return nil
} }
func (d *Plugin) parseEtcHosts(text string) bool { func (p *plug) parseEtcHosts(text string) bool {
if pos := strings.IndexByte(text, '#'); pos != -1 { if pos := strings.IndexByte(text, '#'); pos != -1 {
text = text[0:pos] text = text[0:pos]
} }
@ -222,17 +222,17 @@ func (d *Plugin) parseEtcHosts(text string) bool {
return false return false
} }
for _, host := range fields[1:] { for _, host := range fields[1:] {
if val, ok := d.hosts[host]; ok { if val, ok := p.hosts[host]; ok {
log.Printf("warning: host %s already has value %s, will overwrite it with %s", host, val, addr) log.Printf("warning: host %s already has value %s, will overwrite it with %s", host, val, addr)
} }
d.hosts[host] = addr p.hosts[host] = addr
} }
return true return true
} }
func (d *Plugin) OnShutdown() error { func (p *plug) onShutdown() error {
d.d.Destroy() p.d.Destroy()
d.d = nil p.d = nil
return nil return nil
} }
@ -240,7 +240,7 @@ type statsFunc func(ch interface{}, name string, text string, value float64, val
func doDesc(ch interface{}, name string, text string, value float64, valueType prometheus.ValueType) { func doDesc(ch interface{}, name string, text string, value float64, valueType prometheus.ValueType) {
realch, ok := ch.(chan<- *prometheus.Desc) realch, ok := ch.(chan<- *prometheus.Desc)
if ok == false { if !ok {
log.Printf("Couldn't convert ch to chan<- *prometheus.Desc\n") log.Printf("Couldn't convert ch to chan<- *prometheus.Desc\n")
return return
} }
@ -249,7 +249,7 @@ func doDesc(ch interface{}, name string, text string, value float64, valueType p
func doMetric(ch interface{}, name string, text string, value float64, valueType prometheus.ValueType) { func doMetric(ch interface{}, name string, text string, value float64, valueType prometheus.ValueType) {
realch, ok := ch.(chan<- prometheus.Metric) realch, ok := ch.(chan<- prometheus.Metric)
if ok == false { if !ok {
log.Printf("Couldn't convert ch to chan<- prometheus.Metric\n") log.Printf("Couldn't convert ch to chan<- prometheus.Metric\n")
return return
} }
@ -268,21 +268,23 @@ func doStatsLookup(ch interface{}, doFunc statsFunc, name string, lookupstats *d
gen(ch, doFunc, fmt.Sprintf("coredns_dnsfilter_%s_pending_max", name), fmt.Sprintf("Maximum number of pending %s HTTP requests", name), float64(lookupstats.PendingMax), prometheus.GaugeValue) gen(ch, doFunc, fmt.Sprintf("coredns_dnsfilter_%s_pending_max", name), fmt.Sprintf("Maximum number of pending %s HTTP requests", name), float64(lookupstats.PendingMax), prometheus.GaugeValue)
} }
func (d *Plugin) doStats(ch interface{}, doFunc statsFunc) { func (p *plug) doStats(ch interface{}, doFunc statsFunc) {
stats := d.d.GetStats() stats := p.d.GetStats()
doStatsLookup(ch, doFunc, "safebrowsing", &stats.Safebrowsing) doStatsLookup(ch, doFunc, "safebrowsing", &stats.Safebrowsing)
doStatsLookup(ch, doFunc, "parental", &stats.Parental) doStatsLookup(ch, doFunc, "parental", &stats.Parental)
} }
func (d *Plugin) Describe(ch chan<- *prometheus.Desc) { // Describe is called by prometheus handler to know stat types
d.doStats(ch, doDesc) func (p *plug) Describe(ch chan<- *prometheus.Desc) {
p.doStats(ch, doDesc)
} }
func (d *Plugin) Collect(ch chan<- prometheus.Metric) { // Collect is called by prometheus handler to collect stats
d.doStats(ch, doMetric) func (p *plug) Collect(ch chan<- prometheus.Metric) {
p.doStats(ch, doMetric)
} }
func (d *Plugin) replaceHostWithValAndReply(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, host string, val string, question dns.Question) (int, error) { func (p *plug) replaceHostWithValAndReply(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, host string, val string, question dns.Question) (int, error) {
// check if it's a domain name or IP address // check if it's a domain name or IP address
addr := net.ParseIP(val) addr := net.ParseIP(val)
var records []dns.RR var records []dns.RR
@ -301,7 +303,7 @@ func (d *Plugin) replaceHostWithValAndReply(ctx context.Context, w dns.ResponseW
req.SetQuestion(dns.Fqdn(val), question.Qtype) req.SetQuestion(dns.Fqdn(val), question.Qtype)
req.RecursionDesired = true req.RecursionDesired = true
reqstate := request.Request{W: w, Req: req, Context: ctx} reqstate := request.Request{W: w, Req: req, Context: ctx}
result, err := d.upstream.Lookup(reqstate, dns.Fqdn(val), reqstate.QType()) result, err := p.upstream.Lookup(reqstate, dns.Fqdn(val), reqstate.QType())
if err != nil { if err != nil {
log.Printf("Got error %s\n", err) log.Printf("Got error %s\n", err)
return dns.RcodeServerFailure, fmt.Errorf("plugin/dnsfilter: %s", err) return dns.RcodeServerFailure, fmt.Errorf("plugin/dnsfilter: %s", err)
@ -363,80 +365,80 @@ func writeNXdomain(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int,
return dns.RcodeNameError, nil return dns.RcodeNameError, nil
} }
func (d *Plugin) serveDNSInternal(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error, dnsfilter.Result) { func (p *plug) serveDNSInternal(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, dnsfilter.Result, error) {
if len(r.Question) != 1 { if len(r.Question) != 1 {
// google DNS, bind and others do the same // google DNS, bind and others do the same
return dns.RcodeFormatError, fmt.Errorf("Got DNS request with != 1 questions"), dnsfilter.Result{} return dns.RcodeFormatError, dnsfilter.Result{}, fmt.Errorf("Got DNS request with != 1 questions")
} }
for _, question := range r.Question { for _, question := range r.Question {
host := strings.ToLower(strings.TrimSuffix(question.Name, ".")) host := strings.ToLower(strings.TrimSuffix(question.Name, "."))
// is it a safesearch domain? // is it a safesearch domain?
if val, ok := d.d.SafeSearchDomain(host); ok { if val, ok := p.d.SafeSearchDomain(host); ok {
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question) rcode, err := p.replaceHostWithValAndReply(ctx, w, r, host, val, question)
if err != nil { if err != nil {
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
return rcode, err, dnsfilter.Result{Reason: dnsfilter.FilteredSafeSearch} return rcode, dnsfilter.Result{Reason: dnsfilter.FilteredSafeSearch}, err
} }
// is it in hosts? // is it in hosts?
if val, ok := d.hosts[host]; ok { if val, ok := p.hosts[host]; ok {
// it is, if it's a loopback host, reply with NXDOMAIN // it is, if it's a loopback host, reply with NXDOMAIN
if val.IsLoopback() { if val.IsLoopback() {
rcode, err := writeNXdomain(ctx, w, r) rcode, err := writeNXdomain(ctx, w, r)
if err != nil { if err != nil {
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
return rcode, err, dnsfilter.Result{Reason: dnsfilter.FilteredInvalid} return rcode, dnsfilter.Result{Reason: dnsfilter.FilteredInvalid}, err
} }
// it's not a loopback host, replace it with value specified // it's not a loopback host, replace it with value specified
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val.String(), question) rcode, err := p.replaceHostWithValAndReply(ctx, w, r, host, val.String(), question)
if err != nil { if err != nil {
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
return rcode, err, dnsfilter.Result{Reason: dnsfilter.FilteredSafeSearch} return rcode, dnsfilter.Result{Reason: dnsfilter.FilteredSafeSearch}, err
} }
// needs to be filtered instead // needs to be filtered instead
result, err := d.d.CheckHost(host) result, err := p.d.CheckHost(host)
if err != nil { if err != nil {
log.Printf("plugin/dnsfilter: %s\n", err) log.Printf("plugin/dnsfilter: %s\n", err)
return dns.RcodeServerFailure, fmt.Errorf("plugin/dnsfilter: %s", err), dnsfilter.Result{} return dns.RcodeServerFailure, dnsfilter.Result{}, fmt.Errorf("plugin/dnsfilter: %s", err)
} }
if result.IsFiltered { if result.IsFiltered {
switch result.Reason { switch result.Reason {
case dnsfilter.FilteredSafeBrowsing: case dnsfilter.FilteredSafeBrowsing:
// return cname safebrowsing.block.dns.adguard.com // return cname safebrowsing.block.dns.adguard.com
val := d.SafeBrowsingBlockHost val := p.SafeBrowsingBlockHost
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question) rcode, err := p.replaceHostWithValAndReply(ctx, w, r, host, val, question)
if err != nil { if err != nil {
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
return rcode, err, result return rcode, result, err
case dnsfilter.FilteredParental: case dnsfilter.FilteredParental:
// return cname family.block.dns.adguard.com // return cname family.block.dns.adguard.com
val := d.ParentalBlockHost val := p.ParentalBlockHost
rcode, err := d.replaceHostWithValAndReply(ctx, w, r, host, val, question) rcode, err := p.replaceHostWithValAndReply(ctx, w, r, host, val, question)
if err != nil { if err != nil {
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
return rcode, err, result return rcode, result, err
case dnsfilter.FilteredBlackList: case dnsfilter.FilteredBlackList:
// return NXdomain // return NXdomain
rcode, err := writeNXdomain(ctx, w, r) rcode, err := writeNXdomain(ctx, w, r)
if err != nil { if err != nil {
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
return rcode, err, result return rcode, result, err
default: default:
log.Printf("SHOULD NOT HAPPEN -- got unknown reason for filtering: %T %v %s", result.Reason, result.Reason, result.Reason.String()) log.Printf("SHOULD NOT HAPPEN -- got unknown reason for filtering: %T %v %s", result.Reason, result.Reason, result.Reason.String())
} }
} else { } else {
switch result.Reason { switch result.Reason {
case dnsfilter.NotFilteredWhiteList: case dnsfilter.NotFilteredWhiteList:
rcode, err := plugin.NextOrFailure(d.Name(), d.Next, ctx, w, r) rcode, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
return rcode, err, result return rcode, result, err
case dnsfilter.NotFilteredNotFound: case dnsfilter.NotFilteredNotFound:
// do nothing, pass through to lower code // do nothing, pass through to lower code
default: default:
@ -444,11 +446,12 @@ func (d *Plugin) serveDNSInternal(ctx context.Context, w dns.ResponseWriter, r *
} }
} }
} }
rcode, err := plugin.NextOrFailure(d.Name(), d.Next, ctx, w, r) rcode, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
return rcode, err, dnsfilter.Result{} return rcode, dnsfilter.Result{}, err
} }
func (d *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { // ServeDNS handles the DNS request and refuses if it's in filterlists
func (p *plug) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
start := time.Now() start := time.Now()
requests.Inc() requests.Inc()
state := request.Request{W: w, Req: r} state := request.Request{W: w, Req: r}
@ -456,13 +459,16 @@ func (d *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
// capture the written answer // capture the written answer
rrw := dnstest.NewRecorder(w) rrw := dnstest.NewRecorder(w)
rcode, err, result := d.serveDNSInternal(ctx, rrw, r) rcode, result, err := p.serveDNSInternal(ctx, rrw, r)
if rcode > 0 { if rcode > 0 {
// actually send the answer if we have one // actually send the answer if we have one
answer := new(dns.Msg) answer := new(dns.Msg)
answer.SetRcode(r, rcode) answer.SetRcode(r, rcode)
state.SizeAndDo(answer) state.SizeAndDo(answer)
w.WriteMsg(answer) err = w.WriteMsg(answer)
if err != nil {
return dns.RcodeServerFailure, err
}
} }
// increment counters // increment counters
@ -496,12 +502,13 @@ func (d *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
} }
// log // log
if d.QueryLogEnabled { if p.QueryLogEnabled {
logRequest(r, 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" } // Name returns name of the plugin as seen in Corefile and plugin.cfg
func (p *plug) Name() string { return "dnsfilter" }
var onceQueryLog sync.Once var onceQueryLog sync.Once

View File

@ -46,10 +46,10 @@ func TestEtcHostsParse(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := tmpfile.Write(text); err != nil { if _, err = tmpfile.Write(text); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := tmpfile.Close(); err != nil { if err = tmpfile.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -80,10 +80,10 @@ func TestEtcHostsFilter(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := tmpfile.Write(text); err != nil { if _, err = tmpfile.Write(text); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := tmpfile.Close(); err != nil { if err = tmpfile.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -127,10 +127,10 @@ func TestEtcHostsFilter(t *testing.T) {
t.Fatalf("ServeDNS return value for host %s has rcode %d that does not match captured rcode %d", testcase.host, rcode, rrw.Rcode) t.Fatalf("ServeDNS return value for host %s has rcode %d that does not match captured rcode %d", testcase.host, rcode, rrw.Rcode)
} }
filtered := rcode == dns.RcodeNameError filtered := rcode == dns.RcodeNameError
if testcase.filtered == true && testcase.filtered != filtered { if testcase.filtered && testcase.filtered != filtered {
t.Fatalf("Host %s expected to be filtered, instead it is not filtered", testcase.host) t.Fatalf("Host %s expected to be filtered, instead it is not filtered", testcase.host)
} }
if testcase.filtered == false && testcase.filtered != filtered { if !testcase.filtered && testcase.filtered != filtered {
t.Fatalf("Host %s expected to be not filtered, instead it is filtered", testcase.host) t.Fatalf("Host %s expected to be not filtered, instead it is filtered", testcase.host)
} }
} }

View File

@ -4,7 +4,6 @@ import (
"errors" "errors"
"log" "log"
"strconv" "strconv"
"sync"
"time" "time"
// ratelimiting and per-ip buckets // ratelimiting and per-ip buckets
@ -29,8 +28,8 @@ var (
tokenBuckets = cache.New(time.Hour, time.Hour) tokenBuckets = cache.New(time.Hour, time.Hour)
) )
// main function // ServeDNS handles the DNS request and refuses if it's an beyind specified ratelimit
func (p *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { func (p *plug) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r} state := request.Request{W: w, Req: r}
ip := state.IP() ip := state.IP()
allow, err := p.allowRequest(ip) allow, err := p.allowRequest(ip)
@ -44,7 +43,7 @@ func (p *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r) return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
} }
func (p *Plugin) allowRequest(ip string) (bool, error) { func (p *plug) allowRequest(ip string) (bool, error) {
if _, found := tokenBuckets.Get(ip); !found { if _, found := tokenBuckets.Get(ip); !found {
tokenBuckets.Set(ip, rate.New(p.ratelimit, time.Second), time.Hour) tokenBuckets.Set(ip, rate.New(p.ratelimit, time.Second), time.Hour)
} }
@ -59,7 +58,7 @@ func (p *Plugin) allowRequest(ip string) (bool, error) {
} }
rl, ok := value.(*rate.RateLimiter) rl, ok := value.(*rate.RateLimiter)
if ok == false { if !ok {
text := "SHOULD NOT HAPPEN: non-bool entry found in safebrowsing lookup cache" text := "SHOULD NOT HAPPEN: non-bool entry found in safebrowsing lookup cache"
log.Println(text) log.Println(text)
err := errors.New(text) err := errors.New(text)
@ -80,7 +79,7 @@ func init() {
}) })
} }
type Plugin struct { type plug struct {
Next plugin.Handler Next plugin.Handler
// configuration for creating above // configuration for creating above
@ -88,7 +87,7 @@ type Plugin struct {
} }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
p := &Plugin{ratelimit: defaultRatelimit} p := &plug{ratelimit: defaultRatelimit}
config := dnsserver.GetConfig(c) config := dnsserver.GetConfig(c)
for c.Next() { for c.Next() {
@ -109,22 +108,20 @@ 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(ratelimited)
x.MustRegister(ratelimited) }
}
})
return nil return nil
}) })
return nil return nil
} }
func newDnsCounter(name string, help string) prometheus.Counter { func newDNSCounter(name string, help string) prometheus.Counter {
return prometheus.NewCounter(prometheus.CounterOpts{ return prometheus.NewCounter(prometheus.CounterOpts{
Namespace: plugin.Namespace, Namespace: plugin.Namespace,
Subsystem: "ratelimit", Subsystem: "ratelimit",
@ -134,9 +131,8 @@ func newDnsCounter(name string, help string) prometheus.Counter {
} }
var ( var (
ratelimited = newDnsCounter("dropped_total", "Count of requests that have been dropped because of rate limit") ratelimited = newDNSCounter("dropped_total", "Count of requests that have been dropped because of rate limit")
) )
func (d *Plugin) Name() string { return "ratelimit" } // Name returns name of the plugin as seen in Corefile and plugin.cfg
func (p *plug) Name() string { return "ratelimit" }
var once sync.Once

View File

@ -3,7 +3,6 @@ package refuseany
import ( import (
"fmt" "fmt"
"log" "log"
"sync"
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
@ -15,11 +14,12 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
type Plugin struct { type plug struct {
Next plugin.Handler Next plugin.Handler
} }
func (p *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { // ServeDNS handles the DNS request and refuses if it's an ANY request
func (p *plug) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
if len(r.Question) != 1 { if len(r.Question) != 1 {
// google DNS, bind and others do the same // google DNS, bind and others do the same
return dns.RcodeFormatError, fmt.Errorf("Got DNS request with != 1 questions") return dns.RcodeFormatError, fmt.Errorf("Got DNS request with != 1 questions")
@ -41,9 +41,9 @@ func (p *Plugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
return dns.RcodeServerFailure, err return dns.RcodeServerFailure, err
} }
return rcode, nil return rcode, nil
} else {
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
} }
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
} }
func init() { func init() {
@ -54,7 +54,7 @@ func init() {
} }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
p := &Plugin{} p := &plug{}
config := dnsserver.GetConfig(c) config := dnsserver.GetConfig(c)
config.AddPlugin(func(next plugin.Handler) plugin.Handler { config.AddPlugin(func(next plugin.Handler) plugin.Handler {
@ -63,22 +63,20 @@ 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(ratelimited)
x.MustRegister(ratelimited) }
}
})
return nil return nil
}) })
return nil return nil
} }
func newDnsCounter(name string, help string) prometheus.Counter { func newDNSCounter(name string, help string) prometheus.Counter {
return prometheus.NewCounter(prometheus.CounterOpts{ return prometheus.NewCounter(prometheus.CounterOpts{
Namespace: plugin.Namespace, Namespace: plugin.Namespace,
Subsystem: "refuseany", Subsystem: "refuseany",
@ -88,9 +86,8 @@ func newDnsCounter(name string, help string) prometheus.Counter {
} }
var ( var (
ratelimited = newDnsCounter("refusedany_total", "Count of ANY requests that have been dropped") ratelimited = newDNSCounter("refusedany_total", "Count of ANY requests that have been dropped")
) )
func (d *Plugin) Name() string { return "refuseany" } // Name returns name of the plugin as seen in Corefile and plugin.cfg
func (p *plug) Name() string { return "refuseany" }
var once sync.Once

View File

@ -31,7 +31,10 @@ const defaultSafebrowsingURL = "http://%s/safebrowsing-lookup-hash.html?prefixes
const defaultParentalServer = "pctrl.adguard.com" const defaultParentalServer = "pctrl.adguard.com"
const defaultParentalURL = "http://%s/check-parental-control-hash?prefixes=%s&sensitivity=%d" const defaultParentalURL = "http://%s/check-parental-control-hash?prefixes=%s&sensitivity=%d"
// ErrInvalidSyntax is returned by AddRule when rule is invalid
var ErrInvalidSyntax = errors.New("dnsfilter: invalid rule syntax") var ErrInvalidSyntax = errors.New("dnsfilter: invalid rule syntax")
// ErrInvalidParental is returned by EnableParental when sensitivity is not a valid value
var ErrInvalidParental = errors.New("dnsfilter: invalid parental sensitivity, must be either 3, 10, 13 or 17") var ErrInvalidParental = errors.New("dnsfilter: invalid parental sensitivity, must be either 3, 10, 13 or 17")
const shortcutLength = 6 // used for rule search optimization, 6 hits the sweet spot const shortcutLength = 6 // used for rule search optimization, 6 hits the sweet spot
@ -39,16 +42,16 @@ const shortcutLength = 6 // used for rule search optimization, 6 hits the sweet
const enableFastLookup = true // flag for debugging, must be true in production for faster performance const enableFastLookup = true // flag for debugging, must be true in production for faster performance
const enableDelayedCompilation = true // flag for debugging, must be true in production for faster performance const enableDelayedCompilation = true // flag for debugging, must be true in production for faster performance
type Config struct { type config struct {
parentalServer string
parentalSensitivity int // must be either 3, 10, 13 or 17
parentalEnabled bool
safeSearchEnabled bool safeSearchEnabled bool
safeBrowsingEnabled bool safeBrowsingEnabled bool
safeBrowsingServer string safeBrowsingServer string
parentalEnabled bool
parentalServer string
parentalSensitivity int // must be either 3, 10, 13 or 17
} }
type Rule struct { type rule struct {
text string // text without @@ decorators or $ options text string // text without @@ decorators or $ options
shortcut string // for speeding up lookup shortcut string // for speeding up lookup
originalText string // original text for reporting back to applications originalText string // original text for reporting back to applications
@ -57,9 +60,9 @@ type Rule struct {
options []string // optional options after $ options []string // optional options after $
// parsed options // parsed options
apps []string
isWhitelist bool isWhitelist bool
isImportant bool isImportant bool
apps []string
// user-supplied data // user-supplied data
listID uint32 listID uint32
@ -70,6 +73,7 @@ type Rule struct {
sync.RWMutex sync.RWMutex
} }
// LookupStats store stats collected during safebrowsing or parental checks
type LookupStats struct { type LookupStats struct {
Requests uint64 // number of HTTP requests that were sent Requests uint64 // number of HTTP requests that were sent
CacheHits uint64 // number of lookups that didn't need HTTP requests CacheHits uint64 // number of lookups that didn't need HTTP requests
@ -77,6 +81,7 @@ type LookupStats struct {
PendingMax int64 // maximum number of pending HTTP requests PendingMax int64 // maximum number of pending HTTP requests
} }
// Stats store LookupStats for both safebrowsing and parental
type Stats struct { type Stats struct {
Safebrowsing LookupStats Safebrowsing LookupStats
Parental LookupStats Parental LookupStats
@ -84,7 +89,7 @@ type Stats struct {
// Dnsfilter holds added rules and performs hostname matches against the rules // Dnsfilter holds added rules and performs hostname matches against the rules
type Dnsfilter struct { type Dnsfilter struct {
storage map[string]*Rule // rule storage, not used for matching, needs to be key->value storage map[string]*rule // rule storage, not used for matching, needs to be key->value
storageMutex sync.RWMutex storageMutex sync.RWMutex
// rules are checked against these lists in the order defined here // rules are checked against these lists in the order defined here
@ -96,12 +101,12 @@ type Dnsfilter struct {
client http.Client // handle for http client -- single instance as recommended by docs client http.Client // handle for http client -- single instance as recommended by docs
transport *http.Transport // handle for http transport used by http client transport *http.Transport // handle for http transport used by http client
config Config config config
} }
//go:generate stringer -type=Reason //go:generate stringer -type=Reason
// filtered/notfiltered reason // Reason holds an enum detailing why it was filtered or not filtered
type Reason int type Reason int
const ( const (
@ -125,13 +130,14 @@ var (
parentalCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build() parentalCache = gcache.New(defaultCacheSize).LRU().Expiration(defaultCacheTime).Build()
) )
// search result // Result holds state of hostname check
type Result struct { type Result struct {
IsFiltered bool IsFiltered bool
Reason Reason Reason Reason
Rule string Rule string
} }
// Matched can be used to see if any match at all was found, no matter filtered or not
func (r Reason) Matched() bool { func (r Reason) Matched() bool {
return r != NotFilteredNotFound return r != NotFilteredNotFound
} }
@ -188,19 +194,19 @@ func (d *Dnsfilter) CheckHost(host string) (Result, error) {
// //
type rulesTable struct { type rulesTable struct {
rulesByShortcut map[string][]*Rule rulesByShortcut map[string][]*rule
rulesLeftovers []*Rule rulesLeftovers []*rule
sync.RWMutex sync.RWMutex
} }
func newRulesTable() *rulesTable { func newRulesTable() *rulesTable {
return &rulesTable{ return &rulesTable{
rulesByShortcut: make(map[string][]*Rule), rulesByShortcut: make(map[string][]*rule),
rulesLeftovers: make([]*Rule, 0), rulesLeftovers: make([]*rule, 0),
} }
} }
func (r *rulesTable) Add(rule *Rule) { func (r *rulesTable) Add(rule *rule) {
r.Lock() r.Lock()
if len(rule.shortcut) == shortcutLength && enableFastLookup { if len(rule.shortcut) == shortcutLength && enableFastLookup {
r.rulesByShortcut[rule.shortcut] = append(r.rulesByShortcut[rule.shortcut], rule) r.rulesByShortcut[rule.shortcut] = append(r.rulesByShortcut[rule.shortcut], rule)
@ -295,7 +301,7 @@ func findOptionIndex(text string) int {
return -1 return -1
} }
func (rule *Rule) extractOptions() error { func (rule *rule) extractOptions() error {
optIndex := findOptionIndex(rule.text) optIndex := findOptionIndex(rule.text)
if optIndex == 0 { // starts with $ if optIndex == 0 { // starts with $
return ErrInvalidSyntax return ErrInvalidSyntax
@ -333,7 +339,7 @@ func (rule *Rule) extractOptions() error {
return nil return nil
} }
func (rule *Rule) parseOptions() error { func (rule *rule) parseOptions() error {
err := rule.extractOptions() err := rule.extractOptions()
if err != nil { if err != nil {
return err return err
@ -354,7 +360,7 @@ func (rule *Rule) parseOptions() error {
return nil return nil
} }
func (rule *Rule) extractShortcut() { func (rule *rule) extractShortcut() {
// regex rules have no shortcuts // regex rules have no shortcuts
if rule.text[0] == '/' && rule.text[len(rule.text)-1] == '/' { if rule.text[0] == '/' && rule.text[len(rule.text)-1] == '/' {
return return
@ -379,7 +385,7 @@ func (rule *Rule) extractShortcut() {
rule.shortcut = strings.ToLower(longestField) rule.shortcut = strings.ToLower(longestField)
} }
func (rule *Rule) compile() error { func (rule *rule) compile() error {
rule.RLock() rule.RLock()
isCompiled := rule.compiled != nil isCompiled := rule.compiled != nil
rule.RUnlock() rule.RUnlock()
@ -404,7 +410,7 @@ func (rule *Rule) compile() error {
return nil return nil
} }
func (rule *Rule) match(host string) (Result, error) { func (rule *rule) match(host string) (Result, error) {
res := Result{} res := Result{}
err := rule.compile() err := rule.compile()
if err != nil { if err != nil {
@ -442,7 +448,7 @@ func getCachedReason(cache gcache.Cache, host string) (result Result, isFound bo
// since it can be something else, validate that it belongs to proper type // since it can be something else, validate that it belongs to proper type
cachedValue, ok := rawValue.(Result) cachedValue, ok := rawValue.(Result)
if ok == false { if !ok {
// this is not our type -- error // this is not our type -- error
text := "SHOULD NOT HAPPEN: entry with invalid type was found in lookup cache" text := "SHOULD NOT HAPPEN: entry with invalid type was found in lookup cache"
log.Println(text) log.Println(text)
@ -458,7 +464,7 @@ func hostnameToHashParam(host string, addslash bool) (string, map[string]bool) {
var hashparam bytes.Buffer var hashparam bytes.Buffer
hashes := map[string]bool{} hashes := map[string]bool{}
tld, icann := publicsuffix.PublicSuffix(host) tld, icann := publicsuffix.PublicSuffix(host)
if icann == false { if !icann {
// private suffixes like cloudfront.net // private suffixes like cloudfront.net
tld = "" tld = ""
} }
@ -616,7 +622,10 @@ func (d *Dnsfilter) lookupCommon(host string, lookupstats *LookupStats, cache gc
switch { switch {
case resp.StatusCode == 204: case resp.StatusCode == 204:
// empty result, save cache // empty result, save cache
cache.Set(host, Result{}) err = cache.Set(host, Result{})
if err != nil {
return Result{}, err
}
return Result{}, nil return Result{}, nil
case resp.StatusCode != 200: case resp.StatusCode != 200:
// error, don't save cache // error, don't save cache
@ -629,7 +638,10 @@ func (d *Dnsfilter) lookupCommon(host string, lookupstats *LookupStats, cache gc
return Result{}, err return Result{}, err
} }
cache.Set(host, result) err = cache.Set(host, result)
if err != nil {
return Result{}, err
}
return result, nil return result, nil
} }
@ -652,7 +664,7 @@ func (d *Dnsfilter) AddRule(input string, filterListID uint32) error {
return ErrInvalidSyntax return ErrInvalidSyntax
} }
rule := Rule{ rule := rule{
text: input, // will be modified text: input, // will be modified
originalText: input, originalText: input,
listID: filterListID, listID: filterListID,
@ -716,10 +728,11 @@ func (d *Dnsfilter) matchHost(host string) (Result, error) {
// lifecycle helper functions // lifecycle helper functions
// //
// New creates properly initialized DNS Filter that is ready to be used
func New() *Dnsfilter { func New() *Dnsfilter {
d := new(Dnsfilter) d := new(Dnsfilter)
d.storage = make(map[string]*Rule) d.storage = make(map[string]*rule)
d.important = newRulesTable() d.important = newRulesTable()
d.whiteList = newRulesTable() d.whiteList = newRulesTable()
d.blackList = newRulesTable() d.blackList = newRulesTable()
@ -743,6 +756,8 @@ func New() *Dnsfilter {
return d return d
} }
// Destroy is optional if you want to tidy up goroutines without waiting for them to die off
// right now it closes idle HTTP connections if there are any
func (d *Dnsfilter) Destroy() { func (d *Dnsfilter) Destroy() {
d.transport.CloseIdleConnections() d.transport.CloseIdleConnections()
} }
@ -751,10 +766,12 @@ func (d *Dnsfilter) Destroy() {
// config manipulation helpers // config manipulation helpers
// //
// EnableSafeBrowsing turns on checking hostnames in malware/phishing database
func (d *Dnsfilter) EnableSafeBrowsing() { func (d *Dnsfilter) EnableSafeBrowsing() {
d.config.safeBrowsingEnabled = true d.config.safeBrowsingEnabled = true
} }
// EnableParental turns on checking hostnames for containing adult content
func (d *Dnsfilter) EnableParental(sensitivity int) error { func (d *Dnsfilter) EnableParental(sensitivity int) error {
switch sensitivity { switch sensitivity {
case 3, 10, 13, 17: case 3, 10, 13, 17:
@ -766,10 +783,13 @@ func (d *Dnsfilter) EnableParental(sensitivity int) error {
} }
} }
// EnableSafeSearch turns on enforcing safesearch in search engines
// only used in coredns plugin and requires caller to use SafeSearchDomain()
func (d *Dnsfilter) EnableSafeSearch() { func (d *Dnsfilter) EnableSafeSearch() {
d.config.safeSearchEnabled = true d.config.safeSearchEnabled = true
} }
// SetSafeBrowsingServer lets you optionally change hostname of safesearch lookup
func (d *Dnsfilter) SetSafeBrowsingServer(host string) { func (d *Dnsfilter) SetSafeBrowsingServer(host string) {
if len(host) == 0 { if len(host) == 0 {
d.config.safeBrowsingServer = defaultSafebrowsingServer d.config.safeBrowsingServer = defaultSafebrowsingServer
@ -778,38 +798,35 @@ func (d *Dnsfilter) SetSafeBrowsingServer(host string) {
} }
} }
// SetHTTPTimeout lets you optionally change timeout during lookups
func (d *Dnsfilter) SetHTTPTimeout(t time.Duration) { func (d *Dnsfilter) SetHTTPTimeout(t time.Duration) {
d.client.Timeout = t d.client.Timeout = t
} }
// ResetHTTPTimeout resets lookup timeouts
func (d *Dnsfilter) ResetHTTPTimeout() { func (d *Dnsfilter) ResetHTTPTimeout() {
d.client.Timeout = defaultHTTPTimeout d.client.Timeout = defaultHTTPTimeout
} }
// SafeSearchDomain returns replacement address for search engine
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) { func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
if d.config.safeSearchEnabled == false { if d.config.safeSearchEnabled {
return "", false val, ok := safeSearchDomains[host]
return val, ok
} }
val, ok := safeSearchDomains[host] return "", false
return val, ok
} }
// //
// stats // stats
// //
// GetStats return dns filtering stats since startup
func (d *Dnsfilter) GetStats() Stats { func (d *Dnsfilter) GetStats() Stats {
return stats return stats
} }
// Count returns number of rules added to filter
func (d *Dnsfilter) Count() int { func (d *Dnsfilter) Count() int {
return len(d.storage) return len(d.storage)
} }
//
// cache control, right now needed only for tests
//
func purgeCaches() {
safebrowsingCache.Purge()
parentalCache.Purge()
}

View File

@ -3,6 +3,8 @@ package dnsfilter
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path"
"strings"
"testing" "testing"
"time" "time"
@ -217,12 +219,6 @@ func TestAddRuleFail(t *testing.T) {
d.checkAddRuleFail(t, "lkfaojewhoawehfwacoefawr$@#$@3413841384") d.checkAddRuleFail(t, "lkfaojewhoawehfwacoefawr$@#$@3413841384")
} }
func printMemStats(r runtime.MemStats) {
fmt.Printf("Alloc: %.2f, HeapAlloc: %.2f Mb, Sys: %.2f Mb, HeapSys: %.2f Mb\n",
float64(r.Alloc)/1024.0/1024.0, float64(r.HeapAlloc)/1024.0/1024.0,
float64(r.Sys)/1024.0/1024.0, float64(r.HeapSys)/1024.0/1024.0)
}
func TestLotsOfRulesMemoryUsage(t *testing.T) { func TestLotsOfRulesMemoryUsage(t *testing.T) {
var start, afterLoad, end runtime.MemStats var start, afterLoad, end runtime.MemStats
runtime.GC() runtime.GC()
@ -251,10 +247,10 @@ func TestLotsOfRulesMemoryUsage(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Error while matching host %s: %s", testcase.host, err) t.Errorf("Error while matching host %s: %s", testcase.host, err)
} }
if ret.IsFiltered == false && ret.IsFiltered != testcase.match { if !ret.IsFiltered && ret.IsFiltered != testcase.match {
t.Errorf("Expected hostname %s to not match", testcase.host) t.Errorf("Expected hostname %s to not match", testcase.host)
} }
if ret.IsFiltered == true && ret.IsFiltered != testcase.match { if ret.IsFiltered && ret.IsFiltered != testcase.match {
t.Errorf("Expected hostname %s to match", testcase.host) t.Errorf("Expected hostname %s to match", testcase.host)
} }
} }
@ -644,3 +640,32 @@ func BenchmarkSafeSearchParallel(b *testing.B) {
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
goleak.VerifyTestMain(m) goleak.VerifyTestMain(m)
} }
//
// helper functions for debugging and testing
//
func purgeCaches() {
safebrowsingCache.Purge()
parentalCache.Purge()
}
func _Func() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return path.Base(f.Name())
}
func trace(format string, args ...interface{}) {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
var buf strings.Builder
buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name())))
text := fmt.Sprintf(format, args...)
buf.WriteString(text)
if len(text) == 0 || text[len(text)-1] != '\n' {
buf.WriteRune('\n')
}
fmt.Print(buf.String())
}

View File

@ -1,9 +1,6 @@
package dnsfilter package dnsfilter
import ( import (
"fmt"
"path"
"runtime"
"strings" "strings"
"sync/atomic" "sync/atomic"
) )
@ -49,33 +46,9 @@ func updateMax(valuePtr *int64, maxPtr *int64) {
break break
} }
swapped := atomic.CompareAndSwapInt64(maxPtr, max, current) swapped := atomic.CompareAndSwapInt64(maxPtr, max, current)
if swapped == true { if swapped {
break break
} }
// swapping failed because value has changed after reading, try again // swapping failed because value has changed after reading, try again
} }
} }
//
// helper functions for debugging and testing
//
func _Func() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return path.Base(f.Name())
}
func trace(format string, args ...interface{}) {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
var buf strings.Builder
buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name())))
text := fmt.Sprintf(format, args...)
buf.WriteString(text)
if len(text) == 0 || text[len(text)-1] != '\n' {
buf.WriteRune('\n')
}
fmt.Print(buf.String())
}

View File

@ -5,6 +5,8 @@ import (
"errors" "errors"
"io" "io"
"net/http" "net/http"
"path"
"runtime"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -80,7 +82,7 @@ func generateMapFromStats(stats *periodicStats, start int, end int) map[string]i
result := map[string]interface{}{ result := map[string]interface{}{
"dns_queries": getReversedSlice(stats.entries[totalRequests], start, end), "dns_queries": getReversedSlice(stats.entries[totalRequests], start, end),
"blocked_filtering": getReversedSlice(stats.entries[filteredLists], start, end), "blocked_filtering": getReversedSlice(stats.entries[filteredTotal], start, end),
"replaced_safebrowsing": getReversedSlice(stats.entries[filteredSafebrowsing], start, end), "replaced_safebrowsing": getReversedSlice(stats.entries[filteredSafebrowsing], start, end),
"replaced_safesearch": getReversedSlice(stats.entries[filteredSafesearch], start, end), "replaced_safesearch": getReversedSlice(stats.entries[filteredSafesearch], start, end),
"replaced_parental": getReversedSlice(stats.entries[filteredParental], start, end), "replaced_parental": getReversedSlice(stats.entries[filteredParental], start, end),
@ -89,18 +91,6 @@ func generateMapFromStats(stats *periodicStats, start int, end int) map[string]i
return result return result
} }
func produceTop(m map[string]int, top int) map[string]int {
toMarshal := map[string]int{}
topKeys := sortByValue(m)
for i, k := range topKeys {
if i == top {
break
}
toMarshal[k] = m[k]
}
return toMarshal
}
// ------------------------------------- // -------------------------------------
// helper functions for querylog parsing // helper functions for querylog parsing
// ------------------------------------- // -------------------------------------
@ -206,3 +196,13 @@ func parseParametersFromBody(r io.Reader) (map[string]string, error) {
return parameters, nil return parameters, nil
} }
// ---------------------
// debug logging helpers
// ---------------------
func _Func() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return path.Base(f.Name())
}

View File

@ -26,7 +26,6 @@ const (
statsHistoryElements = 60 + 1 // +1 for calculating delta statsHistoryElements = 60 + 1 // +1 for calculating delta
totalRequests = `coredns_dns_request_count_total` totalRequests = `coredns_dns_request_count_total`
filteredTotal = `coredns_dnsfilter_filtered_total` filteredTotal = `coredns_dnsfilter_filtered_total`
filteredLists = `coredns_dnsfilter_filtered_lists_total`
filteredSafebrowsing = `coredns_dnsfilter_filtered_safebrowsing_total` filteredSafebrowsing = `coredns_dnsfilter_filtered_safebrowsing_total`
filteredSafesearch = `coredns_dnsfilter_safesearch_total` filteredSafesearch = `coredns_dnsfilter_safesearch_total`
filteredParental = `coredns_dnsfilter_filtered_parental_total` filteredParental = `coredns_dnsfilter_filtered_parental_total`
@ -124,9 +123,10 @@ func collectStats() {
defer resp.Body.Close() defer resp.Body.Close()
} }
if err != nil { if err != nil {
if isConnRefused(err) == false { if isConnRefused(err) {
log.Printf("Couldn't get coredns metrics: %T %s\n", err, err) return
} }
log.Printf("Couldn't get coredns metrics: %T %s\n", err, err)
return return
} }