cli: implement tailscale dns status (#13353)

Updates tailscale/tailscale#13326

This PR begins implementing a `tailscale dns` command group in the Tailscale CLI. It provides an initial implementation of `tailscale dns status` which dumps the state of the internal DNS forwarder.

Two new endpoints were added in LocalAPI to support the CLI functionality:

- `/netmap`: dumps a copy of the last received network map (because the CLI shouldn't have to listen to the ipn bus for a copy)
- `/dns-osconfig`: dumps the OS DNS configuration (this will be very handy for the UI clients as well, as they currently do not display this information)

My plan is to implement other subcommands mentioned in tailscale/tailscale#13326, such as `query`, in later PRs.

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
Andrea Gottardo
2024-09-04 12:43:55 -07:00
committed by GitHub
parent 5bc9fafab8
commit d060b3fa02
9 changed files with 365 additions and 1 deletions

View File

@@ -597,6 +597,15 @@ func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Tim
return nil
}
// GetDNSOSConfig returns the base OS DNS configuration, as seen by the DNS manager.
func (b *LocalBackend) GetDNSOSConfig() (dns.OSConfig, error) {
manager, ok := b.sys.DNSManager.GetOK()
if !ok {
return dns.OSConfig{}, errors.New("DNS manager not available")
}
return manager.GetBaseConfig()
}
// GetComponentDebugLogging gets the time that component's debug logging is
// enabled until, or the zero time if component's time is not currently
// enabled.

View File

@@ -98,6 +98,7 @@ var handler = map[string]localAPIHandler{
"derpmap": (*Handler).serveDERPMap,
"dev-set-state-store": (*Handler).serveDevSetStateStore,
"dial": (*Handler).serveDial,
"dns-osconfig": (*Handler).serveDNSOSConfig,
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
"drive/shares": (*Handler).serveShares,
"file-targets": (*Handler).serveFileTargets,
@@ -2707,6 +2708,44 @@ func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(ups)
}
// serveDNSOSConfig serves the current system DNS configuration as a JSON object, if
// supported by the OS.
func (h *Handler) serveDNSOSConfig(w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.GET {
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
return
}
// Require write access for privacy reasons.
if !h.PermitWrite {
http.Error(w, "dns-osconfig dump access denied", http.StatusForbidden)
return
}
bCfg, err := h.b.GetDNSOSConfig()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
nameservers := make([]string, 0, len(bCfg.Nameservers))
for _, ns := range bCfg.Nameservers {
nameservers = append(nameservers, ns.String())
}
searchDomains := make([]string, 0, len(bCfg.SearchDomains))
for _, sd := range bCfg.SearchDomains {
searchDomains = append(searchDomains, sd.WithoutTrailingDot())
}
matchDomains := make([]string, 0, len(bCfg.MatchDomains))
for _, md := range bCfg.MatchDomains {
matchDomains = append(matchDomains, md.WithoutTrailingDot())
}
response := apitype.DNSOSConfig{
Nameservers: nameservers,
SearchDomains: searchDomains,
MatchDomains: matchDomains,
}
json.NewEncoder(w).Encode(response)
}
// serveDriveServerAddr handles updates of the Taildrive file server address.
func (h *Handler) serveDriveServerAddr(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {