filtering of queries (#450) (#474)

This commit is contained in:
Dimitri Herzog 2022-04-01 08:58:09 +02:00 committed by GitHub
parent 5427c1697c
commit 28789ee7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 347 additions and 169 deletions

View File

@ -58,7 +58,11 @@ func init() {
}
func initConfig() {
config.LoadConfig(configPath, false)
err := config.LoadConfig(configPath, false)
if err != nil {
util.FatalOnError("unable to load configuration: ", err)
}
log.ConfigureLogger(config.GetConfig().LogLevel, config.GetConfig().LogFormat, config.GetConfig().LogTimestamp)
if len(config.GetConfig().HTTPPorts) != 0 {

View File

@ -33,7 +33,11 @@ func newServeCommand() *cobra.Command {
func startServer(_ *cobra.Command, _ []string) {
printBanner()
config.LoadConfig(configPath, true)
err := config.LoadConfig(configPath, true)
if err != nil {
util.FatalOnError("unable to load configuration: ", err)
}
log.ConfigureLogger(config.GetConfig().LogLevel, config.GetConfig().LogFormat, config.GetConfig().LogTimestamp)
configureHTTPClient(config.GetConfig())

View File

@ -8,11 +8,15 @@ import (
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/miekg/dns"
"github.com/hako/durafmt"
"github.com/hashicorp/go-multierror"
"github.com/0xERR0R/blocky/log"
"github.com/creasty/defaults"
@ -36,6 +40,12 @@ type NetProtocol uint16
// )
type QueryLogType int16
type QType dns.Type
func (c QType) String() string {
return dns.TypeToString[uint16(c)]
}
type Duration time.Duration
func (c *Duration) String() string {
@ -170,6 +180,30 @@ func (c *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
func (c *QType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var input string
if err := unmarshal(&input); err != nil {
return err
}
t, found := dns.StringToType[input]
if !found {
types := make([]string, 0, len(dns.StringToType))
for k := range dns.StringToType {
types = append(types, k)
}
sort.Strings(types)
return fmt.Errorf("unknown DNS query type: '%s'. Please use following types '%s'",
input, strings.Join(types, ", "))
}
*c = QType(t)
return nil
}
var validDomain = regexp.MustCompile(
`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
@ -270,11 +304,13 @@ type Config struct {
HTTPPorts ListenConfig `yaml:"httpPort"`
HTTPSPorts ListenConfig `yaml:"httpsPort"`
TLSPorts ListenConfig `yaml:"tlsPort"`
DisableIPv6 bool `yaml:"disableIPv6" default:"false"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
BootstrapDNS Upstream `yaml:"bootstrapDns"`
HostsFile HostsFileConfig `yaml:"hostsFile"`
// Deprecated
DisableIPv6 bool `yaml:"disableIPv6" default:"false"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
BootstrapDNS Upstream `yaml:"bootstrapDns"`
HostsFile HostsFileConfig `yaml:"hostsFile"`
Filtering FilteringConfig `yaml:"filtering"`
}
// PrometheusConfig contains the config values for prometheus
@ -375,14 +411,18 @@ type HostsFileConfig struct {
RefreshPeriod Duration `yaml:"refreshPeriod" default:"1h"`
}
type FilteringConfig struct {
QueryTypes []QType `yaml:"queryTypes"`
}
// nolint:gochecknoglobals
var config = &Config{}
// LoadConfig creates new config from YAML file
func LoadConfig(path string, mandatory bool) {
func LoadConfig(path string, mandatory bool) error {
cfg := Config{}
if err := defaults.Set(&cfg); err != nil {
log.Log().Fatal("Can't apply default values: ", err)
return fmt.Errorf("can't apply default values: %w", err)
}
data, err := ioutil.ReadFile(path)
@ -392,34 +432,47 @@ func LoadConfig(path string, mandatory bool) {
// config file does not exist
// return config with default values
config = &cfg
return
return nil
}
log.Log().Fatal("Can't read config file: ", err)
return fmt.Errorf("can't read config file: %w", err)
}
unmarshalConfig(data, cfg)
return unmarshalConfig(data, cfg)
}
func unmarshalConfig(data []byte, cfg Config) {
func unmarshalConfig(data []byte, cfg Config) error {
err := yaml.UnmarshalStrict(data, &cfg)
if err != nil {
log.Log().Fatal("wrong file structure: ", err)
return fmt.Errorf("wrong file structure: %w", err)
}
validateConfig(&cfg)
err = validateConfig(&cfg)
if err != nil {
return fmt.Errorf("unable to validate config: %w", err)
}
config = &cfg
return nil
}
func validateConfig(cfg *Config) {
func validateConfig(cfg *Config) (err error) {
if len(cfg.TLSPorts) != 0 && (cfg.CertFile == "" || cfg.KeyFile == "") {
log.Log().Fatal("certFile and keyFile parameters are mandatory for TLS")
err = multierror.Append(err, errors.New("'certFile' and 'keyFile' parameters are mandatory for TLS"))
}
if len(cfg.HTTPSPorts) != 0 && (cfg.CertFile == "" || cfg.KeyFile == "") {
log.Log().Fatal("certFile and keyFile parameters are mandatory for HTTPS")
err = multierror.Append(err, errors.New("'certFile' and 'keyFile' parameters are mandatory for HTTPS"))
}
if cfg.DisableIPv6 {
log.Log().Warnf("'disableIPv6' is deprecated. Please use 'filtering.queryTypes' with 'AAAA' instead.")
cfg.Filtering.QueryTypes = append(cfg.Filtering.QueryTypes, QType(dns.TypeAAAA))
}
return
}
// GetConfig returns the current config

View File

@ -7,7 +7,7 @@ import (
"os"
"time"
"github.com/0xERR0R/blocky/helpertest"
"github.com/miekg/dns"
. "github.com/0xERR0R/blocky/log"
. "github.com/onsi/ginkgo/v2"
@ -21,7 +21,8 @@ var _ = Describe("Config", func() {
err := os.Chdir("../testdata")
Expect(err).Should(Succeed())
LoadConfig("config.yml", true)
err = LoadConfig("config.yml", true)
Expect(err).Should(Succeed())
Expect(config.DNSPorts).Should(Equal(ListenConfig{"55553", ":55554", "[::1]:55555"}))
Expect(config.Upstream.ExternalResolvers["default"]).Should(HaveLen(3))
@ -44,6 +45,7 @@ var _ = Describe("Config", func() {
Expect(config.Blocking.ClientGroupsBlock).Should(HaveLen(2))
Expect(config.Blocking.BlockTTL).Should(Equal(Duration(time.Minute)))
Expect(config.Blocking.RefreshPeriod).Should(Equal(Duration(2 * time.Hour)))
Expect(config.Filtering.QueryTypes).Should(HaveLen(2))
Expect(config.Caching.MaxCachingTime).Should(Equal(Duration(0)))
Expect(config.Caching.MinCachingTime).Should(Equal(Duration(0)))
@ -53,7 +55,7 @@ var _ = Describe("Config", func() {
})
})
When("config file is malformed", func() {
It("should log with fatal and exit", func() {
It("should return error", func() {
dir, err := ioutil.TempDir("", "blocky")
defer os.Remove(dir)
@ -63,48 +65,48 @@ var _ = Describe("Config", func() {
err = ioutil.WriteFile("config.yml", []byte("malformed_config"), 0600)
Expect(err).Should(Succeed())
helpertest.ShouldLogFatal(func() {
LoadConfig("config.yml", true)
})
err = LoadConfig("config.yml", true)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("wrong file structure"))
})
})
When("duration is in wrong format", func() {
It("should log with fatal and exit", func() {
It("should return error", func() {
cfg := Config{}
data :=
`blocking:
refreshPeriod: wrongduration`
helpertest.ShouldLogFatal(func() {
unmarshalConfig([]byte(data), cfg)
})
err := unmarshalConfig([]byte(data), cfg)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("invalid duration \"wrongduration\""))
})
})
When("CustomDNS hast wrong IP defined", func() {
It("should log with fatal and exit", func() {
It("should return error", func() {
cfg := Config{}
data :=
`customDNS:
mapping:
someDomain: 192.168.178.WRONG`
helpertest.ShouldLogFatal(func() {
unmarshalConfig([]byte(data), cfg)
})
err := unmarshalConfig([]byte(data), cfg)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("invalid IP address '192.168.178.WRONG'"))
})
})
When("Conditional mapping hast wrong defined upstreams", func() {
It("should log with fatal and exit", func() {
It("should return error", func() {
cfg := Config{}
data :=
`conditional:
mapping:
multiple.resolvers: 192.168.178.1,wrongprotocol:4.4.4.4:53`
helpertest.ShouldLogFatal(func() {
unmarshalConfig([]byte(data), cfg)
})
err := unmarshalConfig([]byte(data), cfg)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("wrong host name 'wrongprotocol:4.4.4.4:53'"))
})
})
When("Wrong upstreams are defined", func() {
It("should log with fatal and exit", func() {
It("should return error", func() {
cfg := Config{}
data :=
`upstream:
@ -112,20 +114,31 @@ var _ = Describe("Config", func() {
- 8.8.8.8
- wrongprotocol:8.8.4.4
- 1.1.1.1`
helpertest.ShouldLogFatal(func() {
unmarshalConfig([]byte(data), cfg)
})
err := unmarshalConfig([]byte(data), cfg)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("can't convert upstream 'wrongprotocol:8.8.4.4'"))
})
})
When("config is not YAML", func() {
It("should log with fatal and exit", func() {
It("should return error", func() {
cfg := Config{}
data :=
`///`
helpertest.ShouldLogFatal(func() {
unmarshalConfig([]byte(data), cfg)
})
err := unmarshalConfig([]byte(data), cfg)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("cannot unmarshal !!str `///`"))
})
})
When("Validation fails", func() {
It("should return error", func() {
cfg := Config{}
data :=
`httpsPort: 443`
err := unmarshalConfig([]byte(data), cfg)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("'certFile' and 'keyFile' parameters are mandatory for HTTPS"))
})
})
@ -136,9 +149,9 @@ var _ = Describe("Config", func() {
c := &Config{
TLSPorts: ListenConfig{"953"},
}
helpertest.ShouldLogFatal(func() {
validateConfig(c)
})
err := validateConfig(c)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("'certFile' and 'keyFile' parameters are mandatory for TLS"))
})
By("certFile/keyFile set", func() {
@ -147,11 +160,24 @@ var _ = Describe("Config", func() {
KeyFile: "key",
CertFile: "cert",
}
validateConfig(c)
err := validateConfig(c)
Expect(err).Should(Succeed())
})
})
})
When("Deprecated parameter 'disableIPv6' is set", func() {
It("should add 'AAAA' to filter.queryTypes", func() {
c := &Config{
DisableIPv6: true,
}
err := validateConfig(c)
Expect(err).Should(Succeed())
Expect(c.Filtering.QueryTypes).Should(ContainElements(QType(dns.TypeAAAA)))
})
})
When("HttpsPort is defined", func() {
It("certFile/keyFile must be set", func() {
@ -159,9 +185,9 @@ var _ = Describe("Config", func() {
c := &Config{
HTTPSPorts: ListenConfig{"443"},
}
helpertest.ShouldLogFatal(func() {
validateConfig(c)
})
err := validateConfig(c)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("'certFile' and 'keyFile' parameters are mandatory for HTTPS"))
})
By("certFile/keyFile set", func() {
@ -170,32 +196,30 @@ var _ = Describe("Config", func() {
KeyFile: "key",
CertFile: "cert",
}
validateConfig(c)
err := validateConfig(c)
Expect(err).Should(Succeed())
})
})
})
When("config directory does not exist", func() {
It("should log with fatal and exit if config is mandatory", func() {
It("should return error", func() {
err := os.Chdir("../..")
Expect(err).Should(Succeed())
defer func() { Log().ExitFunc = nil }()
err = LoadConfig("config.yml", true)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("no such file or directory"))
var fatal bool
Log().ExitFunc = func(int) { fatal = true }
LoadConfig("config.yml", true)
Expect(fatal).Should(BeTrue())
})
It("should use default config if config is not mandatory", func() {
err := os.Chdir("../..")
Expect(err).Should(Succeed())
LoadConfig("config.yml", false)
err = LoadConfig("config.yml", false)
Expect(err).Should(Succeed())
Expect(config.LogLevel).Should(Equal(LevelInfo))
})
})
@ -318,6 +342,35 @@ var _ = Describe("Config", func() {
Expect(err).Should(MatchError("some err"))
})
})
Context("QueryTyoe", func() {
It("Should parse existing DNS type as string", func() {
t := QType(0)
err := t.UnmarshalYAML(func(i interface{}) error {
*i.(*string) = "AAAA"
return nil
})
Expect(err).Should(Succeed())
Expect(t).Should(Equal(QType(dns.TypeAAAA)))
Expect(t.String()).Should(Equal("AAAA"))
})
It("should fail if DNS type does not exist", func() {
t := QType(0)
err := t.UnmarshalYAML(func(i interface{}) error {
*i.(*string) = "WRONGTYPE"
return nil
})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("unknown DNS query type: 'WRONGTYPE'"))
})
It("should fail if wrong YAML format", func() {
d := QType(0)
err := d.UnmarshalYAML(func(i interface{}) error {
return errors.New("some err")
})
Expect(err).Should(HaveOccurred())
Expect(err).Should(MatchError("some err"))
})
})
})
DescribeTable("parse upstream string",

View File

@ -190,8 +190,12 @@ httpPort: 4000
#keyFile: server.key
# optional: use this DNS server to resolve blacklist urls and upstream DNS servers. Useful if no DNS resolver is configured and blocky needs to resolve a host name. Format net:IP:port, net must be udp or tcp
bootstrapDns: tcp:1.1.1.1
# optional: Drop all AAAA query if set to true. Default: false
disableIPv6: false
filtering:
# optional: drop all queries with following query types. Default: empty
queryTypes:
- AAAA
# optional: if path defined, use this file for query resolution (A, AAAA and rDNS). Default: empty
hostsFile:
# optional: Path to hosts file (e.g. /etc/hosts on Linux)

View File

@ -20,7 +20,6 @@ configuration properties as [JSON](config.yml).
| certFile | path | yes, if httpsPort > 0 | | Path to cert and key file for SSL encryption (DoH and DoT) |
| keyFile | path | yes, if httpsPort > 0 | | Path to cert and key file for SSL encryption (DoH and DoT)
| bootstrapDns | IP:port | no | | Use this DNS server to resolve blacklist urls and upstream DNS servers. Useful if no DNS resolver is configured and blocky needs to resolve a host name. NOTE: Works only on Linux/*Nix OS due to golang limitations under windows. |
| disableIPv6 | bool | no | false | Drop all AAAA query if set to true |
| logLevel | enum (debug, info, warn, error) | no | info | Log level |
| logFormat | enum (text, json) | no | text | Log format (text or json). |
| logTimestamp | bool | no | true | Log time stamps (true or false). |
@ -109,6 +108,21 @@ value by setting the `upstreamTimeout` configuration parameter (in **duration fo
upstreamTimeout: 5s
```
## Filtering
Under certain circumstances, it may be useful to filter some types of DNS queries. You can define one or more DNS query
types, all queries with these types will be dropped (empty answer will be returned).
!!! example
```yaml
filtering:
queryTypes:
- AAAA
```
This configuration will drop all 'AAAA' (IPv6) queries.
## Custom DNS
You can define your own domain name to IP mappings. For example, you can use a user-friendly name for a network printer

View File

@ -16,6 +16,7 @@ import (
// CONDITIONAL // the query was resolved by the conditional upstream resolver
// CUSTOMDNS // the query was resolved by a custom rule
// HOSTSFILE // the query was resolved by looking up the hosts file
// FILTERED // the query was filtered by query type
// )
type ResponseType int

View File

@ -95,9 +95,12 @@ const (
// ResponseTypeHOSTSFILE is a ResponseType of type HOSTSFILE.
// the query was resolved by looking up the hosts file
ResponseTypeHOSTSFILE
// ResponseTypeFILTERED is a ResponseType of type FILTERED.
// the query was filtered by query type
ResponseTypeFILTERED
)
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILE"
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILEFILTERED"
var _ResponseTypeNames = []string{
_ResponseTypeName[0:8],
@ -106,6 +109,7 @@ var _ResponseTypeNames = []string{
_ResponseTypeName[21:32],
_ResponseTypeName[32:41],
_ResponseTypeName[41:50],
_ResponseTypeName[50:58],
}
// ResponseTypeNames returns a list of possible string values of ResponseType.
@ -122,6 +126,7 @@ var _ResponseTypeMap = map[ResponseType]string{
ResponseTypeCONDITIONAL: _ResponseTypeName[21:32],
ResponseTypeCUSTOMDNS: _ResponseTypeName[32:41],
ResponseTypeHOSTSFILE: _ResponseTypeName[41:50],
ResponseTypeFILTERED: _ResponseTypeName[50:58],
}
// String implements the Stringer interface.
@ -139,6 +144,7 @@ var _ResponseTypeValue = map[string]ResponseType{
_ResponseTypeName[21:32]: ResponseTypeCONDITIONAL,
_ResponseTypeName[32:41]: ResponseTypeCUSTOMDNS,
_ResponseTypeName[41:50]: ResponseTypeHOSTSFILE,
_ResponseTypeName[50:58]: ResponseTypeFILTERED,
}
// ParseResponseType attempts to convert a string to a ResponseType.

View File

@ -0,0 +1,57 @@
package resolver
import (
"fmt"
"sort"
"strings"
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/model"
"github.com/miekg/dns"
)
// FilteringResolver filters DNS queries (for example can drop all AAAA query)
// returns empty ANSWER with NOERROR
type FilteringResolver struct {
NextResolver
queryTypes map[config.QType]bool
}
func (r *FilteringResolver) Resolve(request *model.Request) (*model.Response, error) {
qType := request.Req.Question[0].Qtype
if _, found := r.queryTypes[config.QType(qType)]; found {
response := new(dns.Msg)
response.SetRcode(request.Req, dns.RcodeSuccess)
return &model.Response{Res: response, RType: model.ResponseTypeFILTERED}, nil
}
return r.next.Resolve(request)
}
func (r *FilteringResolver) Configuration() (result []string) {
qTypes := make([]string, len(r.queryTypes))
ix := 0
for qType := range r.queryTypes {
qTypes[ix] = qType.String()
ix++
}
sort.Strings(qTypes)
result = append(result, fmt.Sprintf("filtering query Types: '%v'", strings.Join(qTypes, ", ")))
return
}
func NewFilteringResolver(cfg config.FilteringConfig) ChainedResolver {
queryTypes := make(map[config.QType]bool, len(cfg.QueryTypes))
for _, queryType := range cfg.QueryTypes {
queryTypes[queryType] = true
}
return &FilteringResolver{
queryTypes: queryTypes,
}
}

View File

@ -0,0 +1,81 @@
package resolver
import (
"github.com/0xERR0R/blocky/config"
. "github.com/0xERR0R/blocky/model"
"github.com/miekg/dns"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"
)
var _ = Describe("FilteringResolver", func() {
var (
sut *FilteringResolver
sutConfig config.FilteringConfig
m *MockResolver
mockAnswer *dns.Msg
)
BeforeEach(func() {
mockAnswer = new(dns.Msg)
})
JustBeforeEach(func() {
sut = NewFilteringResolver(sutConfig).(*FilteringResolver)
m = &MockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil)
sut.Next(m)
})
When("Filtering query types are defined", func() {
BeforeEach(func() {
sutConfig = config.FilteringConfig{
QueryTypes: []config.QType{config.QType(dns.TypeAAAA), config.QType(dns.TypeMX)},
}
})
It("Should delegate to next resolver if request query has other type", func() {
resp, err := sut.Resolve(newRequest("example.com", dns.TypeA))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeEmpty())
// delegated to next resolver
Expect(m.Calls).Should(HaveLen(1))
})
It("Should return empty answer for defined query type", func() {
resp, err := sut.Resolve(newRequest("example.com", dns.TypeAAAA))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeFILTERED))
Expect(resp.Res.Answer).Should(BeEmpty())
// no call of next resolver
Expect(m.Calls).Should(BeZero())
})
It("Configure should output all query types", func() {
c := sut.Configuration()
Expect(c).Should(HaveLen(1))
Expect(c[0]).Should(Equal("filtering query Types: 'AAAA, MX'"))
})
})
When("No filtering query types are defined", func() {
BeforeEach(func() {
sutConfig = config.FilteringConfig{}
})
It("Should return empty answer without error", func() {
resp, err := sut.Resolve(newRequest("example.com", dns.TypeAAAA))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(HaveLen(0))
})
It("Configure should output 'empty list'", func() {
c := sut.Configuration()
Expect(c).Should(HaveLen(1))
Expect(c[0]).Should(Equal("filtering query Types: ''"))
})
})
})

View File

@ -1,37 +0,0 @@
package resolver
import (
"github.com/0xERR0R/blocky/model"
"github.com/miekg/dns"
)
// IPv6DisablingResolver can drop all AAAA query (empty ANSWER with NOERROR)
type IPv6DisablingResolver struct {
NextResolver
disableAAAA bool
}
func (r *IPv6DisablingResolver) Resolve(request *model.Request) (*model.Response, error) {
if r.disableAAAA && request.Req.Question[0].Qtype == dns.TypeAAAA {
response := new(dns.Msg)
response.SetRcode(request.Req, dns.RcodeSuccess)
return &model.Response{Res: response, RType: model.ResponseTypeRESOLVED}, nil
}
return r.next.Resolve(request)
}
func (r *IPv6DisablingResolver) Configuration() (result []string) {
if r.disableAAAA {
result = append(result, "drop AAAA")
} else {
result = append(result, "accept AAAA")
}
return
}
func NewIPv6Checker(disable bool) ChainedResolver {
return &IPv6DisablingResolver{disableAAAA: disable}
}

View File

@ -1,66 +0,0 @@
package resolver
import (
"github.com/0xERR0R/blocky/util"
. "github.com/0xERR0R/blocky/model"
"github.com/miekg/dns"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"
)
var _ = Describe("IPv6DisablingResolver", func() {
var (
sut *IPv6DisablingResolver
m *MockResolver
mockAnswer *dns.Msg
disableIPv6 *bool
query = newRequest("example.com", dns.TypeAAAA)
)
JustBeforeEach(func() {
mockAnswer, _ = util.NewMsgWithAnswer("example.com.", 1230, dns.TypeAAAA, "2001:0db8:85a3:08d3:1319:8a2e:0370:7344")
sut = NewIPv6Checker(*disableIPv6).(*IPv6DisablingResolver)
m = &MockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer, Reason: "reason"}, nil)
sut.Next(m)
})
When("Configure IPv6 enabled", func() {
BeforeEach(func() {
b := false
disableIPv6 = &b
})
It("Should return one AAAA answer", func() {
resp, err := sut.Resolve(query)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.Res.Answer).Should(HaveLen(1))
})
It("Configure should output 'accept'", func() {
c := sut.Configuration()
Expect(c).Should(HaveLen(1))
Expect(c[0]).Should(ContainSubstring("accept"))
})
})
When("Configure IPv6 disabled", func() {
BeforeEach(func() {
b := true
disableIPv6 = &b
})
It("Should return empty answer without error", func() {
resp, err := sut.Resolve(query)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.Res.Answer).Should(HaveLen(0))
})
It("Configure should output 'drop'", func() {
c := sut.Configuration()
Expect(c).Should(HaveLen(1))
Expect(c[0]).Should(ContainSubstring("drop"))
})
})
})

View File

@ -194,7 +194,7 @@ func createQueryResolver(cfg *config.Config, redisClient *redis.Client) (resolve
br, brErr := resolver.NewBlockingResolver(cfg.Blocking, redisClient)
return resolver.Chain(
resolver.NewIPv6Checker(cfg.DisableIPv6),
resolver.NewFilteringResolver(cfg.Filtering),
resolver.NewClientNamesResolver(cfg.ClientLookup),
resolver.NewQueryLoggingResolver(cfg.QueryLog),
resolver.NewMetricsResolver(cfg.Prometheus),

4
testdata/config.yml vendored
View File

@ -11,6 +11,10 @@ conditional:
mapping:
fritz.box: tcp+udp:192.168.178.1
multiple.resolvers: tcp+udp:192.168.178.1,tcp+udp:192.168.178.2
filtering:
queryTypes:
- AAAA
- A
blocking:
blackLists:
ads: