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"
"errors"
"net"
"strconv"
"strings"
"unicode"
)
@ -97,3 +98,8 @@ func StringTwoFields(str string) (string, string, bool) {
}
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"`
CacheMaxTTL uint32 `toml:"cache_max_ttl"`
QueryLog QueryLogConfig `toml:"query_log"`
BlockName BlockNameConfig `toml:"block_name"`
BlockName BlockNameConfig `toml:"blacklist"`
ForwardFile string `toml:"forwarding_rules"`
ServersConfig map[string]ServerConfig `toml:"servers"`
SourcesConfig map[string]SourceConfig `toml:"sources"`
@ -67,7 +67,9 @@ type QueryLogConfig 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 {
@ -97,6 +99,7 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
proxy.cacheNegTTL = config.CacheNegTTL
proxy.cacheMinTTL = config.CacheMinTTL
proxy.cacheMaxTTL = config.CacheMaxTTL
if len(config.QueryLog.Format) == 0 {
config.QueryLog.Format = "tsv"
} else {
@ -107,7 +110,19 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
}
proxy.queryLogFile = config.QueryLog.File
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.blockNameFormat = config.BlockName.Format
proxy.blockNameLogFile = config.BlockName.LogFile
proxy.forwardFile = config.ForwardFile
if len(config.ServerNames) == 0 {
for serverName := range config.ServersConfig {

View File

@ -127,11 +127,21 @@ format = "tsv"
## ads*.example.*
## ads*.example[0-9]*.com
[block_name]
[blacklist]
## 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
queryLogFormat string
blockNameFile string
blockNameLogFile string
blockNameFormat string
forwardFile string
pluginsGlobals PluginsGlobals
}

View File

@ -1,9 +1,15 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strings"
"sync"
"time"
"unicode"
"github.com/hashicorp/go-immutable-radix"
@ -22,10 +28,13 @@ const (
)
type PluginBlockName struct {
sync.Mutex
blockedPrefixes *iradix.Tree
blockedSuffixes *iradix.Tree
blockedSubstrings []string
blockedPatterns []string
outFd *os.File
format string
}
func (plugin *PluginBlockName) Name() string {
@ -100,6 +109,17 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
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
}
@ -116,30 +136,66 @@ func (plugin *PluginBlockName) Eval(pluginsState *PluginsState, msg *dns.Msg) er
if len(questions) != 1 {
return nil
}
question := strings.ToLower(StripTrailingDot(questions[0].Name))
revQuestion := StringReverse(question)
match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQuestion))
if found {
if len(match) == len(question) || question[len(match)] == '.' {
pluginsState.action = PluginsActionReject
return nil
qName := strings.ToLower(StripTrailingDot(questions[0].Name))
revQname := StringReverse(qName)
reject, reason := false, ""
if !reject {
match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQname))
if found {
if len(match) == len(qName) || qName[len(match)] == '.' {
reject, reason = true, "*"+string(match)
}
}
}
_, _, found = plugin.blockedPrefixes.Root().LongestPrefix([]byte(question))
if found {
if !reject {
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
return nil
}
for _, substring := range plugin.blockedSubstrings {
if strings.Contains(substring, question) {
pluginsState.action = PluginsActionReject
return nil
}
}
for _, pattern := range plugin.blockedPatterns {
if found, _ := filepath.Match(pattern, question); found {
pluginsState.action = PluginsActionReject
return nil
if plugin.outFd != nil {
var clientIPStr string
if pluginsState.clientProto == "udp" {
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
}
var line string
if plugin.format == "tsv" {
now := time.Now()
year, month, day := now.Date()
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

View File

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