chore: add route.ApplyConfig for CMFA

This commit is contained in:
wwqgtxx 2024-08-31 19:23:40 +08:00
parent f6164ac195
commit 6306c6b580
6 changed files with 174 additions and 118 deletions

View File

@ -77,7 +77,7 @@ func ParseWithBytes(buf []byte) (*config.Config, error) {
return config.Parse(buf) return config.Parse(buf)
} }
// ApplyConfig dispatch configure to all parts // ApplyConfig dispatch configure to all parts without ExternalController
func ApplyConfig(cfg *config.Config, force bool) { func ApplyConfig(cfg *config.Config, force bool) {
mux.Lock() mux.Lock()
defer mux.Unlock() defer mux.Unlock()

View File

@ -1,7 +1,10 @@
package hub package hub
import ( import (
"strings"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route" "github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@ -33,6 +36,33 @@ func WithSecret(secret string) Option {
} }
} }
// ApplyConfig dispatch configure to all parts include ExternalController
func ApplyConfig(cfg *config.Config) {
applyRoute(cfg)
executor.ApplyConfig(cfg, true)
}
func applyRoute(cfg *config.Config) {
if features.CMFA && strings.HasSuffix(cfg.Controller.ExternalUI, ":0") {
// CMFA have set its default override value to end with ":0" for security.
// so we direct return at here
return
}
if cfg.Controller.ExternalUI != "" {
route.SetUIPath(cfg.Controller.ExternalUI)
}
route.ReCreateServer(&route.Config{
Addr: cfg.Controller.ExternalController,
TLSAddr: cfg.Controller.ExternalControllerTLS,
UnixAddr: cfg.Controller.ExternalControllerUnix,
Secret: cfg.Controller.Secret,
Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey,
DohServer: cfg.Controller.ExternalDohServer,
IsDebug: cfg.General.LogLevel == log.DEBUG,
})
}
// Parse call at the beginning of mihomo // Parse call at the beginning of mihomo
func Parse(options ...Option) error { func Parse(options ...Option) error {
cfg, err := executor.Parse() cfg, err := executor.Parse()
@ -44,20 +74,6 @@ func Parse(options ...Option) error {
option(cfg) option(cfg)
} }
if cfg.Controller.ExternalUI != "" { ApplyConfig(cfg)
route.SetUIPath(cfg.Controller.ExternalUI)
}
if cfg.Controller.ExternalController != "" {
go route.Start(cfg.Controller.ExternalController, cfg.Controller.ExternalControllerTLS,
cfg.Controller.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.Controller.ExternalDohServer,
cfg.General.LogLevel == log.DEBUG)
}
if cfg.Controller.ExternalControllerUnix != "" {
go route.StartUnix(cfg.Controller.ExternalControllerUnix, cfg.Controller.ExternalDohServer, cfg.General.LogLevel == log.DEBUG)
}
executor.ApplyConfig(cfg, true)
return nil return nil
} }

View File

