diff --git a/adapter/inbound/listen_notwindows.go b/adapter/inbound/listen_notwindows.go new file mode 100644 index 0000000000..8fdfb7b8e6 --- /dev/null +++ b/adapter/inbound/listen_notwindows.go @@ -0,0 +1,14 @@ +//go:build !windows + +package inbound + +import ( + "net" + "os" +) + +const SupportNamedPipe = false + +func ListenNamedPipe(path string) (net.Listener, error) { + return nil, os.ErrInvalid +} diff --git a/adapter/inbound/listen_windows.go b/adapter/inbound/listen_windows.go new file mode 100644 index 0000000000..0dc8e8ca06 --- /dev/null +++ b/adapter/inbound/listen_windows.go @@ -0,0 +1,27 @@ +package inbound + +import ( + "net" + + "github.com/metacubex/wireguard-go/ipc/namedpipe" + "golang.org/x/sys/windows" +) + +const SupportNamedPipe = true + +// windowsSDDL is the Security Descriptor set on the namedpipe. +// It provides read/write access to all users and the local system. +const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)" + +func ListenNamedPipe(path string) (net.Listener, error) { + securityDescriptor, err := windows.SecurityDescriptorFromString(windowsSDDL) + if err != nil { + return nil, err + } + namedpipeLC := namedpipe.ListenConfig{ + SecurityDescriptor: securityDescriptor, + InputBufferSize: 256 * 1024, + OutputBufferSize: 256 * 1024, + } + return namedpipeLC.Listen(path) +} diff --git a/config/config.go b/config/config.go index 27cde1fbd2..9067d14ff9 100644 --- a/config/config.go +++ b/config/config.go @@ -103,6 +103,7 @@ type Controller struct { ExternalController string ExternalControllerTLS string ExternalControllerUnix string + ExternalControllerPipe string ExternalUI string ExternalDohServer string Secret string @@ -364,6 +365,7 @@ type RawConfig struct { LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` IPv6 bool `yaml:"ipv6" json:"ipv6"` ExternalController string `yaml:"external-controller" json:"external-controller"` + ExternalControllerPipe string `yaml:"external-controller-pipe" json:"external-controller-pipe"` ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"` ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"` ExternalUI string `yaml:"external-ui" json:"external-ui"` @@ -769,6 +771,7 @@ func parseController(cfg *RawConfig) (*Controller, error) { ExternalController: cfg.ExternalController, ExternalUI: cfg.ExternalUI, Secret: cfg.Secret, + ExternalControllerPipe: cfg.ExternalControllerPipe, ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalDohServer: cfg.ExternalDohServer, diff --git a/docs/config.yaml b/docs/config.yaml index b3515a2060..9c480b3f2a 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -63,6 +63,10 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要 # 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ external-controller-unix: mihomo.sock +# RESTful API Windows namedpipe 监听地址 +# !!!注意: 从Windows namedpipe访问api接口不会验证secret, 如果开启请自行保证安全问题 !!! +external-controller-pipe: \\.\pipe\mihomo + # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 diff --git a/hub/hub.go b/hub/hub.go index e22f721970..73a44eee66 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -27,6 +27,12 @@ func WithExternalControllerUnix(externalControllerUnix string) Option { } } +func WithExternalControllerPipe(externalControllerPipe string) Option { + return func(cfg *config.Config) { + cfg.Controller.ExternalControllerPipe = externalControllerPipe + } +} + func WithSecret(secret string) Option { return func(cfg *config.Config) { cfg.Controller.Secret = secret @@ -47,6 +53,7 @@ func applyRoute(cfg *config.Config) { Addr: cfg.Controller.ExternalController, TLSAddr: cfg.Controller.ExternalControllerTLS, UnixAddr: cfg.Controller.ExternalControllerUnix, + PipeAddr: cfg.Controller.ExternalControllerPipe, Secret: cfg.Controller.Secret, Certificate: cfg.TLS.Certificate, PrivateKey: cfg.TLS.PrivateKey, diff --git a/hub/route/server.go b/hub/route/server.go index b707756321..4c22609cfc 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -35,6 +35,7 @@ var ( httpServer *http.Server tlsServer *http.Server unixServer *http.Server + pipeServer *http.Server ) type Traffic struct { @@ -51,6 +52,7 @@ type Config struct { Addr string TLSAddr string UnixAddr string + PipeAddr string Secret string Certificate string PrivateKey string @@ -62,6 +64,9 @@ func ReCreateServer(cfg *Config) { go start(cfg) go startTLS(cfg) go startUnix(cfg) + if inbound.SupportNamedPipe { + go startPipe(cfg) + } } func SetUIPath(path string) { @@ -233,7 +238,37 @@ func startUnix(cfg *Config) { log.Errorln("External controller unix serve error: %s", err) } } +} + +func startPipe(cfg *Config) { + // first stop existing server + if pipeServer != nil { + _ = pipeServer.Close() + pipeServer = nil + } + // handle addr + if len(cfg.PipeAddr) > 0 { + if !strings.HasPrefix(cfg.PipeAddr, "\\\\.\\pipe\\") { // windows namedpipe must start with "\\.\pipe\" + log.Errorln("External controller pipe listen error: windows namedpipe must start with \"\\\\.\\pipe\\\"") + return + } + + l, err := inbound.ListenNamedPipe(cfg.PipeAddr) + if err != nil { + log.Errorln("External controller pipe listen error: %s", err) + return + } + log.Infoln("RESTful API pipe listening at: %s", l.Addr().String()) + + server := &http.Server{ + Handler: router(cfg.IsDebug, "", cfg.DohServer), + } + pipeServer = server + if err = server.Serve(l); err != nil { + log.Errorln("External controller pipe serve error: %s", err) + } + } } func setPrivateNetworkAccess(next http.Handler) http.Handler { diff --git a/main.go b/main.go index 8910a00653..505cdb2566 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ var ( externalUI string externalController string externalControllerUnix string + externalControllerPipe string secret string ) @@ -45,6 +46,7 @@ func init() { flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory") flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address") flag.StringVar(&externalControllerUnix, "ext-ctl-unix", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_UNIX"), "override external controller unix address") + flag.StringVar(&externalControllerPipe, "ext-ctl-pipe", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_PIPE"), "override external controller pipe address") flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API") flag.BoolVar(&geodataMode, "m", false, "set geodata mode") flag.BoolVar(&version, "v", false, "show current version of mihomo") @@ -133,6 +135,9 @@ func main() { if externalControllerUnix != "" { options = append(options, hub.WithExternalControllerUnix(externalControllerUnix)) } + if externalControllerPipe != "" { + options = append(options, hub.WithExternalControllerPipe(externalControllerPipe)) + } if secret != "" { options = append(options, hub.WithSecret(secret)) } @@ -156,19 +161,9 @@ func main() { case <-termSign: return case <-hupSign: - var cfg *config.Config - var err error - if configString != "" { - cfg, err = executor.ParseWithBytes(configBytes) - } else { - cfg, err = executor.ParseWithPath(C.Path.Config()) - } - if err == nil { - hub.ApplyConfig(cfg) - } else { + if err := hub.Parse(configBytes, options...); err != nil { log.Errorln("Parse config error: %s", err.Error()) } - } } }