Skip to content

Commit

Permalink
pillar/nireconciler: resolve ntp servers
Browse files Browse the repository at this point in the history
continuously in the background so that updated
IP addresses can be used if the edge application is
restarted (from controller)

Signed-off-by: Christoph Ostarek <[email protected]>
  • Loading branch information
christoph-zededa committed Dec 4, 2024
1 parent c151136 commit a8b0329
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 175 deletions.
45 changes: 42 additions & 3 deletions pkg/pillar/cmd/zedrouter/networkinstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net"
"strings"

"github.com/lf-edge/eve/pkg/pillar/devicenetwork"
"github.com/lf-edge/eve/pkg/pillar/nireconciler"
"github.com/lf-edge/eve/pkg/pillar/nistate"
"github.com/lf-edge/eve/pkg/pillar/types"
Expand Down Expand Up @@ -92,6 +93,39 @@ func (z *zedrouter) getNIBridgeConfig(
}
}

func (z *zedrouter) attachNTPServersToPortConfigs(portConfigs []nireconciler.Port, status *types.NetworkInstanceStatus) {
for i := range portConfigs {
pc := &portConfigs[i]
ntpServerIPs, ntpServerDomainsOrIPs := types.GetNTPServers(*z.deviceNetworkStatus, pc.IfName)

ntpServers := make([]net.IP, 0, len(ntpServerDomainsOrIPs))
for _, ntpServer := range ntpServerDomainsOrIPs {
ip := net.ParseIP(ntpServer)
if ip != nil {
ntpServers = append(ntpServers, ip)
continue
}
dnsResponses, err := devicenetwork.ResolveWithPortsLambda(
ntpServer,
*z.deviceNetworkStatus,
devicenetwork.ResolveCacheWrap(devicenetwork.ResolveWithSrcIP),
)
if err != nil {
z.log.Warnf("could not resolve '%s': %v", ntpServer, err)
}

for _, dnsResponse := range dnsResponses {
ntpServers = append(ntpServers, dnsResponse.IP)
}
}

ntpServers = append(ntpServers, ntpServerIPs...)
generics.FilterDuplicatesFn(ntpServers, netutils.EqualIPs)

pc.NTPServers = ntpServers
}
}