@ -30,10 +30,11 @@ import (
) )
var ( var (
serverSecret = ""
serverAddr = ""
uiPath = "" uiPath = ""
httpServer *http.Server
tlsServer *http.Server
unixServer *http.Server
) )
type Traffic struct { type Traffic struct {
@ -46,11 +47,28 @@ type Memory struct {
OSLimit uint64 `json:"oslimit"` // maybe we need it in the future OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
} }
type Config struct {
Addr string
TLSAddr string
UnixAddr string
Secret string
Certificate string
PrivateKey string
DohServer string
IsDebug bool
}
func ReCreateServer(cfg *Config) {
go start(cfg)
go startTLS(cfg)
go startUnix(cfg)
}
func SetUIPath(path string) { func SetUIPath(path string) {
uiPath = C.Path.Resolve(path) uiPath = C.Path.Resolve(path)
} }
func router(isDebug bool, withAuth bool, dohServer string) *chi.Mux { func router(isDebug bool, secret string, dohServer string) *chi.Mux {
r := chi.NewRouter() r := chi.NewRouter()
corsM := cors.New(cors.Options{ corsM := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
@ -72,8 +90,8 @@ func router(isDebug bool, withAuth bool, dohServer string) *chi.Mux {
}()) }())
} }
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
if withAuth { if secret != "" {
r.Use(authentication) r.Use(authentication(secret))
} }
r.Get("/", hello) r.Get("/", hello)
r.Get("/logs", getLogs) r.Get("/logs", getLogs)
@ -111,88 +129,111 @@ func router(isDebug bool, withAuth bool, dohServer string) *chi.Mux {
return r return r
} }
func Start(addr string, tlsAddr string, secret string, func start(cfg *Config) {
certificate, privateKey string, dohServer string, isDebug bool) { // first stop existing server
if serverAddr != "" { if httpServer != nil {
return _ = httpServer.Close()
httpServer = nil
} }
serverAddr = addr // handle addr
serverSecret = secret if len(cfg.Addr) > 0 {
l, err := inbound.Listen("tcp", cfg.Addr)
if err != nil {
log.Errorln("External controller listen error: %s", err)
return
}
log.Infoln("RESTful API listening at: %s", l.Addr().String())
if len(tlsAddr) > 0 { server := &http.Server{
go func() { Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer),
c, err := CN.ParseCert(certificate, privateKey, C.Path) }
if err != nil { if err = server.Serve(l); err != nil {
log.Errorln("External controller tls listen error: %s", err) log.Errorln("External controller serve error: %s", err)
return }
} httpServer = server
l, err := inbound.Listen("tcp", tlsAddr)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
serverAddr = l.Addr().String()
log.Infoln("RESTful API tls listening at: %s", serverAddr)
tlsServe := &http.Server{
Handler: router(isDebug, true, dohServer),
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{c},
},
}
if err = tlsServe.ServeTLS(l, "", ""); err != nil {
log.Errorln("External controller tls serve error: %s", err)
}
}()
} }
l, err := inbound.Listen("tcp", addr)
if err != nil {
log.Errorln("External controller listen error: %s", err)
return
}
serverAddr = l.Addr().String()
log.Infoln("RESTful API listening at: %s", serverAddr)
if err = http.Serve(l, router(isDebug, true, dohServer)); err != nil {
log.Errorln("External controller serve error: %s", err)
}
} }
func StartUnix(addr string, dohServer string, isDebug bool) { func startTLS(cfg *Config) {
addr = C.Path.Resolve(addr) // first stop existing server
if tlsServer != nil {
_ = tlsServer.Close()
tlsServer = nil
}
dir := filepath.Dir(addr) // handle tlsAddr
if _, err := os.Stat(dir); os.IsNotExist(err) { if len(cfg.TLSAddr) > 0 {
if err := os.MkdirAll(dir, 0o755); err != nil { c, err := CN.ParseCert(cfg.Certificate, cfg.PrivateKey, C.Path)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
l, err := inbound.Listen("tcp", cfg.TLSAddr)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
server := &http.Server{
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer),
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{c},
},
}
if err = server.ServeTLS(l, "", ""); err != nil {
log.Errorln("External controller tls serve error: %s", err)
}
tlsServer = server
}
}
func startUnix(cfg *Config) {
// first stop existing server
if unixServer != nil {
_ = unixServer.Close()
unixServer = nil
}
// handle addr
if len(cfg.UnixAddr) > 0 {
addr := C.Path.Resolve(cfg.UnixAddr)
dir := filepath.Dir(addr)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Errorln("External controller unix listen error: %s", err)
return
}
}
// 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) log.Errorln("External controller unix listen error: %s", err)
return return
} }
log.Infoln("RESTful API unix listening at: %s", l.Addr().String())
server := &http.Server{
Handler: router(cfg.IsDebug, "", cfg.DohServer),
}
if err = server.Serve(l); err != nil {
log.Errorln("External controller unix serve error: %s", err)
}
unixServer = server
} }
// 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, dohServer)); err != nil {
log.Errorln("External controller unix serve error: %s", err)
}
} }
func setPrivateNetworkAccess(next http.Handler) http.Handler { func setPrivateNetworkAccess(next http.Handler) http.Handler {
@ -210,38 +251,35 @@ func safeEuqal(a, b string) bool {
return subtle.ConstantTimeCompare(aBuf, bBuf) == 1 return subtle.ConstantTimeCompare(aBuf, bBuf) == 1
} }
func authentication(next http.Handler) http.Handler { func authentication(secret string) func(http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) { return func(next http.Handler) http.Handler {
if serverSecret == "" { fn := func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r) // Browser websocket not support custom header
return if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
} token := r.URL.Query().Get("token")
if !safeEuqal(token, secret) {
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
return
}
// Browser websocket not support custom header header := r.Header.Get("Authorization")
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" { bearer, token, found := strings.Cut(header, " ")
token := r.URL.Query().Get("token")
if !safeEuqal(token, serverSecret) { hasInvalidHeader := bearer != "Bearer"
hasInvalidSecret := !found || !safeEuqal(token, secret)
if hasInvalidHeader || hasInvalidSecret {
render.Status(r, http.StatusUnauthorized) render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized) render.JSON(w, r, ErrUnauthorized)
return return
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return
} }
return http.HandlerFunc(fn)
header := r.Header.Get("Authorization")
bearer, token, found := strings.Cut(header, " ")
hasInvalidHeader := bearer != "Bearer"
hasInvalidSecret := !found || !safeEuqal(token, serverSecret)
if hasInvalidHeader || hasInvalidSecret {
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
} }
return http.HandlerFunc(fn)
} }
func hello(w http.ResponseWriter, r *http.Request) { func hello(w http.ResponseWriter, r *http.Request) {

View File

@ -1,3 +1,5 @@
//go:build android && !cmfa
package sing_tun package sing_tun
import ( import (

View File

@ -1,4 +1,4 @@
//go:build !android //go:build !android || cmfa
package sing_tun package sing_tun

View File

@ -135,7 +135,7 @@ func main() {
return return
case <-hupSign: case <-hupSign:
if cfg, err := executor.ParseWithPath(C.Path.Config()); err == nil { if cfg, err := executor.ParseWithPath(C.Path.Config()); err == nil {
executor.ApplyConfig(cfg, true) hub.ApplyConfig(cfg)
} else { } else {
log.Errorln("Parse config error: %s", err.Error()) log.Errorln("Parse config error: %s", err.Error())
} }