feat: add external-controller-pipe for windows

maybe useful for electron and tauri client, node.js and rust still not support AF_UNIX on windows
This commit is contained in:
wwqgtxx 2024-09-27 09:57:09 +08:00
parent 43cb48231a
commit 88bfe7cffe
7 changed files with 96 additions and 11 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -103,6 +103,7 @@ type Controller struct {
ExternalController string ExternalController string
ExternalControllerTLS string ExternalControllerTLS string
ExternalControllerUnix string ExternalControllerUnix string
ExternalControllerPipe string
ExternalUI string ExternalUI string
ExternalDohServer string ExternalDohServer string
Secret string Secret string
@ -364,6 +365,7 @@ type RawConfig struct {
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6 bool `yaml:"ipv6" json:"ipv6"`
ExternalController string `yaml:"external-controller" json:"external-controller"` 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"` ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"`
ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"` ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"`
ExternalUI string `yaml:"external-ui" json:"external-ui"` ExternalUI string `yaml:"external-ui" json:"external-ui"`
@ -769,6 +771,7 @@ func parseController(cfg *RawConfig) (*Controller, error) {
ExternalController: cfg.ExternalController, ExternalController: cfg.ExternalController,
ExternalUI: cfg.ExternalUI, ExternalUI: cfg.ExternalUI,
Secret: cfg.Secret, Secret: cfg.Secret,
ExternalControllerPipe: cfg.ExternalControllerPipe,
ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerUnix: cfg.ExternalControllerUnix,
ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalControllerTLS: cfg.ExternalControllerTLS,
ExternalDohServer: cfg.ExternalDohServer, ExternalDohServer: cfg.ExternalDohServer,

View File

@ -63,6 +63,10 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ # 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/
external-controller-unix: mihomo.sock external-controller-unix: mihomo.sock
# RESTful API Windows namedpipe 监听地址
# !!!注意: 从Windows namedpipe访问api接口不会验证secret 如果开启请自行保证安全问题
external-controller-pipe: \\.\pipe\mihomo
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问

View File

@ -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 { func WithSecret(secret string) Option {
return func(cfg *config.Config) { return func(cfg *config.Config) {
cfg.Controller.Secret = secret cfg.Controller.Secret = secret
@ -47,6 +53,7 @@ func applyRoute(cfg *config.Config) {
Addr: cfg.Controller.ExternalController, Addr: cfg.Controller.ExternalController,
TLSAddr: cfg.Controller.ExternalControllerTLS, TLSAddr: cfg.Controller.ExternalControllerTLS,
UnixAddr: cfg.Controller.ExternalControllerUnix, UnixAddr: cfg.Controller.ExternalControllerUnix,
PipeAddr: cfg.Controller.ExternalControllerPipe,
Secret: cfg.Controller.Secret, Secret: cfg.Controller.Secret,
Certificate: cfg.TLS.Certificate, Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey, PrivateKey: cfg.TLS.PrivateKey,

View File

@ -35,6 +35,7 @@ var (
httpServer *http.Server httpServer *http.Server
tlsServer *http.Server tlsServer *http.Server
unixServer *http.Server unixServer *http.Server
pipeServer *http.Server
) )
type Traffic struct { type Traffic struct {
@ -51,6 +52,7 @@ type Config struct {
Addr string Addr string
TLSAddr string TLSAddr string
UnixAddr string UnixAddr string
PipeAddr string
Secret string Secret string
Certificate string Certificate string
PrivateKey string PrivateKey string
@ -62,6 +64,9 @@ func ReCreateServer(cfg *Config) {
go start(cfg) go start(cfg)
go startTLS(cfg) go startTLS(cfg)
go startUnix(cfg) go startUnix(cfg)
if inbound.SupportNamedPipe {
go startPipe(cfg)
}
} }
func SetUIPath(path string) { func SetUIPath(path string) {
@ -233,7 +238,37 @@ func startUnix(cfg *Config) {
log.Errorln("External controller unix serve error: %s", err) 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 { func setPrivateNetworkAccess(next http.Handler) http.Handler {

17
main.go
View File

@ -35,6 +35,7 @@ var (
externalUI string externalUI string
externalController string externalController string
externalControllerUnix string externalControllerUnix string
externalControllerPipe string
secret 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(&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(&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(&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.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API")
flag.BoolVar(&geodataMode, "m", false, "set geodata mode") flag.BoolVar(&geodataMode, "m", false, "set geodata mode")
flag.BoolVar(&version, "v", false, "show current version of mihomo") flag.BoolVar(&version, "v", false, "show current version of mihomo")
@ -133,6 +135,9 @@ func main() {
if externalControllerUnix != "" { if externalControllerUnix != "" {
options = append(options, hub.WithExternalControllerUnix(externalControllerUnix)) options = append(options, hub.WithExternalControllerUnix(externalControllerUnix))
} }
if externalControllerPipe != "" {
options = append(options, hub.WithExternalControllerPipe(externalControllerPipe))
}
if secret != "" { if secret != "" {
options = append(options, hub.WithSecret(secret)) options = append(options, hub.WithSecret(secret))
} }
@ -156,19 +161,9 @@ func main() {
case <-termSign: case <-termSign:
return return
case <-hupSign: case <-hupSign:
var cfg *config.Config if err := hub.Parse(configBytes, options...); err != nil {
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 {
log.Errorln("Parse config error: %s", err.Error()) log.Errorln("Parse config error: %s", err.Error())
} }
} }
} }
} }