From ca84ab1a94291fdd5b66f550c6613b69dbdeb9cc Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Wed, 17 Apr 2024 09:39:00 +0800 Subject: [PATCH] feat: support `external-controller-unix` --- config/config.go | 19 +++++++++------- docs/config.yaml | 5 +++++ hub/hub.go | 4 ++++ hub/route/server.go | 55 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/config/config.go b/config/config.go index c5c4fa88f4..f425092ac3 100644 --- a/config/config.go +++ b/config/config.go @@ -91,10 +91,11 @@ type Inbound struct { // Controller config type Controller struct { - ExternalController string `json:"-"` - ExternalControllerTLS string `json:"-"` - ExternalUI string `json:"-"` - Secret string `json:"-"` + ExternalController string `json:"-"` + ExternalControllerTLS string `json:"-"` + ExternalControllerUnix string `json:"-"` + ExternalUI string `json:"-"` + Secret string `json:"-"` } // NTP config @@ -304,6 +305,7 @@ type RawConfig struct { LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` IPv6 bool `yaml:"ipv6" json:"ipv6"` ExternalController string `yaml:"external-controller"` + ExternalControllerUnix string `yaml:"external-controller-unix"` ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalUI string `yaml:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` @@ -678,10 +680,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) { InboundMPTCP: cfg.InboundMPTCP, }, Controller: Controller{ - ExternalController: cfg.ExternalController, - ExternalUI: cfg.ExternalUI, - Secret: cfg.Secret, - ExternalControllerTLS: cfg.ExternalControllerTLS, + ExternalController: cfg.ExternalController, + ExternalUI: cfg.ExternalUI, + Secret: cfg.Secret, + ExternalControllerUnix: cfg.ExternalControllerUnix, + ExternalControllerTLS: cfg.ExternalControllerTLS, }, UnifiedDelay: cfg.UnifiedDelay, Mode: cfg.Mode, diff --git a/docs/config.yaml b/docs/config.yaml index 869b5231f7..61238175c8 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -58,6 +58,11 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址 external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件 # secret: "123456" # `Authorization:Bearer ${secret}` +# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 ) +# !!!注意: 从Unix socket访问api接口不会验证secret, 如果开启请自行保证安全问题 !!! +# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ +external-controller-unix: mihomo.sock + # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 diff --git a/hub/hub.go b/hub/hub.go index 323f87495d..6d5bf49f81 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -47,6 +47,10 @@ func Parse(options ...Option) error { cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG) } + if cfg.General.ExternalControllerUnix != "" { + go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.LogLevel == log.DEBUG) + } + executor.ApplyConfig(cfg, true) return nil } diff --git a/hub/route/server.go b/hub/route/server.go index 8e7f225f48..a1a9003e57 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -9,6 +9,7 @@ import ( "net/http" "runtime/debug" "strings" + "syscall" "time" "github.com/metacubex/mihomo/adapter/inbound" @@ -47,15 +48,7 @@ func SetUIPath(path string) { uiPath = C.Path.Resolve(path) } -func Start(addr string, tlsAddr string, secret string, - certificat, privateKey string, isDebug bool) { - if serverAddr != "" { - return - } - - serverAddr = addr - serverSecret = secret - +func router(isDebug bool, withAuth bool) *chi.Mux { r := chi.NewRouter() corsM := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, @@ -77,7 +70,9 @@ func Start(addr string, tlsAddr string, secret string, }()) } r.Group(func(r chi.Router) { - r.Use(authentication) + if withAuth { + r.Use(authentication) + } r.Get("/", hello) r.Get("/logs", getLogs) r.Get("/traffic", traffic) @@ -107,10 +102,21 @@ func Start(addr string, tlsAddr string, secret string, }) }) } + return r +} + +func Start(addr string, tlsAddr string, secret string, + certificate, privateKey string, isDebug bool) { + if serverAddr != "" { + return + } + + serverAddr = addr + serverSecret = secret if len(tlsAddr) > 0 { go func() { - c, err := CN.ParseCert(certificat, privateKey, C.Path) + c, err := CN.ParseCert(certificate, privateKey, C.Path) if err != nil { log.Errorln("External controller tls listen error: %s", err) return @@ -125,7 +131,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: r, + Handler: router(isDebug, true), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{c}, }, @@ -144,12 +150,35 @@ 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, r); err != nil { + if err = http.Serve(l, router(isDebug, true)); err != nil { log.Errorln("External controller serve error: %s", err) } } +func StartUnix(addr string, isDebug bool) { + // https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ + // + // Note: As mentioned above in the ‘security’ section, when a socket binds a socket to a valid pathname address, + // a socket file is created within the filesystem. On Linux, the application is expected to unlink + // (see the notes section in the man page for AF_UNIX) before any other socket can be bound to the same address. + // The same applies to Windows unix sockets, except that, DeleteFile (or any other file delete API) + // should be used to delete the socket file prior to calling bind with the same path. + _ = syscall.Unlink(addr) + + l, err := inbound.Listen("unix", addr) + if err != nil { + log.Errorln("External controller unix listen error: %s", err) + return + } + serverAddr = l.Addr().String() + log.Infoln("RESTful API unix listening at: %s", serverAddr) + + if err = http.Serve(l, router(isDebug, false)); err != nil { + log.Errorln("External controller unix serve error: %s", err) + } +} + func setPrivateNetworkAccess(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {