diff --git a/Dockerfile b/Dockerfile index b1836831..ee7d980c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -175,7 +175,6 @@ ENV VPN_SERVICE_PROVIDER=pia \ HEALTH_SMALL_CHECK_TYPE=icmp \ HEALTH_RESTART_VPN=on \ # DNS - DNS_SERVER=on \ DNS_UPSTREAM_RESOLVER_TYPE=DoT \ DNS_UPSTREAM_RESOLVERS=cloudflare \ DNS_BLOCK_IPS= \ @@ -188,8 +187,7 @@ ENV VPN_SERVICE_PROVIDER=pia \ DNS_UNBLOCK_HOSTNAMES= \ DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES= \ DNS_UPDATE_PERIOD=24h \ - DNS_ADDRESS=127.0.0.1 \ - DNS_KEEP_NAMESERVER=off \ + DNS_UPSTREAM_PLAIN_ADDRESSES= \ # HTTP proxy HTTPPROXY= \ HTTPPROXY_LOG=off \ diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index f82d40f9..d71db02b 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -402,7 +402,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, dnsLogger := logger.New(log.SetComponent("dns")) dnsLooper, err := dns.NewLoop(allSettings.DNS, httpClient, - dnsLogger) + dnsLogger, localNetworksToPrefixes(localNetworks)) if err != nil { return fmt.Errorf("creating DNS loop: %w", err) } @@ -567,6 +567,14 @@ func printVersions(ctx context.Context, logger infoer, return nil } +func localNetworksToPrefixes(localNetworks []routing.LocalNetwork) (prefixes []netip.Prefix) { + prefixes = make([]netip.Prefix, len(localNetworks)) + for i, localNetwork := range localNetworks { + prefixes[i] = localNetwork.IPNet + } + return prefixes +} + type netLinker interface { Addresser Router diff --git a/go.mod b/go.mod index 15a25626..f90b81dc 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/mdlayher/genetlink v1.3.2 github.com/mdlayher/netlink v1.7.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260216151239-36b3306f2205 + github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260227125914-6dbc4b280dc6 github.com/qdm12/gosettings v0.4.4 github.com/qdm12/goshutdown v0.3.0 github.com/qdm12/gosplash v0.2.1-0.20260305164749-b713de4fee6c diff --git a/go.sum b/go.sum index d30650d1..11971008 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPA github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260216151239-36b3306f2205 h1:0ycKUDQ50cYb2QpeyGcEnvVs9HJmC9jsb/XZNC1z28c= -github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260216151239-36b3306f2205/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE= +github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260227125914-6dbc4b280dc6 h1:N0UtsWF8NS1YuQ18Ws3hrhRAPmngoCns4KxoQjAcwaw= +github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260227125914-6dbc4b280dc6/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE= github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c= github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg= github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4= diff --git a/internal/configuration/settings/deprecated.go b/internal/configuration/settings/deprecated.go index 1e745eb0..5da27053 100644 --- a/internal/configuration/settings/deprecated.go +++ b/internal/configuration/settings/deprecated.go @@ -14,6 +14,10 @@ func readObsolete(r *reader.Reader) (warnings []string) { "DOT_VALIDATION_LOGLEVEL": "DOT_VALIDATION_LOGLEVEL is obsolete because DNSSEC validation is not implemented.", "HEALTH_VPN_DURATION_INITIAL": "HEALTH_VPN_DURATION_INITIAL is obsolete", "HEALTH_VPN_DURATION_ADDITION": "HEALTH_VPN_DURATION_ADDITION is obsolete", + "DNS_SERVER": "DNS_SERVER is obsolete because the forwarding server is always enabled.", + "DOT": "DOT is obsolete because the forwarding server is always enabled.", + "DNS_KEEP_NAMESERVER": "DNS_KEEP_NAMESERVER is obsolete because the forwarding server is always used and " + + "forwards local names to private DNS resolvers found in /etc/resolv.conf", } sortedKeys := maps.Keys(keyToMessage) slices.Sort(sortedKeys) diff --git a/internal/configuration/settings/dns.go b/internal/configuration/settings/dns.go index 4bf62b06..dcd29c36 100644 --- a/internal/configuration/settings/dns.go +++ b/internal/configuration/settings/dns.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/netip" + "slices" "time" "github.com/qdm12/dns/v2/pkg/provider" @@ -13,20 +14,25 @@ import ( "github.com/qdm12/gotree" ) +const ( + DNSUpstreamTypeDot = "dot" + DNSUpstreamTypeDoh = "doh" + DNSUpstreamTypePlain = "plain" +) + // DNS contains settings to configure DNS. type DNS struct { - // ServerEnabled is true if the server should be running - // and used. It defaults to true, and cannot be nil - // in the internal state. - ServerEnabled *bool - // UpstreamType can be dot or plain, and defaults to dot. + // UpstreamType can be [DNSUpstreamTypeDot], [DNSUpstreamTypeDoh] + // or [DNSUpstreamTypePlain]. It defaults to [DNSUpstreamTypeDot]. UpstreamType string `json:"upstream_type"` // UpdatePeriod is the period to update DNS block lists. // It can be set to 0 to disable the update. // It defaults to 24h and cannot be nil in // the internal state. UpdatePeriod *time.Duration - // Providers is a list of DNS providers + // Providers is a list of DNS providers. + // It defaults to either ["cloudflare"] or [] if the + // UpstreamPlainAddresses field is set. Providers []string `json:"providers"` // Caching is true if the server should cache // DNS responses. @@ -36,32 +42,23 @@ type DNS struct { // Blacklist contains settings to configure the filter // block lists. Blacklist DNSBlacklist - // ServerAddress is the DNS server to use inside - // the Go program and for the system. - // It defaults to '127.0.0.1' to be used with the - // local server. It cannot be the zero value in the internal - // state. - ServerAddress netip.Addr - // KeepNameserver is true if the existing DNS server - // found in /etc/resolv.conf should be used - // Note setting this to true will likely DNS traffic - // outside the VPN tunnel since it would go through - // the local DNS server of your Docker/Kubernetes - // configuration, which is likely not going through the tunnel. - // This will also disable the DNS forwarder server and the - // `ServerAddress` field will be ignored. - // It defaults to false and cannot be nil in the - // internal state. - KeepNameserver *bool + // UpstreamPlainAddresses are the upstream plaintext DNS resolver + // addresses to use by the built-in DNS server forwarder. + // Note, if the upstream type is [dnsUpstreamTypePlain] these are merged + // together with provider names set in the Providers field. + // If this field is set, the Providers field will default to the empty slice. + UpstreamPlainAddresses []netip.AddrPort } var ( ErrDNSUpstreamTypeNotValid = errors.New("DNS upstream type is not valid") ErrDNSUpdatePeriodTooShort = errors.New("update period is too short") + ErrDNSUpstreamPlainNoIPv6 = errors.New("upstream plain addresses do not contain any IPv6 address") + ErrDNSUpstreamPlainNoIPv4 = errors.New("upstream plain addresses do not contain any IPv4 address") ) func (d DNS) validate() (err error) { - if !helpers.IsOneOf(d.UpstreamType, "dot", "doh", "plain") { + if !helpers.IsOneOf(d.UpstreamType, DNSUpstreamTypeDot, DNSUpstreamTypeDoh, DNSUpstreamTypePlain) { return fmt.Errorf("%w: %s", ErrDNSUpstreamTypeNotValid, d.UpstreamType) } @@ -79,6 +76,18 @@ func (d DNS) validate() (err error) { } } + if d.UpstreamType == DNSUpstreamTypePlain { + if *d.IPv6 && !slices.ContainsFunc(d.UpstreamPlainAddresses, func(addrPort netip.AddrPort) bool { + return addrPort.Addr().Is6() + }) { + return fmt.Errorf("%w: in %d addresses", ErrDNSUpstreamPlainNoIPv6, len(d.UpstreamPlainAddresses)) + } else if !slices.ContainsFunc(d.UpstreamPlainAddresses, func(addrPort netip.AddrPort) bool { + return addrPort.Addr().Is4() + }) { + return fmt.Errorf("%w: in %d addresses", ErrDNSUpstreamPlainNoIPv4, len(d.UpstreamPlainAddresses)) + } + } + err = d.Blacklist.validate() if err != nil { return err @@ -89,15 +98,13 @@ func (d DNS) validate() (err error) { func (d *DNS) Copy() (copied DNS) { return DNS{ - ServerEnabled: gosettings.CopyPointer(d.ServerEnabled), - UpstreamType: d.UpstreamType, - UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod), - Providers: gosettings.CopySlice(d.Providers), - Caching: gosettings.CopyPointer(d.Caching), - IPv6: gosettings.CopyPointer(d.IPv6), - Blacklist: d.Blacklist.copy(), - ServerAddress: d.ServerAddress, - KeepNameserver: gosettings.CopyPointer(d.KeepNameserver), + UpstreamType: d.UpstreamType, + UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod), + Providers: gosettings.CopySlice(d.Providers), + Caching: gosettings.CopyPointer(d.Caching), + IPv6: gosettings.CopyPointer(d.IPv6), + Blacklist: d.Blacklist.copy(), + UpstreamPlainAddresses: d.UpstreamPlainAddresses, } } @@ -105,20 +112,17 @@ func (d *DNS) Copy() (copied DNS) { // settings object with any field set in the other // settings. func (d *DNS) overrideWith(other DNS) { - d.ServerEnabled = gosettings.OverrideWithPointer(d.ServerEnabled, other.ServerEnabled) d.UpstreamType = gosettings.OverrideWithComparable(d.UpstreamType, other.UpstreamType) d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod) d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers) d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching) d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6) d.Blacklist.overrideWith(other.Blacklist) - d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress) - d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver) + d.UpstreamPlainAddresses = gosettings.OverrideWithSlice(d.UpstreamPlainAddresses, other.UpstreamPlainAddresses) } func (d *DNS) setDefaults() { - d.ServerEnabled = gosettings.DefaultPointer(d.ServerEnabled, true) - d.UpstreamType = gosettings.DefaultComparable(d.UpstreamType, "dot") + d.UpstreamType = gosettings.DefaultComparable(d.UpstreamType, DNSUpstreamTypeDot) const defaultUpdatePeriod = 24 * time.Hour d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod) d.Providers = gosettings.DefaultSlice(d.Providers, []string{ @@ -127,26 +131,53 @@ func (d *DNS) setDefaults() { d.Caching = gosettings.DefaultPointer(d.Caching, true) d.IPv6 = gosettings.DefaultPointer(d.IPv6, false) d.Blacklist.setDefaults() - d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, - netip.AddrFrom4([4]byte{127, 0, 0, 1})) - d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false) + d.UpstreamPlainAddresses = gosettings.DefaultSlice(d.UpstreamPlainAddresses, []netip.AddrPort{}) +} + +func defaultDNSProviders() []string { + return []string{ + provider.Cloudflare().Name, + } } func (d DNS) GetFirstPlaintextIPv4() (ipv4 netip.Addr) { - localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1}) - if d.ServerAddress.Compare(localhost) != 0 && d.ServerAddress.Is4() { - return d.ServerAddress + if d.UpstreamType == DNSUpstreamTypePlain { + for _, addrPort := range d.UpstreamPlainAddresses { + if addrPort.Addr().Is4() { + return addrPort.Addr() + } + } } + ipv4 = findPlainIPv4InProviders(d.Providers) + if ipv4.IsValid() { + return ipv4 + } + + // Either: + // - all upstream plain addresses are IPv6 and no provider is set + // - all providers set do not have a plaintext IPv4 address + ipv4 = findPlainIPv4InProviders(defaultDNSProviders()) + if !ipv4.IsValid() { + panic("no plaintext IPv4 address found in default DNS providers") + } + return ipv4 +} + +func findPlainIPv4InProviders(providerNames []string) netip.Addr { providers := provider.NewProviders() - provider, err := providers.Get(d.Providers[0]) - if err != nil { - // Settings should be validated before calling this function, - // so an error happening here is a programming error. - panic(err) + for _, name := range providerNames { + provider, err := providers.Get(name) + if err != nil { + // Settings should be validated before calling this function, + // so an error happening here is a programming error. + panic(err) + } + if len(provider.Plain.IPv4) > 0 { + return provider.Plain.IPv4[0].Addr() + } } - - return provider.Plain.IPv4[0].Addr() + return netip.Addr{} } func (d DNS) String() string { @@ -155,22 +186,22 @@ func (d DNS) String() string { func (d DNS) toLinesNode() (node *gotree.Node) { node = gotree.New("DNS settings:") - node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver)) - if *d.KeepNameserver { - return node - } - node.Appendf("DNS server address to use: %s", d.ServerAddress) - - node.Appendf("DNS forwarder server enabled: %s", gosettings.BoolToYesNo(d.ServerEnabled)) - if !*d.ServerEnabled { - return node - } node.Appendf("Upstream resolver type: %s", d.UpstreamType) upstreamResolvers := node.Append("Upstream resolvers:") - for _, provider := range d.Providers { - upstreamResolvers.Append(provider) + if len(d.UpstreamPlainAddresses) > 0 { + if d.UpstreamType == DNSUpstreamTypePlain { + for _, addr := range d.UpstreamPlainAddresses { + upstreamResolvers.Append(addr.String()) + } + } else { + node.Appendf("Upstream plain addresses: ignored because upstream type is not plain") + } + } else { + for _, provider := range d.Providers { + upstreamResolvers.Append(provider) + } } node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching)) @@ -188,11 +219,6 @@ func (d DNS) toLinesNode() (node *gotree.Node) { } func (d *DNS) read(r *reader.Reader) (err error) { - d.ServerEnabled, err = r.BoolPtr("DNS_SERVER", reader.RetroKeys("DOT")) - if err != nil { - return err - } - d.UpstreamType = r.String("DNS_UPSTREAM_RESOLVER_TYPE") d.UpdatePeriod, err = r.DurationPtr("DNS_UPDATE_PERIOD") @@ -217,15 +243,43 @@ func (d *DNS) read(r *reader.Reader) (err error) { return err } - d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS")) - if err != nil { - return err - } - - d.KeepNameserver, err = r.BoolPtr("DNS_KEEP_NAMESERVER") + err = d.readUpstreamPlainAddresses(r) if err != nil { return err } return nil } + +func (d *DNS) readUpstreamPlainAddresses(r *reader.Reader) (err error) { + // If DNS_UPSTREAM_PLAIN_ADDRESSES is set, the user must also set DNS_UPSTREAM_TYPE=plain + // for these to be used. This is an added safety measure to reduce misunderstandings, and + // reduce odd settings overrides. + d.UpstreamPlainAddresses, err = r.CSVNetipAddrPorts("DNS_UPSTREAM_PLAIN_ADDRESSES") + if err != nil { + return err + } + + // Retro-compatibility - remove in v4 + // If DNS_ADDRESS is set to a non-localhost address, append it to the other + // upstream plain addresses, assuming port 53, and force the upstream type to plain AND + // clear any user picked providers, to maintain retro-compatibility behavior. + serverAddress, err := r.NetipAddr("DNS_ADDRESS", + reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"), + reader.IsRetro("DNS_UPSTREAM_PLAIN_ADDRESSES")) + if err != nil { + return err + } else if !serverAddress.IsValid() { + return nil + } + isLocalhost := serverAddress.Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) == 0 + if isLocalhost { + return nil + } + const defaultPlainPort = 53 + addrPort := netip.AddrPortFrom(serverAddress, defaultPlainPort) + d.UpstreamPlainAddresses = append(d.UpstreamPlainAddresses, addrPort) + d.UpstreamType = DNSUpstreamTypePlain + d.Providers = []string{} + return nil +} diff --git a/internal/configuration/settings/dns_test.go b/internal/configuration/settings/dns_test.go new file mode 100644 index 00000000..81f29d06 --- /dev/null +++ b/internal/configuration/settings/dns_test.go @@ -0,0 +1,26 @@ +package settings + +import ( + "testing" + + "github.com/qdm12/dns/v2/pkg/provider" + "github.com/stretchr/testify/require" +) + +func Test_defaultDNSProviders(t *testing.T) { + t.Parallel() + + names := defaultDNSProviders() + + found := false + providers := provider.NewProviders() + for _, name := range names { + provider, err := providers.Get(name) + require.NoError(t, err) + if len(provider.Plain.IPv4) > 0 { + found = true + break + } + } + require.True(t, found, "no default DNS provider has a plaintext IPv4 address") +} diff --git a/internal/configuration/settings/settings.go b/internal/configuration/settings/settings.go index beff08f3..66944215 100644 --- a/internal/configuration/settings/settings.go +++ b/internal/configuration/settings/settings.go @@ -2,7 +2,6 @@ package settings import ( "fmt" - "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/constants/providers" @@ -180,13 +179,11 @@ func (s Settings) Warnings() (warnings []string) { "by creating an issue, attaching the new certificate and we will update Gluetun.") } - // TODO remove in v4 - if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { - warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+ - " so the local forwarding DNS server will not be used."+ - " The default value changed to 127.0.0.1 so it uses the internal DNS server."+ - " If this server fails to start, the IPv4 address of the first plaintext DNS server"+ - " corresponding to the first DNS provider chosen is used.") + for _, upstreamAddress := range s.DNS.UpstreamPlainAddresses { + if upstreamAddress.Addr().IsPrivate() { + warnings = append(warnings, "DNS upstream address "+upstreamAddress.String()+" is private: "+ + "DNS traffic might leak out of the VPN tunnel to that address.") + } } return warnings diff --git a/internal/configuration/settings/settings_test.go b/internal/configuration/settings/settings_test.go index 73c3cb28..b4e23cc4 100644 --- a/internal/configuration/settings/settings_test.go +++ b/internal/configuration/settings/settings_test.go @@ -51,9 +51,6 @@ func Test_Settings_String(t *testing.T) { | ├── [2606:4700:4700::1111]:443 | └── [2001:4860:4860::8888]:443 ├── DNS settings: -| ├── Keep existing nameserver(s): no -| ├── DNS server address to use: 127.0.0.1 -| ├── DNS forwarder server enabled: yes | ├── Upstream resolver type: dot | ├── Upstream resolvers: | | └── Cloudflare diff --git a/internal/dns/logger.go b/internal/dns/logger.go index e661a13d..07a93fe1 100644 --- a/internal/dns/logger.go +++ b/internal/dns/logger.go @@ -3,6 +3,8 @@ package dns type Logger interface { Debug(s string) Info(s string) + Infof(format string, args ...any) Warn(s string) + Warnf(format string, args ...any) Error(s string) } diff --git a/internal/dns/loop.go b/internal/dns/loop.go index a8f3fa1a..a91ffab6 100644 --- a/internal/dns/loop.go +++ b/internal/dns/loop.go @@ -22,6 +22,7 @@ type Loop struct { server *server.Server filter *mapfilter.Filter localResolvers []netip.Addr + localSubnets []netip.Prefix resolvConf string client *http.Client logger Logger @@ -39,7 +40,7 @@ type Loop struct { const defaultBackoffTime = 10 * time.Second func NewLoop(settings settings.DNS, - client *http.Client, logger Logger, + client *http.Client, logger Logger, localSubnets []netip.Prefix, ) (loop *Loop, err error) { start := make(chan struct{}) running := make(chan models.LoopStatus) @@ -62,6 +63,7 @@ func NewLoop(settings settings.DNS, state: state, server: nil, filter: filter, + localSubnets: localSubnets, resolvConf: "/etc/resolv.conf", client: client, logger: logger, diff --git a/internal/dns/run.go b/internal/dns/run.go index 76d20481..f31a9dea 100644 --- a/internal/dns/run.go +++ b/internal/dns/run.go @@ -17,14 +17,8 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) { return } - if *l.GetSettings().KeepNameserver { - l.logger.Warn("⚠️⚠️⚠️ keeping the default container nameservers, " + - "this will likely leak DNS traffic outside the VPN " + - "and go through your container network DNS outside the VPN tunnel!") - } else { - const fallback = false - l.useUnencryptedDNS(fallback) - } + const fallback = false + l.useUnencryptedDNS(fallback) select { case <-l.start: @@ -37,13 +31,13 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) { // Their values are to be used if DOT=off var runError <-chan error - settings := l.GetSettings() - for !*settings.KeepNameserver && *settings.ServerEnabled { + for { + settings := l.GetSettings() var err error - runError, err = l.setupServer(ctx) + runError, err = l.setupServer(ctx, settings) if err == nil { l.backoffTime = defaultBackoffTime - l.logger.Info("ready and using DNS server at address " + settings.ServerAddress.String()) + l.logger.Infof("ready and using DNS server with %s upstream resolvers", settings.UpstreamType) err = l.updateFiles(ctx, settings) if err != nil { @@ -58,16 +52,9 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) { return } l.logAndWait(ctx, err) - settings = l.GetSettings() } l.signalOrSetStatus(constants.Running) - settings = l.GetSettings() - if !*settings.KeepNameserver && !*settings.ServerEnabled { - const fallback = false - l.useUnencryptedDNS(fallback) - } - l.userTrigger = false exitLoop := l.runWait(ctx, runError) @@ -81,21 +68,15 @@ func (l *Loop) runWait(ctx context.Context, runError <-chan error) (exitLoop boo for { select { case <-ctx.Done(): - settings := l.GetSettings() - if !*settings.KeepNameserver && *settings.ServerEnabled { - l.stopServer() - // TODO revert OS and Go nameserver when exiting - } + l.stopServer() + // TODO revert OS and Go nameserver when exiting return true case <-l.stop: l.userTrigger = true l.logger.Info("stopping") - settings := l.GetSettings() - if !*settings.KeepNameserver && *settings.ServerEnabled { - const fallback = false - l.useUnencryptedDNS(fallback) - l.stopServer() - } + const fallback = false + l.useUnencryptedDNS(fallback) + l.stopServer() l.stopped <- struct{}{} case <-l.start: l.userTrigger = true diff --git a/internal/dns/settings.go b/internal/dns/settings.go index ead3cec6..60840e01 100644 --- a/internal/dns/settings.go +++ b/internal/dns/settings.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/netip" + "slices" "github.com/qdm12/dns/v2/pkg/doh" "github.com/qdm12/dns/v2/pkg/dot" @@ -26,31 +27,23 @@ func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) ( return l.state.SetSettings(ctx, settings) } -func buildServerSettings(settings settings.DNS, +func buildServerSettings(userSettings settings.DNS, filter *mapfilter.Filter, localResolvers []netip.Addr, - logger Logger) ( + localSubnets []netip.Prefix, logger Logger) ( serverSettings server.Settings, err error, ) { serverSettings.Logger = logger - providersData := provider.NewProviders() - upstreamResolvers := make([]provider.Provider, len(settings.Providers)) - for i := range settings.Providers { - var err error - upstreamResolvers[i], err = providersData.Get(settings.Providers[i]) - if err != nil { - panic(err) // this should already had been checked - } - } + upstreamResolvers := buildProviders(userSettings, localSubnets, logger) ipVersion := "ipv4" - if *settings.IPv6 { + if *userSettings.IPv6 { ipVersion = "ipv6" } var dialer server.Dialer - switch settings.UpstreamType { - case "dot": + switch userSettings.UpstreamType { + case settings.DNSUpstreamTypeDot: dialerSettings := dot.Settings{ UpstreamResolvers: upstreamResolvers, IPVersion: ipVersion, @@ -59,7 +52,7 @@ func buildServerSettings(settings settings.DNS, if err != nil { return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err) } - case "doh": + case settings.DNSUpstreamTypeDoh: dialerSettings := doh.Settings{ UpstreamResolvers: upstreamResolvers, IPVersion: ipVersion, @@ -68,7 +61,7 @@ func buildServerSettings(settings settings.DNS, if err != nil { return server.Settings{}, fmt.Errorf("creating DNS over HTTPS dialer: %w", err) } - case "plain": + case settings.DNSUpstreamTypePlain: dialerSettings := plain.Settings{ UpstreamResolvers: upstreamResolvers, IPVersion: ipVersion, @@ -78,11 +71,11 @@ func buildServerSettings(settings settings.DNS, return server.Settings{}, fmt.Errorf("creating plain DNS dialer: %w", err) } default: - panic("unknown upstream type: " + settings.UpstreamType) + panic("unknown upstream type: " + userSettings.UpstreamType) } serverSettings.Dialer = dialer - if *settings.Caching { + if *userSettings.Caching { lruCache, err := lru.New(lru.Settings{}) if err != nil { return server.Settings{}, fmt.Errorf("creating LRU cache: %w", err) @@ -123,3 +116,46 @@ func buildServerSettings(settings settings.DNS, return serverSettings, nil } + +func buildProviders(userSettings settings.DNS, localSubnets []netip.Prefix, + logger Logger, +) (providers []provider.Provider) { + providersCount := len(userSettings.Providers) + if userSettings.UpstreamType == settings.DNSUpstreamTypePlain { + providersCount += len(userSettings.UpstreamPlainAddresses) + } + providers = make([]provider.Provider, 0, providersCount) + + providersData := provider.NewProviders() + for _, providerName := range userSettings.Providers { + provider, err := providersData.Get(providerName) + if err != nil { + panic(err) // this should already had been checked + } + providers = append(providers, provider) + } + + for _, addrPort := range userSettings.UpstreamPlainAddresses { + addr := addrPort.Addr() + if addr.IsPrivate() && !addr.IsLoopback() && + !slices.ContainsFunc(localSubnets, func(prefix netip.Prefix) bool { + return prefix.Contains(addr) + }) { + logger.Warnf("DNS server address %s is not in local subnets, "+ + "make sure to specify it in FIREWALL_OUTBOUND_SUBNETS as %s", + addr, netip.PrefixFrom(addr, addr.BitLen())) + } + + provider := provider.Provider{ + Name: addrPort.String(), + } + if addr.Is4() { + provider.Plain.IPv4 = []netip.AddrPort{addrPort} + } else { + provider.Plain.IPv6 = []netip.AddrPort{addrPort} + } + providers = append(providers, provider) + } + + return providers +} diff --git a/internal/dns/setup.go b/internal/dns/setup.go index c9ebc0b8..8e83de3c 100644 --- a/internal/dns/setup.go +++ b/internal/dns/setup.go @@ -3,16 +3,15 @@ package dns import ( "context" "fmt" - "net/netip" "github.com/qdm12/dns/v2/pkg/check" "github.com/qdm12/dns/v2/pkg/middlewares/filter/update" "github.com/qdm12/dns/v2/pkg/nameserver" "github.com/qdm12/dns/v2/pkg/server" + "github.com/qdm12/gluetun/internal/configuration/settings" ) -func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err error) { - settings := l.GetSettings() +func (l *Loop) setupServer(ctx context.Context, settings settings.DNS) (runError <-chan error, err error) { var updateSettings update.Settings updateSettings.SetRebindingProtectionExempt(settings.Blacklist.RebindingProtectionExemptHostnames) err = l.filter.Update(updateSettings) @@ -20,7 +19,7 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro return nil, fmt.Errorf("updating filter for rebinding protection: %w", err) } - serverSettings, err := buildServerSettings(settings, l.filter, l.localResolvers, l.logger) + serverSettings, err := buildServerSettings(settings, l.filter, l.localResolvers, l.localSubnets, l.logger) if err != nil { return nil, fmt.Errorf("building server settings: %w", err) } @@ -37,12 +36,8 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro l.server = server // use internal DNS server - const defaultDNSPort = 53 - nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{ - AddrPort: netip.AddrPortFrom(settings.ServerAddress, defaultDNSPort), - }) + nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{}) err = nameserver.UseDNSSystemWide(nameserver.SettingsSystemDNS{ - IPs: []netip.Addr{settings.ServerAddress}, ResolvPath: l.resolvConf, }) if err != nil { diff --git a/internal/dns/state/settings.go b/internal/dns/state/settings.go index 302c0e7c..5eab8271 100644 --- a/internal/dns/state/settings.go +++ b/internal/dns/state/settings.go @@ -40,8 +40,6 @@ func (s *State) SetSettings(ctx context.Context, settings settings.DNS) ( // Restart _, _ = s.statusApplier.ApplyStatus(ctx, constants.Stopped) - if *settings.ServerEnabled { - outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running) - } + outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running) return outcome } diff --git a/internal/vpn/tunnelup.go b/internal/vpn/tunnelup.go index a8eb31a2..6d132ef4 100644 --- a/internal/vpn/tunnelup.go +++ b/internal/vpn/tunnelup.go @@ -6,7 +6,6 @@ import ( "net/netip" "time" - "github.com/qdm12/dns/v2/pkg/check" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/netlink" "github.com/qdm12/gluetun/internal/pmtud" @@ -91,14 +90,7 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) { // to start monitoring health and auto-healing. go l.collectHealthErrors(ctx, loopCtx, healthErrCh) - if *l.dnsLooper.GetSettings().ServerEnabled { - _, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running) - } else { - err := check.WaitForDNS(ctx, check.Settings{}) - if err != nil { - l.logger.Error("waiting for DNS to be ready: " + err.Error()) - } - } + _, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running) err = l.publicip.RunOnce(ctx) if err != nil {