Skip to content

Commit

Permalink
feat: add external-controller-pipe for windows
Browse files Browse the repository at this point in the history
maybe useful for electron and tauri client, node.js and rust still not support AF_UNIX on windows
  • Loading branch information
wwqgtxx committed Sep 27, 2024
1 parent 43cb482 commit 88bfe7c
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 11 deletions.
14 changes: 14 additions & 0 deletions adapter/inbound/listen_notwindows.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 27 additions & 0 deletions adapter/inbound/listen_windows.go
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type Controller struct {
ExternalController string
ExternalControllerTLS string
ExternalControllerUnix string
ExternalControllerPipe string
ExternalUI string
ExternalDohServer string
Secret string
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 访问
Expand Down
7 changes: 7 additions & 0 deletions hub/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
35 changes: 35 additions & 0 deletions hub/route/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
httpServer *http.Server
tlsServer *http.Server
unixServer *http.Server
pipeServer *http.Server
)

type Traffic struct {
Expand All @@ -51,6 +52,7 @@ type Config struct {
Addr string
TLSAddr string
UnixAddr string
PipeAddr string
Secret string
Certificate string
PrivateKey string
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 6 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
externalUI string
externalController string
externalControllerUnix string
externalControllerPipe string
secret string
)

Expand All @@ -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")
Expand Down Expand Up @@ -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))
}
Expand All @@ -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())
}

}
}
}

0 comments on commit 88bfe7c

Please sign in to comment.