Files
gluetun/internal/dns/loop.go
Quentin McGaw 199ad77ec9 chore(dns): remove DNS_SERVER, DNS_KEEP_NAMESERVER and replace DNS_ADDRESS with DNS_UPSTREAM_PLAIN_ADDRESSES (#2988)
- 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.
2026-03-07 14:07:57 +01:00

121 lines
2.9 KiB
Go

package dns
import (
"context"
"fmt"
"net/http"
"net/netip"
"time"
"github.com/qdm12/dns/v2/pkg/middlewares/filter/mapfilter"
"github.com/qdm12/dns/v2/pkg/server"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns/state"
"github.com/qdm12/gluetun/internal/loopstate"
"github.com/qdm12/gluetun/internal/models"
)
type Loop struct {
statusManager *loopstate.State
state *state.State
server *server.Server
filter *mapfilter.Filter
localResolvers []netip.Addr
localSubnets []netip.Prefix
resolvConf string
client *http.Client
logger Logger
userTrigger bool
start <-chan struct{}
running chan<- models.LoopStatus
stop <-chan struct{}
stopped chan<- struct{}
updateTicker <-chan struct{}
backoffTime time.Duration
timeNow func() time.Time
timeSince func(time.Time) time.Duration
}
const defaultBackoffTime = 10 * time.Second
func NewLoop(settings settings.DNS,
client *http.Client, logger Logger, localSubnets []netip.Prefix,
) (loop *Loop, err error) {
start := make(chan struct{})
running := make(chan models.LoopStatus)
stop := make(chan struct{})
stopped := make(chan struct{})
updateTicker := make(chan struct{})
statusManager := loopstate.New(constants.Stopped, start, running, stop, stopped)
state := state.New(statusManager, settings, updateTicker)
filter, err := mapfilter.New(mapfilter.Settings{
Logger: buildFilterLogger(logger),
})
if err != nil {
return nil, fmt.Errorf("creating map filter: %w", err)
}
return &Loop{
statusManager: statusManager,
state: state,
server: nil,
filter: filter,
localSubnets: localSubnets,
resolvConf: "/etc/resolv.conf",
client: client,
logger: logger,
userTrigger: true,
start: start,
running: running,
stop: stop,
stopped: stopped,
updateTicker: updateTicker,
backoffTime: defaultBackoffTime,
timeNow: time.Now,
timeSince: time.Since,
}, nil
}
func (l *Loop) logAndWait(ctx context.Context, err error) {
if err != nil {
l.logger.Warn(err.Error())
}
l.logger.Info("attempting restart in " + l.backoffTime.String())
timer := time.NewTimer(l.backoffTime)
l.backoffTime *= 2
select {
case <-timer.C:
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
}
}
func (l *Loop) signalOrSetStatus(status models.LoopStatus) {
if l.userTrigger {
l.userTrigger = false
select {
case l.running <- status:
default: // receiver dropped out - avoid deadlock on events routing when shutting down
}
} else {
l.statusManager.SetStatus(status)
}
}
type filterLogger struct {
logger Logger
}
func (l *filterLogger) Log(msg string) {
l.logger.Debug(msg)
}
func buildFilterLogger(logger Logger) *filterLogger {
return &filterLogger{logger: logger}
}