Skip to content

Commit

Permalink
feat: add upstream strategy strict (#1093)
Browse files Browse the repository at this point in the history
  • Loading branch information
DerRockWolf committed Aug 21, 2023
1 parent 39208d8 commit c112e86
Show file tree
Hide file tree
Showing 39 changed files with 1,234 additions and 246 deletions.
4 changes: 4 additions & 0 deletions config/config.go
Expand Up @@ -123,6 +123,10 @@ func (s *StartStrategyType) do(setup func() error, logErr func(error)) error {
// ENUM(clientIP,clientName,responseReason,responseAnswer,question,duration)
type QueryLogField string

// UpstreamStrategy data field to be logged
// ENUM(parallel_best,strict)
type UpstreamStrategy uint8

//nolint:gochecknoglobals
var netDefaultPort = map[NetProtocol]uint16{
NetProtocolTcpUdp: udpPort,
Expand Down
80 changes: 80 additions & 0 deletions config/config_enum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions config/upstreams.go
Expand Up @@ -8,8 +8,9 @@ const UpstreamDefaultCfgName = "default"

// UpstreamsConfig upstream servers configuration
type UpstreamsConfig struct {
Timeout Duration `yaml:"timeout" default:"2s"`
Groups UpstreamGroups `yaml:"groups"`
Timeout Duration `yaml:"timeout" default:"2s"`
Groups UpstreamGroups `yaml:"groups"`
Strategy UpstreamStrategy `yaml:"strategy" default:"parallel_best"`
}

type UpstreamGroups map[string][]Upstream
Expand All @@ -22,7 +23,7 @@ func (c *UpstreamsConfig) IsEnabled() bool {
// LogConfig implements `config.Configurable`.
func (c *UpstreamsConfig) LogConfig(logger *logrus.Entry) {
logger.Info("timeout: ", c.Timeout)

logger.Info("strategy: ", c.Strategy)
logger.Info("groups:")

for name, upstreams := range c.Groups {
Expand Down
4 changes: 4 additions & 0 deletions docs/config.yml
Expand Up @@ -18,6 +18,10 @@ upstreams:
# or single ip address / client subnet as CIDR notation
laptop*:
- 123.123.123.123
# optional: Determines what strategy blocky uses to choose the upstream servers.
# accepted: parallel_best, strict
# default: parallel_best
strategy: parallel_best
# optional: timeout to query the upstream resolver. Default: 2s
timeout: 2s

Expand Down
53 changes: 39 additions & 14 deletions docs/configuration.md
Expand Up @@ -79,9 +79,9 @@ following network protocols (net part of the resolver URL):

!!! hint

You can (and should!) configure multiple DNS resolvers. Blocky picks 2 random resolvers from the list for each query and
returns the answer from the fastest one. This improves your network speed and increases your privacy - your DNS traffic
will be distributed over multiple providers.
You can (and should!) configure multiple DNS resolvers.
Per default blocky uses the `parallel_best` upstream strategy where blocky picks 2 random resolvers from the list for each query and
returns the answer from the fastest one.

Each resolver must be defined as a string in following format: `[net:]host:[port][/path][#commonName]`.

Expand All @@ -92,13 +92,15 @@ Each resolver must be defined as a string in following format: `[net:]host:[port
| port | int (1 - 65535) | no | 53 for udp/tcp, 853 for tcp-tls and 443 for https |
| commonName | string | no | the host value |

The commonName parameter overrides the expected certificate common name value used for verification.
The `commonName` parameter overrides the expected certificate common name value used for verification.

Blocky needs at least the configuration of the **default** group. This group will be used as a fallback, if no client
specific resolver configuration is available.
!!! note
Blocky needs at least the configuration of the **default** group with at least one upstream DNS server. This group will be used as a fallback, if no client
specific resolver configuration is available.

See [List of public DNS servers](additional_information.md#list-of-public-dns-servers) if you need some ideas, which public free DNS server you could use.

You can use the client name (see [Client name lookup](#client-name-lookup)), client's IP address or a client subnet as
CIDR notation.
You can specify multiple upstream groups (additional to the `default` group) to use different upstream servers for different clients, based on client name (see [Client name lookup](#client-name-lookup)), client IP address or client subnet (as CIDR).

!!! tip

Expand All @@ -121,15 +123,38 @@ CIDR notation.
- 9.9.9.9
```

Use `123.123.123.123` as single upstream DNS resolver for client laptop-home,
`1.1.1.1` and `9.9.9.9` for all clients in the sub-net `10.43.8.67/28` and 4 resolvers (default) for all others clients.
The above example results in:

!!! note
- `123.123.123.123` as the only upstream DNS resolver for clients with a name starting with "laptop"
- `1.1.1.1` and `9.9.9.9` for all clients in the subnet `10.43.8.67/28`
- 4 resolvers (default) for all others clients.

The logic determining what group a client belongs to follows a strict order: IP, client name, CIDR

If a client matches multiple client name or CIDR groups, a warning is logged and the first found group is used.

### Upstream strategy

Blocky supports different upstream strategies (default `parallel_best`) that determine how and to which upstream DNS servers requests are forwarded.

Currently available strategies:

** Blocky needs at least one upstream DNS server **
- `parallel_best`: blocky picks 2 random (weighted) resolvers from the upstream group for each query and returns the answer from the fastest one.
If an upstream failed to answer within the last hour, it is less likely to be chosen for the race.
This improves your network speed and increases your privacy - your DNS traffic will be distributed over multiple providers
(When using 10 upstream servers, each upstream will get on average 20% of the DNS requests)
- `strict`: blocky forwards the request in a strict order. If the first upstream does not respond, the second is asked, and so on.

See [List of public DNS servers](additional_information.md#list-of-public-dns-servers) if you need some ideas, which
public free DNS server you could use.
!!! example

```yaml
upstreams:
strategy: strict
groups:
default:
- 1.2.3.4
- 9.8.7.6
```

### Upstream lookup timeout

Expand Down
8 changes: 6 additions & 2 deletions e2e/upstream_test.go
Expand Up @@ -70,7 +70,9 @@ var _ = Describe("Upstream resolver configuration tests", func() {
It("should not start", func() {
Expect(blocky.IsRunning()).Should(BeFalse())
Expect(getContainerLogs(blocky)).
Should(ContainElement(ContainSubstring("no valid upstream for group default")))
Should(ContainElements(
ContainSubstring("creation of upstream branches failed: "),
ContainSubstring("no valid upstream for group default")))
})
})
When("'startVerifyUpstream' is true and upstream server as host name is not reachable", func() {
Expand All @@ -89,7 +91,9 @@ var _ = Describe("Upstream resolver configuration tests", func() {
It("should not start", func() {
Expect(blocky.IsRunning()).Should(BeFalse())
Expect(getContainerLogs(blocky)).
Should(ContainElement(ContainSubstring("no valid upstream for group default")))
Should(ContainElements(
ContainSubstring("creation of upstream branches failed: "),
ContainSubstring("no valid upstream for group default")))
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -37,7 +37,7 @@ require (

require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/ThinkChaos/parcour v0.0.0-20230418015731-5c82efbe68f5
github.com/ThinkChaos/parcour v0.0.0-20230710171753-fbf917c9eaef
github.com/docker/go-connections v0.4.0
github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198
github.com/testcontainers/testcontainers-go v0.22.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -17,8 +17,8 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek=
github.com/ThinkChaos/parcour v0.0.0-20230418015731-5c82efbe68f5 h1:3ubNg+3q/Y3lqxga0G90jste3i+HGDgrlPXK/feKUEI=
github.com/ThinkChaos/parcour v0.0.0-20230418015731-5c82efbe68f5/go.mod h1:hkcYs23P9zbezt09v8168B4lt69PGuoxRPQ6IJHKpHo=
github.com/ThinkChaos/parcour v0.0.0-20230710171753-fbf917c9eaef h1:lg6zRor4+PZN1Pxqtieo/NMhd61ZdV1Z/+bFURWIVfU=
github.com/ThinkChaos/parcour v0.0.0-20230710171753-fbf917c9eaef/go.mod h1:hkcYs23P9zbezt09v8168B4lt69PGuoxRPQ6IJHKpHo=
github.com/abice/go-enum v0.5.7 h1:vOrobjpce5D/x5hYNqrWRkFUXFk7A6BlsJyVy4BS1jM=
github.com/abice/go-enum v0.5.7/go.mod h1:FBDp+2Ygv9ZZzgcd+Gx3XbyClH7xxFfw8ghMrOpwu+A=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
Expand Down
7 changes: 7 additions & 0 deletions lists/list_cache_enum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions log/logger_enum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions model/models_enum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resolver/blocking_resolver_test.go
Expand Up @@ -69,6 +69,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {

JustBeforeEach(func() {
var err error

m = &mockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil)
sut, err = NewBlockingResolver(sutConfig, nil, systemResolverBootstrap)
Expand Down
5 changes: 1 addition & 4 deletions resolver/bootstrap.go
Expand Up @@ -66,10 +66,7 @@ func NewBootstrap(cfg *config.Config) (b *Bootstrap, err error) {
// where `ParallelBestResolver` uses its config, we can just use an empty one.
var pbCfg config.UpstreamsConfig

parallelResolver, err := newParallelBestResolver(pbCfg, bootstraped.ResolverGroups())
if err != nil {
return nil, fmt.Errorf("could not create bootstrap ParallelBestResolver: %w", err)
}
parallelResolver := newParallelBestResolver(pbCfg, bootstraped.ResolverGroups())

// Always enable prefetching to avoid stalling user requests
// Otherwise, a request to blocky could end up waiting for 2 DNS requests:
Expand Down
5 changes: 3 additions & 2 deletions resolver/client_names_resolver_test.go
Expand Up @@ -30,9 +30,10 @@ var _ = Describe("ClientResolver", Label("clientNamesResolver"), func() {
})

JustBeforeEach(func() {
res, err := NewClientNamesResolver(sutConfig, nil, false)
var err error

sut, err = NewClientNamesResolver(sutConfig, nil, false)
Expect(err).Should(Succeed())
sut = res
m = &mockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
sut.Next(m)
Expand Down
2 changes: 1 addition & 1 deletion resolver/conditional_upstream_resolver.go
Expand Up @@ -24,7 +24,7 @@ type ConditionalUpstreamResolver struct {
// NewConditionalUpstreamResolver returns new resolver instance
func NewConditionalUpstreamResolver(
cfg config.ConditionalUpstreamConfig, bootstrap *Bootstrap, shouldVerifyUpstreams bool,
) (ChainedResolver, error) {
) (*ConditionalUpstreamResolver, error) {
m := make(map[string]Resolver, len(cfg.Mapping.Upstreams))

for domain, upstream := range cfg.Mapping.Upstreams {
Expand Down
2 changes: 1 addition & 1 deletion resolver/conditional_upstream_resolver_test.go
Expand Up @@ -15,7 +15,7 @@ import (

var _ = Describe("ConditionalUpstreamResolver", Label("conditionalResolver"), func() {
var (
sut ChainedResolver
sut *ConditionalUpstreamResolver
m *mockResolver
)

Expand Down
2 changes: 1 addition & 1 deletion resolver/custom_dns_resolver.go
Expand Up @@ -24,7 +24,7 @@ type CustomDNSResolver struct {
}

// NewCustomDNSResolver creates new resolver instance
func NewCustomDNSResolver(cfg config.CustomDNSConfig) ChainedResolver {
func NewCustomDNSResolver(cfg config.CustomDNSConfig) *CustomDNSResolver {
m := make(map[string][]net.IP, len(cfg.Mapping.HostIPs))
reverse := make(map[string][]string, len(cfg.Mapping.HostIPs))

Expand Down
2 changes: 1 addition & 1 deletion resolver/custom_dns_resolver_test.go
Expand Up @@ -18,7 +18,7 @@ var _ = Describe("CustomDNSResolver", func() {
var (
TTL = uint32(time.Now().Second())

sut ChainedResolver
sut *CustomDNSResolver
m *mockResolver
cfg config.CustomDNSConfig
)
Expand Down
2 changes: 1 addition & 1 deletion resolver/ede_resolver.go
Expand Up @@ -12,7 +12,7 @@ type EdeResolver struct {
typed
}

func NewEdeResolver(cfg config.EdeConfig) ChainedResolver {
func NewEdeResolver(cfg config.EdeConfig) *EdeResolver {
return &EdeResolver{
configurable: withConfig(&cfg),
typed: withType("extended_error_code"),
Expand Down

0 comments on commit c112e86

Please sign in to comment.