blob: d0671eb666a6a130f96f36d104b4faa58ed8e702 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !build_with_native_toolchain
package dns
import (
"time"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/sync"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
"fidl/fuchsia/net/name"
"gvisor.dev/gvisor/pkg/tcpip"
)
const (
// DefaultDNSPort is the default port used by DNS servers.
DefaultDNSPort = 53
syslogTagName = "dns"
)
// expiringDNSServerState is the state for an expiring DNS server.
type expiringDNSServerState struct {
// Invalidates a DNS server.
//
// May not be nil.
job *tcpip.Job
}
// Server is a DNS server with an address and configuration source.
type Server struct {
Address tcpip.FullAddress
Source name.DnsServerSource
}
// ServersConfig holds DNS resolvers' DNS servers configuration.
type ServersConfig struct {
clock tcpip.Clock
mu struct {
sync.RWMutex
// Default DNS server addresses.
//
// defaultServers are assumed to use DefaultDNSPort for DNS queries.
defaultServers []tcpip.Address
// References to slices of DNS servers configured at runtime.
//
// Unlike ndpServers, these servers do not have a set lifetime; they
// are valid forever until updated.
//
// dhcpServers are assumed to use DefaultDNSPort for DNS queries.
dhcpServers map[tcpip.NICID]*[]tcpip.Address
// DNS server and associated port configured at runtime by NDP that may expire
// after some lifetime.
ndpServers map[tcpip.FullAddress]expiringDNSServerState
// A cache of the available DNS server addresses and associated port that
// may be used until invalidation.
//
// Must be cleared when defaultServers, dhcpServers or ndpServers
// is updated.
serversCache []Server
// Closed and replaced when the server list changes.
serversChanged chan struct{}
}
}
func MakeServersConfig(clock tcpip.Clock) ServersConfig {
d := ServersConfig{
clock: clock,
}
d.mu.ndpServers = make(map[tcpip.FullAddress]expiringDNSServerState)
d.mu.dhcpServers = make(map[tcpip.NICID]*[]tcpip.Address)
d.mu.serversChanged = make(chan struct{})
return d
}
// GetServersCache returns a list of tcpip.FullAddress to DNS servers.
//
// The expiring servers will be at the front of the list, followed by
// the runtime and default servers. The list will be deduplicated.
func (d *ServersConfig) GetServersCache() []Server {
servers, _ := d.GetServersCacheAndChannel()
return servers
}
// GetServersCacheAndChannel returns a list of tcpip.FullAddress to DNS
// servers, and the receive end of a channel which will be closed when the list
// changes.
//
// The expiring servers will be at the front of the list, followed by
// the runtime and default servers. The list will be deduplicated.
func (d *ServersConfig) GetServersCacheAndChannel() ([]Server, <-chan struct{}) {
// If we already have a populated cache, return it.
d.mu.RLock()
cache := d.mu.serversCache
ch := d.mu.serversChanged
d.mu.RUnlock()
if cache != nil {
return cache, ch
}
// At this point the cache may need to be generated.
d.mu.Lock()
defer d.mu.Unlock()
// We check if the cache is populated again so that the read lock is leveraged
// above. We need to check d.mu.serversCache after taking the Write
// lock to avoid duplicate work when multiple concurrent calls to
// GetServersCache are made.
//
// Without this check, the following can occur (T1 and T2 are goroutines
// that are trying to get the servers cache):
// T1: Take RLock, Drop RLock, cache == nil
// T2: Take RLock Drop RLock, cache == nil
// T1: Take WLock
// T2: Attempt to take WLock, block
// T1: Generate cache, drop WLock
// T2: Obtain WLock, generate cache, drop WLock
//
// This example can be expanded to many more goroutines like T2.
//
// Here we can see that T2 unnessessarily regenerates the cache after T1. By
// checking if the servers cache is populated after obtaining the WLock, this
// can be avoided.
if cache := d.mu.serversCache; cache != nil {
return cache, d.mu.serversChanged
}
have := make(map[tcpip.FullAddress]struct{})
for s := range d.mu.ndpServers {
// We don't check if s is already in have since d.mu.ndpServers
// is a map - we will not see the same key twice.
have[s] = struct{}{}
var ndpSource name.NdpDnsServerSource
if s.NIC != 0 {
ndpSource.SetSourceInterface(uint64(s.NIC))
}
var source name.DnsServerSource
source.SetNdp(ndpSource)
cache = append(cache, Server{
Address: s,
Source: source,
})
}
// Add DHCP servers.
for nic, serverLists := range d.mu.dhcpServers {
for _, server := range *serverLists {
s := tcpip.FullAddress{Addr: server, Port: DefaultDNSPort}
if _, ok := have[s]; ok {
// Cache already has s in it, do not duplicate it.
continue
}
have[s] = struct{}{}
var dhcpSource name.DhcpDnsServerSource
if nic != 0 {
dhcpSource.SetSourceInterface(uint64(nic))
}
var source name.DnsServerSource
source.SetDhcp(dhcpSource)
cache = append(cache, Server{
Address: s,
Source: source,
})
}
}
// Add default servers.
for _, server := range d.mu.defaultServers {
s := tcpip.FullAddress{Addr: server, Port: DefaultDNSPort}
if _, ok := have[s]; ok {
// Cache already has s in it, do not duplicate it.
continue
}
have[s] = struct{}{}
var source name.DnsServerSource
source.SetStaticSource(name.StaticDnsServerSource{})
cache = append(cache, Server{
Address: s,
Source: source,
})
}
d.mu.serversCache = cache
return cache, d.mu.serversChanged
}
// SetDefaultServers sets the default list of nameservers to query.
// This usually comes from a system-wide configuration file.
// Servers are checked sequentially, in order.
// Takes ownership of the passed-in slice of addrs.
func (d *ServersConfig) SetDefaultServers(servers []tcpip.Address) {
d.mu.Lock()
d.mu.defaultServers = servers
// Clear cache and broadcast change by closing the channel.
d.mu.serversCache = nil
ch := d.mu.serversChanged
d.mu.serversChanged = make(chan struct{})
d.mu.Unlock()
close(ch)
}
// UpdateDhcpServers updates the list of lists of runtime servers to query (e.g.
// collected from DHCP responses) referenced by a NICID. Servers are checked
// sequentially, in order.
//
// Takes ownership of the passed-in list of serverRefs.
//
// It's possible to introduce aliasing issues if a slice pointer passed here is
// obviated later but UpdateDhcpServers isn't called again.
//
// E.g., if one of the network interface structs containing a slice pointer is
// deleted, UpdateDhcpServers should be called again with an updated list of
// runtime server refs.
func (d *ServersConfig) UpdateDhcpServers(nicid tcpip.NICID, serverRefs *[]tcpip.Address) {
d.mu.Lock()
if serverRefs != nil {
d.mu.dhcpServers[nicid] = serverRefs
} else {
delete(d.mu.dhcpServers, nicid)
}
// Clear cache and broadcast change by closing the channel.
d.mu.serversCache = nil
ch := d.mu.serversChanged
d.mu.serversChanged = make(chan struct{})
d.mu.Unlock()
close(ch)
}
// UpdateNdpServers updates the list of NDP-discovered servers to query.
//
// If a server already known by c, its lifetime will be refreshed. If a server
// is not known and has a non-zero lifetime, it will be stored and set to
// expire after lifetime.
//
// A lifetime value of less than 0 indicates that servers are not to expire
// (they will become valid forever until another update refreshes the lifetime).
func (d *ServersConfig) UpdateNdpServers(servers []tcpip.FullAddress, lifetime time.Duration) {
d.mu.Lock()
changed := false
for _, s := range servers {
// If s.Port is 0, then assume the default DNS port.
if s.Port == 0 {
s.Port = DefaultDNSPort
}
state, ok := d.mu.ndpServers[s]
if lifetime != 0 {
if !ok {
changed = true
_ = syslog.InfoTf(syslogTagName, "adding new NDP learned DNS server %+v with initial lifetime %s", s, lifetime)
// We do not yet have the server and it has a non-zero lifetime.
s := s
state = expiringDNSServerState{
job: tcpip.NewJob(d.clock, &d.mu, func() {
// Clear the cache of DNS servers.
d.mu.serversCache = nil
delete(d.mu.ndpServers, s)
_ = syslog.InfoTf(syslogTagName, "expired NDP learned DNS server %+v", s)
}),
}
}
// Refresh s's lifetime.
state.job.Cancel()
if lifetime > 0 {
// s is valid for a finite lifetime.
state.job.Schedule(lifetime)
}
d.mu.ndpServers[s] = state
} else if ok {
changed = true
// We have the server and it is no longer to be used.
state.job.Cancel()
delete(d.mu.ndpServers, s)
_ = syslog.InfoTf(syslogTagName, "immediately expired NDP learned DNS server %+v", s)
}
}
var ch chan<- struct{}
if changed {
// Clear cache and broadcast change by closing the channel.
d.mu.serversCache = nil
ch = d.mu.serversChanged
d.mu.serversChanged = make(chan struct{})
}
d.mu.Unlock()
if ch != nil {
close(ch)
}
}
// RemoveAllServersWithNIC removes all servers associated with the specified
// NIC.
//
// If a NIC is not specified (nicID == 0), then RemoveAllServersWithNIC does
// nothing.
func (d *ServersConfig) RemoveAllServersWithNIC(nicID tcpip.NICID) {
if nicID == 0 {
return
}
d.mu.Lock()
for server, state := range d.mu.ndpServers {
if server.NIC == nicID {
state.job.Cancel()
delete(d.mu.ndpServers, server)
}
}
// Clear cache and broadcast change by closing the channel.
d.mu.serversCache = nil
ch := d.mu.serversChanged
d.mu.serversChanged = make(chan struct{})
d.mu.Unlock()
close(ch)
}