Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions lib/vnet/admin_process_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ func RunDarwinAdminProcess(ctx context.Context, config daemon.Config) error {
return trace.Wrap(err, "reporting network stack info to client application")
}

osConfigProvider, err := newOSConfigProvider(
clt,
tunName,
networkStackConfig.ipv6Prefix.String(),
networkStackConfig.dnsIPv6.String(),
)
osConfigProvider, err := newOSConfigProvider(osConfigProviderConfig{
clt: clt,
tunName: tunName,
ipv6Prefix: networkStackConfig.ipv6Prefix.String(),
dnsIPv6: networkStackConfig.dnsIPv6.String(),
addDNSAddress: networkStack.addDNSAddress,
})
if err != nil {
return trace.Wrap(err, "creating OS config provider")
}
Expand Down
13 changes: 7 additions & 6 deletions lib/vnet/admin_process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,13 @@ func runWindowsAdminProcess(ctx context.Context, cfg *windowsAdminProcessConfig)
return trace.Wrap(err, "reporting network stack info to client application")
}

osConfigProvider, err := newOSConfigProvider(
clt,
tunName,
networkStackConfig.ipv6Prefix.String(),
networkStackConfig.dnsIPv6.String(),
)
osConfigProvider, err := newOSConfigProvider(osConfigProviderConfig{
clt: clt,
tunName: tunName,
ipv6Prefix: networkStackConfig.ipv6Prefix.String(),
dnsIPv6: networkStackConfig.dnsIPv6.String(),
addDNSAddress: networkStack.addDNSAddress,
})
if err != nil {
return trace.Wrap(err, "creating OS config provider")
}
Expand Down
35 changes: 24 additions & 11 deletions lib/vnet/network_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ type networkStack struct {
// ipv6Prefix holds the 96-bit prefix that will be used for all IPv6 addresses assigned in the VNet.
ipv6Prefix tcpip.Address

// dnsServer is the VNet's local DNS server that can handle UDP DNS
// requests.
dnsServer *dns.Server

// tcpHandlerResolver resolves FQDNs to a TCP handler that will be used to handle all future TCP
// connections to IP addresses that will be assigned to that FQDN.
tcpHandlerResolver *tcpHandlerResolver
Expand Down Expand Up @@ -233,24 +237,26 @@ func newNetworkStack(cfg *networkStackConfig) (*networkStack, error) {
slog: slog,
}

upstreamNameserverSource := cfg.upstreamNameserverSource
if upstreamNameserverSource == nil {
upstreamNameserverSource, err = dns.NewOSUpstreamNameserverSource()
if err != nil {
return nil, trace.Wrap(err)
}
}
dnsServer, err := dns.NewServer(ns, upstreamNameserverSource)
if err != nil {
return nil, trace.Wrap(err)
}
ns.dnsServer = dnsServer

tcpForwarder := tcp.NewForwarder(ns.stack, tcpReceiveBufferSize, maxInFlightTCPConnectionAttempts, ns.handleTCP)
ns.stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)

udpForwarder := udp.NewForwarder(ns.stack, ns.handleUDP)
ns.stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)

if cfg.dnsIPv6 != (tcpip.Address{}) {
upstreamNameserverSource := cfg.upstreamNameserverSource
if upstreamNameserverSource == nil {
upstreamNameserverSource, err = dns.NewOSUpstreamNameserverSource()
if err != nil {
return nil, trace.Wrap(err)
}
}
dnsServer, err := dns.NewServer(ns, upstreamNameserverSource)
if err != nil {
return nil, trace.Wrap(err)
}
if err := ns.assignUDPHandler(cfg.dnsIPv6, dnsServer); err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -542,6 +548,13 @@ func (ns *networkStack) assignUDPHandler(addr tcpip.Address, handler udpHandler)
return nil
}

// addDNSAddress adds a DNS handler at the given IP.
func (ns *networkStack) addDNSAddress(ip net.IP) error {
slog.DebugContext(context.Background(), "Serving DNS on IPv4.", "dns_addr", ip.String())
return trace.Wrap(ns.assignUDPHandler(tcpip.AddrFromSlice(ip), ns.dnsServer),
"adding UDP handler at %s", ip.String())
}

