Browse Source

Add support for clash DNS query API

世界 2 years ago
parent
commit
41ec2e7944
2 changed files with 83 additions and 0 deletions
  1. 82 0
      experimental/clashapi/dns.go
  2. 1 0
      experimental/clashapi/server.go

+ 82 - 0
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)
+	}
+}

+ 1 - 0
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) {