mirror of https://github.com/0xERR0R/blocky.git
parent
5427c1697c
commit
28789ee7fe
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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"`
|
||||
// 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
|
||||
|
|
|
@ -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,21 +114,32 @@ 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"))
|
||||
})
|
||||
})
|
||||
|
||||
When("TlsPort is defined", func() {
|
||||
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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: ''"))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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}
|
||||
}
|
|
@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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),
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue