[vnet] get upstream nameservers on Windows (#51738)

This commit is contained in:
Nic Klaassen
2025-02-06 17:38:25 +00:00
committed by GitHub
parent 5590855a2d
commit 341b1d066b
6 changed files with 123 additions and 66 deletions
+1
View File
@@ -218,6 +218,7 @@ require (
golang.org/x/text v0.21.0
golang.org/x/time v0.9.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/windows v0.5.3
google.golang.org/api v0.219.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287
google.golang.org/grpc v1.70.0
+2
View File
@@ -2853,6 +2853,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+61
View File
@@ -0,0 +1,61 @@
// Teleport
// Copyright (C) 2025 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dns
import (
"context"
"net/netip"
"time"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/lib/utils"
)
// OSUpstreamNameserverSource provides the list of upstream DNS nameservers
// configured in the OS. The VNet DNS resolver will forward unhandles queries to
// these nameservers.
type OSUpstreamNameserverSource struct {
ttlCache *utils.FnCache
}
// NewOSUpstreamNameserverSource returns a new *OSUpstreamNameserverSource.
func NewOSUpstreamNameserverSource() (*OSUpstreamNameserverSource, error) {
ttlCache, err := utils.NewFnCache(utils.FnCacheConfig{
TTL: 10 * time.Second,
})
if err != nil {
return nil, trace.Wrap(err)
}
return &OSUpstreamNameserverSource{
ttlCache: ttlCache,
}, nil
}
// UpstreamNameservers returns a cached view of the host OS's current default
// nameservers.
func (s *OSUpstreamNameserverSource) UpstreamNameservers(ctx context.Context) ([]string, error) {
return utils.FnCacheGet(ctx, s.ttlCache, 0, loadUpstreamNameservers)
}
func loadUpstreamNameservers(ctx context.Context) ([]string, error) {
return platformLoadUpstreamNameservers(ctx)
}
func withDNSPort(addr netip.Addr) string {
return netip.AddrPortFrom(addr, 53).String()
}
+12 -45
View File
@@ -20,47 +20,24 @@ import (
"bufio"
"context"
"log/slog"
"net"
"net/netip"
"os"
"strings"
"time"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/lib/utils"
)
const (
confFilePath = "/etc/resolv.conf"
)
// OSUpstreamNameserverSource provides the list of upstream DNS nameservers
// configured in the OS. The VNet DNS resolver will forward unhandles queries to
// these nameservers.
type OSUpstreamNameserverSource struct {
ttlCache *utils.FnCache
}
// NewOSUpstreamNameserverSource returns a new *OSUpstreamNameserverSource.
func NewOSUpstreamNameserverSource() (*OSUpstreamNameserverSource, error) {
ttlCache, err := utils.NewFnCache(utils.FnCacheConfig{
TTL: 10 * time.Second,
})
if err != nil {
return nil, trace.Wrap(err)
}
return &OSUpstreamNameserverSource{
ttlCache: ttlCache,
}, nil
}
// UpstreamNameservers returns a cached view of the host OS's current default
// nameservers, as found in /etc/resolv.conf.
func (s *OSUpstreamNameserverSource) UpstreamNameservers(ctx context.Context) ([]string, error) {
return utils.FnCacheGet(ctx, s.ttlCache, 0, s.upstreamNameservers)
}
func (s *OSUpstreamNameserverSource) upstreamNameservers(ctx context.Context) ([]string, error) {
// platformLoadUpstreamNameservers reads the OS DNS nameservers found in
// /etc/resolv.conf. The comments in that file make it clear it is not actually
// consulted for DNS hostname resolution, but MacOS seems to keep it up to date
// with the current default nameservers as configured for the OS, and it is the
// easiest place to read them. Eventually we should probably use a better
// method, but for now this works.
func platformLoadUpstreamNameservers(ctx context.Context) ([]string, error) {
f, err := os.Open(confFilePath)
if err != nil {
return nil, trace.Wrap(err, "opening %s", confFilePath)
@@ -80,23 +57,13 @@ func (s *OSUpstreamNameserverSource) upstreamNameservers(ctx context.Context) ([
}
address := fields[1]
ip := net.ParseIP(address)
if ip == nil {
slog.DebugContext(ctx, "Skipping invalid IP", "ip", address)
ip, err := netip.ParseAddr(address)
if err != nil {
slog.DebugContext(ctx, "Skipping invalid IP", "ip", address, "error", err)
continue
}
// Add port 53 suffix, the only port supported on MacOS.
var nameserver string
switch {
case ip.To4() != nil:
nameserver = address + ":53"
case ip.To16() != nil:
nameserver = "[" + address + "]:53"
default:
continue
}
nameservers = append(nameservers, nameserver)
nameservers = append(nameservers, withDNSPort(ip))
}
slog.DebugContext(ctx, "Loaded host upstream nameservers.", "nameservers", nameservers, "config_file", confFilePath)
+4 -7
View File
@@ -29,14 +29,11 @@ import (
var (
// vnetNotImplemented is an error indicating that VNet is not implemented on the host OS.
vnetNotImplemented = &trace.NotImplementedError{Message: "VNet is not implemented on " + runtime.GOOS}
// Satisfy unused linter.
_ = withDNSPort
)
type OSUpstreamNameserverSource struct{}
func NewOSUpstreamNameserverSource() (*OSUpstreamNameserverSource, error) {
return nil, trace.Wrap(vnetNotImplemented)
}
func (s *OSUpstreamNameserverSource) UpstreamNameservers(ctx context.Context) ([]string, error) {
func platformLoadUpstreamNameservers(ctx context.Context) ([]string, error) {
return nil, trace.Wrap(vnetNotImplemented)
}
+43 -14
View File
@@ -16,22 +16,51 @@
package dns
import "context"
import (
"context"
"log/slog"
"net/netip"
"sort"
// OSUpstreamNameserverSource provides the list of upstream DNS nameservers
// configured in the OS. The VNet DNS resolver will forward unhandles queries to
// these nameservers.
type OSUpstreamNameserverSource struct{}
"github.com/gravitational/trace"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
// NewOSUpstreamNameserverSource returns a new *OSUpstreamNameserverSource.
func NewOSUpstreamNameserverSource() (*OSUpstreamNameserverSource, error) {
return &OSUpstreamNameserverSource{}, nil
// platformLoadUpstreamNameservers attempts to find the default DNS nameservers
// that VNet should forward unmatched queries to. To do this, it finds the
// nameservers configured for each interface and sorts by the interface metric.
func platformLoadUpstreamNameservers(ctx context.Context) ([]string, error) {
interfaces, err := winipcfg.GetIPInterfaceTable(windows.AF_INET)
if err != nil {
return nil, trace.Wrap(err, "looking up local network interfaces")
}
sort.Slice(interfaces, func(i, j int) bool {
return interfaces[i].Metric < interfaces[j].Metric
})
var nameservers []string
for _, iface := range interfaces {
ifaceNameservers, err := iface.InterfaceLUID.DNS()
if err != nil {
return nil, trace.Wrap(err, "looking up DNS nameservers for interface")
}
for _, ifaceNameserver := range ifaceNameservers {
if ignoreUpstreamNameserver(ifaceNameserver) {
continue
}
nameservers = append(nameservers, withDNSPort(ifaceNameserver))
}
}
slog.DebugContext(ctx, "Loaded host upstream nameservers", "nameservers", nameservers)
return nameservers, nil
}
// UpstreamNameservers is net yet implemented and currently returns a nil/empty
// list of upstream nameservers. It does not return an error so that the
// networking stack can actually run without just immediately exiting.
func (s *OSUpstreamNameserverSource) UpstreamNameservers(ctx context.Context) ([]string, error) {
// TODO(nklaassen): implement UpstreamNameservers on windows.
return nil, nil
var (
// Ignore site-local addresses, which Windows seems to have set on
// most interfaces, implementing a deprecated IPv6 spec.
siteLocalPrefix = netip.MustParsePrefix("fec0::/10")
)
func ignoreUpstreamNameserver(addr netip.Addr) bool {
return siteLocalPrefix.Contains(addr)
}