mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-12 19:24:40 +00:00
net/dns: fix resolved dbus issue
When dbus restarts it can cause the tailscaled to crash because the nil signal was not handled in resolved.Fixing so the nil signal is handled and tailscaled stayes connected to systemd when dbus restarted. Fixes #4645 Signed-off-by: nyghtowl <warrick@tailscale.com>
This commit is contained in:
parent
aa37aece9c
commit
627a4cb430
@ -14,6 +14,8 @@
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"tailscale.com/logtail/backoff"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -79,102 +81,106 @@ type resolvedManager struct {
|
|||||||
logf logger.Logf
|
logf logger.Logf
|
||||||
ifidx int
|
ifidx int
|
||||||
|
|
||||||
cancelSyncer context.CancelFunc // run to shut down syncer goroutine
|
|
||||||
syncerDone chan struct{} // closed when syncer is stopped
|
|
||||||
resolved dbus.BusObject
|
|
||||||
|
|
||||||
mu sync.Mutex // guards RPCs made by syncLocked, and the following
|
mu sync.Mutex // guards RPCs made by syncLocked, and the following
|
||||||
config OSConfig // last SetDNS config
|
config OSConfig // last SetDNS config
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManager, error) {
|
func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManager, error) {
|
||||||
conn, err := dbus.SystemBus()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err := net.InterfaceByName(interfaceName)
|
iface, err := net.InterfaceByName(interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
return &resolvedManager{
|
||||||
|
logf: logf,
|
||||||
|
ifidx: iface.Index,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
ret := &resolvedManager{
|
func (m *resolvedManager) SetDNS(config OSConfig) error {
|
||||||
logf: logf,
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
ifidx: iface.Index,
|
defer cancel()
|
||||||
cancelSyncer: cancel,
|
|
||||||
syncerDone: make(chan struct{}),
|
var err error
|
||||||
resolved: conn.Object(dbusResolvedObject, dbus.ObjectPath(dbusResolvedPath)),
|
bo := backoff.NewBackoff("resolvedSetDNS", m.logf, 30*time.Second)
|
||||||
|
m.config = config
|
||||||
|
|
||||||
|
for ctx.Err() == nil {
|
||||||
|
err = m.trySet(ctx)
|
||||||
|
if err == nil {
|
||||||
|
bo.BackOff(ctx, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resolvedManager) trySet(ctx context.Context) error {
|
||||||
|
|
||||||
|
conn, err := dbus.SystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r1Manager := conn.Object(dbusResolvedObject, dbus.ObjectPath(dbusResolvedPath))
|
||||||
signals := make(chan *dbus.Signal, 16)
|
signals := make(chan *dbus.Signal, 16)
|
||||||
go ret.resync(ctx, signals)
|
|
||||||
// Only receive the DBus signals we need to resync our config on
|
// Only receive the DBus signals we need to resync our config on
|
||||||
// resolved restart. Failure to set filters isn't a fatal error,
|
// resolved restart. Failure to set filters isn't a fatal error,
|
||||||
// we'll just receive all broadcast signals and have to ignore
|
// we'll just receive all broadcast signals and have to ignore
|
||||||
// them on our end.
|
// them on our end.
|
||||||
if err := conn.AddMatchSignal(dbus.WithMatchObjectPath(dbusPath), dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(dbusOwnerSignal), dbus.WithMatchArg(0, dbusResolvedObject)); err != nil {
|
if err := conn.AddMatchSignal(dbus.WithMatchObjectPath(dbusPath), dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(dbusOwnerSignal), dbus.WithMatchArg(0, dbusResolvedObject)); err != nil {
|
||||||
logf("[v1] Setting DBus signal filter failed: %v", err)
|
m.logf("[v1] Setting DBus signal filter failed: %v", err)
|
||||||
}
|
}
|
||||||
conn.Signal(signals)
|
conn.Signal(signals)
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resolvedManager) SetDNS(config OSConfig) error {
|
for signal := range signals {
|
||||||
m.mu.Lock()
|
if signal == nil {
|
||||||
defer m.mu.Unlock()
|
return fmt.Errorf("Empty signal returned.")
|
||||||
|
}
|
||||||
m.config = config
|
// In theory the signal was filtered by DBus, but if
|
||||||
return m.syncLocked(context.TODO()) // would be nice to plumb context through from SetDNS
|
// AddMatchSignal in the constructor failed, we may be
|
||||||
}
|
// getting other spam.
|
||||||
|
if signal.Path != dbusPath || signal.Name != dbusInterface+"."+dbusOwnerSignal {
|
||||||
func (m *resolvedManager) resync(ctx context.Context, signals chan *dbus.Signal) {
|
continue
|
||||||
defer close(m.syncerDone)
|
}
|
||||||
for {
|
// signal.Body is a []any of 3 strings: bus name, previous owner, new owner.
|
||||||
select {
|
if len(signal.Body) != 3 {
|
||||||
case <-ctx.Done():
|
m.logf("[unexpectected] DBus NameOwnerChanged len(Body) = %d, want 3")
|
||||||
return
|
}
|
||||||
case signal := <-signals:
|
if name, ok := signal.Body[0].(string); !ok || name != dbusResolvedObject {
|
||||||
// In theory the signal was filtered by DBus, but if
|
continue
|
||||||
// AddMatchSignal in the constructor failed, we may be
|
}
|
||||||
// getting other spam.
|
newOwner, ok := signal.Body[2].(string)
|
||||||
if signal.Path != dbusPath || signal.Name != dbusInterface+"."+dbusOwnerSignal {
|
if !ok {
|
||||||
continue
|
m.logf("[unexpected] DBus NameOwnerChanged.new_owner is a %T, not a string", signal.Body[2])
|
||||||
}
|
}
|
||||||
// signal.Body is a []any of 3 strings: bus name, previous owner, new owner.
|
if newOwner == "" {
|
||||||
if len(signal.Body) != 3 {
|
// systemd-resolved left the bus, no current owner,
|
||||||
m.logf("[unexpectected] DBus NameOwnerChanged len(Body) = %d, want 3")
|
// nothing to do.
|
||||||
}
|
continue
|
||||||
if name, ok := signal.Body[0].(string); !ok || name != dbusResolvedObject {
|
}
|
||||||
continue
|
// The resolved bus name has a new owner, meaning resolved
|
||||||
}
|
// restarted. Reprogram current config.
|
||||||
newOwner, ok := signal.Body[2].(string)
|
m.logf("systemd-resolved restarted, syncing DNS config")
|
||||||
if !ok {
|
m.mu.Lock()
|
||||||
m.logf("[unexpected] DBus NameOwnerChanged.new_owner is a %T, not a string", signal.Body[2])
|
err := m.syncLocked(ctx, r1Manager)
|
||||||
}
|
// Set health while holding the lock, because this will
|
||||||
if newOwner == "" {
|
// graciously serialize the resync's health outcome with a
|
||||||
// systemd-resolved left the bus, no current owner,
|
// concurrent SetDNS call.
|
||||||
// nothing to do.
|
health.SetDNSOSHealth(err)
|
||||||
continue
|
m.mu.Unlock()
|
||||||
}
|
if err != nil {
|
||||||
// The resolved bus name has a new owner, meaning resolved
|
m.logf("failed to configure systemd-resolved: %v", err)
|
||||||
// restarted. Reprogram current config.
|
|
||||||
m.logf("systemd-resolved restarted, syncing DNS config")
|
|
||||||
m.mu.Lock()
|
|
||||||
err := m.syncLocked(ctx)
|
|
||||||
// Set health while holding the lock, because this will
|
|
||||||
// graciously serialize the resync's health outcome with a
|
|
||||||
// concurrent SetDNS call.
|
|
||||||
health.SetDNSOSHealth(err)
|
|
||||||
m.mu.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
m.logf("failed to configure systemd-resolved: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// RevertLink on connection if signals are interrupted in order to create a new connection.
|
||||||
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".RevertLink", 0, m.ifidx); call.Err != nil {
|
||||||
|
return fmt.Errorf("RevertLink: %w", call.Err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resolvedManager) syncLocked(ctx context.Context) error {
|
func (m *resolvedManager) syncLocked(ctx context.Context, r1Manager dbus.BusObject) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, reconfigTimeout)
|
ctx, cancel := context.WithTimeout(ctx, reconfigTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -194,7 +200,7 @@ func (m *resolvedManager) syncLocked(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := m.resolved.CallWithContext(
|
err := r1Manager.CallWithContext(
|
||||||
ctx, dbusResolvedInterface+".SetLinkDNS", 0,
|
ctx, dbusResolvedInterface+".SetLinkDNS", 0,
|
||||||
m.ifidx, linkNameservers,
|
m.ifidx, linkNameservers,
|
||||||
).Store()
|
).Store()
|
||||||
@ -235,14 +241,14 @@ func (m *resolvedManager) syncLocked(ctx context.Context) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.resolved.CallWithContext(
|
err = r1Manager.CallWithContext(
|
||||||
ctx, dbusResolvedInterface+".SetLinkDomains", 0,
|
ctx, dbusResolvedInterface+".SetLinkDomains", 0,
|
||||||
m.ifidx, linkDomains,
|
m.ifidx, linkDomains,
|
||||||
).Store()
|
).Store()
|
||||||
if err != nil && err.Error() == "Argument list too long" { // TODO: better error match
|
if err != nil && err.Error() == "Argument list too long" { // TODO: better error match
|
||||||
// Issue 3188: older systemd-resolved had argument length limits.
|
// Issue 3188: older systemd-resolved had argument length limits.
|
||||||
// Trim out the *.arpa. entries and try again.
|
// Trim out the *.arpa. entries and try again.
|
||||||
err = m.resolved.CallWithContext(
|
err = r1Manager.CallWithContext(
|
||||||
ctx, dbusResolvedInterface+".SetLinkDomains", 0,
|
ctx, dbusResolvedInterface+".SetLinkDomains", 0,
|
||||||
m.ifidx, linkDomainsWithoutReverseDNS(linkDomains),
|
m.ifidx, linkDomainsWithoutReverseDNS(linkDomains),
|
||||||
).Store()
|
).Store()
|
||||||
@ -251,7 +257,7 @@ func (m *resolvedManager) syncLocked(ctx context.Context) error {
|
|||||||
return fmt.Errorf("setLinkDomains: %w", err)
|
return fmt.Errorf("setLinkDomains: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDefaultRoute", 0, m.ifidx, len(m.config.MatchDomains) == 0); call.Err != nil {
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDefaultRoute", 0, m.ifidx, len(m.config.MatchDomains) == 0); call.Err != nil {
|
||||||
if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name {
|
if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name {
|
||||||
// on some older systems like Kubuntu 18.04.6 with systemd 237 method SetLinkDefaultRoute is absent,
|
// on some older systems like Kubuntu 18.04.6 with systemd 237 method SetLinkDefaultRoute is absent,
|
||||||
// but otherwise it's working good
|
// but otherwise it's working good
|
||||||
@ -266,26 +272,26 @@ func (m *resolvedManager) syncLocked(ctx context.Context) error {
|
|||||||
// or something).
|
// or something).
|
||||||
|
|
||||||
// Disable LLMNR, we don't do multicast.
|
// Disable LLMNR, we don't do multicast.
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".SetLinkLLMNR", 0, m.ifidx, "no"); call.Err != nil {
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkLLMNR", 0, m.ifidx, "no"); call.Err != nil {
|
||||||
m.logf("[v1] failed to disable LLMNR: %v", call.Err)
|
m.logf("[v1] failed to disable LLMNR: %v", call.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable mdns.
|
// Disable mdns.
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".SetLinkMulticastDNS", 0, m.ifidx, "no"); call.Err != nil {
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkMulticastDNS", 0, m.ifidx, "no"); call.Err != nil {
|
||||||
m.logf("[v1] failed to disable mdns: %v", call.Err)
|
m.logf("[v1] failed to disable mdns: %v", call.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't support dnssec consistently right now, force it off to
|
// We don't support dnssec consistently right now, force it off to
|
||||||
// avoid partial failures when we split DNS internally.
|
// avoid partial failures when we split DNS internally.
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSSEC", 0, m.ifidx, "no"); call.Err != nil {
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSSEC", 0, m.ifidx, "no"); call.Err != nil {
|
||||||
m.logf("[v1] failed to disable DNSSEC: %v", call.Err)
|
m.logf("[v1] failed to disable DNSSEC: %v", call.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSOverTLS", 0, m.ifidx, "no"); call.Err != nil {
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSOverTLS", 0, m.ifidx, "no"); call.Err != nil {
|
||||||
m.logf("[v1] failed to disable DoT: %v", call.Err)
|
m.logf("[v1] failed to disable DoT: %v", call.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".FlushCaches", 0); call.Err != nil {
|
if call := r1Manager.CallWithContext(ctx, dbusResolvedInterface+".FlushCaches", 0); call.Err != nil {
|
||||||
m.logf("failed to flush resolved DNS cache: %v", call.Err)
|
m.logf("failed to flush resolved DNS cache: %v", call.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,20 +307,7 @@ func (m *resolvedManager) GetBaseConfig() (OSConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *resolvedManager) Close() error {
|
func (m *resolvedManager) Close() error {
|
||||||
m.cancelSyncer()
|
// No need to do anything on close which is handled in trySet
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
|
|
||||||
defer cancel()
|
|
||||||
if call := m.resolved.CallWithContext(ctx, dbusResolvedInterface+".RevertLink", 0, m.ifidx); call.Err != nil {
|
|
||||||
return fmt.Errorf("RevertLink: %w", call.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-m.syncerDone:
|
|
||||||
case <-ctx.Done():
|
|
||||||
m.logf("timeout in systemd-resolved syncer shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user