From a26b670420c14247bb99c0205a48c51622781d22 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 16 Jan 2023 15:20:39 +0800 Subject: [PATCH] Feature: add dns query json api --- component/resolver/resolver.go | 3 ++ hub/route/configs.go | 38 ++++++++--------- hub/route/dns.go | 76 ++++++++++++++++++++++++++++++++++ hub/route/proxies.go | 8 ++-- hub/route/server.go | 1 + 5 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 hub/route/dns.go diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index daf98a0..c69bc51 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -10,6 +10,8 @@ import ( "time" "github.com/Dreamacro/clash/component/trie" + + "github.com/miekg/dns" ) var ( @@ -40,6 +42,7 @@ type Resolver interface { ResolveIP(host string) (ip net.IP, err error) ResolveIPv4(host string) (ip net.IP, err error) ResolveIPv6(host string) (ip net.IP, err error) + ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) } // LookupIPv4 with a host, return ipv4 list diff --git a/hub/route/configs.go b/hub/route/configs.go index 48cb95e..526e7bb 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -24,19 +24,6 @@ func configRouter() http.Handler { return r } -type configSchema struct { - Port *int `json:"port"` - SocksPort *int `json:"socks-port"` - RedirPort *int `json:"redir-port"` - TProxyPort *int `json:"tproxy-port"` - MixedPort *int `json:"mixed-port"` - AllowLan *bool `json:"allow-lan"` - BindAddress *string `json:"bind-address"` - Mode *tunnel.TunnelMode `json:"mode"` - LogLevel *log.LogLevel `json:"log-level"` - IPv6 *bool `json:"ipv6"` -} - func getConfigs(w http.ResponseWriter, r *http.Request) { general := executor.GetGeneral() render.JSON(w, r, general) @@ -51,8 +38,19 @@ func pointerOrDefault(p *int, def int) int { } func patchConfigs(w http.ResponseWriter, r *http.Request) { - general := &configSchema{} - if err := render.DecodeJSON(r.Body, general); err != nil { + general := struct { + Port *int `json:"port"` + SocksPort *int `json:"socks-port"` + RedirPort *int `json:"redir-port"` + TProxyPort *int `json:"tproxy-port"` + MixedPort *int `json:"mixed-port"` + AllowLan *bool `json:"allow-lan"` + BindAddress *string `json:"bind-address"` + Mode *tunnel.TunnelMode `json:"mode"` + LogLevel *log.LogLevel `json:"log-level"` + IPv6 *bool `json:"ipv6"` + }{} + if err := render.DecodeJSON(r.Body, &general); err != nil { render.Status(r, http.StatusBadRequest) render.JSON(w, r, ErrBadRequest) return @@ -92,13 +90,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 0000000..c02db02 --- /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 bba9e2a..0cafd8b 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -66,12 +66,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 d1f85e5..1384360 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -70,6 +70,7 @@ func Start(addr string, secret string) { r.Mount("/rules", ruleRouter()) r.Mount("/connections", connectionRouter()) r.Mount("/providers/proxies", proxyProviderRouter()) + r.Mount("/dns", dnsRouter()) }) if uiPath != "" {