tailscale/net/tsdial/dnsmap.go
Brad Fitzpatrick e1bd7488d0 all: remove LenIter, use Go 1.22 range-over-int instead
Updates #11058
Updates golang/go#65685

Change-Id: Ibb216b346e511d486271ab3d84e4546c521e4e22
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-02-25 12:29:45 -08:00

122 lines
2.9 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tsdial
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
"tailscale.com/types/netmap"
"tailscale.com/util/dnsname"
)
// dnsMap maps MagicDNS names (both base + FQDN) to their first IP.
// It must not be mutated once created.
//
// Example keys are "foo.domain.tld.beta.tailscale.net" and "foo",
// both without trailing dots, and both always lowercase.
type dnsMap map[string]netip.Addr
// canonMapKey canonicalizes its input s to be a dnsMap map key.
func canonMapKey(s string) string {
return strings.ToLower(strings.TrimSuffix(s, "."))
}
func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
if nm == nil {
return nil
}
ret := make(dnsMap)
suffix := nm.MagicDNSSuffix()
have4 := false
addrs := nm.GetAddresses()
if nm.Name != "" && addrs.Len() > 0 {
ip := addrs.At(0).Addr()
ret[canonMapKey(nm.Name)] = ip
if dnsname.HasSuffix(nm.Name, suffix) {
ret[canonMapKey(dnsname.TrimSuffix(nm.Name, suffix))] = ip
}
for i := range addrs.Len() {
if addrs.At(i).Addr().Is4() {
have4 = true
}
}
}
for _, p := range nm.Peers {
if p.Name() == "" {
continue
}
for i := range p.Addresses().Len() {
a := p.Addresses().At(i)
ip := a.Addr()
if ip.Is4() && !have4 {
continue
}
ret[canonMapKey(p.Name())] = ip
if dnsname.HasSuffix(p.Name(), suffix) {
ret[canonMapKey(dnsname.TrimSuffix(p.Name(), suffix))] = ip
}
break
}
}
for _, rec := range nm.DNS.ExtraRecords {
if rec.Type != "" {
continue
}
ip, err := netip.ParseAddr(rec.Value)
if err != nil {
continue
}
ret[canonMapKey(rec.Name)] = ip
}
return ret
}
// errUnresolved is a sentinel error returned by dnsMap.resolveMemory.
var errUnresolved = errors.New("address well formed but not resolved")
func splitHostPort(addr string) (host string, port uint16, err error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return "", 0, err
}
port16, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return "", 0, fmt.Errorf("invalid port in address %q", addr)
}
return host, uint16(port16), nil
}
// Resolve resolves addr into an IP:port using first the MagicDNS contents
// of m, else using the system resolver.
//
// The error is [exactly] errUnresolved if the addr is a name that isn't known
// in the map.
func (m dnsMap) resolveMemory(ctx context.Context, network, addr string) (_ netip.AddrPort, err error) {
host, port, err := splitHostPort(addr)
if err != nil {
// addr malformed or invalid port.
return netip.AddrPort{}, err
}
if ip, err := netip.ParseAddr(host); err == nil {
// addr was literal ip:port.
return netip.AddrPortFrom(ip, port), nil
}
// Host is not an IP, so assume it's a DNS name.
// Try MagicDNS first, otherwise a real DNS lookup.
ip := m[canonMapKey(host)]
if ip.IsValid() {
return netip.AddrPortFrom(ip, port), nil
}
return netip.AddrPort{}, errUnresolved
}