Feature: add dns query json api

This commit is contained in:
Dreamacro 2023-01-16 15:20:39 +08:00 committed by metacubex
parent 85db58aeb5
commit a6a72a5b54
5 changed files with 88 additions and 13 deletions

View file

@ -11,6 +11,8 @@ import (
"time" "time"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/miekg/dns"
) )
var ( var (
@ -44,6 +46,7 @@ type Resolver interface {
ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error) ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error)
ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error) ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error)
ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error) ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error)
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
} }
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver // LookupIPv4WithResolver same as LookupIPv4, but with a resolver

View file

@ -104,7 +104,6 @@ func pointerOrDefault(p *int, def int) int {
if p != nil { if p != nil {
return *p return *p
} }
return def return def
} }
@ -210,7 +209,7 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
func patchConfigs(w http.ResponseWriter, r *http.Request) { func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{} general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil { if err := render.DecodeJSON(r.Body, &general); err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
@ -266,13 +265,11 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
render.NoContent(w, r) render.NoContent(w, r)
} }
type updateConfigRequest struct { func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := struct {
Path string `json:"path"` Path string `json:"path"`
Payload string `json:"payload"` Payload string `json:"payload"`
} }{}
func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := updateConfigRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)

76
hub/route/dns.go Normal file
View file

@ -0,0 +1,76 @@
package route
import (
"context"
"math"
"net/http"
"github.com/Dreamacro/clash/component/resolver"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/miekg/dns"
"github.com/samber/lo"
)
func dnsRouter() http.Handler {
r := chi.NewRouter()
r.Get("/query", queryDNS)
return r
}
func queryDNS(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
qTypeStr, _ := lo.Coalesce(r.URL.Query().Get("type"), "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(), resolver.DefaultDNSTimeout)
defer cancel()
msg := dns.Msg{}
msg.SetQuestion(dns.Fqdn(name), qType)
resp, err := resolver.DefaultResolver.ExchangeContext(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,
"TC": resp.Truncated,
"RD": resp.RecursionDesired,
"RA": resp.RecursionAvailable,
"AD": resp.AuthenticatedData,
"CD": resp.CheckingDisabled,
}
rr2Json := func(rr dns.RR, _ int) render.M {
header := rr.Header()
return render.M{
"name": header.Name,
"type": header.Rrtype,
"TTL": header.Ttl,
"data": lo.Substring(rr.String(), len(header.String()), math.MaxUint),
}
}
if len(resp.Answer) > 0 {
responseData["Answer"] = lo.Map(resp.Answer, rr2Json)
}
if len(resp.Ns) > 0 {
responseData["Authority"] = lo.Map(resp.Ns, rr2Json)
}
if len(resp.Extra) > 0 {
responseData["Additional"] = lo.Map(resp.Extra, rr2Json)
}
render.JSON(w, r, responseData)
}

View file

@ -70,12 +70,10 @@ func getProxy(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, proxy) render.JSON(w, r, proxy)
} }
type UpdateProxyRequest struct {
Name string `json:"name"`
}
func updateProxy(w http.ResponseWriter, r *http.Request) { func updateProxy(w http.ResponseWriter, r *http.Request) {
req := UpdateProxyRequest{} req := struct {
Name string `json:"name"`
}{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)

View file

@ -73,6 +73,7 @@ func Start(addr string, tlsAddr string, secret string,
r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/cache", cacheRouter()) r.Mount("/cache", cacheRouter())
r.Mount("/dns", dnsRouter())
}) })
if uiPath != "" { if uiPath != "" {