Add the ability to log blocked queries

This commit is contained in:
Frank Denis 2018-01-17 17:03:42 +01:00
parent 9f8bce28a4
commit b9c43c8ef3
6 changed files with 117 additions and 32 deletions

View File

@ -4,6 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"net" "net"
"strconv"
"strings" "strings"
"unicode" "unicode"
) )
@ -97,3 +98,8 @@ func StringTwoFields(str string) (string, string, bool) {
} }
return a, b, true return a, b, true
} }
func StringQuote(str string) string {
str = strconv.QuoteToGraphic(str)
return str[1 : len(str)-1]
}

View File

@ -25,7 +25,7 @@ type Config struct {
CacheMinTTL uint32 `toml:"cache_min_ttl"` CacheMinTTL uint32 `toml:"cache_min_ttl"`
CacheMaxTTL uint32 `toml:"cache_max_ttl"` CacheMaxTTL uint32 `toml:"cache_max_ttl"`
QueryLog QueryLogConfig `toml:"query_log"` QueryLog QueryLogConfig `toml:"query_log"`
BlockName BlockNameConfig `toml:"block_name"` BlockName BlockNameConfig `toml:"blacklist"`
ForwardFile string `toml:"forwarding_rules"` ForwardFile string `toml:"forwarding_rules"`
ServersConfig map[string]ServerConfig `toml:"servers"` ServersConfig map[string]ServerConfig `toml:"servers"`
SourcesConfig map[string]SourceConfig `toml:"sources"` SourcesConfig map[string]SourceConfig `toml:"sources"`
@ -67,7 +67,9 @@ type QueryLogConfig struct {
} }
type BlockNameConfig struct { type BlockNameConfig struct {
File string File string `toml:"blacklist_file"`
LogFile string `toml:"log_file"`
Format string `toml:"log_format"`
} }
func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error { func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
@ -97,6 +99,7 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
proxy.cacheNegTTL = config.CacheNegTTL proxy.cacheNegTTL = config.CacheNegTTL
proxy.cacheMinTTL = config.CacheMinTTL proxy.cacheMinTTL = config.CacheMinTTL
proxy.cacheMaxTTL = config.CacheMaxTTL proxy.cacheMaxTTL = config.CacheMaxTTL
if len(config.QueryLog.Format) == 0 { if len(config.QueryLog.Format) == 0 {
config.QueryLog.Format = "tsv" config.QueryLog.Format = "tsv"
} else { } else {
@ -107,7 +110,19 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
} }
proxy.queryLogFile = config.QueryLog.File proxy.queryLogFile = config.QueryLog.File
proxy.queryLogFormat = config.QueryLog.Format proxy.queryLogFormat = config.QueryLog.Format
if len(config.BlockName.Format) == 0 {
config.BlockName.Format = "tsv"
} else {
config.BlockName.Format = strings.ToLower(config.BlockName.Format)
}
if config.BlockName.Format != "tsv" && config.BlockName.Format != "ltsv" {
return errors.New("Unsupported block log format")
}
proxy.blockNameFile = config.BlockName.File proxy.blockNameFile = config.BlockName.File
proxy.blockNameFormat = config.BlockName.Format
proxy.blockNameLogFile = config.BlockName.LogFile
proxy.forwardFile = config.ForwardFile proxy.forwardFile = config.ForwardFile
if len(config.ServerNames) == 0 { if len(config.ServerNames) == 0 {
for serverName := range config.ServersConfig { for serverName := range config.ServersConfig {

View File

@ -127,11 +127,21 @@ format = "tsv"
## ads*.example.* ## ads*.example.*
## ads*.example[0-9]*.com ## ads*.example[0-9]*.com
[block_name] [blacklist]
## Full path to the file of blocking rules ## Full path to the file of blocking rules
# file = "blacklist.txt" # blacklist_file = "blacklist.txt"
## Optional path to a file logging blocked queries
# log_file = "blocked.log"
## Optional log format: tsv or ltsv (default: tsv)
# log_format = "tsv"

View File

@ -35,6 +35,8 @@ type Proxy struct {
queryLogFile string queryLogFile string
queryLogFormat string queryLogFormat string
blockNameFile string blockNameFile string
blockNameLogFile string
blockNameFormat string
forwardFile string forwardFile string
pluginsGlobals PluginsGlobals pluginsGlobals PluginsGlobals
} }

View File

@ -1,9 +1,15 @@
package main package main
import ( import (
"errors"
"fmt"
"io/ioutil" "io/ioutil"
"net"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"time"
"unicode" "unicode"
"github.com/hashicorp/go-immutable-radix" "github.com/hashicorp/go-immutable-radix"
@ -22,10 +28,13 @@ const (
) )
type PluginBlockName struct { type PluginBlockName struct {
sync.Mutex
blockedPrefixes *iradix.Tree blockedPrefixes *iradix.Tree
blockedSuffixes *iradix.Tree blockedSuffixes *iradix.Tree
blockedSubstrings []string blockedSubstrings []string
blockedPatterns []string blockedPatterns []string
outFd *os.File
format string
} }
func (plugin *PluginBlockName) Name() string { func (plugin *PluginBlockName) Name() string {
@ -100,6 +109,17 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
dlog.Fatal("Unexpected block type") dlog.Fatal("Unexpected block type")
} }
} }
if len(proxy.blockNameLogFile) == 0 {
return nil
}
plugin.Lock()
defer plugin.Unlock()
outFd, err := os.OpenFile(proxy.blockNameLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return err
}
plugin.outFd = outFd
plugin.format = proxy.blockNameFormat
return nil return nil
} }
@ -116,30 +136,66 @@ func (plugin *PluginBlockName) Eval(pluginsState *PluginsState, msg *dns.Msg) er
if len(questions) != 1 { if len(questions) != 1 {
return nil return nil
} }
question := strings.ToLower(StripTrailingDot(questions[0].Name)) qName := strings.ToLower(StripTrailingDot(questions[0].Name))
revQuestion := StringReverse(question) revQname := StringReverse(qName)
match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQuestion)) reject, reason := false, ""
if found { if !reject {
if len(match) == len(question) || question[len(match)] == '.' { match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQname))
pluginsState.action = PluginsActionReject if found {
return nil if len(match) == len(qName) || qName[len(match)] == '.' {
reject, reason = true, "*"+string(match)
}
} }
} }
_, _, found = plugin.blockedPrefixes.Root().LongestPrefix([]byte(question)) if !reject {
if found { match, _, found := plugin.blockedPrefixes.Root().LongestPrefix([]byte(qName))
if found {
reject, reason = true, string(match)+"*"
}
}
if !reject {
for _, substring := range plugin.blockedSubstrings {
if strings.Contains(substring, qName) {
reject, reason = true, "*"+substring+"*"
break
}
}
}
if !reject {
for _, pattern := range plugin.blockedPatterns {
if found, _ := filepath.Match(pattern, qName); found {
reject, reason = true, pattern
break
}
}
}
if reject {
pluginsState.action = PluginsActionReject pluginsState.action = PluginsActionReject
return nil if plugin.outFd != nil {
} var clientIPStr string
for _, substring := range plugin.blockedSubstrings { if pluginsState.clientProto == "udp" {
if strings.Contains(substring, question) { clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
pluginsState.action = PluginsActionReject } else {
return nil clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
} }
} var line string
for _, pattern := range plugin.blockedPatterns { if plugin.format == "tsv" {
if found, _ := filepath.Match(pattern, question); found { now := time.Now()
pluginsState.action = PluginsActionReject year, month, day := now.Date()
return nil hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), StringQuote(reason))
} else if plugin.format == "ltsv" {
line = fmt.Sprintf("time:%d\thost:%s\tqname:%s\tmessage:%s\n", time.Now().Unix(), clientIPStr, StringQuote(qName), StringQuote(reason))
} else {
dlog.Fatalf("Unexpected log format: [%s]", plugin.format)
}
plugin.Lock()
if plugin.outFd == nil {
return errors.New("Log file not initialized")
}
plugin.outFd.WriteString(line)
defer plugin.Unlock()
} }
} }
return nil return nil

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
@ -60,10 +59,7 @@ func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) err
} else { } else {
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String() clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
} }
qName := question.Name qName := StripTrailingDot(question.Name)
if len(qName) > 1 && strings.HasSuffix(qName, ".") {
qName = qName[0 : len(qName)-1]
}
qType, ok := dns.TypeToString[question.Qtype] qType, ok := dns.TypeToString[question.Qtype]
if !ok { if !ok {
qType = string(qType) qType = string(qType)
@ -74,10 +70,10 @@ func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) err
year, month, day := now.Date() year, month, day := now.Date()
hour, minute, second := now.Clock() hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second) tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\n", tsStr, clientIPStr, qName, qType) line = fmt.Sprintf("%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), qType)
} else if plugin.format == "ltsv" { } else if plugin.format == "ltsv" {
line = fmt.Sprintf("time:%d\thost:%s\tmessage:%s\ttype:%s\n", line = fmt.Sprintf("time:%d\thost:%s\tmessage:%s\ttype:%s\n",
time.Now().Unix(), clientIPStr, qName, qType) time.Now().Unix(), clientIPStr, StringQuote(qName), qType)
} else { } else {
dlog.Fatalf("Unexpected log format: [%s]", plugin.format) dlog.Fatalf("Unexpected log format: [%s]", plugin.format)
} }