mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-29 08:46:33 -04:00
cmd/tailscale/tsdnsjsonv0: extract a new package for tailscale dns --json (#20017)
This patch extracts all the DNS related JSON handling from the cmd/tailscale/jsonoutput package into a new tsdnsjsonv0 package. It adds package documentation for tsdnsjsonv0 with a big WARNING that this is an unstable format with no backwards compatibility guarantees. When we stabilize this format, we should spin off a new tsdnsjsonv1 package that uses jsonoutput.ResponseEnvelope to declare version 1. Updates #13326 Updates #18750 Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
14
cmd/tailscale/tsdnsjsonv0/doc.go
Normal file
14
cmd/tailscale/tsdnsjsonv0/doc.go
Normal file
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
25
cmd/tailscale/tsdnsjsonv0/query.go
Normal file
25
cmd/tailscale/tsdnsjsonv0/query.go
Normal file
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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+
|
||||
|
||||
Reference in New Issue
Block a user