diff --git a/cmd/tailscale/cli/dns-query.go b/cmd/tailscale/cli/dns-query.go index e1b4277db..6336b159c 100644 --- a/cmd/tailscale/cli/dns-query.go +++ b/cmd/tailscale/cli/dns-query.go @@ -16,7 +16,7 @@ "github.com/peterbourgon/ff/v3/ffcli" "golang.org/x/net/dns/dnsmessage" - "tailscale.com/cmd/tailscale/jsonoutput" + "tailscale.com/cmd/tailscale/tsdnsjsonv0" ) var dnsQueryArgs struct { @@ -74,7 +74,7 @@ func runDNSQuery(ctx context.Context, args []string) error { return fmt.Errorf("failed to query DNS: %w", err) } - data := &jsonoutput.DNSQueryResult{ + data := &tsdnsjsonv0.QueryResponse{ Name: name, QueryType: queryType, } @@ -97,9 +97,9 @@ func runDNSQuery(ctx context.Context, args []string) error { if err != nil { return fmt.Errorf("failed to parse DNS answers: %w", err) } - data.Answers = make([]jsonoutput.DNSAnswer, 0, len(answers)) + data.Answers = make([]tsdnsjsonv0.Answer, 0, len(answers)) for _, a := range answers { - data.Answers = append(data.Answers, jsonoutput.DNSAnswer{ + data.Answers = append(data.Answers, tsdnsjsonv0.Answer{ Name: a.Header.Name.String(), TTL: a.Header.TTL, Class: a.Header.Class.String(), @@ -121,7 +121,7 @@ func runDNSQuery(ctx context.Context, args []string) error { return nil } -func formatDNSQueryText(data *jsonoutput.DNSQueryResult) string { +func formatDNSQueryText(data *tsdnsjsonv0.QueryResponse) string { var sb strings.Builder fmt.Fprintf(&sb, "DNS query for %q (%s) using internal resolver:\n", data.Name, data.QueryType) @@ -159,8 +159,8 @@ func formatDNSQueryText(data *jsonoutput.DNSQueryResult) string { return sb.String() } -// formatResolverString formats a jsonoutput.DNSResolverInfo for human-readable text output. -func formatResolverString(r jsonoutput.DNSResolverInfo) string { +// formatResolverString formats a [tsdnsjsonv0.ResolverInfo] for human-readable text output. +func formatResolverString(r tsdnsjsonv0.ResolverInfo) string { if len(r.BootstrapResolution) > 0 { return fmt.Sprintf("%s (bootstrap: %v)", r.Addr, r.BootstrapResolution) } diff --git a/cmd/tailscale/cli/dns-status.go b/cmd/tailscale/cli/dns-status.go index 2e13131a1..2b2e64871 100644 --- a/cmd/tailscale/cli/dns-status.go +++ b/cmd/tailscale/cli/dns-status.go @@ -14,7 +14,7 @@ "github.com/peterbourgon/ff/v3/ffcli" - "tailscale.com/cmd/tailscale/jsonoutput" + "tailscale.com/cmd/tailscale/tsdnsjsonv0" "tailscale.com/types/dnstype" ) @@ -85,9 +85,9 @@ json bool } -// makeDNSResolverInfo converts a dnstype.Resolver to a jsonoutput.DNSResolverInfo. -func makeDNSResolverInfo(r *dnstype.Resolver) jsonoutput.DNSResolverInfo { - info := jsonoutput.DNSResolverInfo{Addr: r.Addr} +// makeDNSResolverInfo converts a [dnstype.Resolver] to a [tsdnsjsonv0.ResolverInfo]. +func makeDNSResolverInfo(r *dnstype.Resolver) tsdnsjsonv0.ResolverInfo { + info := tsdnsjsonv0.ResolverInfo{Addr: r.Addr} if r.BootstrapResolution != nil { info.BootstrapResolution = make([]string, 0, len(r.BootstrapResolution)) for _, a := range r.BootstrapResolution { @@ -108,12 +108,12 @@ func runDNSStatus(ctx context.Context, args []string) error { return err } - data := &jsonoutput.DNSStatusResult{ + data := &tsdnsjsonv0.StatusResponse{ TailscaleDNS: prefs.CorpDNS, } if s.CurrentTailnet != nil { - data.CurrentTailnet = &jsonoutput.DNSTailnetInfo{ + data.CurrentTailnet = &tsdnsjsonv0.TailnetInfo{ MagicDNSEnabled: s.CurrentTailnet.MagicDNSEnabled, MagicDNSSuffix: s.CurrentTailnet.MagicDNSSuffix, SelfDNSName: s.Self.DNSName, @@ -128,7 +128,7 @@ func runDNSStatus(ctx context.Context, args []string) error { data.Resolvers = append(data.Resolvers, makeDNSResolverInfo(r)) } - data.SplitDNSRoutes = make(map[string][]jsonoutput.DNSResolverInfo) + data.SplitDNSRoutes = make(map[string][]tsdnsjsonv0.ResolverInfo) for k, v := range dnsConfig.Routes { for _, r := range v { data.SplitDNSRoutes[k] = append(data.SplitDNSRoutes[k], makeDNSResolverInfo(r)) @@ -150,7 +150,7 @@ func runDNSStatus(ctx context.Context, args []string) error { data.CertDomains = dnsConfig.CertDomains for _, er := range dnsConfig.ExtraRecords { - data.ExtraRecords = append(data.ExtraRecords, jsonoutput.DNSExtraRecord{ + data.ExtraRecords = append(data.ExtraRecords, tsdnsjsonv0.ExtraRecord{ Name: er.Name, Type: er.Type, Value: er.Value, @@ -167,7 +167,7 @@ func runDNSStatus(ctx context.Context, args []string) error { data.SystemDNSError = err.Error() } } else if osCfg != nil { - data.SystemDNS = &jsonoutput.DNSSystemConfig{ + data.SystemDNS = &tsdnsjsonv0.SystemConfig{ Nameservers: osCfg.Nameservers, SearchDomains: osCfg.SearchDomains, MatchDomains: osCfg.MatchDomains, @@ -187,7 +187,7 @@ func runDNSStatus(ctx context.Context, args []string) error { return nil } -func formatDNSStatusText(data *jsonoutput.DNSStatusResult, all bool) string { +func formatDNSStatusText(data *tsdnsjsonv0.StatusResponse, all bool) string { var sb strings.Builder fmt.Fprintf(&sb, "\n") diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 057a59e5f..c240581e7 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -187,6 +187,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete tailscale.com/cmd/tailscale/jsonoutput from tailscale.com/cmd/tailscale/cli + tailscale.com/cmd/tailscale/tsdnsjsonv0 from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscale/tsroutecheckjsonv0 from tailscale.com/cmd/tailscale/cli tailscale.com/control/controlbase from tailscale.com/control/controlhttp+ tailscale.com/control/controlhttp from tailscale.com/control/ts2021 diff --git a/cmd/tailscale/tsdnsjsonv0/doc.go b/cmd/tailscale/tsdnsjsonv0/doc.go new file mode 100644 index 000000000..6017e157c --- /dev/null +++ b/cmd/tailscale/tsdnsjsonv0/doc.go @@ -0,0 +1,14 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package tsdnsjsonv0 provides types for unmarshalling the JSON output of the +// "tailscale dns --json" command: +// +// - [QueryResponse] will unmarshal the output of "tailscale dns query --json=1" +// - [StatusResponse] will unmarshal the output of "tailscale dns status --json=1". +// +// # WARNING: unstable +// +// Format is "v0" and is subject to change. +// There is no guarantee of backwards or forwards compatibility. +package tsdnsjsonv0 diff --git a/cmd/tailscale/jsonoutput/example_dnsqueryresult_test.go b/cmd/tailscale/tsdnsjsonv0/example_dnsqueryresult_test.go similarity index 60% rename from cmd/tailscale/jsonoutput/example_dnsqueryresult_test.go rename to cmd/tailscale/tsdnsjsonv0/example_dnsqueryresult_test.go index 2cfc5111a..718bd18c5 100644 --- a/cmd/tailscale/jsonoutput/example_dnsqueryresult_test.go +++ b/cmd/tailscale/tsdnsjsonv0/example_dnsqueryresult_test.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package jsonoutput_test +package tsdnsjsonv0_test import ( "encoding/json" @@ -10,10 +10,10 @@ "os" "os/exec" - "tailscale.com/cmd/tailscale/jsonoutput" + "tailscale.com/cmd/tailscale/tsdnsjsonv0" ) -func ExampleDNSQueryResult() { +func ExampleQueryResponse() { cmd := exec.Command("tailscale", "dns", "query", "--json", "hello.ts.net") out, err := cmd.Output() if err != nil { @@ -23,9 +23,9 @@ func ExampleDNSQueryResult() { panic(err) } - var dnsQuery jsonoutput.DNSQueryResult - if err := json.Unmarshal(out, &dnsQuery); err != nil { + var resp tsdnsjsonv0.QueryResponse + if err := json.Unmarshal(out, &resp); err != nil { panic(err) } - fmt.Printf("{type: %s, name: %q}\n", dnsQuery.QueryType, dnsQuery.Name) + fmt.Printf("{type: %s, name: %q}\n", resp.QueryType, resp.Name) } diff --git a/cmd/tailscale/jsonoutput/example_dnsstatusresult_test.go b/cmd/tailscale/tsdnsjsonv0/example_dnsstatusresult_test.go similarity index 57% rename from cmd/tailscale/jsonoutput/example_dnsstatusresult_test.go rename to cmd/tailscale/tsdnsjsonv0/example_dnsstatusresult_test.go index 5b6a35a0c..aabe92574 100644 --- a/cmd/tailscale/jsonoutput/example_dnsstatusresult_test.go +++ b/cmd/tailscale/tsdnsjsonv0/example_dnsstatusresult_test.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package jsonoutput_test +package tsdnsjsonv0_test import ( "encoding/json" @@ -10,10 +10,10 @@ "os" "os/exec" - "tailscale.com/cmd/tailscale/jsonoutput" + "tailscale.com/cmd/tailscale/tsdnsjsonv0" ) -func ExampleDNSStatusResult() { +func ExampleStatusResponse() { cmd := exec.Command("tailscale", "dns", "status", "--json") out, err := cmd.Output() if err != nil { @@ -23,9 +23,9 @@ func ExampleDNSStatusResult() { panic(err) } - var dnsStatus jsonoutput.DNSStatusResult - if err := json.Unmarshal(out, &dnsStatus); err != nil { + var resp tsdnsjsonv0.StatusResponse + if err := json.Unmarshal(out, &resp); err != nil { panic(err) } - fmt.Printf("{accept-dns: %t, resolvers: %q}\n", dnsStatus.TailscaleDNS, dnsStatus.Resolvers) + fmt.Printf("{accept-dns: %t, resolvers: %q}\n", resp.TailscaleDNS, resp.Resolvers) } diff --git a/cmd/tailscale/tsdnsjsonv0/query.go b/cmd/tailscale/tsdnsjsonv0/query.go new file mode 100644 index 000000000..311ff1d5c --- /dev/null +++ b/cmd/tailscale/tsdnsjsonv0/query.go @@ -0,0 +1,25 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package tsdnsjsonv0 + +// Answer is a single DNS resource record from a query response. +type Answer struct { + Name string + TTL uint32 + Class string // e.g. "ClassINET" + Type string // e.g. "TypeA", "TypeAAAA" + Body string // human-readable record data +} + +// QueryResponse is the result of a DNS query via the Tailscale +// internal forwarder (100.100.100.100). It is the output of: +// +// $ tailscale dns query --json NAME +type QueryResponse struct { + Name string + QueryType string // e.g. "A", "AAAA" + Resolvers []ResolverInfo `json:",omitzero"` + ResponseCode string // e.g. "RCodeSuccess", "RCodeNameError" + Answers []Answer `json:",omitzero"` +} diff --git a/cmd/tailscale/jsonoutput/dns.go b/cmd/tailscale/tsdnsjsonv0/status.go similarity index 62% rename from cmd/tailscale/jsonoutput/dns.go rename to cmd/tailscale/tsdnsjsonv0/status.go index a03113f0e..1397a36ea 100644 --- a/cmd/tailscale/jsonoutput/dns.go +++ b/cmd/tailscale/tsdnsjsonv0/status.go @@ -1,10 +1,17 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package jsonoutput +package tsdnsjsonv0 -// DNSResolverInfo is the JSON form of [tailscale.com/types/dnstype.Resolver]. -type DNSResolverInfo struct { +// ExtraRecord is the JSON form of [tailscale.com/tailcfg.DNSRecord]. +type ExtraRecord struct { + Name string + Type string `json:",omitempty"` // empty means A or AAAA, depending on Value + Value string // typically an IP address +} + +// ResolverInfo is the JSON form of [tailscale.com/types/dnstype.Resolver]. +type ResolverInfo struct { // Addr is a plain IP, IP:port, DoH URL, or HTTP-over-WireGuard URL. Addr string @@ -13,16 +20,9 @@ type DNSResolverInfo struct { BootstrapResolution []string `json:",omitempty"` } -// DNSExtraRecord is the JSON form of [tailscale.com/tailcfg.DNSRecord]. -type DNSExtraRecord struct { - Name string - Type string `json:",omitempty"` // empty means A or AAAA, depending on Value - Value string // typically an IP address -} - -// DNSSystemConfig is the OS DNS configuration as observed by Tailscale, +// SystemConfig is the OS DNS configuration as observed by Tailscale, // mirroring [tailscale.com/net/dns.OSConfig]. -type DNSSystemConfig struct { +type SystemConfig struct { Nameservers []string `json:",omitzero"` SearchDomains []string `json:",omitzero"` @@ -32,10 +32,10 @@ type DNSSystemConfig struct { MatchDomains []string `json:",omitzero"` } -// DNSTailnetInfo describes MagicDNS configuration for the tailnet, +// TailnetInfo describes MagicDNS configuration for the tailnet, // combining [tailscale.com/ipn/ipnstate.TailnetStatus] // and [tailscale.com/ipn/ipnstate.PeerStatus]. -type DNSTailnetInfo struct { +type TailnetInfo struct { // MagicDNSEnabled is whether MagicDNS is enabled for the // tailnet. The device may still not use it if // --accept-dns=false. @@ -50,30 +50,30 @@ type DNSTailnetInfo struct { SelfDNSName string `json:",omitempty"` } -// DNSStatusResult is the full DNS status collected from the local +// StatusResponse is the full DNS status collected from the local // Tailscale daemon. It is the output of: // // $ tailscale dns status --json -type DNSStatusResult struct { +type StatusResponse struct { // TailscaleDNS is whether the Tailscale DNS configuration is // installed on this device (the --accept-dns setting). TailscaleDNS bool // CurrentTailnet describes MagicDNS configuration for the tailnet. - CurrentTailnet *DNSTailnetInfo `json:",omitzero"` // nil if not connected + CurrentTailnet *TailnetInfo `json:",omitzero"` // nil if not connected // Resolvers are the DNS resolvers, in preference order. If // empty, the system defaults are used. - Resolvers []DNSResolverInfo `json:",omitzero"` + Resolvers []ResolverInfo `json:",omitzero"` // SplitDNSRoutes maps domain suffixes to dedicated resolvers. // An empty resolver slice means the suffix is handled by // Tailscale's built-in resolver (100.100.100.100). - SplitDNSRoutes map[string][]DNSResolverInfo `json:",omitzero"` + SplitDNSRoutes map[string][]ResolverInfo `json:",omitzero"` // FallbackResolvers are like Resolvers but only used when // split DNS needs explicit default resolvers. - FallbackResolvers []DNSResolverInfo `json:",omitzero"` + FallbackResolvers []ResolverInfo `json:",omitzero"` SearchDomains []string `json:",omitzero"` @@ -87,7 +87,7 @@ type DNSStatusResult struct { CertDomains []string `json:",omitzero"` // ExtraRecords contains extra DNS records in the MagicDNS config. - ExtraRecords []DNSExtraRecord `json:",omitzero"` + ExtraRecords []ExtraRecord `json:",omitzero"` // ExitNodeFilteredSet are DNS suffixes this node won't resolve // when acting as an exit node DNS proxy. Period-prefixed @@ -95,27 +95,6 @@ type DNSStatusResult struct { // lowercase, no trailing dots. ExitNodeFilteredSet []string `json:",omitzero"` - SystemDNS *DNSSystemConfig `json:",omitzero"` // nil if unavailable - SystemDNSError string `json:",omitempty"` -} - -// DNSAnswer is a single DNS resource record from a query response. -type DNSAnswer struct { - Name string - TTL uint32 - Class string // e.g. "ClassINET" - Type string // e.g. "TypeA", "TypeAAAA" - Body string // human-readable record data -} - -// DNSQueryResult is the result of a DNS query via the Tailscale -// internal forwarder (100.100.100.100). It is the output of: -// -// $ tailscale dns query --json NAME -type DNSQueryResult struct { - Name string - QueryType string // e.g. "A", "AAAA" - Resolvers []DNSResolverInfo `json:",omitzero"` - ResponseCode string // e.g. "RCodeSuccess", "RCodeNameError" - Answers []DNSAnswer `json:",omitzero"` + SystemDNS *SystemConfig `json:",omitzero"` // nil if unavailable + SystemDNSError string `json:",omitempty"` } diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 8058d6dd1..cf34705e6 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -55,7 +55,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscaled tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete - tailscale.com/cmd/tailscale/jsonoutput from tailscale.com/cmd/tailscale/cli + tailscale.com/cmd/tailscale/tsdnsjsonv0 from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled tailscale.com/control/controlbase from tailscale.com/control/controlhttp+ tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+