github.com/yaling888/clash@v1.53.0/hub/route/dns.go (about)

     1  package route
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/go-chi/chi/v5"
    11  	"github.com/go-chi/render"
    12  	"github.com/miekg/dns"
    13  	"github.com/samber/lo"
    14  
    15  	"github.com/yaling888/clash/common/util"
    16  	"github.com/yaling888/clash/component/resolver"
    17  )
    18  
    19  func dnsRouter() http.Handler {
    20  	r := chi.NewRouter()
    21  	r.Get("/query", queryDNS)
    22  	return r
    23  }
    24  
    25  func queryDNS(w http.ResponseWriter, r *http.Request) {
    26  	if resolver.DefaultResolver == nil {
    27  		render.Status(r, http.StatusInternalServerError)
    28  		render.JSON(w, r, newError("DNS section is disabled"))
    29  		return
    30  	}
    31  
    32  	var (
    33  		name     = r.URL.Query().Get("name")
    34  		proxy    = r.URL.Query().Get("proxy")
    35  		qTypeStr = util.EmptyOr(r.URL.Query().Get("type"), "A")
    36  		cacheStr = util.EmptyOr(r.URL.Query().Get("cache"), "1")
    37  	)
    38  
    39  	qType, exist := dns.StringToType[strings.ToUpper(qTypeStr)]
    40  	if !exist {
    41  		render.Status(r, http.StatusBadRequest)
    42  		render.JSON(w, r, newError("invalid query type"))
    43  		return
    44  	}
    45  
    46  	var (
    47  		resp   *dns.Msg
    48  		source string
    49  		msg    = dns.Msg{}
    50  		cache  = true
    51  	)
    52  
    53  	c, err := strconv.ParseBool(cacheStr)
    54  	if err == nil {
    55  		cache = c
    56  	}
    57  
    58  	msg.SetQuestion(dns.Fqdn(name), qType)
    59  
    60  	ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
    61  	defer cancel()
    62  
    63  	if proxy != "" {
    64  		ctx = resolver.WithProxy(ctx, proxy)
    65  	}
    66  
    67  	if cache {
    68  		resp, source, err = resolver.DefaultResolver.ExchangeContext(ctx, &msg)
    69  	} else {
    70  		resp, source, err = resolver.DefaultResolver.ExchangeContextWithoutCache(ctx, &msg)
    71  	}
    72  	if err != nil {
    73  		render.Status(r, http.StatusInternalServerError)
    74  		render.JSON(w, r, newError(err.Error()))
    75  		return
    76  	}
    77  
    78  	responseData := render.M{
    79  		"Server":   source,
    80  		"Cache":    cache,
    81  		"Status":   resp.Rcode,
    82  		"Question": resp.Question,
    83  		"TC":       resp.Truncated,
    84  		"RD":       resp.RecursionDesired,
    85  		"RA":       resp.RecursionAvailable,
    86  		"AD":       resp.AuthenticatedData,
    87  		"CD":       resp.CheckingDisabled,
    88  	}
    89  
    90  	rr2Json := func(rr dns.RR, _ int) render.M {
    91  		header := rr.Header()
    92  		return render.M{
    93  			"name": header.Name,
    94  			"type": header.Rrtype,
    95  			"TTL":  header.Ttl,
    96  			"data": lo.Substring(rr.String(), len(header.String()), math.MaxUint),
    97  		}
    98  	}
    99  
   100  	if len(resp.Answer) > 0 {
   101  		responseData["Answer"] = lo.Map(resp.Answer, rr2Json)
   102  	}
   103  	if len(resp.Ns) > 0 {
   104  		responseData["Authority"] = lo.Map(resp.Ns, rr2Json)
   105  	}
   106  	if len(resp.Extra) > 0 {
   107  		responseData["Additional"] = lo.Map(resp.Extra, rr2Json)
   108  	}
   109  
   110  	render.JSON(w, r, responseData)
   111  }