mirror of https://github.com/0xERR0R/blocky.git
#79: Support for multiple conditional forwarders per domain
This commit is contained in:
parent
4d69c26ae2
commit
914a04e5b1
|
@ -57,6 +57,34 @@ func (u *Upstream) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *ConditionalUpstreamMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var input map[string]string
|
||||
if err := unmarshal(&input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make(map[string][]Upstream)
|
||||
|
||||
for k, v := range input {
|
||||
var upstreams []Upstream
|
||||
|
||||
for _, part := range strings.Split(v, ",") {
|
||||
upstream, err := ParseUpstream(strings.TrimSpace(part))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upstreams = append(upstreams, upstream)
|
||||
}
|
||||
|
||||
result[k] = upstreams
|
||||
}
|
||||
|
||||
c.Upstreams = result
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseUpstream creates new Upstream from passed string in format [net]:host[:port][/path]
|
||||
func ParseUpstream(upstream string) (result Upstream, err error) {
|
||||
if strings.TrimSpace(upstream) == "" {
|
||||
|
@ -190,7 +218,11 @@ type CustomDNSConfig struct {
|
|||
}
|
||||
|
||||
type ConditionalUpstreamConfig struct {
|
||||
Mapping map[string]Upstream `yaml:"mapping"`
|
||||
Mapping ConditionalUpstreamMapping `yaml:"mapping"`
|
||||
}
|
||||
|
||||
type ConditionalUpstreamMapping struct {
|
||||
Upstreams map[string][]Upstream
|
||||
}
|
||||
|
||||
type BlockingConfig struct {
|
||||
|
|
|
@ -28,7 +28,9 @@ var _ = Describe("Config", func() {
|
|||
Expect(cfg.Upstream.ExternalResolvers[2].Host).Should(Equal("1.1.1.1"))
|
||||
Expect(cfg.CustomDNS.Mapping).Should(HaveLen(1))
|
||||
Expect(cfg.CustomDNS.Mapping["my.duckdns.org"]).Should(Equal(net.ParseIP("192.168.178.3")))
|
||||
Expect(cfg.Conditional.Mapping).Should(HaveLen(1))
|
||||
Expect(cfg.Conditional.Mapping.Upstreams).Should(HaveLen(2))
|
||||
Expect(cfg.Conditional.Mapping.Upstreams["fritz.box"]).Should(HaveLen(1))
|
||||
Expect(cfg.Conditional.Mapping.Upstreams["multiple.resolvers"]).Should(HaveLen(2))
|
||||
Expect(cfg.ClientLookup.Upstream.Host).Should(Equal("192.168.178.1"))
|
||||
Expect(cfg.ClientLookup.SingleNameOrder).Should(Equal([]uint{2, 1}))
|
||||
Expect(cfg.Blocking.BlackLists).Should(HaveLen(2))
|
||||
|
|
|
@ -35,34 +35,35 @@ Create `config.yml` file with your configuration [as yml](config.yml):
|
|||
```yml
|
||||
upstream:
|
||||
# these external DNS resolvers will be used. Blocky picks 2 random resolvers from the list for each query
|
||||
# format for resolver: [net:]host:[port][/path]. net could be empty (default, shortcut for tcp+udp), tcp+udp, tcp, udp, tcp-tls or https (DoH). If port is empty, default port will be used (53 for udp and tcp, 853 for tcp-tls, 443 for https (Doh))
|
||||
externalResolvers:
|
||||
- 46.182.19.48
|
||||
- 80.241.218.68
|
||||
- tcp-tls:fdns1.dismail.de:853
|
||||
- https://dns.digitale-gesellschaft.ch/dns-query
|
||||
|
||||
# format for resolver: [net:]host:[port][/path]. net could be empty (default, shortcut for tcp+udp), tcp+udp, tcp, udp, tcp-tls or https (DoH). If port is empty, default port will be used (53 for udp and tcp, 853 for tcp-tls, 443 for https (Doh))
|
||||
externalResolvers:
|
||||
- 46.182.19.48
|
||||
- 80.241.218.68
|
||||
- tcp-tls:fdns1.dismail.de:853
|
||||
- https://dns.digitale-gesellschaft.ch/dns-query
|
||||
|
||||
# optional: custom IP address for domain name (with all sub-domains)
|
||||
# example: query "printer.lan" or "my.printer.lan" will return 192.168.178.3
|
||||
customDNS:
|
||||
mapping:
|
||||
printer.lan: 192.168.178.3
|
||||
mapping:
|
||||
printer.lan: 192.168.178.3
|
||||
|
||||
# optional: definition, which DNS resolver should be used for queries to the domain (with all sub-domains).
|
||||
# optional: definition, which DNS resolver(s) should be used for queries to the domain (with all sub-domains). Multiple resolvers must be separated by comma
|
||||
# Example: Query client.fritz.box will ask DNS server 192.168.178.1. This is necessary for local network, to resolve clients by host name
|
||||
conditional:
|
||||
mapping:
|
||||
fritz.box: udp:192.168.178.1
|
||||
|
||||
mapping:
|
||||
fritz.box: udp:192.168.178.1
|
||||
lan.net: udp:192.168.178.1,udp:192.168.178.2
|
||||
|
||||
# optional: use black and white lists to block queries (for example ads, trackers, adult pages etc.)
|
||||
blocking:
|
||||
# definition of blacklist groups. Can be external link (http/https) or local file
|
||||
blackLists:
|
||||
ads:
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
- https://mirror1.malwaredomains.com/files/justdomains
|
||||
- http://sysctl.org/cameleon/hosts
|
||||
# definition of blacklist groups. Can be external link (http/https) or local file
|
||||
blackLists:
|
||||
ads:
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
- https://mirror1.malwaredomains.com/files/justdomains
|
||||
- http://sysctl.org/cameleon/hosts
|
||||
- https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
|
||||
special:
|
||||
|
|
|
@ -13,11 +13,12 @@ customDNS:
|
|||
mapping:
|
||||
printer.lan: 192.168.178.3
|
||||
|
||||
# optional: definition, which DNS resolver should be used for queries to the domain (with all sub-domains).
|
||||
# optional: definition, which DNS resolver(s) should be used for queries to the domain (with all sub-domains). Multiple resolvers must be separated by comma
|
||||
# Example: Query client.fritz.box will ask DNS server 192.168.178.1. This is necessary for local network, to resolve clients by host name
|
||||
conditional:
|
||||
mapping:
|
||||
fritz.box: udp:192.168.178.1
|
||||
lan.net: udp:192.168.178.1,udp:192.168.178.2
|
||||
|
||||
# optional: use black and white lists to block queries (for example ads, trackers, adult pages etc.)
|
||||
blocking:
|
||||
|
|
|
@ -17,8 +17,8 @@ type ConditionalUpstreamResolver struct {
|
|||
|
||||
func NewConditionalUpstreamResolver(cfg config.ConditionalUpstreamConfig) ChainedResolver {
|
||||
m := make(map[string]Resolver)
|
||||
for domain, upstream := range cfg.Mapping {
|
||||
m[strings.ToLower(domain)] = NewUpstreamResolver(upstream)
|
||||
for domain, upstream := range cfg.Mapping.Upstreams {
|
||||
m[strings.ToLower(domain)] = NewParallelBestResolver(upstream)
|
||||
}
|
||||
|
||||
return &ConditionalUpstreamResolver{mapping: m}
|
||||
|
|
|
@ -25,18 +25,19 @@ var _ = Describe("ConditionalUpstreamResolver", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
sut = NewConditionalUpstreamResolver(config.ConditionalUpstreamConfig{
|
||||
Mapping: map[string]config.Upstream{
|
||||
"fritz.box": TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
|
||||
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, dns.TypeA, "123.124.122.122")
|
||||
Mapping: config.ConditionalUpstreamMapping{
|
||||
Upstreams: map[string][]config.Upstream{
|
||||
"fritz.box": {TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
|
||||
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, dns.TypeA, "123.124.122.122")
|
||||
|
||||
return response
|
||||
}),
|
||||
"other.box": TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
|
||||
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 250, dns.TypeA, "192.192.192.192")
|
||||
return response
|
||||
})},
|
||||
"other.box": {TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
|
||||
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 250, dns.TypeA, "192.192.192.192")
|
||||
|
||||
return response
|
||||
}),
|
||||
},
|
||||
return response
|
||||
})},
|
||||
}},
|
||||
})
|
||||
m = &resolverMock{}
|
||||
m.On("Resolve", mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"blocky/util"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mroth/weightedrand"
|
||||
|
@ -26,10 +27,10 @@ type requestResponse struct {
|
|||
err error
|
||||
}
|
||||
|
||||
func NewParallelBestResolver(cfg config.UpstreamConfig) Resolver {
|
||||
resolvers := make([]*upstreamResolverStatus, len(cfg.ExternalResolvers))
|
||||
func NewParallelBestResolver(upstreamResolvers []config.Upstream) Resolver {
|
||||
resolvers := make([]*upstreamResolverStatus, len(upstreamResolvers))
|
||||
|
||||
for i, u := range cfg.ExternalResolvers {
|
||||
for i, u := range upstreamResolvers {
|
||||
resolvers[i] = &upstreamResolverStatus{
|
||||
resolver: NewUpstreamResolver(u),
|
||||
lastErrorTime: time.Unix(0, 0),
|
||||
|
@ -48,11 +49,20 @@ func (r *ParallelBestResolver) Configuration() (result []string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (r ParallelBestResolver) String() string {
|
||||
result := make([]string, len(r.resolvers))
|
||||
for i, s := range r.resolvers {
|
||||
result[i] = fmt.Sprintf("%s", s.resolver)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("parallel upstreams '%s'", strings.Join(result, "; "))
|
||||
}
|
||||
|
||||
func (r *ParallelBestResolver) Resolve(request *Request) (*Response, error) {
|
||||
logger := request.Log.WithField("prefix", "parallel_best_resolver")
|
||||
|
||||
if len(r.resolvers) == 1 {
|
||||
logger.WithField("resolver", r.resolvers[0]).Debug("delegating to resolver")
|
||||
logger.WithField("resolver", r.resolvers[0].resolver).Debug("delegating to resolver")
|
||||
return r.resolvers[0].resolver.Resolve(request)
|
||||
}
|
||||
|
||||
|
@ -63,11 +73,11 @@ func (r *ParallelBestResolver) Resolve(request *Request) (*Response, error) {
|
|||
|
||||
var collectedErrors []error
|
||||
|
||||
logger.WithField("resolver", r1).Debug("delegating to resolver")
|
||||
logger.WithField("resolver", r1.resolver).Debug("delegating to resolver")
|
||||
|
||||
go resolve(request, r1, ch)
|
||||
|
||||
logger.WithField("resolver", r2).Debug("delegating to resolver")
|
||||
logger.WithField("resolver", r2.resolver).Debug("delegating to resolver")
|
||||
|
||||
go resolve(request, r2, ch)
|
||||
|
||||
|
@ -80,7 +90,7 @@ func (r *ParallelBestResolver) Resolve(request *Request) (*Response, error) {
|
|||
collectedErrors = append(collectedErrors, result.err)
|
||||
} else {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"resolver": r1,
|
||||
"resolver": r1.resolver,
|
||||
"answer": util.AnswerToString(result.response.Res.Answer),
|
||||
}).Debug("using response from resolver")
|
||||
return result.response, nil
|
||||
|
|
|
@ -37,9 +37,7 @@ var _ = Describe("ParallelBestResolver", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
return response
|
||||
})
|
||||
sut = NewParallelBestResolver(config.UpstreamConfig{
|
||||
ExternalResolvers: []config.Upstream{fast, slow},
|
||||
})
|
||||
sut = NewParallelBestResolver([]config.Upstream{fast, slow})
|
||||
})
|
||||
It("Should use result from fastest one", func() {
|
||||
request := newRequest("example.com.", dns.TypeA)
|
||||
|
@ -63,9 +61,7 @@ var _ = Describe("ParallelBestResolver", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
return response
|
||||
})
|
||||
sut = NewParallelBestResolver(config.UpstreamConfig{
|
||||
ExternalResolvers: []config.Upstream{withError, slow},
|
||||
})
|
||||
sut = NewParallelBestResolver([]config.Upstream{withError, slow})
|
||||
})
|
||||
It("Should use result from successful resolver", func() {
|
||||
request := newRequest("example.com.", dns.TypeA)
|
||||
|
@ -83,9 +79,7 @@ var _ = Describe("ParallelBestResolver", func() {
|
|||
withError1 := config.Upstream{Host: "wrong"}
|
||||
withError2 := config.Upstream{Host: "wrong"}
|
||||
|
||||
sut = NewParallelBestResolver(config.UpstreamConfig{
|
||||
ExternalResolvers: []config.Upstream{withError1, withError2},
|
||||
})
|
||||
sut = NewParallelBestResolver([]config.Upstream{withError1, withError2})
|
||||
})
|
||||
It("Should return error", func() {
|
||||
request := newRequest("example.com.", dns.TypeA)
|
||||
|
@ -104,9 +98,7 @@ var _ = Describe("ParallelBestResolver", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
return response
|
||||
})
|
||||
sut = NewParallelBestResolver(config.UpstreamConfig{
|
||||
ExternalResolvers: []config.Upstream{fast},
|
||||
})
|
||||
sut = NewParallelBestResolver([]config.Upstream{fast})
|
||||
})
|
||||
It("Should use result from defined resolver", func() {
|
||||
request := newRequest("example.com.", dns.TypeA)
|
||||
|
@ -137,9 +129,7 @@ var _ = Describe("ParallelBestResolver", func() {
|
|||
return response
|
||||
})
|
||||
|
||||
sut := NewParallelBestResolver(config.UpstreamConfig{
|
||||
ExternalResolvers: []config.Upstream{withError1, fast1, fast2, withError2},
|
||||
}).(*ParallelBestResolver)
|
||||
sut := NewParallelBestResolver([]config.Upstream{withError1, fast1, fast2, withError2}).(*ParallelBestResolver)
|
||||
|
||||
By("all resolvers have same weight for random -> equal distribution", func() {
|
||||
resolverCount := make(map[Resolver]int)
|
||||
|
@ -194,11 +184,9 @@ var _ = Describe("ParallelBestResolver", func() {
|
|||
|
||||
Describe("Configuration output", func() {
|
||||
BeforeEach(func() {
|
||||
sut = NewParallelBestResolver(config.UpstreamConfig{
|
||||
ExternalResolvers: []config.Upstream{
|
||||
{Host: "host1"},
|
||||
{Host: "host2"},
|
||||
},
|
||||
sut = NewParallelBestResolver([]config.Upstream{
|
||||
{Host: "host1"},
|
||||
{Host: "host2"},
|
||||
})
|
||||
})
|
||||
It("should return configuration", func() {
|
||||
|
|
|
@ -115,7 +115,7 @@ func createQueryResolver(cfg *config.Config, router *chi.Mux) resolver.Resolver
|
|||
resolver.NewCustomDNSResolver(cfg.CustomDNS),
|
||||
resolver.NewBlockingResolver(router, cfg.Blocking),
|
||||
resolver.NewCachingResolver(cfg.Caching),
|
||||
resolver.NewParallelBestResolver(cfg.Upstream),
|
||||
resolver.NewParallelBestResolver(cfg.Upstream.ExternalResolvers),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,11 @@ var _ = Describe("Running DNS server", func() {
|
|||
},
|
||||
},
|
||||
Conditional: config.ConditionalUpstreamConfig{
|
||||
Mapping: map[string]config.Upstream{"fritz.box": upstreamFritzbox},
|
||||
Mapping: config.ConditionalUpstreamMapping{
|
||||
Upstreams: map[string][]config.Upstream{
|
||||
"fritz.box": {upstreamFritzbox},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocking: config.BlockingConfig{
|
||||
BlackLists: map[string][]string{
|
||||
|
|
|
@ -9,6 +9,7 @@ customDNS:
|
|||
conditional:
|
||||
mapping:
|
||||
fritz.box: udp:192.168.178.1
|
||||
multiple.resolvers: udp:192.168.178.1,udp:192.168.178.2
|
||||
blocking:
|
||||
blackLists:
|
||||
ads:
|
||||
|
|
Loading…
Reference in New Issue