Skip to content

Commit

Permalink
change(ipv6): detect automatically and use only IPv6 if supported
Browse files Browse the repository at this point in the history
- Keeps Go API to be up to the user with an `IPVersion` setting field
- `DOT_IP_VERSION`, `DOH_IP_VERSION` removed
  • Loading branch information
qdm12 committed Nov 24, 2023
1 parent 0028083 commit 5ef315d
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 34 deletions.
2 changes: 0 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ ENV \
MIDDLEWARE_LOG_RESPONSES=off \
CACHE_TYPE=lru \
CACHE_LRU_MAX_ENTRIES=10000 \
DOT_IP_VERSION=ipv4 \
DOH_IP_VERSION=ipv4 \
BLOCK_MALICIOUS=on \
BLOCK_SURVEILLANCE=off \
BLOCK_ADS=off \
Expand Down
1 change: 0 additions & 1 deletion KUBERNETES.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ data:

CHECK_DNS: 'off'
UPDATE_PERIOD: '0' # actually 24h, but there is bug currently
DOT_IP_VERSION: 'ipv4'
REBINDING_PROTECTION: 'on'
```
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ If you're running Kubernetes, there is a separate article on [how to set up K8s]
| `METRICS_PROMETHEUS_SUBSYSTEM` | `dns` | Prometheus metrics prefix/subsystem |
| `REBINDING_PROTECTION` | `on` | `on` or `off`. Enabling will prevent the server from returning any private IP address to the client. |
| `CHECK_DNS` | `on` | `on` or `off`. Check resolving github.com using `127.0.0.1:53` at start |
| `DOT_IP_VERSION` | `ipv4` | `ipv4` or `ipv6`. Connects to the DNS upstream servers only over IPv4 addresses or only over IPv6 addresses |
| `DOH_IP_VERSION` | `ipv4` | `ipv4` or `ipv6`. Connects to the DNS upstream servers only over IPv4 addresses or only over IPv6 addresses |
| `UPDATE_PERIOD` | `24h` | Period to update block lists and restart Unbound. Set to `0` to disable. |
## Migrate
Expand Down
13 changes: 0 additions & 13 deletions internal/config/doh.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ import (
"github.com/qdm12/dns/v2/pkg/provider"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)

type DoH struct {
// UpstreamResolvers is a list of DNS over HTTPS upstream
// resolvers to use.
UpstreamResolvers []string
// IPVersion defines the only IP version to use to connect to
// upstream DNS over HTTPS servers. If left unset, it defaults
// to "ipv4".
IPVersion string
// Timeout is the maximum duration to wait for a response from
// upstream DNS over HTTPS servers. If left unset, it defaults
// to 1 second.
Expand All @@ -30,7 +25,6 @@ func (d *DoH) setDefaults() {
provider.Cloudflare().Name,
provider.Google().Name,
})
d.IPVersion = gosettings.DefaultComparable(d.IPVersion, "ipv4")
d.Timeout = gosettings.DefaultComparable(d.Timeout, time.Second)
}

Expand All @@ -40,11 +34,6 @@ func (d *DoH) validate() (err error) {
return fmt.Errorf("upstream resolvers: %w", err)
}

err = validate.IsOneOf(d.IPVersion, "ipv4", "ipv6")
if err != nil {
return fmt.Errorf("IP version: %w", err)
}

const minTimeout = time.Millisecond
if d.Timeout < minTimeout {
return fmt.Errorf("%w: %s must be at least %s",
Expand All @@ -62,15 +51,13 @@ func (d *DoH) ToLinesNode() (node *gotree.Node) {
node = gotree.New("DNS over HTTPs:")

node.Appendf("Upstream resolvers: %s", andStrings(d.UpstreamResolvers))
node.Appendf("Connecting over %s", d.IPVersion)
node.Appendf("Query timeout: %s", d.Timeout)

return node
}

func (d *DoH) read(reader *reader.Reader) (err error) {
d.UpstreamResolvers = reader.CSV("DOH_RESOLVERS")
d.IPVersion = reader.String("DOH_IP_VERSION")

d.Timeout, err = reader.Duration("DOH_TIMEOUT")
if err != nil {
Expand Down
6 changes: 1 addition & 5 deletions internal/config/dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ type DoT struct {
// Timeout is the maximum duration to wait for a response from
// upstream DNS over TLS servers. If left unset, it defaults to
// 1 second.
Timeout time.Duration
IPVersion string
Timeout time.Duration
}

func (d *DoT) setDefaults() {
Expand All @@ -29,7 +28,6 @@ func (d *DoT) setDefaults() {
})

d.Timeout = gosettings.DefaultComparable(d.Timeout, time.Second)
d.IPVersion = gosettings.DefaultComparable(d.IPVersion, "ipv4")
}

var (
Expand Down Expand Up @@ -60,7 +58,6 @@ func (d *DoT) ToLinesNode() (node *gotree.Node) {

node.Appendf("Upstream resolvers: %s", andStrings(d.UpstreamResolvers))
node.Appendf("Request timeout: %s", d.Timeout)
node.Appendf("Connecting over: %s", d.IPVersion)

return node
}
Expand All @@ -72,6 +69,5 @@ func (d *DoT) read(reader *reader.Reader) (err error) {
return err
}

d.IPVersion = reader.String("DOT_IP_VERSION")
return nil
}
20 changes: 17 additions & 3 deletions internal/config/outdated.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,28 @@ import (
)

func checkOutdatedEnv(reader *reader.Reader) (warnings []string) {
outdatedToMessage := map[string]string{
"IPV4": "The environment variable IPV4 is no longer functional or needed, " +
"since IPv6 addresses are used automatically if IPv6 support is detected.",
"IPV6": "The environment variable IPV6 is no longer functional or needed, " +
"since IPv6 addresses are used automatically if IPv6 support is detected.",
"DOT_CONNECT_IPV6": "The environment variable IPV6 is no longer functional or needed, " +
"since IPv6 addresses are used automatically if IPv6 support is detected.", // v2.0.0-beta variable
}

for outdated, warning := range outdatedToMessage {
value := reader.Get(outdated)
if value == nil {
continue
}
warnings = append(warnings, warning)
}

outdatedToNew := map[string][]string{
"LISTENINGPORT": {"LISTENING_ADDRESS"},
"PROVIDERS": {"DOT_RESOLVERS", "DOH_RESOLVERS"},
"PROVIDER": {"DOT_RESOLVERS", "DOH_RESOLVERS"},
"CACHING": {"CACHE_TYPE"},
"IPV4": {"DOT_IP_VERSION", "DOH_IP_VERSION"},
"IPV6": {"DOT_IP_VERSION", "DOH_IP_VERSION"},
"DOT_CONNECT_IPV6": {"DOT_IP_VERSION"}, // v2.0.0-beta variable
"UNBLOCK": {"ALLOWED_HOSTNAMES"},
"PRIVATE_ADDRESS": {"REBINDING_PROTECTION"},
"CHECK_UNBOUND": {"CHECK_DNS"},
Expand Down
23 changes: 22 additions & 1 deletion internal/dns/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"
"errors"
"fmt"
"net/netip"
"time"

"github.com/qdm12/dns/v2/internal/config"
"github.com/qdm12/dns/v2/internal/setup"
"github.com/qdm12/dns/v2/internal/support"
"github.com/qdm12/dns/v2/pkg/check"
"github.com/qdm12/dns/v2/pkg/middlewares/filter/mapfilter"
)
Expand All @@ -24,6 +26,7 @@ type Loop struct {
updateTimer *time.Timer
runCancel context.CancelFunc
runDone chan struct{}
ipv6Support bool
}

func New(settings config.Settings, logger Logger,
Expand All @@ -50,6 +53,11 @@ func (l *Loop) String() string {

func (l *Loop) Start(ctx context.Context) (
runError <-chan error, err error) {
err = l.checkIPv6Support(ctx)
if err != nil {
return nil, fmt.Errorf("checking IPv6 support: %w", err)
}

err = l.runFirst(ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -90,6 +98,19 @@ func (l *Loop) Stop() (err error) {
return l.dnsServer.Stop()
}

func (l *Loop) checkIPv6Support(ctx context.Context) (err error) {
l.ipv6Support, err = support.IPv6(ctx, netip.MustParseAddrPort("[2606:4700:4700::1111]:443"))
if err != nil {
return err
}
if l.ipv6Support {
l.logger.Info("IPv6 is supported, communicating with upstream resolvers only over IPv6")
} else {
l.logger.Info("IPv6 is not supported, communicating with upstream resolvers only over IPv4")
}
return nil
}

func (l *Loop) runFirst(ctx context.Context) (err error) {
const downloadBlockFiles = false
l.dnsServer, err = l.setupAll(ctx, downloadBlockFiles)
Expand Down Expand Up @@ -187,7 +208,7 @@ func (l *Loop) setupAll(ctx context.Context, downloadBlockFiles bool) ( //nolint
return nil, fmt.Errorf("setting up filter: %w", err)
}

server, err := setup.DNS(l.settings, l.cache,
server, err := setup.DNS(l.settings, l.ipv6Support, l.cache,
filter, l.logger, l.prometheusRegistry)
if err != nil {
return nil, err
Expand Down
6 changes: 3 additions & 3 deletions internal/setup/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Service interface {
Stop() (err error)
}

func DNS(userSettings config.Settings, //nolint:ireturn
func DNS(userSettings config.Settings, ipv6Support bool, //nolint:ireturn
cache Cache, filter Filter, logger Logger, promRegistry PrometheusRegistry) (
server Service, err error) {
var middlewares []Middleware
Expand Down Expand Up @@ -65,15 +65,15 @@ func DNS(userSettings config.Settings, //nolint:ireturn
return nil, fmt.Errorf("DoT metrics: %w", err)
}

return dotServer(userSettings, middlewares,
return dotServer(userSettings, ipv6Support, middlewares,
logger, dotMetrics)
case "doh":
dohMetrics, err := dohMetrics(metricsType, commonPrometheus)
if err != nil {
return nil, fmt.Errorf("DoH metrics: %w", err)
}

return dohServer(userSettings, middlewares,
return dohServer(userSettings, ipv6Support, middlewares,
logger, dohMetrics)
default:
panic(fmt.Sprintf("unknown upstream: %s", userSettings.Upstream))
Expand Down
9 changes: 7 additions & 2 deletions internal/setup/doh.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/qdm12/gosettings"
)

func dohServer(userSettings config.Settings,
func dohServer(userSettings config.Settings, ipv6Support bool,
middlewares []Middleware, logger Logger, metrics DoHMetrics) (
server *doh.Server, err error) {
providers := provider.NewProviders()
Expand All @@ -22,9 +22,14 @@ func dohServer(userSettings config.Settings,
return nil, fmt.Errorf("upstream resolvers: %w", err)
}

ipVersion := "ipv4"
if ipv6Support {
ipVersion = "ipv6"
}

resolverSettings := doh.ResolverSettings{
UpstreamResolvers: upstreamResolvers,
IPVersion: userSettings.DoH.IPVersion,
IPVersion: ipVersion,
Metrics: metrics,
}

Expand Down
9 changes: 7 additions & 2 deletions internal/setup/dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/qdm12/gosettings"
)

func dotServer(userSettings config.Settings,
func dotServer(userSettings config.Settings, ipv6Support bool,
middlewares []Middleware, logger Logger, metrics DoTMetrics) (
server *dot.Server, err error) {
providers := provider.NewProviders()
Expand All @@ -22,9 +22,14 @@ func dotServer(userSettings config.Settings,
return nil, fmt.Errorf("upstream resolvers: %w", err)
}

ipVersion := "ipv4"
if ipv6Support {
ipVersion = "ipv6"
}

resolverSettings := dot.ResolverSettings{
UpstreamResolvers: upstreamResolvers,
IPVersion: userSettings.DoT.IPVersion,
IPVersion: ipVersion,
Warner: logger,
Metrics: metrics,
}
Expand Down
39 changes: 39 additions & 0 deletions internal/support/ipv6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package support

import (
"context"
"fmt"
"net"
"net/netip"
"strings"
"time"
)

func IPv6(ctx context.Context, ipv6AddrPort netip.AddrPort) (
ipv6Supported bool, err error) {
if !ipv6AddrPort.IsValid() {
const cloudflareIPv6AddrPort = "[2606:4700:4700::1111]:443"
ipv6AddrPort = netip.MustParseAddrPort(cloudflareIPv6AddrPort)
}

dialer := net.Dialer{
Timeout: time.Second,
}
conn, err := dialer.DialContext(ctx, "tcp", ipv6AddrPort.String())
if err != nil {
if ctxErr := ctx.Err(); ctxErr != nil {
return false, ctxErr
}
if strings.HasSuffix(err.Error(), "cannot assign requested address") {
return false, nil
}
return false, fmt.Errorf("unknown error: %w", err)
}

err = conn.Close()
if err != nil {
return false, fmt.Errorf("closing connection: %w", err)
}

return true, nil
}
25 changes: 25 additions & 0 deletions internal/support/ipv6_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build integration
// +build integration

package support

import (
"context"
"net/netip"
"testing"
)

func Test_IPv6(t *testing.T) {
t.Parallel()

ctx := context.Background()

cloudflareIPv6AddrPort := netip.MustParseAddrPort("[2606:4700:4700::1111]:443")

ipv6Supported, err := IPv6(ctx, cloudflareIPv6AddrPort)
if err != nil {
t.Fatal(err)
}

t.Log("IPv6 supported:", ipv6Supported)
}

0 comments on commit 5ef315d

Please sign in to comment.