Compare commits

...

2 Commits

Author SHA1 Message Date
Viktor Liu
fd3f98792a Clarify checkStub doc and parse-error behavior 2026-04-21 06:44:40 +02:00
Viktor Liu
d5e9eb814e Prefer systemd-resolved stub over file mode regardless of resolv.conf header 2026-04-21 06:29:24 +02:00

View File

@@ -75,6 +75,16 @@ func newHostManagerFromType(wgInterface string, osManager osManagerType) (restor
}
func getOSDNSManagerType() (osManagerType, error) {
// If systemd-resolved is serving on 127.0.0.53, prefer it regardless of
// who owns /etc/resolv.conf. NetworkManager often rewrites resolv.conf with
// its own header while still deferring resolution to systemd-resolved, and
// falling back to file mode there snapshots 127.0.0.53 as the fallback
// upstream. systemd-resolved in foreign mode re-reads /etc/resolv.conf and
// ingests our address as global DNS, which closes the loop.
if isSystemdResolvedRunning() && checkStub() {
return systemdManager, nil
}
file, err := os.Open(defaultResolvConfPath)
if err != nil {
return 0, fmt.Errorf("unable to open %s for checking owner, got error: %w", defaultResolvConfPath, err)
@@ -100,13 +110,6 @@ func getOSDNSManagerType() (osManagerType, error) {
if strings.Contains(text, "NetworkManager") && isDbusListenerRunning(networkManagerDest, networkManagerDbusObjectNode) && isNetworkManagerSupported() {
return networkManager, nil
}
if strings.Contains(text, "systemd-resolved") && isSystemdResolvedRunning() {
if checkStub() {
return systemdManager, nil
} else {
return fileManager, nil
}
}
if strings.Contains(text, "resolvconf") {
if isSystemdResolveConfMode() {
return systemdManager, nil
@@ -122,11 +125,17 @@ func getOSDNSManagerType() (osManagerType, error) {
return fileManager, nil
}
// checkStub checks if the stub resolver is disabled in systemd-resolved. If it is disabled, we fall back to file manager.
// checkStub reports whether systemd-resolved's stub address (127.0.0.53) is
// listed in /etc/resolv.conf. A true return value signals that callers should
// prefer systemd-resolved; it does not make the final manager decision by
// itself (non-stub systems still fall through to the header scanner).
// On parse failure we assume the stub is present to avoid dropping into file
// mode while resolved is active, which would re-ingest NetBird's address as
// an upstream and form a resolution loop.
func checkStub() bool {
rConf, err := parseDefaultResolvConf()
if err != nil {
log.Warnf("failed to parse resolv conf: %s", err)
log.Warnf("failed to parse resolv conf, assuming stub is active: %s", err)
return true
}