mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-07 12:30:11 +02:00
199ad77ec9
- Remove `DNS_SERVER` (aka DOT) option: the DNS server forwarder part is now always enabled (see below why) - Remove `DNS_KEEP_NAMESERVER`: the container will always use the built-in DNS server forwarder, because it can handle now local names with local resolvers (see #2970), it can use the `plain` upstream type (see https://github.com/qdm12/gluetun/commit/5ed6e8292278b54bb5081de0e8ccd0d63a275b3c) AND you can use `DNS_UPSTREAM_PLAIN_ADDRESSES` (see below) - Replace `DNS_ADDRESS` with `DNS_UPSTREAM_PLAIN_ADDRESSES`: - New CSV format with port, for example `ip1:port1,ip2:port2` - requires `DNS_UPSTREAM_TYPE=plain` to be set to use `DNS_UPSTREAM_PLAIN_ADDRESSES` (unless using retro `DNS_ADDRESS`) - retrocompatibility with `DNS_ADDRESS`. If set, force upstream type to plain and empty user-picked providers. 127.0.0.1 is now ignored since it's always set to this value internally. - Warning log on using private upstream resolvers updated - Warning log if using a private IP address for the plain DNS server which is not in your local subnets All in all, this greatly simplifies code and available options (less options for the same features is a win). It also allows you to specify multiple plain DNS resolvers on ports other than 53 if needed.
286 lines
8.8 KiB
Go
286 lines
8.8 KiB
Go
package settings
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/qdm12/dns/v2/pkg/provider"
|
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
|
"github.com/qdm12/gosettings"
|
|
"github.com/qdm12/gosettings/reader"
|
|
"github.com/qdm12/gotree"
|
|
)
|
|
|
|
const (
|
|
DNSUpstreamTypeDot = "dot"
|
|
DNSUpstreamTypeDoh = "doh"
|
|
DNSUpstreamTypePlain = "plain"
|
|
)
|
|
|
|
// DNS contains settings to configure DNS.
|
|
type DNS struct {
|
|
// 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.
|
|
// 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.
|
|
Caching *bool `json:"caching"`
|
|
// IPv6 is true if the server should connect over IPv6.
|
|
IPv6 *bool `json:"ipv6"`
|
|
// Blacklist contains settings to configure the filter
|
|
// block lists.
|
|
Blacklist DNSBlacklist
|
|
// 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, DNSUpstreamTypeDot, DNSUpstreamTypeDoh, DNSUpstreamTypePlain) {
|
|
return fmt.Errorf("%w: %s", ErrDNSUpstreamTypeNotValid, d.UpstreamType)
|
|
}
|
|
|
|
const minUpdatePeriod = 30 * time.Second
|
|
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
|
|
return fmt.Errorf("%w: %s must be bigger than %s",
|
|
ErrDNSUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
|
}
|
|
|
|
providers := provider.NewProviders()
|
|
for _, providerName := range d.Providers {
|
|
_, err := providers.Get(providerName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *DNS) Copy() (copied DNS) {
|
|
return DNS{
|
|
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,
|
|
}
|
|
}
|
|
|
|
// overrideWith overrides fields of the receiver
|
|
// settings object with any field set in the other
|
|
// settings.
|
|
func (d *DNS) overrideWith(other DNS) {
|
|
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.UpstreamPlainAddresses = gosettings.OverrideWithSlice(d.UpstreamPlainAddresses, other.UpstreamPlainAddresses)
|
|
}
|
|
|
|
func (d *DNS) setDefaults() {
|
|
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{
|
|
provider.Cloudflare().Name,
|
|
})
|
|
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
|
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
|
d.Blacklist.setDefaults()
|
|
d.UpstreamPlainAddresses = gosettings.DefaultSlice(d.UpstreamPlainAddresses, []netip.AddrPort{})
|
|
}
|
|
|
|
func defaultDNSProviders() []string {
|
|
return []string{
|
|
provider.Cloudflare().Name,
|
|
}
|
|
}
|
|
|
|
func (d DNS) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
|
|
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()
|
|
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 netip.Addr{}
|
|
}
|
|
|
|
func (d DNS) String() string {
|
|
return d.toLinesNode().String()
|
|
}
|
|
|
|
func (d DNS) toLinesNode() (node *gotree.Node) {
|
|
node = gotree.New("DNS settings:")
|
|
|
|
node.Appendf("Upstream resolver type: %s", d.UpstreamType)
|
|
|
|
upstreamResolvers := node.Append("Upstream resolvers:")
|
|
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))
|
|
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
|
|
|
update := "disabled"
|
|
if *d.UpdatePeriod > 0 {
|
|
update = "every " + d.UpdatePeriod.String()
|
|
}
|
|
node.Appendf("Update period: %s", update)
|
|
|
|
node.AppendNode(d.Blacklist.toLinesNode())
|
|
|
|
return node
|
|
}
|
|
|
|
func (d *DNS) read(r *reader.Reader) (err error) {
|
|
d.UpstreamType = r.String("DNS_UPSTREAM_RESOLVER_TYPE")
|
|
|
|
d.UpdatePeriod, err = r.DurationPtr("DNS_UPDATE_PERIOD")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.Providers = r.CSV("DNS_UPSTREAM_RESOLVERS", reader.RetroKeys("DOT_PROVIDERS"))
|
|
|
|
d.Caching, err = r.BoolPtr("DNS_CACHING", reader.RetroKeys("DOT_CACHING"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.IPv6, err = r.BoolPtr("DNS_UPSTREAM_IPV6", reader.RetroKeys("DOT_IPV6"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = d.Blacklist.read(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|