diff --git a/dnscrypt-proxy/config.go b/dnscrypt-proxy/config.go index d79aa6ed..c210fcad 100644 --- a/dnscrypt-proxy/config.go +++ b/dnscrypt-proxy/config.go @@ -29,6 +29,7 @@ type Config struct { CacheMinTTL uint32 `toml:"cache_min_ttl"` CacheMaxTTL uint32 `toml:"cache_max_ttl"` QueryLog QueryLogConfig `toml:"query_log"` + NxLog NxLogConfig `toml:"nx_log"` BlockName BlockNameConfig `toml:"blacklist"` ForwardFile string `toml:"forwarding_rules"` ServersConfig map[string]ServerConfig `toml:"servers"` @@ -73,6 +74,7 @@ type SourceConfig struct { CacheFile string `toml:"cache_file"` FormatStr string `toml:"format"` RefreshDelay int `toml:"refresh_delay"` + Prefix string } type QueryLogConfig struct { @@ -81,6 +83,11 @@ type QueryLogConfig struct { IgnoredQtypes []string `toml:"ignored_qtypes"` } +type NxLogConfig struct { + File string + Format string +} + type BlockNameConfig struct { File string `toml:"blacklist_file"` LogFile string `toml:"log_file"` @@ -144,6 +151,17 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error { proxy.queryLogFormat = config.QueryLog.Format proxy.queryLogIgnoredQtypes = config.QueryLog.IgnoredQtypes + if len(config.NxLog.Format) == 0 { + config.NxLog.Format = "tsv" + } else { + config.NxLog.Format = strings.ToLower(config.NxLog.Format) + } + if config.NxLog.Format != "tsv" && config.NxLog.Format != "ltsv" { + return errors.New("Unsupported NX log format") + } + proxy.nxLogFile = config.NxLog.File + proxy.nxLogFormat = config.NxLog.Format + if len(config.BlockName.Format) == 0 { config.BlockName.Format = "tsv" } else { @@ -166,31 +184,31 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error { requiredProps |= ServerInformalPropertyNoLog } - for sourceName, source := range config.SourcesConfig { - if source.URL == "" { - return fmt.Errorf("Missing URL for source [%s]", sourceName) + for cfgSourceName, cfgSource := range config.SourcesConfig { + if cfgSource.URL == "" { + return fmt.Errorf("Missing URL for source [%s]", cfgSourceName) } - if source.MinisignKeyStr == "" { - return fmt.Errorf("Missing Minisign key for source [%s]", sourceName) + if cfgSource.MinisignKeyStr == "" { + return fmt.Errorf("Missing Minisign key for source [%s]", cfgSourceName) } - if source.CacheFile == "" { - return fmt.Errorf("Missing cache file for source [%s]", sourceName) + if cfgSource.CacheFile == "" { + return fmt.Errorf("Missing cache file for source [%s]", cfgSourceName) } - if source.FormatStr == "" { - return fmt.Errorf("Missing format for source [%s]", sourceName) + if cfgSource.FormatStr == "" { + return fmt.Errorf("Missing format for source [%s]", cfgSourceName) } - if source.RefreshDelay <= 0 { - source.RefreshDelay = 24 + if cfgSource.RefreshDelay <= 0 { + cfgSource.RefreshDelay = 24 } - source, sourceUrlsToPrefetch, err := NewSource(source.URL, source.MinisignKeyStr, source.CacheFile, source.FormatStr, time.Duration(source.RefreshDelay)*time.Hour) + source, sourceUrlsToPrefetch, err := NewSource(cfgSource.URL, cfgSource.MinisignKeyStr, cfgSource.CacheFile, cfgSource.FormatStr, time.Duration(cfgSource.RefreshDelay)*time.Hour) proxy.urlsToPrefetch = append(proxy.urlsToPrefetch, sourceUrlsToPrefetch...) if err != nil { - dlog.Criticalf("Unable use source [%s]: [%s]", sourceName, err) + dlog.Criticalf("Unable use source [%s]: [%s]", cfgSourceName, err) continue } - registeredServers, err := source.Parse() + registeredServers, err := source.Parse(cfgSource.Prefix) if err != nil { - dlog.Criticalf("Unable use source [%s]: [%s]", sourceName, err) + dlog.Criticalf("Unable use source [%s]: [%s]", cfgSourceName, err) continue } for _, registeredServer := range registeredServers { diff --git a/dnscrypt-proxy/dnscrypt-proxy.toml b/dnscrypt-proxy/dnscrypt-proxy.toml index 5ee25caf..dbac7989 100644 --- a/dnscrypt-proxy/dnscrypt-proxy.toml +++ b/dnscrypt-proxy/dnscrypt-proxy.toml @@ -14,7 +14,7 @@ ## List of servers to use ## If this line is commented, all registered servers will be used -# server_names = ['dnscrypt.org-fr'] +# server_names = ['scaleway-fr'] ## List of local addresses and ports to listen to. Can be IPv4 and/or IPv6. @@ -155,6 +155,24 @@ format = 'tsv' # ignored_qtypes = ['DNSKEY', 'NS'] +############################################ +# Suspicious queries logging # +############################################ + +## Log queries for nonexistent zones + +[nx_log] + +## Path to the query log file (absolute, or relative to the same directory as the executable file) + +# file = 'nx.log' + + +## Query log format (currently supported: tsv and ltsv) + +format = 'tsv' + + ###################################################### # Pattern-based blocking (blacklists) # ###################################################### @@ -199,10 +217,11 @@ format = 'tsv' cache_file = 'dnscrypt-resolvers.csv' format = 'v1' refresh_delay = 168 + prefix = '' ## Local, static list of available servers [servers] - [servers.'dnscrypt.org-fr'] + [servers.'scaleway-fr'] stamp = 'dnsc://AQMAAAAAAAAAEjIxMi40Ny4yMjguMTM2OjQ0M09FODAxOkI4NEU6QTYwNjpCRkIwOkJBQzA6Q0U0Mzo0NDVCOkIxNUU6QkE2NDpCMDJGOkEzQzQ6QUEzMTpBRTEwOjYzNkE6MDc5MDozMjREHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc' diff --git a/dnscrypt-proxy/main.go b/dnscrypt-proxy/main.go index fe0eb386..6f94c76c 100644 --- a/dnscrypt-proxy/main.go +++ b/dnscrypt-proxy/main.go @@ -39,6 +39,8 @@ type Proxy struct { queryLogFile string queryLogFormat string queryLogIgnoredQtypes []string + nxLogFile string + nxLogFormat string blockNameFile string blockNameLogFile string blockNameFormat string diff --git a/dnscrypt-proxy/plugin_nx_log.go b/dnscrypt-proxy/plugin_nx_log.go new file mode 100644 index 00000000..7c694e34 --- /dev/null +++ b/dnscrypt-proxy/plugin_nx_log.go @@ -0,0 +1,93 @@ +package main + +import ( + "errors" + "fmt" + "net" + "os" + "sync" + "time" + + "github.com/jedisct1/dlog" + "github.com/miekg/dns" +) + +type PluginNxLog struct { + sync.Mutex + outFd *os.File + format string + ignoredQtypes []string +} + +func (plugin *PluginNxLog) Name() string { + return "nx_log" +} + +func (plugin *PluginNxLog) Description() string { + return "Log DNS queries for nonexistent zones." +} + +func (plugin *PluginNxLog) Init(proxy *Proxy) error { + plugin.Lock() + defer plugin.Unlock() + outFd, err := os.OpenFile(proxy.nxLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + return err + } + plugin.outFd = outFd + plugin.format = proxy.nxLogFormat + + return nil +} + +func (plugin *PluginNxLog) Drop() error { + return nil +} + +func (plugin *PluginNxLog) Reload() error { + return nil +} + +func (plugin *PluginNxLog) Eval(pluginsState *PluginsState, msg *dns.Msg) error { + if msg.Rcode != dns.RcodeNameError { + return nil + } + questions := msg.Question + if len(questions) == 0 { + return nil + } + question := questions[0] + qType, ok := dns.TypeToString[question.Qtype] + if !ok { + qType = string(qType) + } + var clientIPStr string + if pluginsState.clientProto == "udp" { + clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String() + } else { + clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String() + } + qName := StripTrailingDot(question.Name) + + 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), qType) + } else if plugin.format == "ltsv" { + line = fmt.Sprintf("time:%d\thost:%s\tmessage:%s\ttype:%s\n", + time.Now().Unix(), clientIPStr, StringQuote(qName), qType) + } 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 +} diff --git a/dnscrypt-proxy/plugins.go b/dnscrypt-proxy/plugins.go index 09433e81..f3a3b79b 100644 --- a/dnscrypt-proxy/plugins.go +++ b/dnscrypt-proxy/plugins.go @@ -58,10 +58,14 @@ func InitPluginsGlobals(pluginsGlobals *PluginsGlobals, proxy *Proxy) error { if len(proxy.forwardFile) != 0 { *queryPlugins = append(*queryPlugins, Plugin(new(PluginForward))) } + responsePlugins := &[]Plugin{} if proxy.cache { *responsePlugins = append(*responsePlugins, Plugin(new(PluginCacheResponse))) } + if len(proxy.nxLogFile) != 0 { + *responsePlugins = append(*responsePlugins, Plugin(new(PluginNxLog))) + } for _, plugin := range *queryPlugins { if err := plugin.Init(proxy); err != nil { diff --git a/dnscrypt-proxy/sources.go b/dnscrypt-proxy/sources.go index e4b5745c..3a0d9a03 100644 --- a/dnscrypt-proxy/sources.go +++ b/dnscrypt-proxy/sources.go @@ -151,7 +151,7 @@ func NewSource(url string, minisignKeyStr string, cacheFile string, formatStr st return source, urlsToPrefetch, nil } -func (source *Source) Parse() ([]RegisteredServer, error) { +func (source *Source) Parse(prefix string) ([]RegisteredServer, error) { var registeredServers []RegisteredServer csvReader := csv.NewReader(strings.NewReader(source.in)) @@ -169,7 +169,7 @@ func (source *Source) Parse() ([]RegisteredServer, error) { if lineNo == 0 { continue } - name := record[0] + name := prefix + record[0] serverAddrStr := record[10] providerName := record[11] serverPkStr := record[12]