mirror of https://github.com/0xERR0R/blocky.git
Enable start as long as at least one upstream resolver in group is reachable (#608)
* Enable start if one upstream resolver fails * Will now check if upstream actually works * Fixed default upstream in some tests * Increase timeouts in some tests * change default value of "StartVerifyUpstream" to false Co-authored-by: Dimitri Herzog <dimitri.herzog@gmail.com>
This commit is contained in:
parent
421807fc22
commit
377f4764fe
|
@ -1,4 +1,5 @@
|
|||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
/*.pem
|
||||
bin/
|
||||
|
|
|
@ -399,26 +399,27 @@ func extractNet(upstream string) (NetProtocol, string) {
|
|||
// Config main configuration
|
||||
// nolint:maligned
|
||||
type Config struct {
|
||||
Upstream UpstreamConfig `yaml:"upstream"`
|
||||
UpstreamTimeout Duration `yaml:"upstreamTimeout" default:"2s"`
|
||||
CustomDNS CustomDNSConfig `yaml:"customDNS"`
|
||||
Conditional ConditionalUpstreamConfig `yaml:"conditional"`
|
||||
Blocking BlockingConfig `yaml:"blocking"`
|
||||
ClientLookup ClientLookupConfig `yaml:"clientLookup"`
|
||||
Caching CachingConfig `yaml:"caching"`
|
||||
QueryLog QueryLogConfig `yaml:"queryLog"`
|
||||
Prometheus PrometheusConfig `yaml:"prometheus"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
LogLevel log.Level `yaml:"logLevel" default:"info"`
|
||||
LogFormat log.FormatType `yaml:"logFormat" default:"text"`
|
||||
LogPrivacy bool `yaml:"logPrivacy" default:"false"`
|
||||
LogTimestamp bool `yaml:"logTimestamp" default:"true"`
|
||||
DNSPorts ListenConfig `yaml:"port" default:"[\"53\"]"`
|
||||
HTTPPorts ListenConfig `yaml:"httpPort"`
|
||||
HTTPSPorts ListenConfig `yaml:"httpsPort"`
|
||||
TLSPorts ListenConfig `yaml:"tlsPort"`
|
||||
DoHUserAgent string `yaml:"dohUserAgent"`
|
||||
MinTLSServeVer string `yaml:"minTlsServeVersion" default:"1.2"`
|
||||
Upstream UpstreamConfig `yaml:"upstream"`
|
||||
UpstreamTimeout Duration `yaml:"upstreamTimeout" default:"2s"`
|
||||
CustomDNS CustomDNSConfig `yaml:"customDNS"`
|
||||
Conditional ConditionalUpstreamConfig `yaml:"conditional"`
|
||||
Blocking BlockingConfig `yaml:"blocking"`
|
||||
ClientLookup ClientLookupConfig `yaml:"clientLookup"`
|
||||
Caching CachingConfig `yaml:"caching"`
|
||||
QueryLog QueryLogConfig `yaml:"queryLog"`
|
||||
Prometheus PrometheusConfig `yaml:"prometheus"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
LogLevel log.Level `yaml:"logLevel" default:"info"`
|
||||
LogFormat log.FormatType `yaml:"logFormat" default:"text"`
|
||||
LogPrivacy bool `yaml:"logPrivacy" default:"false"`
|
||||
LogTimestamp bool `yaml:"logTimestamp" default:"true"`
|
||||
DNSPorts ListenConfig `yaml:"port" default:"[\"53\"]"`
|
||||
HTTPPorts ListenConfig `yaml:"httpPort"`
|
||||
HTTPSPorts ListenConfig `yaml:"httpsPort"`
|
||||
TLSPorts ListenConfig `yaml:"tlsPort"`
|
||||
DoHUserAgent string `yaml:"dohUserAgent"`
|
||||
MinTLSServeVer string `yaml:"minTlsServeVersion" default:"1.2"`
|
||||
StartVerifyUpstream bool `yaml:"startVerifyUpstream" default:"false"`
|
||||
// Deprecated
|
||||
DisableIPv6 bool `yaml:"disableIPv6" default:"false"`
|
||||
CertFile string `yaml:"certFile"`
|
||||
|
|
|
@ -579,6 +579,7 @@ func defaultTestFileConfig() {
|
|||
|
||||
Expect(config.DoHUserAgent).Should(Equal("testBlocky"))
|
||||
Expect(config.MinTLSServeVer).Should(Equal("1.3"))
|
||||
Expect(config.StartVerifyUpstream).Should(BeFalse())
|
||||
|
||||
Expect(GetConfig()).Should(Not(BeNil()))
|
||||
}
|
||||
|
@ -635,7 +636,8 @@ func writeConfigYml(tmpDir *helpertest.TmpFolder) *helpertest.TmpFile {
|
|||
"port: 55553,:55554,[::1]:55555",
|
||||
"logLevel: debug",
|
||||
"dohUserAgent: testBlocky",
|
||||
"minTlsServeVersion: 1.3")
|
||||
"minTlsServeVersion: 1.3",
|
||||
"startVerifyUpstream: false")
|
||||
}
|
||||
|
||||
func writeConfigDir(tmpDir *helpertest.TmpFolder) error {
|
||||
|
@ -695,7 +697,8 @@ func writeConfigDir(tmpDir *helpertest.TmpFolder) error {
|
|||
"port: 55553,:55554,[::1]:55555",
|
||||
"logLevel: debug",
|
||||
"dohUserAgent: testBlocky",
|
||||
"minTlsServeVersion: 1.3")
|
||||
"minTlsServeVersion: 1.3",
|
||||
"startVerifyUpstream: false")
|
||||
|
||||
return f2.Error
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ upstream:
|
|||
# optional: timeout to query the upstream resolver. Default: 2s
|
||||
upstreamTimeout: 2s
|
||||
|
||||
#optional: If true, blocky will fail to start unless at least one upstream server per group is reachable. Default: false
|
||||
startVerifyUpstream: true
|
||||
|
||||
# optional: custom IP address(es) for domain name (with all sub-domains). Multiple addresses must be separated by a comma
|
||||
# example: query "printer.lan" or "my.printer.lan" will return 192.168.178.3
|
||||
customDNS:
|
||||
|
|
|
@ -25,6 +25,7 @@ configuration properties as [JSON](config.yml).
|
|||
| logPrivacy | bool | no | false | Obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. |
|
||||
| dohUserAgent | string | no | | HTTP User Agent for DoH upstreams |
|
||||
| minTlsServeVersion | string | no | 1.2 | Minimum TLS version that the DoT and DoH server use to serve those encrypted DNS requests |
|
||||
| startVerifyUpstream | bool | no | false | If true, blocky will fail to start unless at least one upstream server per group is reachable. |
|
||||
|
||||
!!! example
|
||||
|
||||
|
|
|
@ -197,9 +197,9 @@ var _ = Describe("Downloader", func() {
|
|||
When("DNS resolution of passed URL fails", func() {
|
||||
BeforeEach(func() {
|
||||
sut = NewDownloader(
|
||||
WithTimeout(100*time.Millisecond),
|
||||
WithTimeout(500*time.Millisecond),
|
||||
WithAttempts(3),
|
||||
WithCooldown(time.Millisecond))
|
||||
WithCooldown(200*time.Millisecond))
|
||||
})
|
||||
It("Should perform a retry until max retry attempt count is reached and return DNSError", func() {
|
||||
reader, err := sut.DownloadFile("http://some.domain.which.does.not.exist")
|
||||
|
|
|
@ -26,7 +26,8 @@ var (
|
|||
|
||||
// Bootstrap allows resolving hostnames using the configured bootstrap DNS.
|
||||
type Bootstrap struct {
|
||||
log *logrus.Entry
|
||||
log *logrus.Entry
|
||||
startVerifyUpstream bool
|
||||
|
||||
resolver Resolver
|
||||
upstream Resolver // the upstream that's part of the above resolver
|
||||
|
@ -64,9 +65,10 @@ func NewBootstrap(cfg *config.Config) (b *Bootstrap, err error) {
|
|||
// This also prevents the GC to clean up these two structs, but is not currently an
|
||||
// issue since they stay allocated until the process terminates
|
||||
b = &Bootstrap{
|
||||
log: log,
|
||||
upstreamIPs: ips,
|
||||
systemResolver: net.DefaultResolver, // allow replacing it during tests
|
||||
log: log,
|
||||
upstreamIPs: ips,
|
||||
systemResolver: net.DefaultResolver, // allow replacing it during tests
|
||||
startVerifyUpstream: cfg.StartVerifyUpstream,
|
||||
}
|
||||
|
||||
if upstream.IsDefault() {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/mroth/weightedrand"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -36,23 +37,56 @@ type requestResponse struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// testResolver sends a test query to verify the resolver is reachable and working
|
||||
func testResolver(r *UpstreamResolver) error {
|
||||
request := newRequest("github.com.", dns.Type(dns.TypeA))
|
||||
|
||||
resp, err := r.Resolve(request)
|
||||
if err != nil || resp.RType != model.ResponseTypeRESOLVED {
|
||||
return fmt.Errorf("test resolve of upstream server failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewParallelBestResolver creates new resolver instance
|
||||
func NewParallelBestResolver(upstreamResolvers map[string][]config.Upstream, bootstrap *Bootstrap) (Resolver, error) {
|
||||
s := make(map[string][]*upstreamResolverStatus, len(upstreamResolvers))
|
||||
logger := logger("parallel resolver")
|
||||
s := make(map[string][]*upstreamResolverStatus)
|
||||
|
||||
for name, res := range upstreamResolvers {
|
||||
resolvers := make([]*upstreamResolverStatus, len(res))
|
||||
var resolvers []*upstreamResolverStatus
|
||||
|
||||
for i, u := range res {
|
||||
var errResolvers int
|
||||
|
||||
for _, u := range res {
|
||||
r, err := NewUpstreamResolver(u, bootstrap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
logger.Warnf("upstream group %s: %v", name, err)
|
||||
errResolvers++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers[i] = &upstreamResolverStatus{
|
||||
if bootstrap != skipUpstreamCheck {
|
||||
err = testResolver(r)
|
||||
if err != nil {
|
||||
logger.Warn(err)
|
||||
errResolvers++
|
||||
}
|
||||
}
|
||||
|
||||
resolver := &upstreamResolverStatus{
|
||||
resolver: r,
|
||||
}
|
||||
resolvers[i].lastErrorTime.Store(time.Unix(0, 0))
|
||||
resolver.lastErrorTime.Store(time.Unix(0, 0))
|
||||
resolvers = append(resolvers, resolver)
|
||||
}
|
||||
|
||||
if bootstrap != skipUpstreamCheck {
|
||||
if bootstrap.startVerifyUpstream && errResolvers == len(res) {
|
||||
return nil, fmt.Errorf("unable to reach any DNS resolvers configured for resolver group %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
s[name] = resolvers
|
||||
|
|
|
@ -26,6 +26,67 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("Some default upstream resolvers cannot be reached", func() {
|
||||
It("should start normally", func() {
|
||||
skipUpstreamCheck.startVerifyUpstream = true
|
||||
|
||||
mockUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
||||
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
|
||||
return
|
||||
})
|
||||
defer mockUpstream.Close()
|
||||
|
||||
upstream := map[string][]config.Upstream{
|
||||
upstreamDefaultCfgName: {
|
||||
config.Upstream{
|
||||
Host: "wrong",
|
||||
},
|
||||
mockUpstream.Start(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := NewParallelBestResolver(upstream, skipUpstreamCheck)
|
||||
Expect(err).Should(Not(HaveOccurred()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("All default upstream resolvers cannot be reached", func() {
|
||||
var (
|
||||
upstream map[string][]config.Upstream
|
||||
b *Bootstrap
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
b = TestBootstrap(&dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}})
|
||||
|
||||
upstream = map[string][]config.Upstream{
|
||||
upstreamDefaultCfgName: {
|
||||
config.Upstream{
|
||||
Host: "wrong",
|
||||
},
|
||||
config.Upstream{
|
||||
Host: "127.0.0.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("should fail to start if strict checking is enabled", func() {
|
||||
b.startVerifyUpstream = true
|
||||
|
||||
_, err := NewParallelBestResolver(upstream, b)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should start if strict checking is disabled", func() {
|
||||
b.startVerifyUpstream = false
|
||||
|
||||
_, err := NewParallelBestResolver(upstream, b)
|
||||
Expect(err).Should(Not(HaveOccurred()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Resolving result from fastest upstream resolver", func() {
|
||||
var (
|
||||
sut Resolver
|
||||
|
@ -310,6 +371,8 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
sut Resolver
|
||||
)
|
||||
BeforeEach(func() {
|
||||
config.GetConfig().StartVerifyUpstream = false
|
||||
|
||||
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{upstreamDefaultCfgName: {
|
||||
{Host: "host1"},
|
||||
{Host: "host2"},
|
||||
|
|
|
@ -61,8 +61,15 @@ var _ = BeforeSuite(func() {
|
|||
DeferCleanup(fritzboxMockUpstream.Close)
|
||||
|
||||
clientMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
||||
var clientName string
|
||||
client := mockClientName.Load()
|
||||
|
||||
if client != nil {
|
||||
clientName = mockClientName.Load().(string)
|
||||
}
|
||||
|
||||
response, err := util.NewMsgWithAnswer(
|
||||
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), mockClientName.Load().(string),
|
||||
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), clientName,
|
||||
)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -550,7 +557,7 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(cErr).Should(Succeed())
|
||||
|
||||
cfg.Upstream.ExternalResolvers = map[string][]config.Upstream{
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}}}
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}}}
|
||||
|
||||
cfg.Redis.Address = "test-fail"
|
||||
})
|
||||
|
@ -680,7 +687,7 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(cErr).Should(Succeed())
|
||||
|
||||
cfg.Upstream.ExternalResolvers = map[string][]config.Upstream{
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}}}
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}}}
|
||||
})
|
||||
|
||||
It("should create self-signed certificate if key/cert files are not provided", func() {
|
||||
|
|
Loading…
Reference in New Issue