diff --git a/config/config.go b/config/config.go index ae82ac3f7..f58709c9c 100644 --- a/config/config.go +++ b/config/config.go @@ -96,6 +96,7 @@ type Controller struct { ExternalControllerTLS string `json:"-"` ExternalControllerUnix string `json:"-"` ExternalUI string `json:"-"` + ExternalDohServer string `json:"-"` Secret string `json:"-"` } @@ -322,6 +323,7 @@ type RawConfig struct { ExternalUI string `yaml:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` + ExternalDohServer string `yaml:"external-doh-server"` Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` @@ -697,6 +699,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Secret: cfg.Secret, ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerTLS: cfg.ExternalControllerTLS, + ExternalDohServer: cfg.ExternalDohServer, }, UnifiedDelay: cfg.UnifiedDelay, Mode: cfg.Mode, diff --git a/docs/config.yaml b/docs/config.yaml index 9c51bc10b..4e4b9b16e 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -70,6 +70,10 @@ external-ui: /path/to/ui/folder/ external-ui-name: xd external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip" +# 在RESTful API端口上开启DOH服务器 +# !!!该URL不会验证secret, 如果开启请自行保证安全问题 !!! +external-doh-server: /dns-query + # interface-name: en0 # 设置出口网卡 # 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint diff --git a/hub/hub.go b/hub/hub.go index 38779e13b..57c91aaef 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -50,11 +50,12 @@ func Parse(options ...Option) error { if cfg.General.ExternalController != "" { go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS, - cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG) + cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.ExternalDohServer, + cfg.General.LogLevel == log.DEBUG) } if cfg.General.ExternalControllerUnix != "" { - go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.LogLevel == log.DEBUG) + go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.ExternalDohServer, cfg.General.LogLevel == log.DEBUG) } executor.ApplyConfig(cfg, true) diff --git a/hub/route/doh.go b/hub/route/doh.go new file mode 100644 index 000000000..5f993c021 --- /dev/null +++ b/hub/route/doh.go @@ -0,0 +1,67 @@ +package route + +import ( + "context" + "encoding/base64" + "io" + "net/http" + + "github.com/metacubex/mihomo/component/resolver" + + "github.com/go-chi/render" +) + +func dohRouter() http.Handler { + return http.HandlerFunc(dohHandler) +} + +func dohHandler(w http.ResponseWriter, r *http.Request) { + if resolver.DefaultResolver == nil { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError("DNS section is disabled")) + return + } + + if r.Header.Get("Accept") != "application/dns-message" { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError("invalid accept header")) + return + } + + var dnsData []byte + var err error + switch r.Method { + case "GET": + dnsData, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("dns")) + case "POST": + if r.Header.Get("Content-Type") != "application/dns-message" { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError("invalid content-type")) + return + } + dnsData, err = io.ReadAll(r.Body) + _ = r.Body.Close() + default: + render.Status(r, http.StatusMethodNotAllowed) + render.JSON(w, r, newError("method not allowed")) + return + } + if err != nil { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(err.Error())) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) + defer cancel() + + dnsData, err = resolver.RelayDnsPacket(ctx, dnsData, dnsData) + if err != nil { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(err.Error())) + return + } + + render.Status(r, http.StatusOK) + render.Data(w, r, dnsData) +} diff --git a/hub/route/server.go b/hub/route/server.go index c28782b9c..165c7c697 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -50,7 +50,7 @@ func SetUIPath(path string) { uiPath = C.Path.Resolve(path) } -func router(isDebug bool, withAuth bool) *chi.Mux { +func router(isDebug bool, withAuth bool, dohServer string) *chi.Mux { r := chi.NewRouter() corsM := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, @@ -104,11 +104,15 @@ func router(isDebug bool, withAuth bool) *chi.Mux { }) }) } + if len(dohServer) > 0 && dohServer[0] == '/' { + r.Mount(dohServer, dohRouter()) + } + return r } func Start(addr string, tlsAddr string, secret string, - certificate, privateKey string, isDebug bool) { + certificate, privateKey string, dohServer string, isDebug bool) { if serverAddr != "" { return } @@ -133,7 +137,7 @@ func Start(addr string, tlsAddr string, secret string, serverAddr = l.Addr().String() log.Infoln("RESTful API tls listening at: %s", serverAddr) tlsServe := &http.Server{ - Handler: router(isDebug, true), + Handler: router(isDebug, true, dohServer), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{c}, }, @@ -152,13 +156,13 @@ func Start(addr string, tlsAddr string, secret string, serverAddr = l.Addr().String() log.Infoln("RESTful API listening at: %s", serverAddr) - if err = http.Serve(l, router(isDebug, true)); err != nil { + if err = http.Serve(l, router(isDebug, true, dohServer)); err != nil { log.Errorln("External controller serve error: %s", err) } } -func StartUnix(addr string, isDebug bool) { +func StartUnix(addr string, dohServer string, isDebug bool) { addr = C.Path.Resolve(addr) dir := filepath.Dir(addr) @@ -186,7 +190,7 @@ func StartUnix(addr string, isDebug bool) { serverAddr = l.Addr().String() log.Infoln("RESTful API unix listening at: %s", serverAddr) - if err = http.Serve(l, router(isDebug, false)); err != nil { + if err = http.Serve(l, router(isDebug, false, dohServer)); err != nil { log.Errorln("External controller unix serve error: %s", err) } }