From 41ec2e7944c7e46a50363e59208239903adce5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 2 Feb 2023 15:58:13 +0800 Subject: [PATCH] Add support for clash DNS query API --- experimental/clashapi/dns.go | 82 +++++++++++++++++++++++++++++++++ experimental/clashapi/server.go | 1 + 2 files changed, 83 insertions(+) create mode 100644 experimental/clashapi/dns.go diff --git a/experimental/clashapi/dns.go b/experimental/clashapi/dns.go new file mode 100644 index 00000000..2a21a7c1 --- /dev/null +++ b/experimental/clashapi/dns.go @@ -0,0 +1,82 @@ +package clashapi + +import ( + "context" + "net/http" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/miekg/dns" +) + +func dnsRouter(router adapter.Router) http.Handler { + r := chi.NewRouter() + r.Get("/query", queryDNS(router)) + return r +} + +func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + qTypeStr := r.URL.Query().Get("type") + if qTypeStr == "" { + qTypeStr = "A" + } + + qType, exist := dns.StringToType[qTypeStr] + if !exist { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("invalid query type")) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), C.DNSTimeout) + defer cancel() + + msg := dns.Msg{} + msg.SetQuestion(dns.Fqdn(name), qType) + resp, err := router.Exchange(ctx, &msg) + if err != nil { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(err.Error())) + return + } + + responseData := render.M{ + "Status": resp.Rcode, + "Question": resp.Question, + "Server": "internal", + "TC": resp.Truncated, + "RD": resp.RecursionDesired, + "RA": resp.RecursionAvailable, + "AD": resp.AuthenticatedData, + "CD": resp.CheckingDisabled, + } + + rr2Json := func(rr dns.RR) render.M { + header := rr.Header() + return render.M{ + "name": header.Name, + "type": header.Rrtype, + "TTL": header.Ttl, + "data": rr.String()[len(header.String()):], + } + } + + if len(resp.Answer) > 0 { + responseData["Answer"] = common.Map(resp.Answer, rr2Json) + } + if len(resp.Ns) > 0 { + responseData["Authority"] = common.Map(resp.Ns, rr2Json) + } + if len(resp.Extra) > 0 { + responseData["Additional"] = common.Map(resp.Extra, rr2Json) + } + + render.JSON(w, r, responseData) + } +} diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 8ff90d20..c2e20df5 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -99,6 +99,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options r.Mount("/script", scriptRouter()) r.Mount("/profile", profileRouter()) r.Mount("/cache", cacheRouter()) + r.Mount("/dns", dnsRouter(router)) }) if options.ExternalUI != "" { chiRouter.Group(func(r chi.Router) {