mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-22 16:46:29 +00:00
cmd/containerboot: switch to tsclient QueryDNS to convert FQDN to Tailscale IPs
Fixes #18262
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
(cherry picked from commit 39881c0187)
This commit is contained in:
@@ -173,7 +173,7 @@ func (ep *egressProxy) sync(ctx context.Context, n ipn.Notify) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving current egress proxy status: %w", err)
|
||||
}
|
||||
newStatus, err := ep.syncEgressConfigs(cfgs, status, n)
|
||||
newStatus, err := ep.syncEgressConfigs(ctx, cfgs, status, n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error syncing egress service configs: %w", err)
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func (ep *egressProxy) addrsHaveChanged(n ipn.Notify) bool {
|
||||
// syncEgressConfigs adds and deletes firewall rules to match the desired
|
||||
// configuration. It uses the provided status to determine what is currently
|
||||
// applied and updates the status after a successful sync.
|
||||
func (ep *egressProxy) syncEgressConfigs(cfgs *egressservices.Configs, status *egressservices.Status, n ipn.Notify) (*egressservices.Status, error) {
|
||||
func (ep *egressProxy) syncEgressConfigs(ctx context.Context, cfgs *egressservices.Configs, status *egressservices.Status, n ipn.Notify) (*egressservices.Status, error) {
|
||||
if !(wantsServicesConfigured(cfgs) || hasServicesConfigured(status)) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -202,7 +202,6 @@ func (ep *egressProxy) syncEgressConfigs(cfgs *egressservices.Configs, status *e
|
||||
// Delete unnecessary services.
|
||||
if err := ep.deleteUnnecessaryServices(cfgs, status); err != nil {
|
||||
return nil, fmt.Errorf("error deleting services: %w", err)
|
||||
|
||||
}
|
||||
newStatus := &egressservices.Status{}
|
||||
if !wantsServicesConfigured(cfgs) {
|
||||
@@ -213,7 +212,7 @@ func (ep *egressProxy) syncEgressConfigs(cfgs *egressservices.Configs, status *e
|
||||
rulesPerSvcToAdd := make(map[string][]rule, 0)
|
||||
rulesPerSvcToDelete := make(map[string][]rule, 0)
|
||||
for svcName, cfg := range *cfgs {
|
||||
tailnetTargetIPs, err := ep.tailnetTargetIPsForSvc(cfg, n)
|
||||
tailnetTargetIPs, err := ep.tailnetTargetIPsForSvc(ctx, cfg, n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error determining tailnet target IPs: %w", err)
|
||||
}
|
||||
@@ -243,9 +242,6 @@ func (ep *egressProxy) syncEgressConfigs(cfgs *egressservices.Configs, status *e
|
||||
local = pfx.Addr()
|
||||
break
|
||||
}
|
||||
if !local.IsValid() {
|
||||
return nil, fmt.Errorf("no valid local IP: %v", local)
|
||||
}
|
||||
if err := ep.nfr.EnsureSNATForDst(local, t); err != nil {
|
||||
return nil, fmt.Errorf("error setting up SNAT rule: %w", err)
|
||||
}
|
||||
@@ -456,7 +452,7 @@ func (ep *egressProxy) setStatus(ctx context.Context, status *egressservices.Sta
|
||||
// FQDN, resolve the FQDN and return the resolved IPs. It checks if the
|
||||
// netfilter runner supports IPv6 NAT and skips any IPv6 addresses if it
|
||||
// doesn't.
|
||||
func (ep *egressProxy) tailnetTargetIPsForSvc(svc egressservices.Config, n ipn.Notify) (addrs []netip.Addr, err error) {
|
||||
func (ep *egressProxy) tailnetTargetIPsForSvc(ctx context.Context, svc egressservices.Config, n ipn.Notify) (addrs []netip.Addr, err error) {
|
||||
if svc.TailnetTarget.IP != "" {
|
||||
addr, err := netip.ParseAddr(svc.TailnetTarget.IP)
|
||||
if err != nil {
|
||||
@@ -476,11 +472,8 @@ func (ep *egressProxy) tailnetTargetIPsForSvc(svc egressservices.Config, n ipn.N
|
||||
log.Printf("netmap is not available, unable to determine backend addresses for %s", svc.TailnetTarget.FQDN)
|
||||
return addrs, nil
|
||||
}
|
||||
egressAddrs, err := resolveTailnetFQDN(n.NetMap, svc.TailnetTarget.FQDN)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching backend addresses for %q: %w", svc.TailnetTarget.FQDN, err)
|
||||
}
|
||||
if len(egressAddrs) == 0 {
|
||||
egressAddrs, err := resolveTailnetFQDN(ctx, ep.tsClient, svc.TailnetTarget.FQDN)
|
||||
if err != nil || len(egressAddrs) == 0 {
|
||||
log.Printf("tailnet target %q does not have any backend addresses, skipping", svc.TailnetTarget.FQDN)
|
||||
return addrs, nil
|
||||
}
|
||||
@@ -532,7 +525,6 @@ func (ep *egressProxy) shouldResync(n ipn.Notify) bool {
|
||||
// ensureServiceDeleted ensures that any rules for an egress service are removed
|
||||
// from the firewall configuration.
|
||||
func ensureServiceDeleted(svcName string, svc *egressservices.ServiceStatus, nfr linuxfw.NetfilterRunner) error {
|
||||
|
||||
// Note that the portmap is needed for iptables based firewall only.
|
||||
// Nftables group rules for a service in a chain, so there is no need to
|
||||
// specify individual portmapping based rules.
|
||||
|
||||
@@ -117,7 +117,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
kubeutils "tailscale.com/k8s-operator"
|
||||
@@ -528,7 +530,7 @@ runLoop:
|
||||
}
|
||||
}
|
||||
if cfg.TailnetTargetFQDN != "" {
|
||||
egressAddrs, err := resolveTailnetFQDN(n.NetMap, cfg.TailnetTargetFQDN)
|
||||
egressAddrs, err := resolveTailnetFQDN(ctx, client, cfg.TailnetTargetFQDN)
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
break
|
||||
@@ -884,22 +886,58 @@ func runHTTPServer(mux *http.ServeMux, addr string) (close func() error) {
|
||||
|
||||
// resolveTailnetFQDN resolves a tailnet FQDN to a list of IP prefixes, which
|
||||
// can be either a peer device or a Tailscale Service.
|
||||
func resolveTailnetFQDN(nm *netmap.NetworkMap, fqdn string) ([]netip.Prefix, error) {
|
||||
func resolveTailnetFQDN(ctx context.Context, c *local.Client, fqdn string) ([]netip.Prefix, error) {
|
||||
dnsFQDN, err := dnsname.ToFQDN(fqdn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing %q as FQDN: %w", fqdn, err)
|
||||
}
|
||||
|
||||
// Check all peer devices first.
|
||||
for _, p := range nm.Peers {
|
||||
if strings.EqualFold(p.Name(), dnsFQDN.WithTrailingDot()) {
|
||||
return p.Addresses().AsSlice(), nil
|
||||
bytes, _, err := c.QueryDNS(ctx, dnsFQDN.WithTrailingDot(), "A")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying tailscale DNS: %w", err)
|
||||
}
|
||||
|
||||
var p dnsmessage.Parser
|
||||
header, err := p.Start(bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DNS response: %w", err)
|
||||
}
|
||||
p.SkipAllQuestions()
|
||||
if header.RCode != dnsmessage.RCodeSuccess {
|
||||
return nil, fmt.Errorf("no answers in DNS query response for FQDN %q", dnsFQDN.WithTrailingDot())
|
||||
}
|
||||
|
||||
answers, err := p.AllAnswers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DNS answers: %w", err)
|
||||
}
|
||||
|
||||
addrs := []netip.Prefix{}
|
||||
for _, a := range answers {
|
||||
if a.Header.Type == dnsmessage.TypeA {
|
||||
ar, ok := a.Body.(*dnsmessage.AResource)
|
||||
if !ok {
|
||||
log.Printf("error: failed to cast body to *dnsmessage.AResource")
|
||||
continue
|
||||
}
|
||||
|
||||
addr := netip.AddrFrom4(ar.A)
|
||||
if !addr.IsValid() {
|
||||
log.Printf("record %q is not a valid address", addr.String())
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := netip.PrefixFrom(addr, 32)
|
||||
if !prefix.IsValid() {
|
||||
log.Printf("address %q is not a valid prefix", prefix.String())
|
||||
continue
|
||||
}
|
||||
addrs = append(addrs, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// If not found yet, check for a matching Tailscale Service.
|
||||
if svcIPs := serviceIPsFromNetMap(nm, dnsFQDN); len(svcIPs) != 0 {
|
||||
return svcIPs, nil
|
||||
if len(addrs) > 0 {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find Tailscale node or service %q; it either does not exist, or not reachable because of ACLs", fqdn)
|
||||
|
||||
Reference in New Issue
Block a user