Compare commits

...

4 Commits

5 changed files with 146 additions and 99 deletions

View File

@ -14,24 +14,69 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
var ( type UIUpdater struct {
ExternalUIURL string externalUIURL string
ExternalUIPath string externalUIPath string
AutoDownloadUI bool autoDownloadUI bool
)
var xdMutex sync.Mutex mutex sync.Mutex
}
func DownloadUI() error { var DefaultUiUpdater = &UIUpdater{}
xdMutex.Lock()
defer xdMutex.Unlock()
err := prepareUIPath() func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
updater := &UIUpdater{}
// checkout externalUI exist
if externalUI != "" {
updater.autoDownloadUI = true
updater.externalUIPath = C.Path.Resolve(externalUI)
} else {
// default externalUI path
updater.externalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
// checkout UIpath/name exist
if externalUIName != "" {
updater.autoDownloadUI = true
updater.externalUIPath = path.Join(updater.externalUIPath, externalUIName)
}
if externalUIURL != "" {
updater.externalUIURL = externalUIURL
}
return updater
}
func (u *UIUpdater) AutoDownloadUI() {
u.mutex.Lock()
defer u.mutex.Unlock()
if u.autoDownloadUI {
dirEntries, _ := os.ReadDir(u.externalUIPath)
if len(dirEntries) > 0 {
log.Infoln("UI already exists, skip downloading")
} else {
log.Infoln("External UI downloading ...")
err := u.downloadUI()
if err != nil {
log.Errorln("Error downloading UI: %s", err)
}
}
}
}
func (u *UIUpdater) DownloadUI() error {
u.mutex.Lock()
defer u.mutex.Unlock()
return u.downloadUI()
}
func (u *UIUpdater) downloadUI() error {
err := u.prepareUIPath()
if err != nil { if err != nil {
return fmt.Errorf("prepare UI path failed: %w", err) return fmt.Errorf("prepare UI path failed: %w", err)
} }
data, err := downloadForBytes(ExternalUIURL) data, err := downloadForBytes(u.externalUIURL)
if err != nil { if err != nil {
return fmt.Errorf("can't download file: %w", err) return fmt.Errorf("can't download file: %w", err)
} }
@ -42,7 +87,7 @@ func DownloadUI() error {
} }
defer os.Remove(saved) defer os.Remove(saved)
err = cleanup(ExternalUIPath) err = cleanup(u.externalUIPath)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err) return fmt.Errorf("cleanup exist file error: %w", err)
@ -54,18 +99,18 @@ func DownloadUI() error {
return fmt.Errorf("can't extract zip file: %w", err) return fmt.Errorf("can't extract zip file: %w", err)
} }
err = os.Rename(unzipFolder, ExternalUIPath) err = os.Rename(unzipFolder, u.externalUIPath)
if err != nil { if err != nil {
return fmt.Errorf("rename UI folder failed: %w", err) return fmt.Errorf("rename UI folder failed: %w", err)
} }
return nil return nil
} }
func prepareUIPath() error { func (u *UIUpdater) prepareUIPath() error {
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { if _, err := os.Stat(u.externalUIPath); os.IsNotExist(err) {
log.Infoln("dir %s does not exist, creating", ExternalUIPath) log.Infoln("dir %s does not exist, creating", u.externalUIPath)
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil { if err := os.MkdirAll(u.externalUIPath, os.ModePerm); err != nil {
log.Warnln("create dir %s error: %s", ExternalUIPath, err) log.Warnln("create dir %s error: %s", u.externalUIPath, err)
} }
} }
return nil return nil

View File

@ -7,9 +7,9 @@ import (
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"path"
"strings" "strings"
"time" "time"
_ "unsafe"
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
@ -20,15 +20,10 @@ import (
"github.com/metacubex/mihomo/component/cidr" "github.com/metacubex/mihomo/component/cidr"
"github.com/metacubex/mihomo/component/fakeip" "github.com/metacubex/mihomo/component/fakeip"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/keepalive"
P "github.com/metacubex/mihomo/component/process" P "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/resource"
"github.com/metacubex/mihomo/component/sniffer" "github.com/metacubex/mihomo/component/sniffer"
tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
"github.com/metacubex/mihomo/component/updater"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
providerTypes "github.com/metacubex/mihomo/constant/provider" providerTypes "github.com/metacubex/mihomo/constant/provider"
snifferTypes "github.com/metacubex/mihomo/constant/sniffer" snifferTypes "github.com/metacubex/mihomo/constant/sniffer"
@ -67,6 +62,9 @@ type General struct {
GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalClientFingerprint string `json:"global-client-fingerprint"`
GlobalUA string `json:"global-ua"` GlobalUA string `json:"global-ua"`
ETagSupport bool `json:"etag-support"` ETagSupport bool `json:"etag-support"`
KeepAliveIdle int `json:"keep-alive-idle"`
KeepAliveInterval int `json:"keep-alive-interval"`
DisableKeepAlive bool `json:"disable-keep-alive"`
} }
// Inbound config // Inbound config
@ -105,6 +103,8 @@ type Controller struct {
ExternalControllerUnix string ExternalControllerUnix string
ExternalControllerPipe string ExternalControllerPipe string
ExternalUI string ExternalUI string
ExternalUIURL string
ExternalUIName string
ExternalDohServer string ExternalDohServer string
Secret string Secret string
Cors Cors Cors Cors
@ -586,10 +586,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.General = general config.General = general
if len(config.General.GlobalClientFingerprint) != 0 { // We need to temporarily apply some configuration in general and roll back after parsing the complete configuration.
log.Debugln("GlobalClientFingerprint: %s", config.General.GlobalClientFingerprint) // The loading and downloading of geodata in the parseRules and parseRuleProviders rely on these.
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint) // This implementation is very disgusting, but there is currently no better solution
} rollback := temporaryUpdateGeneral(config.General)
defer rollback()
controller, err := parseController(rawCfg) controller, err := parseController(rawCfg)
if err != nil { if err != nil {
@ -705,46 +706,10 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
return config, nil return config, nil
} }
//go:linkname temporaryUpdateGeneral
func temporaryUpdateGeneral(general *General) func()
func parseGeneral(cfg *RawConfig) (*General, error) { func parseGeneral(cfg *RawConfig) (*General, error) {
updater.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
updater.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
geodata.SetGeodataMode(cfg.GeodataMode)
geodata.SetLoader(cfg.GeodataLoader)
geodata.SetSiteMatcher(cfg.GeositeMatcher)
geodata.SetGeoIpUrl(cfg.GeoXUrl.GeoIp)
geodata.SetGeoSiteUrl(cfg.GeoXUrl.GeoSite)
geodata.SetMmdbUrl(cfg.GeoXUrl.Mmdb)
geodata.SetASNUrl(cfg.GeoXUrl.ASN)
mihomoHttp.SetUA(cfg.GlobalUA)
resource.SetETag(cfg.ETagSupport)
if cfg.KeepAliveIdle != 0 {
keepalive.SetKeepAliveIdle(time.Duration(cfg.KeepAliveIdle) * time.Second)
}
if cfg.KeepAliveInterval != 0 {
keepalive.SetKeepAliveInterval(time.Duration(cfg.KeepAliveInterval) * time.Second)
}
keepalive.SetDisableKeepAlive(cfg.DisableKeepAlive)
// checkout externalUI exist
if cfg.ExternalUI != "" {
updater.AutoDownloadUI = true
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
} else {
// default externalUI path
updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.AutoDownloadUI = true
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
}
return &General{ return &General{
Inbound: Inbound{ Inbound: Inbound{
Port: cfg.Port, Port: cfg.Port,
@ -778,11 +743,15 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeoUpdateInterval: cfg.GeoUpdateInterval, GeoUpdateInterval: cfg.GeoUpdateInterval,
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
GeositeMatcher: cfg.GeositeMatcher,
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode, FindProcessMode: cfg.FindProcessMode,
GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalClientFingerprint: cfg.GlobalClientFingerprint,
GlobalUA: cfg.GlobalUA, GlobalUA: cfg.GlobalUA,
ETagSupport: cfg.ETagSupport, ETagSupport: cfg.ETagSupport,
KeepAliveIdle: cfg.KeepAliveIdle,
KeepAliveInterval: cfg.KeepAliveInterval,
DisableKeepAlive: cfg.DisableKeepAlive,
}, nil }, nil
} }
@ -790,6 +759,8 @@ func parseController(cfg *RawConfig) (*Controller, error) {
return &Controller{ return &Controller{
ExternalController: cfg.ExternalController, ExternalController: cfg.ExternalController,
ExternalUI: cfg.ExternalUI, ExternalUI: cfg.ExternalUI,
ExternalUIURL: cfg.ExternalUIURL,
ExternalUIName: cfg.ExternalUIName,
Secret: cfg.Secret, Secret: cfg.Secret,
ExternalControllerPipe: cfg.ExternalControllerPipe, ExternalControllerPipe: cfg.ExternalControllerPipe,
ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerUnix: cfg.ExternalControllerUnix,

View File

@ -31,12 +31,12 @@ type systemClient struct {
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
dnsClients, err := c.getDnsClients() dnsClients, err := c.getDnsClients()
if len(dnsClients) == 0 && len(c.defaultNS) > 0 {
dnsClients = c.defaultNS
err = nil
}
if err != nil { if err != nil {
if len(c.defaultNS) > 0 { return
dnsClients = c.defaultNS
} else {
return
}
} }
msg, _, err = batchExchange(ctx, dnsClients, m) msg, _, err = batchExchange(ctx, dnsClients, m)
return return
@ -45,11 +45,16 @@ func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Ms
// Address implements dnsClient // Address implements dnsClient
func (c *systemClient) Address() string { func (c *systemClient) Address() string {
dnsClients, _ := c.getDnsClients() dnsClients, _ := c.getDnsClients()
isDefault := ""
if len(dnsClients) == 0 && len(c.defaultNS) > 0 {
dnsClients = c.defaultNS
isDefault = "[defaultNS]"
}
addrs := make([]string, 0, len(dnsClients)) addrs := make([]string, 0, len(dnsClients))
for _, c := range dnsClients { for _, c := range dnsClients {
addrs = append(addrs, c.Address()) addrs = append(addrs, c.Address())
} }
return fmt.Sprintf("system(%s)", strings.Join(addrs, ",")) return fmt.Sprintf("system%s(%s)", isDefault, strings.Join(addrs, ","))
} }
var _ dnsClient = (*systemClient)(nil) var _ dnsClient = (*systemClient)(nil)

View File

@ -9,6 +9,7 @@ import (
"strconv" "strconv"
"sync" "sync"
"time" "time"
_ "unsafe"
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
@ -16,9 +17,10 @@ import (
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
G "github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/iface" "github.com/metacubex/mihomo/component/iface"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/profile" "github.com/metacubex/mihomo/component/profile"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@ -100,7 +102,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders) updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
updateSniffer(cfg.Sniffer) updateSniffer(cfg.Sniffer)
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
updateGeneral(cfg.General) updateGeneral(cfg.General, true)
updateNTP(cfg.NTP) updateNTP(cfg.NTP)
updateDNS(cfg.DNS, cfg.General.IPv6) updateDNS(cfg.DNS, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force) updateListeners(cfg.General, cfg.Listeners, force)
@ -117,7 +119,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
runtime.GC() runtime.GC()
tunnel.OnRunning() tunnel.OnRunning()
hcCompatibleProvider(cfg.Providers) hcCompatibleProvider(cfg.Providers)
initExternalUI() updateUpdater(cfg)
resolver.ResetConnection() resolver.ResetConnection()
} }
@ -160,22 +162,25 @@ func GetGeneral() *config.General {
Interface: dialer.DefaultInterface.Load(), Interface: dialer.DefaultInterface.Load(),
RoutingMark: int(dialer.DefaultRoutingMark.Load()), RoutingMark: int(dialer.DefaultRoutingMark.Load()),
GeoXUrl: config.GeoXUrl{ GeoXUrl: config.GeoXUrl{
GeoIp: G.GeoIpUrl(), GeoIp: geodata.GeoIpUrl(),
Mmdb: G.MmdbUrl(), Mmdb: geodata.MmdbUrl(),
ASN: G.ASNUrl(), ASN: geodata.ASNUrl(),
GeoSite: G.GeoSiteUrl(), GeoSite: geodata.GeoSiteUrl(),
}, },
GeoAutoUpdate: updater.GeoAutoUpdate(), GeoAutoUpdate: updater.GeoAutoUpdate(),
GeoUpdateInterval: updater.GeoUpdateInterval(), GeoUpdateInterval: updater.GeoUpdateInterval(),
GeodataMode: G.GeodataMode(), GeodataMode: geodata.GeodataMode(),
GeodataLoader: G.LoaderName(), GeodataLoader: geodata.LoaderName(),
GeositeMatcher: G.SiteMatcherName(), GeositeMatcher: geodata.SiteMatcherName(),
TCPConcurrent: dialer.GetTcpConcurrent(), TCPConcurrent: dialer.GetTcpConcurrent(),
FindProcessMode: tunnel.FindProcessMode(), FindProcessMode: tunnel.FindProcessMode(),
Sniffing: tunnel.IsSniffing(), Sniffing: tunnel.IsSniffing(),
GlobalClientFingerprint: tlsC.GetGlobalFingerprint(), GlobalClientFingerprint: tlsC.GetGlobalFingerprint(),
GlobalUA: mihomoHttp.UA(), GlobalUA: mihomoHttp.UA(),
ETagSupport: resource.ETag(), ETagSupport: resource.ETag(),
KeepAliveInterval: int(keepalive.KeepAliveInterval() / time.Second),
KeepAliveIdle: int(keepalive.KeepAliveIdle() / time.Second),
DisableKeepAlive: keepalive.DisableKeepAlive(),
} }
return general return general
@ -394,42 +399,63 @@ func updateTunnels(tunnels []LC.Tunnel) {
listener.PatchTunnel(tunnels, tunnel.Tunnel) listener.PatchTunnel(tunnels, tunnel.Tunnel)
} }
func initExternalUI() { func updateUpdater(cfg *config.Config) {
if updater.AutoDownloadUI { general := cfg.General
dirEntries, _ := os.ReadDir(updater.ExternalUIPath) updater.SetGeoAutoUpdate(general.GeoAutoUpdate)
if len(dirEntries) > 0 { updater.SetGeoUpdateInterval(general.GeoUpdateInterval)
log.Infoln("UI already exists, skip downloading")
} else { controller := cfg.Controller
log.Infoln("External UI downloading ...") updater.DefaultUiUpdater = updater.NewUiUpdater(controller.ExternalUI, controller.ExternalUIURL, controller.ExternalUIName)
updater.DownloadUI() updater.DefaultUiUpdater.AutoDownloadUI()
} }
//go:linkname temporaryUpdateGeneral github.com/metacubex/mihomo/config.temporaryUpdateGeneral
func temporaryUpdateGeneral(general *config.General) func() {
oldGeneral := GetGeneral()
updateGeneral(general, false)
return func() {
updateGeneral(oldGeneral, false)
} }
} }
func updateGeneral(general *config.General) { func updateGeneral(general *config.General, logging bool) {
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
tunnel.SetFindProcessMode(general.FindProcessMode) tunnel.SetFindProcessMode(general.FindProcessMode)
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
if general.TCPConcurrent { dialer.SetTcpConcurrent(general.TCPConcurrent)
dialer.SetTcpConcurrent(general.TCPConcurrent) if logging && general.TCPConcurrent {
log.Infoln("Use tcp concurrent") log.Infoln("Use tcp concurrent")
} }
inbound.SetTfo(general.InboundTfo) inbound.SetTfo(general.InboundTfo)
inbound.SetMPTCP(general.InboundMPTCP) inbound.SetMPTCP(general.InboundMPTCP)
keepalive.SetKeepAliveIdle(time.Duration(general.KeepAliveIdle) * time.Second)
keepalive.SetKeepAliveInterval(time.Duration(general.KeepAliveInterval) * time.Second)
keepalive.SetDisableKeepAlive(general.DisableKeepAlive)
adapter.UnifiedDelay.Store(general.UnifiedDelay) adapter.UnifiedDelay.Store(general.UnifiedDelay)
dialer.DefaultInterface.Store(general.Interface) dialer.DefaultInterface.Store(general.Interface)
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark)) dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
if general.RoutingMark > 0 { if logging && general.RoutingMark > 0 {
log.Infoln("Use routing mark: %#x", general.RoutingMark) log.Infoln("Use routing mark: %#x", general.RoutingMark)
} }
iface.FlushCache() iface.FlushCache()
G.SetLoader(general.GeodataLoader)
G.SetSiteMatcher(general.GeositeMatcher) geodata.SetGeodataMode(general.GeodataMode)
geodata.SetLoader(general.GeodataLoader)
geodata.SetSiteMatcher(general.GeositeMatcher)
geodata.SetGeoIpUrl(general.GeoXUrl.GeoIp)
geodata.SetGeoSiteUrl(general.GeoXUrl.GeoSite)
geodata.SetMmdbUrl(general.GeoXUrl.Mmdb)
geodata.SetASNUrl(general.GeoXUrl.ASN)
mihomoHttp.SetUA(general.GlobalUA)
resource.SetETag(general.ETagSupport)
tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint)
} }
func updateUsers(users []auth.AuthUser) { func updateUsers(users []auth.AuthUser) {

View File

@ -47,7 +47,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
} }
func updateUI(w http.ResponseWriter, r *http.Request) { func updateUI(w http.ResponseWriter, r *http.Request) {
err := updater.DownloadUI() err := updater.DefaultUiUpdater.DownloadUI()
if err != nil { if err != nil {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError) render.Status(r, http.StatusInternalServerError)