func (z *zedrouter) getNIPortConfig(
status *types.NetworkInstanceStatus) (portConfigs []nireconciler.Port) {
if len(status.Ports) == 0 {
Expand All @@ -103,6 +137,7 @@ func (z *zedrouter) getNIPortConfig(
if port == nil {
continue
}
basicNTPServerIPs, _ := types.GetNTPServers(*z.deviceNetworkStatus, port.IfName)
portConfigs = append(portConfigs, nireconciler.Port{
LogicalLabel: port.Logicallabel,
SharedLabels: port.SharedLabels,
Expand All @@ -111,7 +146,7 @@ func (z *zedrouter) getNIPortConfig(
MTU: port.MTU,
DhcpType: port.Dhcp,
DNSServers: types.GetDNSServers(*z.deviceNetworkStatus, port.IfName),
NTPServers: types.GetNTPServers(*z.deviceNetworkStatus, port.IfName),
NTPServers: basicNTPServerIPs,
})
}
return portConfigs
Expand Down Expand Up @@ -493,8 +528,10 @@ func (z *zedrouter) updateNIRoutePort(route types.IPRouteConfig, port string,
func (z *zedrouter) doActivateNetworkInstance(config types.NetworkInstanceConfig,
status *types.NetworkInstanceStatus) {
// Create network instance inside the network stack.
bridgeConfig := z.getNIBridgeConfig(status)
z.attachNTPServersToPortConfigs(bridgeConfig.Ports, status)
niRecStatus, err := z.niReconciler.AddNI(
z.runCtx, config, z.getNIBridgeConfig(status))
z.runCtx, config, bridgeConfig)
if err != nil {
z.log.Errorf("Failed to activate network instance %s: %v", status.UUID, err)
status.ReconcileErr.SetErrorNow(err.Error())
Expand Down Expand Up @@ -538,8 +575,10 @@ func (z *zedrouter) doInactivateNetworkInstance(status *types.NetworkInstanceSta

func (z *zedrouter) doUpdateActivatedNetworkInstance(config types.NetworkInstanceConfig,
status *types.NetworkInstanceStatus) {
bridgeConfig := z.getNIBridgeConfig(status)
niRecStatus, err := z.niReconciler.UpdateNI(
z.runCtx, config, z.getNIBridgeConfig(status))
z.runCtx, config, bridgeConfig)
z.attachNTPServersToPortConfigs(bridgeConfig.Ports, status)
if err != nil {
z.log.Errorf("Failed to update activated network instance %s: %v",
status.UUID, err)
Expand Down
36 changes: 36 additions & 0 deletions pkg/pillar/devicenetwork/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package devicenetwork

import (
"fmt"
"math"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -96,6 +97,41 @@ func ResolveWithSrcIP(domain string, dnsServerIP net.IP, srcIP net.IP) ([]DNSRes
return response, nil
}

type cachedDNSResponses struct {
dnsResponses []DNSResponse
validUntil time.Time
}

var resolveCache = map[string]cachedDNSResponses{}

// ResolveCacheWrap wraps around a resolve func (e.g. ResolveWithSrcIP) and caches DNS entries
func ResolveCacheWrap(resolve func(string, net.IP, net.IP) ([]DNSResponse, error)) func(domain string, dnsServerIP net.IP, srcIP net.IP) ([]DNSResponse, error) {
return func(domain string, dnsServerIP net.IP, srcIP net.IP) ([]DNSResponse, error) {

dnsResponses, found := resolveCache[domain]
if !found || !dnsResponses.validUntil.After(time.Now()) {
dnsResponses, err := resolve(domain, dnsServerIP, srcIP)
if err == nil {
minValidUntil := uint32(math.MaxUint32)
for _, dnsResponse := range dnsResponses {
if dnsResponse.TTL < uint32(minValidUntil) {
minValidUntil = dnsResponse.TTL
}
}
validUntil := time.Now().Add(time.Duration(minValidUntil * uint32(time.Second)))
resolveCache[domain] = cachedDNSResponses{
dnsResponses: dnsResponses,
validUntil: validUntil,
}
}

return dnsResponses, err
}

return dnsResponses.dnsResponses, nil
}
}

// ResolveWithPortsLambda resolves a domain by using source IPs and dns servers from DeviceNetworkStatus
// As a resolver func ResolveWithSrcIP can be used
func ResolveWithPortsLambda(domain string,
Expand Down
34 changes: 34 additions & 0 deletions pkg/pillar/devicenetwork/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,37 @@ func TestResolveWithPortsLambdaWithErrors(t *testing.T) {
t.Errorf("expected empty response, but got: %+v", res)
}
}

func TestResolveCacheWrap(t *testing.T) {
t.Parallel()
called := 0

cw := devicenetwork.ResolveCacheWrap(func(domain string, dnsServerIP, srcIP net.IP) ([]devicenetwork.DNSResponse, error) {
called++
return []devicenetwork.DNSResponse{
{
IP: []byte{127, 0, 0, 1},
TTL: 2,
},
}, nil
})

cw("localhost", net.IP{8, 8, 8, 8}, net.IP{0, 0, 0, 0})

if called != 1 {
t.Fatalf("resolver func should have been called once, but called=%d", called)
}

cw("localhost", net.IP{8, 8, 8, 8}, net.IP{0, 0, 0, 0})

if called != 1 {
t.Fatalf("resolver func should have been called once, but called=%d", called)
}

time.Sleep(5 * time.Second)
cw("localhost", net.IP{8, 8, 8, 8}, net.IP{0, 0, 0, 0})
if called != 2 {
t.Fatalf("resolver func should have been called twice, but called=%d", called)
}

}
36 changes: 6 additions & 30 deletions pkg/pillar/nireconciler/linux_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/base64"
"fmt"
"net"
"os"
"strings"
"syscall"

Expand Down Expand Up @@ -1095,30 +1096,6 @@ func (r *LinuxNIReconciler) getIntendedMetadataSrvCfg(niID uuid.UUID) (items []d
return items
}

func (r *LinuxNIReconciler) resolveNTPServers(ntpServers []string) []net.IP {
ret := make([]net.IP, 0)
for _, ntpServer := range ntpServers {
ip := net.ParseIP(ntpServer)
if ip != nil {
ret = append(ret, ip)
continue
}

// TODO: discuss with Milan how to use:
// devicenetwork.ResolveWithPortsLambda(ntpServer, XXX, devicenetwork.ResolveWithSrcIP)
// TODO: add timeout or resolve in background
ips, err := net.LookupIP(ntpServer)
if err != nil {
r.log.Warnf("could not lookup '%s': %v, skipping", ntpServer, err)
continue
}
ret = append(ret, ips...)
}

ret = generics.FilterDuplicatesFn(ret, netutils.EqualIPs)
return ret
}

func (r *LinuxNIReconciler) getIntendedDnsmasqCfg(niID uuid.UUID) (items []dg.Item) {
ni := r.nis[niID]
if ni.config.Type == types.NetworkInstanceTypeSwitch {
Expand Down Expand Up @@ -1154,15 +1131,14 @@ func (r *LinuxNIReconciler) getIntendedDnsmasqCfg(niID uuid.UUID) (items []dg.It
}
// Combine NTP servers assigned to the port(s) together with those statically
// configured for the network instance.
var ntpServers []string
ntpServerIPs := make([]net.IP, 0)
for _, port := range ni.bridge.Ports {
ntpServers = append(ntpServers, port.NTPServers...)
ntpServerIPs = append(ntpServerIPs, port.NTPServers...)
}
if ni.config.NtpServers != nil {
ntpServers = append(ntpServers, ni.config.NtpServers...)
generics.FilterDuplicatesFn(ntpServerIPs, netutils.EqualIPs)
for _, ntpServer := range ntpServerIPs {
fmt.Fprintf(os.Stderr, "AAAAA dnsmasq ntpServer: %+v\n", ntpServer.String())
}
ntpServers = generics.FilterDuplicates(ntpServers)
ntpServerIPs := r.resolveNTPServers(ntpServers)
var propagateRoutes []generic.IPRoute
// Use DHCP to propagate host routes towards user-configured NTP and DNS servers.
if bridgeIP != nil {
Expand Down
Loading

0 comments on commit a8b0329

Please sign in to comment.