diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index 55f98d3c87..fa1e7c02a5 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -11,6 +11,8 @@ import ( "time" "github.com/Dreamacro/clash/component/trie" + + "github.com/miekg/dns" ) var ( @@ -44,6 +46,7 @@ type Resolver interface { ResolveIP(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) + ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) } // LookupIPv4WithResolver same as LookupIPv4, but with a resolver diff --git a/hub/route/configs.go b/hub/route/configs.go index 37acac5ec6..5047c6d605 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -104,7 +104,6 @@ func pointerOrDefault(p *int, def int) int { if p != nil { return *p } - return def } @@ -210,7 +209,7 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS func patchConfigs(w http.ResponseWriter, r *http.Request) { 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.JSON(w, r, ErrBadRequest) return @@ -266,13 +265,11 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { render.NoContent(w, r) } -type updateConfigRequest struct { - Path string `json:"path"` - Payload string `json:"payload"` -} - func updateConfigs(w http.ResponseWriter, r *http.Request) { - req := updateConfigRequest{} + req := struct { + Path string `json:"path"` + Payload string `json:"payload"` + }{} if err := render.DecodeJSON(r.Body, &req); err != nil { render.Status(r, http.StatusBadRequest) render.JSON(w, r, ErrBadRequest) diff --git a/hub/route/dns.go b/hub/route/dns.go new file mode 100644 index 0000000000..c02db0226d --- /dev/null +++ b/hub/route/dns.go @@ -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) +} diff --git a/hub/route/proxies.go b/hub/route/proxies.go index baffb1f514..5bf6eb9c2c 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -70,12 +70,10 @@ func getProxy(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, proxy) } -type UpdateProxyRequest struct { - Name string `json:"name"` -} - 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 { render.Status(r, http.StatusBadRequest) render.JSON(w, r, ErrBadRequest) diff --git a/hub/route/server.go b/hub/route/server.go index 2bef92c162..0d6a47acd3 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -73,6 +73,7 @@ func Start(addr string, tlsAddr string, secret string, r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/cache", cacheRouter()) + r.Mount("/dns", dnsRouter()) }) if uiPath != "" {