@ -5,11 +5,11 @@
package dns
package dns
import (
import (
"bytes"
"context"
"context"
"errors"
"errors"
"fmt"
"fmt"
"os"
"os"
"os/exec"
"time"
"time"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5"
@ -27,36 +27,52 @@ func (kv kv) String() string {
}
}
func NewOSConfigurator ( logf logger . Logf , interfaceName string ) ( ret OSConfigurator , err error ) {
func NewOSConfigurator ( logf logger . Logf , interfaceName string ) ( ret OSConfigurator , err error ) {
return newOSConfigurator ( logf , interfaceName , newOSConfigEnv {
env := newOSConfigEnv {
fs : directFS { } ,
fs : directFS { } ,
resolvOwner : resolvOwner ,
dbusPing : dbusPing ,
resolvedIsActuallyResolver : resolvedIsActuallyResolver ,
nmIsUsingResolved : nmIsUsingResolved ,
dbusPing : dbusPing ,
nmVersionBetween : nmVersionBetween ,
nmIsUsingResolved : nmIsUsingResolved ,
resolvconfStyle : resolvconfStyle ,
nmVersionBetween : nmVersionBetween ,
}
getResolvConfVersion : getResolvConfVersion ,
mode , err := dnsMode ( logf , env )
} )
if err != nil {
return nil , err
}
switch mode {
case "direct" :
return newDirectManagerOnFS ( env . fs ) , nil
case "systemd-resolved" :
return newResolvedManager ( logf , interfaceName )
case "network-manager" :
return newNMManager ( interfaceName )
case "debian-resolvconf" :
return newDebianResolvconfManager ( logf )
case "openresolv" :
return newOpenresolvManager ( )
default :
logf ( "[unexpected] detected unknown DNS mode %q, using direct manager as last resort" , mode )
return newDirectManagerOnFS ( env . fs ) , nil
}
}
}
// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
type newOSConfigEnv struct {
type newOSConfigEnv struct {
fs wholeFileFS
fs wholeFileFS
resolvOwner func ( resolvConfContents [ ] byte ) string
dbusPing func ( string , string ) error
resolvedIsActuallyResolver func ( wholeFileFS ) error
nmIsUsingResolved func ( ) error
dbusPing func ( string , string ) error
nmVersionBetween func ( v1 , v2 string ) ( safe bool , err error )
nmIsUsingResolved func ( ) error
resolvconfStyle func ( ) string
nmVersionBetween func ( v1 , v2 string ) ( safe bool , err error )
isResolvconfDebianVersion func ( ) bool
getResolvConfVersion func ( ) ( [ ] byte , error )
}
}
func newOSConfigurator ( logf logger . Logf , interfaceName string , env newOSConfigEnv ) ( ret OSConfigurator , err error ) {
func dnsMode ( logf logger . Logf , env newOSConfigEnv ) ( ret string , err error ) {
var debug [ ] kv
var debug [ ] kv
dbg := func ( k , v string ) {
dbg := func ( k , v string ) {
debug = append ( debug , kv { k , v } )
debug = append ( debug , kv { k , v } )
}
}
defer func ( ) {
defer func ( ) {
if ret != nil {
if ret != "" {
dbg ( "ret" , fmt. Sprintf ( "%T" , ret) )
dbg ( "ret" , ret)
}
}
logf ( "dns: %v" , debug )
logf ( "dns: %v" , debug )
} ( )
} ( )
@ -64,13 +80,13 @@ func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEn
bs , err := env . fs . ReadFile ( resolvConf )
bs , err := env . fs . ReadFile ( resolvConf )
if os . IsNotExist ( err ) {
if os . IsNotExist ( err ) {
dbg ( "rc" , "missing" )
dbg ( "rc" , "missing" )
return newDirectManager ( ) , nil
return "direct" , nil
}
}
if err != nil {
if err != nil {
return nil , fmt . Errorf ( "reading /etc/resolv.conf: %w" , err )
return "" , fmt . Errorf ( "reading /etc/resolv.conf: %w" , err )
}
}
switch env . resolvOwner ( bs ) {
switch resolvOwner ( bs ) {
case "systemd-resolved" :
case "systemd-resolved" :
dbg ( "rc" , "resolved" )
dbg ( "rc" , "resolved" )
// Some systems, for reasons known only to them, have a
// Some systems, for reasons known only to them, have a
@ -78,22 +94,22 @@ func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEn
// header, but doesn't actually point to resolved. We mustn't
// header, but doesn't actually point to resolved. We mustn't
// try to program resolved in that case.
// try to program resolved in that case.
// https://github.com/tailscale/tailscale/issues/2136
// https://github.com/tailscale/tailscale/issues/2136
if err := env . resolvedIsActuallyResolver ( env . f s) ; err != nil {
if err := resolvedIsActuallyResolver ( b s) ; err != nil {
dbg ( "resolved" , "not-in-use" )
dbg ( "resolved" , "not-in-use" )
return newDirectManagerOnFS ( env . fs ) , nil
return "direct" , nil
}
}
if err := env . dbusPing ( "org.freedesktop.resolve1" , "/org/freedesktop/resolve1" ) ; err != nil {
if err := env . dbusPing ( "org.freedesktop.resolve1" , "/org/freedesktop/resolve1" ) ; err != nil {
dbg ( "resolved" , "no" )
dbg ( "resolved" , "no" )
return newDirectManagerOnFS ( env . fs ) , nil
return "direct" , nil
}
}
if err := env . dbusPing ( "org.freedesktop.NetworkManager" , "/org/freedesktop/NetworkManager/DnsManager" ) ; err != nil {
if err := env . dbusPing ( "org.freedesktop.NetworkManager" , "/org/freedesktop/NetworkManager/DnsManager" ) ; err != nil {
dbg ( "nm" , "no" )
dbg ( "nm" , "no" )
return newResolvedManager ( logf , interfaceName )
return "systemd-resolved" , nil
}
}
dbg ( "nm" , "yes" )
dbg ( "nm" , "yes" )
if err := env . nmIsUsingResolved ( ) ; err != nil {
if err := env . nmIsUsingResolved ( ) ; err != nil {
dbg ( "nm-resolved" , "no" )
dbg ( "nm-resolved" , "no" )
return newResolvedManager ( logf , interfaceName )
return "systemd-resolved" , nil
}
}
dbg ( "nm-resolved" , "yes" )
dbg ( "nm-resolved" , "yes" )
@ -131,26 +147,38 @@ func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEn
// that comes with it (see
// that comes with it (see
// https://github.com/tailscale/tailscale/issues/1699,
// https://github.com/tailscale/tailscale/issues/1699,
// https://github.com/tailscale/tailscale/pull/1945)
// https://github.com/tailscale/tailscale/pull/1945)
safe , err := nmVersionBetween ( "1.26.0" , "1.26.5" )
safe , err := env . nmVersionBetween ( "1.26.0" , "1.26.5" )
if err != nil {
if err != nil {
// Failed to figure out NM's version, can't make a correct
// Failed to figure out NM's version, can't make a correct
// decision.
// decision.
return nil , fmt . Errorf ( "checking NetworkManager version: %v" , err )
return "" , fmt . Errorf ( "checking NetworkManager version: %v" , err )
}
}
if safe {
if safe {
dbg ( "nm-safe" , "yes" )
dbg ( "nm-safe" , "yes" )
return newNMManager ( interfaceName )
return "network-manager" , nil
}
}
dbg ( "nm-safe" , "no" )
dbg ( "nm-safe" , "no" )
return newResolvedManager ( logf , interfaceName )
return "systemd-resolved" , nil
case "resolvconf" :
case "resolvconf" :
dbg ( "rc" , "resolvconf" )
dbg ( "rc" , "resolvconf" )
if _ , err := exec . LookPath ( "resolvconf" ) ; err != nil {
style := env . resolvconfStyle ( )
switch style {
case "" :
dbg ( "resolvconf" , "no" )
dbg ( "resolvconf" , "no" )
return newDirectManagerOnFS ( env . fs ) , nil
return "direct" , nil
case "debian" :
dbg ( "resolvconf" , "debian" )
return "debian-resolvconf" , nil
case "openresolv" :
dbg ( "resolvconf" , "openresolv" )
return "openresolv" , nil
default :
// Shouldn't happen, that means we updated flavors of
// resolvconf without updating here.
dbg ( "resolvconf" , style )
logf ( "[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager" , env . resolvconfStyle ( ) )
return "direct" , nil
}
}
dbg ( "resolvconf" , "yes" )
return newResolvconfManager ( logf , env . getResolvConfVersion )
case "NetworkManager" :
case "NetworkManager" :
// You'd think we would use newNMManager somewhere in
// You'd think we would use newNMManager somewhere in
// here. However, as explained in
// here. However, as explained in
@ -165,10 +193,10 @@ func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEn
// anyway, so you still need a fallback path that uses
// anyway, so you still need a fallback path that uses
// directManager.
// directManager.
dbg ( "rc" , "nm" )
dbg ( "rc" , "nm" )
return newDirectManagerOnFS ( env . fs ) , nil
return "direct" , nil
default :
default :
dbg ( "rc" , "unknown" )
dbg ( "rc" , "unknown" )
return newDirectManagerOnFS ( env . fs ) , nil
return "direct" , nil
}
}
}
}
@ -216,8 +244,8 @@ func nmIsUsingResolved() error {
return nil
return nil
}
}
func resolvedIsActuallyResolver ( fs wholeFileFS ) error {
func resolvedIsActuallyResolver ( bs [ ] byte ) error {
cfg , err := newDirectManagerOnFS( fs ) . readResolvConf ( )
cfg , err := readResolv( bytes . NewBuffer ( bs ) )
if err != nil {
if err != nil {
return err
return err
}
}