mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 13:18:53 +00:00
cli: add tailscale dns query
(#13368)
Updates tailscale/tailscale#13326 Adds a CLI subcommand to perform DNS queries using the internal DNS forwarder and observe its internals (namely, which upstream resolvers are being used). Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
@@ -38,6 +38,7 @@ import (
|
||||
"go4.org/mem"
|
||||
"go4.org/netipx"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"tailscale.com/appc"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
@@ -606,6 +607,50 @@ func (b *LocalBackend) GetDNSOSConfig() (dns.OSConfig, error) {
|
||||
return manager.GetBaseConfig()
|
||||
}
|
||||
|
||||
// QueryDNS performs a DNS query for name and queryType using the built-in DNS resolver, and returns
|
||||
// the raw DNS response and the resolvers that are were able to handle the query (the internal forwarder
|
||||
// may race multiple resolvers).
|
||||
func (b *LocalBackend) QueryDNS(name string, queryType dnsmessage.Type) (res []byte, resolvers []*dnstype.Resolver, err error) {
|
||||
manager, ok := b.sys.DNSManager.GetOK()
|
||||
if !ok {
|
||||
return nil, nil, errors.New("DNS manager not available")
|
||||
}
|
||||
fqdn, err := dnsname.ToFQDN(name)
|
||||
if err != nil {
|
||||
b.logf("DNSQuery: failed to parse FQDN %q: %v", name, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
n, err := dnsmessage.NewName(fqdn.WithTrailingDot())
|
||||
if err != nil {
|
||||
b.logf("DNSQuery: failed to parse name %q: %v", name, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
from := netip.MustParseAddrPort("127.0.0.1:0")
|
||||
db := dnsmessage.NewBuilder(nil, dnsmessage.Header{
|
||||
OpCode: 0,
|
||||
RecursionDesired: true,
|
||||
ID: 1,
|
||||
})
|
||||
db.StartQuestions()
|
||||
db.Question(dnsmessage.Question{
|
||||
Name: n,
|
||||
Type: queryType,
|
||||
Class: dnsmessage.ClassINET,
|
||||
})
|
||||
q, err := db.Finish()
|
||||
if err != nil {
|
||||
b.logf("DNSQuery: failed to build query: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
res, err = manager.Query(b.ctx, q, "tcp", from)
|
||||
if err != nil {
|
||||
b.logf("DNSQuery: failed to query %q: %v", name, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
rr := manager.Resolver().GetUpstreamResolvers(fqdn)
|
||||
return res, rr, nil
|
||||
}
|
||||
|
||||
// GetComponentDebugLogging gets the time that component's debug logging is
|
||||
// enabled until, or the zero time if component's time is not currently
|
||||
// enabled.
|
||||
|
@@ -32,6 +32,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/clientupdate"
|
||||
"tailscale.com/drive"
|
||||
@@ -49,6 +50,7 @@ import (
|
||||
"tailscale.com/taildrop"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
@@ -99,6 +101,7 @@ var handler = map[string]localAPIHandler{
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"dial": (*Handler).serveDial,
|
||||
"dns-osconfig": (*Handler).serveDNSOSConfig,
|
||||
"dns-query": (*Handler).serveDNSQuery,
|
||||
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
|
||||
"drive/shares": (*Handler).serveShares,
|
||||
"file-targets": (*Handler).serveFileTargets,
|
||||
@@ -2746,6 +2749,49 @@ func (h *Handler) serveDNSOSConfig(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// serveDNSQuery provides the ability to perform DNS queries using the internal
|
||||
// DNS forwarder. This is useful for debugging and testing purposes.
|
||||
// URL parameters:
|
||||
// - name: the domain name to query
|
||||
// - type: the DNS record type to query as a number (default if empty: A = '1')
|
||||
//
|
||||
// The response if successful is a DNSQueryResponse JSON object.
|
||||
func (h *Handler) serveDNSQuery(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
// Require write access for privacy reasons.
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "dns-query access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
q := r.URL.Query()
|
||||
name := q.Get("name")
|
||||
queryType := q.Get("type")
|
||||
qt := dnsmessage.TypeA
|
||||
if queryType != "" {
|
||||
t, err := dnstype.DNSMessageTypeForString(queryType)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
qt = t
|
||||
}
|
||||
|
||||
res, rrs, err := h.b.QueryDNS(name, qt)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(&apitype.DNSQueryResponse{
|
||||
Bytes: res,
|
||||
Resolvers: rrs,
|
||||
})
|
||||
}
|
||||
|
||||
// serveDriveServerAddr handles updates of the Taildrive file server address.
|
||||
func (h *Handler) serveDriveServerAddr(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "PUT" {
|
||||
|
Reference in New Issue
Block a user