mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 22:47:30 +00:00
{types/dnstype, ipn/ipnlocal}: allow other DNS resolvers with exit nodes
dnstype.Resolver adds a boolean UseWithExitNode that controls whether the resolver should be used in tailscale exit node contexts (not wireguard exit nodes). If UseWithExitNode resolvers are found, they are installed as the global resolvers. If no UseWithExitNode resolvers are found, the exit node resolver continues to be installed as the global resolver. Split DNS Routes referencing UseWithExitNode resolvers are also installed. Updates #8237 Fixes tailscale/corp#30906 Fixes tailscale/corp#30907 Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
This commit is contained in:
@@ -3,17 +3,45 @@
|
||||
|
||||
package apitype
|
||||
|
||||
// DNSConfig is the DNS configuration for a tailnet
|
||||
// used in /tailnet/{tailnet}/dns/config.
|
||||
type DNSConfig struct {
|
||||
Resolvers []DNSResolver `json:"resolvers"`
|
||||
FallbackResolvers []DNSResolver `json:"fallbackResolvers"`
|
||||
Routes map[string][]DNSResolver `json:"routes"`
|
||||
Domains []string `json:"domains"`
|
||||
Nameservers []string `json:"nameservers"`
|
||||
Proxied bool `json:"proxied"`
|
||||
TempCorpIssue13969 string `json:"TempCorpIssue13969,omitempty"`
|
||||
// Resolvers are the DNS resolvers to use.
|
||||
Resolvers []DNSResolver `json:"resolvers"`
|
||||
|
||||
// FallbackResolvers are used in special split DNS configurations.
|
||||
// See https://github.com/tailscale/tailscale/issues/1743.
|
||||
FallbackResolvers []DNSResolver `json:"fallbackResolvers"`
|
||||
|
||||
// Routes map DNS name suffixes to a set of DNS resolvers,
|
||||
// used for Split DNS and other advanced routing overlays.
|
||||
Routes map[string][]DNSResolver `json:"routes"`
|
||||
|
||||
// Domains are the search domains to use.
|
||||
Domains []string `json:"domains"`
|
||||
|
||||
// Proxied means MagicDNS is enabled.
|
||||
Proxied bool `json:"proxied"`
|
||||
|
||||
// TempCorpIssue13969 is from an internal hack day prototype,
|
||||
// See https://github.com/tailscale/corp/issues/13969.
|
||||
TempCorpIssue13969 string `json:"TempCorpIssue13969,omitempty"`
|
||||
|
||||
// Nameservers are the IP addresses of the nameservers to use, and
|
||||
// are used in some legacy places instead of Resolvers.
|
||||
Nameservers []string `json:"nameservers"`
|
||||
}
|
||||
|
||||
// DNSResolver is a DNS resolver in a DNS configuration.
|
||||
type DNSResolver struct {
|
||||
Addr string `json:"addr"`
|
||||
// Addr is the address of the DNS resolver.
|
||||
Addr string `json:"addr"`
|
||||
|
||||
// BootstrapResolution is an optional suggested resolution for
|
||||
// the DoT/DoH resolver.
|
||||
BootstrapResolution []string `json:"bootstrapResolution,omitempty"`
|
||||
|
||||
// UseWithExitNode signals this resolver should be used
|
||||
// even when a tailscale exit node is configured on a device.
|
||||
UseWithExitNode bool `json:"useWithExitNode,omitempty"`
|
||||
}
|
||||
|
@@ -2080,7 +2080,14 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
|
||||
wantRoutes map[dnsname.FQDN][]*dnstype.Resolver
|
||||
}
|
||||
|
||||
defaultResolvers := []*dnstype.Resolver{{Addr: "default.example.com"}}
|
||||
const tsUseWithExitNodeResolverAddr = "usewithexitnode.example.com"
|
||||
defaultResolvers := []*dnstype.Resolver{
|
||||
{Addr: "default.example.com"},
|
||||
}
|
||||
containsFlaggedResolvers := append([]*dnstype.Resolver{
|
||||
{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true},
|
||||
}, defaultResolvers...)
|
||||
|
||||
wgResolvers := []*dnstype.Resolver{{Addr: "wg.example.com"}}
|
||||
peers := []tailcfg.NodeView{
|
||||
(&tailcfg.Node{
|
||||
@@ -2099,9 +2106,33 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
|
||||
}).View(),
|
||||
}
|
||||
exitDOH := peerAPIBase(&netmap.NetworkMap{Peers: peers}, peers[0]) + "/dns-query"
|
||||
routes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
baseRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"route.example.com.": {{Addr: "route.example.com"}},
|
||||
}
|
||||
containsEmptyRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"route.example.com.": {{Addr: "route.example.com"}},
|
||||
"empty.example.com.": {},
|
||||
}
|
||||
containsFlaggedRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"route.example.com.": {{Addr: "route.example.com"}},
|
||||
"withexit.example.com.": {{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
}
|
||||
containsFlaggedAndEmptyRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"empty.example.com.": {},
|
||||
"route.example.com.": {{Addr: "route.example.com"}},
|
||||
"withexit.example.com.": {{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
}
|
||||
flaggedRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"withexit.example.com.": {{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
}
|
||||
emptyRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"empty.example.com.": {},
|
||||
}
|
||||
flaggedAndEmptyRoutes := map[dnsname.FQDN][]*dnstype.Resolver{
|
||||
"empty.example.com.": {},
|
||||
"withexit.example.com.": {{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
}
|
||||
|
||||
stringifyRoutes := func(routes map[dnsname.FQDN][]*dnstype.Resolver) map[string][]*dnstype.Resolver {
|
||||
if routes == nil {
|
||||
return nil
|
||||
@@ -2138,19 +2169,23 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: exitDOH}},
|
||||
wantRoutes: nil,
|
||||
},
|
||||
{
|
||||
name: "tsExit/noRoutes/flaggedResolverOnly",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Resolvers: containsFlaggedResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
wantRoutes: nil,
|
||||
},
|
||||
|
||||
// The following two cases may need to be revisited. For a shared-in
|
||||
// exit node split-DNS may effectively break, furthermore in the future
|
||||
// if different nodes observe different DNS configurations, even a
|
||||
// tailnet local exit node may present a different DNS configuration,
|
||||
// which may not meet expectations in some use cases.
|
||||
// In the case where a default resolver is set, the default resolver
|
||||
// should also perhaps take precedence also.
|
||||
// When at tailscale exit node is in use,
|
||||
// only routes that reference resolvers with the UseWithExitNode should be installed,
|
||||
// as well as routes with 0-length resolver lists, which should be installed in all cases.
|
||||
{
|
||||
name: "tsExit/routes/noResolver",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(routes)},
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(baseRoutes)},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: exitDOH}},
|
||||
wantRoutes: nil,
|
||||
},
|
||||
@@ -2158,10 +2193,58 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
|
||||
name: "tsExit/routes/defaultResolver",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(routes), Resolvers: defaultResolvers},
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(baseRoutes), Resolvers: defaultResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: exitDOH}},
|
||||
wantRoutes: nil,
|
||||
},
|
||||
{
|
||||
name: "tsExit/routes/flaggedResolverOnly",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(baseRoutes), Resolvers: containsFlaggedResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
wantRoutes: nil,
|
||||
},
|
||||
{
|
||||
name: "tsExit/flaggedRoutesOnly/defaultResolver",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(containsFlaggedRoutes), Resolvers: defaultResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: exitDOH}},
|
||||
wantRoutes: flaggedRoutes,
|
||||
},
|
||||
{
|
||||
name: "tsExit/flaggedRoutesOnly/flaggedResolverOnly",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(containsFlaggedRoutes), Resolvers: containsFlaggedResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
wantRoutes: flaggedRoutes,
|
||||
},
|
||||
{
|
||||
name: "tsExit/emptyRoutesOnly/defaultResolver",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(containsEmptyRoutes), Resolvers: defaultResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: exitDOH}},
|
||||
wantRoutes: emptyRoutes,
|
||||
},
|
||||
{
|
||||
name: "tsExit/flaggedAndEmptyRoutesOnly/defaultResolver",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(containsFlaggedAndEmptyRoutes), Resolvers: defaultResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: exitDOH}},
|
||||
wantRoutes: flaggedAndEmptyRoutes,
|
||||
},
|
||||
{
|
||||
name: "tsExit/flaggedAndEmptyRoutesOnly/flaggedResolverOnly",
|
||||
exitNode: "ts",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(containsFlaggedAndEmptyRoutes), Resolvers: containsFlaggedResolvers},
|
||||
wantDefaultResolvers: []*dnstype.Resolver{{Addr: tsUseWithExitNodeResolverAddr, UseWithExitNode: true}},
|
||||
wantRoutes: flaggedAndEmptyRoutes,
|
||||
},
|
||||
|
||||
// WireGuard exit nodes with DNS capabilities provide a "fallback" type
|
||||
// behavior, they have a lower precedence than a default resolver, but
|
||||
@@ -2187,17 +2270,17 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
|
||||
name: "wgExit/routes/defaultResolver",
|
||||
exitNode: "wg",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(routes), Resolvers: defaultResolvers},
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(baseRoutes), Resolvers: defaultResolvers},
|
||||
wantDefaultResolvers: defaultResolvers,
|
||||
wantRoutes: routes,
|
||||
wantRoutes: baseRoutes,
|
||||
},
|
||||
{
|
||||
name: "wgExit/routes/noResolver",
|
||||
exitNode: "wg",
|
||||
peers: peers,
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(routes)},
|
||||
dnsConfig: &tailcfg.DNSConfig{Routes: stringifyRoutes(baseRoutes)},
|
||||
wantDefaultResolvers: wgResolvers,
|
||||
wantRoutes: routes,
|
||||
wantRoutes: baseRoutes,
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -578,6 +578,48 @@ func (nb *nodeBackend) doShutdown(cause error) {
|
||||
nb.eventClient.Close()
|
||||
}
|
||||
|
||||
// useWithExitNodeResolvers filters out resolvers so the ones that remain
|
||||
// are all the ones marked for use with exit nodes.
|
||||
func useWithExitNodeResolvers(resolvers []*dnstype.Resolver) []*dnstype.Resolver {
|
||||
var filtered []*dnstype.Resolver
|
||||
for _, res := range resolvers {
|
||||
if res.UseWithExitNode {
|
||||
filtered = append(filtered, res)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// useWithExitNodeRoutes filters out routes so the ones that remain
|
||||
// are either zero-length resolver lists, or lists containing only
|
||||
// resolvers marked for use with exit nodes.
|
||||
func useWithExitNodeRoutes(routes map[string][]*dnstype.Resolver) map[string][]*dnstype.Resolver {
|
||||
var filtered map[string][]*dnstype.Resolver
|
||||
for suffix, resolvers := range routes {
|
||||
// Suffixes with no resolvers represent a valid configuration,
|
||||
// and should persist regardless of exit node considerations.
|
||||
if len(resolvers) == 0 {
|
||||
if filtered == nil {
|
||||
filtered = map[string][]*dnstype.Resolver{}
|
||||
}
|
||||
filtered[suffix] = make([]*dnstype.Resolver, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
// In exit node contexts, we filter out resolvers not configured for use with
|
||||
// exit nodes. If there are no such configured resolvers, there should not be an entry for that suffix.
|
||||
filteredResolvers := useWithExitNodeResolvers(resolvers)
|
||||
if len(filteredResolvers) > 0 {
|
||||
if filtered == nil {
|
||||
filtered = map[string][]*dnstype.Resolver{}
|
||||
}
|
||||
filtered[suffix] = filteredResolvers
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// dnsConfigForNetmap returns a *dns.Config for the given netmap,
|
||||
// prefs, client OS version, and cloud hosting environment.
|
||||
//
|
||||
@@ -700,10 +742,41 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
dcfg.DefaultResolvers = append(dcfg.DefaultResolvers, resolvers...)
|
||||
}
|
||||
|
||||
addSplitDNSRoutes := func(routes map[string][]*dnstype.Resolver) {
|
||||
for suffix, resolvers := range routes {
|
||||
fqdn, err := dnsname.ToFQDN(suffix)
|
||||
if err != nil {
|
||||
logf("[unexpected] non-FQDN route suffix %q", suffix)
|
||||
}
|
||||
|
||||
// Create map entry even if len(resolvers) == 0; Issue 2706.
|
||||
// This lets the control plane send ExtraRecords for which we
|
||||
// can authoritatively answer "name not exists" for when the
|
||||
// control plane also sends this explicit but empty route
|
||||
// making it as something we handle.
|
||||
//
|
||||
// While we're already populating it, might as well size the
|
||||
// slice appropriately.
|
||||
// Per #9498 the exact requirements of nil vs empty slice remain
|
||||
// unclear, this is a haunted graveyard to be resolved.
|
||||
dcfg.Routes[fqdn] = slices.Clone(resolvers)
|
||||
}
|
||||
}
|
||||
|
||||
// If we're using an exit node and that exit node is new enough (1.19.x+)
|
||||
// to run a DoH DNS proxy, then send all our DNS traffic through it.
|
||||
// to run a DoH DNS proxy, then send all our DNS traffic through it,
|
||||
// unless we find resolvers with UseWithExitNode set, in which case we use that.
|
||||
if dohURL, ok := exitNodeCanProxyDNS(nm, peers, prefs.ExitNodeID()); ok {
|
||||
addDefault([]*dnstype.Resolver{{Addr: dohURL}})
|
||||
filtered := useWithExitNodeResolvers(nm.DNS.Resolvers)
|
||||
if len(filtered) > 0 {
|
||||
addDefault(filtered)
|
||||
} else {
|
||||
// If no default global resolvers with the override
|
||||
// are configured, configure the exit node's resolver.
|
||||
addDefault([]*dnstype.Resolver{{Addr: dohURL}})
|
||||
}
|
||||
|
||||
addSplitDNSRoutes(useWithExitNodeRoutes(nm.DNS.Routes))
|
||||
return dcfg
|
||||
}
|
||||
|
||||
@@ -718,25 +791,8 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
}
|
||||
}
|
||||
|
||||
for suffix, resolvers := range nm.DNS.Routes {
|
||||
fqdn, err := dnsname.ToFQDN(suffix)
|
||||
if err != nil {
|
||||
logf("[unexpected] non-FQDN route suffix %q", suffix)
|
||||
}
|
||||
|
||||
// Create map entry even if len(resolvers) == 0; Issue 2706.
|
||||
// This lets the control plane send ExtraRecords for which we
|
||||
// can authoritatively answer "name not exists" for when the
|
||||
// control plane also sends this explicit but empty route
|
||||
// making it as something we handle.
|
||||
//
|
||||
// While we're already populating it, might as well size the
|
||||
// slice appropriately.
|
||||
// Per #9498 the exact requirements of nil vs empty slice remain
|
||||
// unclear, this is a haunted graveyard to be resolved.
|
||||
dcfg.Routes[fqdn] = make([]*dnstype.Resolver, 0, len(resolvers))
|
||||
dcfg.Routes[fqdn] = append(dcfg.Routes[fqdn], resolvers...)
|
||||
}
|
||||
// Add split DNS routes, with no regard to exit node configuration.
|
||||
addSplitDNSRoutes(nm.DNS.Routes)
|
||||
|
||||
// Set FallbackResolvers as the default resolvers in the
|
||||
// scenarios that can't handle a purely split-DNS config. See
|
||||
|
@@ -169,7 +169,8 @@ type CapabilityVersion int
|
||||
// - 122: 2025-07-21: Client sends Hostinfo.ExitNodeID to report which exit node it has selected, if any.
|
||||
// - 123: 2025-07-28: fix deadlock regression from cryptokey routing change (issue #16651)
|
||||
// - 124: 2025-08-08: removed NodeAttrDisableMagicSockCryptoRouting support, crypto routing is now mandatory
|
||||
const CurrentCapabilityVersion CapabilityVersion = 124
|
||||
// - 125: 2025-08-11: dnstype.Resolver adds UseWithExitNode field.
|
||||
const CurrentCapabilityVersion CapabilityVersion = 125
|
||||
|
||||
// ID is an integer ID for a user, node, or login allocated by the
|
||||
// control plane.
|
||||
@@ -1730,12 +1731,6 @@ type DNSConfig struct {
|
||||
// proxying to be enabled.
|
||||
Proxied bool `json:",omitempty"`
|
||||
|
||||
// The following fields are only set and used by
|
||||
// MapRequest.Version >=9 and <14.
|
||||
|
||||
// Nameservers are the IP addresses of the nameservers to use.
|
||||
Nameservers []netip.Addr `json:",omitempty"`
|
||||
|
||||
// CertDomains are the set of DNS names for which the control
|
||||
// plane server will assist with provisioning TLS
|
||||
// certificates. See SetDNSRequest, which can be used to
|
||||
@@ -1767,6 +1762,12 @@ type DNSConfig struct {
|
||||
// It contains a user inputed URL that should have a list of domains to be blocked.
|
||||
// See https://github.com/tailscale/corp/issues/13969.
|
||||
TempCorpIssue13969 string `json:",omitempty"`
|
||||
|
||||
// The following fields are only set and used by
|
||||
// MapRequest.Version >=9 and <14.
|
||||
|
||||
// Nameservers are the IP addresses of the nameservers to use.
|
||||
Nameservers []netip.Addr `json:",omitempty"`
|
||||
}
|
||||
|
||||
// DNSRecord is an extra DNS record to add to MagicDNS.
|
||||
|
@@ -278,10 +278,10 @@ func (src *DNSConfig) Clone() *DNSConfig {
|
||||
}
|
||||
}
|
||||
dst.Domains = append(src.Domains[:0:0], src.Domains...)
|
||||
dst.Nameservers = append(src.Nameservers[:0:0], src.Nameservers...)
|
||||
dst.CertDomains = append(src.CertDomains[:0:0], src.CertDomains...)
|
||||
dst.ExtraRecords = append(src.ExtraRecords[:0:0], src.ExtraRecords...)
|
||||
dst.ExitNodeFilteredSet = append(src.ExitNodeFilteredSet[:0:0], src.ExitNodeFilteredSet...)
|
||||
dst.Nameservers = append(src.Nameservers[:0:0], src.Nameservers...)
|
||||
return dst
|
||||
}
|
||||
|
||||
@@ -292,11 +292,11 @@ var _DNSConfigCloneNeedsRegeneration = DNSConfig(struct {
|
||||
FallbackResolvers []*dnstype.Resolver
|
||||
Domains []string
|
||||
Proxied bool
|
||||
Nameservers []netip.Addr
|
||||
CertDomains []string
|
||||
ExtraRecords []DNSRecord
|
||||
ExitNodeFilteredSet []string
|
||||
TempCorpIssue13969 string
|
||||
Nameservers []netip.Addr
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of RegisterResponse.
|
||||
|
@@ -552,13 +552,13 @@ func (v DNSConfigView) FallbackResolvers() views.SliceView[*dnstype.Resolver, dn
|
||||
}
|
||||
func (v DNSConfigView) Domains() views.Slice[string] { return views.SliceOf(v.ж.Domains) }
|
||||
func (v DNSConfigView) Proxied() bool { return v.ж.Proxied }
|
||||
func (v DNSConfigView) Nameservers() views.Slice[netip.Addr] { return views.SliceOf(v.ж.Nameservers) }
|
||||
func (v DNSConfigView) CertDomains() views.Slice[string] { return views.SliceOf(v.ж.CertDomains) }
|
||||
func (v DNSConfigView) ExtraRecords() views.Slice[DNSRecord] { return views.SliceOf(v.ж.ExtraRecords) }
|
||||
func (v DNSConfigView) ExitNodeFilteredSet() views.Slice[string] {
|
||||
return views.SliceOf(v.ж.ExitNodeFilteredSet)
|
||||
}
|
||||
func (v DNSConfigView) TempCorpIssue13969() string { return v.ж.TempCorpIssue13969 }
|
||||
func (v DNSConfigView) TempCorpIssue13969() string { return v.ж.TempCorpIssue13969 }
|
||||
func (v DNSConfigView) Nameservers() views.Slice[netip.Addr] { return views.SliceOf(v.ж.Nameservers) }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _DNSConfigViewNeedsRegeneration = DNSConfig(struct {
|
||||
@@ -567,11 +567,11 @@ var _DNSConfigViewNeedsRegeneration = DNSConfig(struct {
|
||||
FallbackResolvers []*dnstype.Resolver
|
||||
Domains []string
|
||||
Proxied bool
|
||||
Nameservers []netip.Addr
|
||||
CertDomains []string
|
||||
ExtraRecords []DNSRecord
|
||||
ExitNodeFilteredSet []string
|
||||
TempCorpIssue13969 string
|
||||
Nameservers []netip.Addr
|
||||
}{})
|
||||
|
||||
// View returns a read-only view of RegisterResponse.
|
||||
|
@@ -35,6 +35,12 @@ type Resolver struct {
|
||||
//
|
||||
// As of 2022-09-08, BootstrapResolution is not yet used.
|
||||
BootstrapResolution []netip.Addr `json:",omitempty"`
|
||||
|
||||
// UseWithExitNode designates that this resolver should continue to be used when an
|
||||
// exit node is in use. Normally, DNS resolution is delegated to the exit node but
|
||||
// there are situations where it is preferable to still use a Split DNS server and/or
|
||||
// global DNS server instead of the exit node.
|
||||
UseWithExitNode bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
// IPPort returns r.Addr as an IP address and port if either
|
||||
@@ -64,5 +70,7 @@ func (r *Resolver) Equal(other *Resolver) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
return r.Addr == other.Addr && slices.Equal(r.BootstrapResolution, other.BootstrapResolution)
|
||||
return r.Addr == other.Addr &&
|
||||
slices.Equal(r.BootstrapResolution, other.BootstrapResolution) &&
|
||||
r.UseWithExitNode == other.UseWithExitNode
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ func (src *Resolver) Clone() *Resolver {
|
||||
var _ResolverCloneNeedsRegeneration = Resolver(struct {
|
||||
Addr string
|
||||
BootstrapResolution []netip.Addr
|
||||
UseWithExitNode bool
|
||||
}{})
|
||||
|
||||
// Clone duplicates src into dst and reports whether it succeeded.
|
||||
|
@@ -17,7 +17,7 @@ func TestResolverEqual(t *testing.T) {
|
||||
fieldNames = append(fieldNames, field.Name)
|
||||
}
|
||||
sort.Strings(fieldNames)
|
||||
if !slices.Equal(fieldNames, []string{"Addr", "BootstrapResolution"}) {
|
||||
if !slices.Equal(fieldNames, []string{"Addr", "BootstrapResolution", "UseWithExitNode"}) {
|
||||
t.Errorf("Resolver fields changed; update test")
|
||||
}
|
||||
|
||||
@@ -68,6 +68,18 @@ func TestResolverEqual(t *testing.T) {
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "equal UseWithExitNode",
|
||||
a: &Resolver{Addr: "dns.example.com", UseWithExitNode: true},
|
||||
b: &Resolver{Addr: "dns.example.com", UseWithExitNode: true},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not equal UseWithExitNode",
|
||||
a: &Resolver{Addr: "dns.example.com", UseWithExitNode: true},
|
||||
b: &Resolver{Addr: "dns.example.com", UseWithExitNode: false},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@@ -64,10 +64,12 @@ func (v ResolverView) Addr() string { return v.ж.Addr }
|
||||
func (v ResolverView) BootstrapResolution() views.Slice[netip.Addr] {
|
||||
return views.SliceOf(v.ж.BootstrapResolution)
|
||||
}
|
||||
func (v ResolverView) UseWithExitNode() bool { return v.ж.UseWithExitNode }
|
||||
func (v ResolverView) Equal(v2 ResolverView) bool { return v.ж.Equal(v2.ж) }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _ResolverViewNeedsRegeneration = Resolver(struct {
|
||||
Addr string
|
||||
BootstrapResolution []netip.Addr
|
||||
UseWithExitNode bool
|
||||
}{})
|
||||
|
Reference in New Issue
Block a user