mirror of
https://github.com/tailscale/tailscale.git
synced 2026-06-03 21:01:54 +08:00
Currently we are picking a peer for the split dns routes when we get a netmap. Use the new custom scheme resolvers, installed per app in the config in the netmap, to allow us to choose which connector peer should handle a DNS request at the time the request is made. Fixes tailscale/corp#39858 Signed-off-by: Fran Bull <fran@tailscale.com>
87 lines
2.6 KiB
Go
87 lines
2.6 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package appc
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"tailscale.com/ipn/ipnext"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/appctype"
|
|
"tailscale.com/types/dnstype"
|
|
"tailscale.com/util/set"
|
|
)
|
|
|
|
const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental"
|
|
|
|
func isPeerEligibleConnector(peer tailcfg.NodeView) bool {
|
|
if !peer.Valid() || !peer.Hostinfo().Valid() {
|
|
return false
|
|
}
|
|
isConn, _ := peer.Hostinfo().AppConnector().Get()
|
|
return isConn
|
|
}
|
|
|
|
func sortByPreference(ns []tailcfg.NodeView) {
|
|
// The ordering of the nodes is semantic (callers use the first node they can
|
|
// get a peer api url for). We don't (currently 2026-02-27) have any
|
|
// preference over which node is chosen as long as it's consistent. In the
|
|
// future we anticipate integrating with traffic steering.
|
|
slices.SortFunc(ns, func(a, b tailcfg.NodeView) int {
|
|
return cmp.Compare(a.ID(), b.ID())
|
|
})
|
|
}
|
|
|
|
// PickConnector returns peers the backend knows about that match the app, in order of preference to use as
|
|
// a connector.
|
|
func PickConnector(nb ipnext.NodeBackend, app appctype.Conn25Attr) []tailcfg.NodeView {
|
|
appTagsSet := set.SetOf(app.Connectors)
|
|
matches := nb.AppendMatchingPeers(nil, func(n tailcfg.NodeView) bool {
|
|
if !isPeerEligibleConnector(n) {
|
|
return false
|
|
}
|
|
for _, t := range n.Tags().All() {
|
|
if appTagsSet.Contains(t) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
sortByPreference(matches)
|
|
return matches
|
|
}
|
|
|
|
// DNSAddrScheme is the custom URI scheme used for conn25-managed split DNS
|
|
// entries to determine the destination at query time rather than configuration
|
|
// time.
|
|
const DNSAddrScheme = "tailscale-app"
|
|
|
|
func AppDNSRoutes(hasCap func(c tailcfg.NodeCapability) bool, self tailcfg.NodeView) map[string][]*dnstype.Resolver {
|
|
if !hasCap(AppConnectorsExperimentalAttrName) {
|
|
return nil
|
|
}
|
|
apps, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.AppConnectorAttr](self.CapMap(), AppConnectorsExperimentalAttrName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
appNamesByDomain := map[string]string{}
|
|
for _, app := range apps {
|
|
for _, domain := range app.Domains {
|
|
domain, _ = strings.CutPrefix(domain, "*.")
|
|
domain = strings.ToLower(domain)
|
|
// in the case of multiple apps specifying the same domain (which is misconfiguration
|
|
// that should be validated at point of input) last write wins.
|
|
appNamesByDomain[domain] = app.Name
|
|
}
|
|
}
|
|
m := make(map[string][]*dnstype.Resolver, len(appNamesByDomain))
|
|
for domain, appName := range appNamesByDomain {
|
|
m[domain] = []*dnstype.Resolver{{Addr: fmt.Sprintf("%s:%s", DNSAddrScheme, appName)}}
|
|
}
|
|
return m
|
|
}
|