// ResolveA implements [dns.Resolver.ResolveA].
func (ns *networkStack) ResolveA(ctx context.Context, fqdn string) (dns.Result, error) {
// Do the actual resolution within a [singleflight.Group] keyed by [fqdn] to avoid concurrent requests to
Expand Down
17 changes: 1 addition & 16 deletions lib/vnet/osconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package vnet

import (
"context"
"net"
"net/netip"
"os/exec"
"strings"
Expand All @@ -32,7 +31,7 @@ type osConfig struct {
tunIPv4 string
tunIPv6 string
cidrRanges []string
dnsAddr string
dnsAddrs []string
dnsZones []string
}

Expand Down Expand Up @@ -125,20 +124,6 @@ func tunIPv6ForPrefix(ipv6Prefix string) (string, error) {
return addr.Next().String(), nil
}

// tunIPv4ForCIDR returns the IPv4 address to use for the TUN interface in
// cidrRange. It always returns the second address in the range.
func tunIPv4ForCIDR(cidrRange string) (string, error) {
_, ipnet, err := net.ParseCIDR(cidrRange)
if err != nil {
return "", trace.Wrap(err, "parsing CIDR %q", cidrRange)
}
// ipnet.IP is the network address, ending in 0s, like 100.64.0.0
// Add 1 to assign the TUN address, like 100.64.0.1
tunAddress := ipnet.IP
tunAddress[len(tunAddress)-1]++
return tunAddress.String(), nil
}

func runCommand(ctx context.Context, path string, args ...string) error {
cmdString := strings.Join(append([]string{path}, args...), " ")
log.DebugContext(ctx, "Running command", "cmd", cmdString)
Expand Down
30 changes: 21 additions & 9 deletions lib/vnet/osconfig_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package vnet

import (
"bufio"
"bytes"
"context"
"os"
"path/filepath"

"github.com/google/renameio/v2"
"github.com/gravitational/trace"
)

Expand Down Expand Up @@ -69,7 +71,7 @@ func platformConfigureOS(ctx context.Context, cfg *osConfig, _ *platformOSConfig
}
}

if err := configureDNS(ctx, cfg.dnsAddr, cfg.dnsZones); err != nil {
if err := configureDNS(ctx, cfg.dnsAddrs, cfg.dnsZones); err != nil {
return trace.Wrap(err, "configuring DNS")
}

Expand All @@ -80,12 +82,14 @@ const resolverFileComment = "# automatically installed by Teleport VNet"

var resolverPath = filepath.Join("/", "etc", "resolver")

func configureDNS(ctx context.Context, nameserver string, zones []string) error {
if len(nameserver) == 0 && len(zones) > 0 {
return trace.BadParameter("empty nameserver with non-empty zones")
func configureDNS(ctx context.Context, nameservers []string, zones []string) error {
if len(nameservers) == 0 {
// There are no nameservers so VNet can't handle any DNS zones. Continue
// so that any VNet-managed resolver files can be deleted.
zones = nil
}

log.DebugContext(ctx, "Configuring DNS.", "nameserver", nameserver, "zones", zones)
log.DebugContext(ctx, "Configuring DNS.", "nameservers", nameservers, "zones", zones)
if err := os.MkdirAll(resolverPath, os.FileMode(0755)); err != nil {
return trace.Wrap(err, "creating %s", resolverPath)
}
Expand All @@ -95,14 +99,22 @@ func configureDNS(ctx context.Context, nameserver string, zones []string) error
return trace.Wrap(err, "finding VNet managed files in /etc/resolver")
}

// Always attempt to write or clean up all files below, even if encountering errors with one or more of
// them.
// Always attempt to write or clean up all files below, even if encountering
// errors with one or more of them.
var allErrors []error

var fileContents bytes.Buffer
fileContents.WriteString(resolverFileComment)
fileContents.WriteByte('\n')
for _, nameserver := range nameservers {
fileContents.WriteString("nameserver ")
fileContents.WriteString(nameserver)
fileContents.WriteByte('\n')
}

for _, zone := range zones {
fileName := filepath.Join(resolverPath, zone)
contents := resolverFileComment + "\nnameserver " + nameserver
if err := os.WriteFile(fileName, []byte(contents), 0644); err != nil {
if err := renameio.WriteFile(fileName, fileContents.Bytes(), 0644); err != nil {
allErrors = append(allErrors, trace.Wrap(err, "writing DNS configuration file %s", fileName))
} else {
// Successfully wrote this file, don't clean it up below.
Expand Down
75 changes: 55 additions & 20 deletions lib/vnet/osconfig_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package vnet

import (
"context"
"net"
"slices"

"github.com/gravitational/trace"

Expand All @@ -27,60 +29,93 @@ import (
// osConfigProvider fetches a target OS configuration based on cluster
// configuration fetched via the client application process available over gRPC.
type osConfigProvider struct {
clt targetOSConfigGetter
tunName string
dnsAddr string
tunIPv6 string
tunIPv4 string
cfg osConfigProviderConfig
dnsAddrs []string
tunIPv6 string
tunIPv4 string
}

// osConfigProviderConfig holds configuration parameters for an osConfigProvider.
type osConfigProviderConfig struct {
clt targetOSConfigGetter
tunName string
ipv6Prefix string
dnsIPv6 string
addDNSAddress func(net.IP) error
}

type targetOSConfigGetter interface {
GetTargetOSConfiguration(context.Context) (*vnetv1.TargetOSConfiguration, error)
}

func newOSConfigProvider(clt targetOSConfigGetter, tunName, ipv6Prefix, dnsAddr string) (*osConfigProvider, error) {
tunIPv6, err := tunIPv6ForPrefix(ipv6Prefix)
func newOSConfigProvider(cfg osConfigProviderConfig) (*osConfigProvider, error) {
tunIPv6, err := tunIPv6ForPrefix(cfg.ipv6Prefix)
if err != nil {
return nil, trace.Wrap(err)
}
return &osConfigProvider{
clt: clt,
tunName: tunName,
dnsAddr: dnsAddr,
tunIPv6: tunIPv6,
cfg: cfg,
dnsAddrs: []string{cfg.dnsIPv6},
tunIPv6: tunIPv6,
}, nil
}

func (p *osConfigProvider) targetOSConfig(ctx context.Context) (*osConfig, error) {
targetOSConfig, err := p.clt.GetTargetOSConfiguration(ctx)
targetOSConfig, err := p.cfg.clt.GetTargetOSConfiguration(ctx)
if err != nil {
return nil, trace.Wrap(err, "getting target OS configuration from client application")
}
if p.tunIPv4 == "" && len(targetOSConfig.Ipv4CidrRanges) > 0 {
// Choose an IPv4 address for the TUN interface from the CIDR range of one arbitrary currently
// logged-in cluster. Only one IPv4 address is needed.
if err := p.setTunIPv4FromCIDR(targetOSConfig.Ipv4CidrRanges[0]); err != nil {
// Choose an IPv4 address for the TUN interface and the IPv4 DNS server
// from the CIDR range of one arbitrary currently logged-in cluster.
// We currently only assign one V4 address to the interface and only
// advertise DNS on one V4 address.
if err := p.setV4IPsFromFirstCIDR(targetOSConfig.Ipv4CidrRanges[0]); err != nil {
return nil, trace.Wrap(err, "setting TUN IPv4 address")
}
}
return &osConfig{
tunName: p.tunName,
tunName: p.cfg.tunName,
tunIPv6: p.tunIPv6,
tunIPv4: p.tunIPv4,
dnsAddr: p.dnsAddr,
dnsAddrs: p.dnsAddrs,
dnsZones: targetOSConfig.GetDnsZones(),
cidrRanges: targetOSConfig.GetIpv4CidrRanges(),
}, nil
}

func (p *osConfigProvider) setTunIPv4FromCIDR(cidrRange string) error {
func (p *osConfigProvider) setV4IPsFromFirstCIDR(cidrRange string) error {
if p.tunIPv4 != "" {
// Only set these once.
return nil
}
ip, err := tunIPv4ForCIDR(cidrRange)
tunIPv4, dnsIPv4, err := ipsForCIDR(cidrRange)
if err != nil {
return trace.Wrap(err, "setting TUN IPv4 address for range %s", cidrRange)
}
p.tunIPv4 = ip
if err := p.cfg.addDNSAddress(dnsIPv4); err != nil {
return trace.Wrap(err, "adding IPv4 DNS server at %s", dnsIPv4.String())
}
p.tunIPv4 = tunIPv4.String()
p.dnsAddrs = append(p.dnsAddrs, dnsIPv4.String())
return nil
}

// ipsForCIDR returns the V4 IPs to assign to the interface and use for DNS in
// cidrRange.
func ipsForCIDR(cidrRange string) (tunIP net.IP, dnsIP net.IP, err error) {
_, ipnet, err := net.ParseCIDR(cidrRange)
if err != nil {
return nil, nil, trace.Wrap(err, "parsing CIDR %q", cidrRange)
}
// ipnet.IP is the network address, ending in 0s, like 100.64.0.0
// Add 1 to assign the TUN address, like 100.64.0.1
tunIP = ipnet.IP
tunIP[len(tunIP)-1]++

// Add 1 again to assign the DNS address, like 100.64.0.2
dnsIP = slices.Clone(tunIP)
dnsIP[len(dnsIP)-1]++

return tunIP, dnsIP, nil
}
Loading
Loading