package main import ( "context" "encoding/json" "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/common/observable" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/constant" cp "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/listener" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel/statistic" "net" "os" "runtime" "sort" "strconv" "time" ) var ( isInit = false externalProviders = map[string]cp.Provider{} logSubscriber observable.Subscription[log.Event] ) func handleInitClash(paramsString string) bool { var params = InitParams{} err := json.Unmarshal([]byte(paramsString), ¶ms) if err != nil { return false } version = params.Version if !isInit { constant.SetHomeDir(params.HomeDir) isInit = true } return isInit } func handleStartListener() bool { runLock.Lock() defer runLock.Unlock() isRunning = true updateListeners() resolver.ResetConnection() return true } func handleStopListener() bool { runLock.Lock() defer runLock.Unlock() isRunning = false listener.StopListener() resolver.ResetConnection() return true } func handleGetIsInit() bool { return isInit } func handleForceGC() { go func() { log.Infoln("[APP] request force GC") runtime.GC() }() } func handleShutdown() bool { stopListeners() executor.Shutdown() runtime.GC() isInit = false return true } func handleValidateConfig(path string) string { buf, err := readFile(path) _, err = config.UnmarshalRawConfig(buf) if err != nil { return err.Error() } return "" } func handleGetProxies() map[string]constant.Proxy { runLock.Lock() defer runLock.Unlock() return tunnel.ProxiesWithProviders() } func handleChangeProxy(data string, fn func(string string)) { runLock.Lock() go func() { defer runLock.Unlock() var params = &ChangeProxyParams{} err := json.Unmarshal([]byte(data), params) if err != nil { fn(err.Error()) return } groupName := *params.GroupName proxyName := *params.ProxyName proxies := tunnel.ProxiesWithProviders() group, ok := proxies[groupName] if !ok { fn("Not found group") return } adapterProxy := group.(*adapter.Proxy) selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble) if !ok { fn("Group is not selectable") return } if proxyName == "" { selector.ForceSet(proxyName) } else { err = selector.Set(proxyName) } if err != nil { fn(err.Error()) return } fn("") return }() } func handleGetTraffic(onlyStatisticsProxy bool) string { up, down := statistic.DefaultManager.Current(onlyStatisticsProxy) traffic := map[string]int64{ "up": up, "down": down, } data, err := json.Marshal(traffic) if err != nil { log.Errorln("Error: %s", err) return "" } return string(data) } func handleGetTotalTraffic(onlyStatisticsProxy bool) string { up, down := statistic.DefaultManager.Total(onlyStatisticsProxy) traffic := map[string]int64{ "up": up, "down": down, } data, err := json.Marshal(traffic) if err != nil { log.Errorln("Error: %s", err) return "" } return string(data) } func handleResetTraffic() { statistic.DefaultManager.ResetStatistic() } func handleAsyncTestDelay(paramsString string, fn func(string)) { mBatch.Go(paramsString, func() (bool, error) { var params = &TestDelayParams{} err := json.Unmarshal([]byte(paramsString), params) if err != nil { fn("") return false, nil } expectedStatus, err := utils.NewUnsignedRanges[uint16]("") if err != nil { fn("") return false, nil } ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout)) defer cancel() proxies := tunnel.ProxiesWithProviders() proxy := proxies[params.ProxyName] delayData := &Delay{ Name: params.ProxyName, } if proxy == nil { delayData.Value = -1 data, _ := json.Marshal(delayData) fn(string(data)) return false, nil } testUrl := constant.DefaultTestURL if params.TestUrl != "" { testUrl = params.TestUrl } delayData.Url = testUrl delay, err := proxy.URLTest(ctx, testUrl, expectedStatus) if err != nil || delay == 0 { delayData.Value = -1 data, _ := json.Marshal(delayData) fn(string(data)) return false, nil } delayData.Value = int32(delay) data, _ := json.Marshal(delayData) fn(string(data)) return false, nil }) } func handleGetConnections() string { runLock.Lock() defer runLock.Unlock() snapshot := statistic.DefaultManager.Snapshot() data, err := json.Marshal(snapshot) if err != nil { log.Errorln("Error: %s", err) return "" } return string(data) } func handleCloseConnections() bool { runLock.Lock() defer runLock.Unlock() closeConnections() return true } func closeConnections() { statistic.DefaultManager.Range(func(c statistic.Tracker) bool { err := c.Close() if err != nil { return false } return true }) } func handleResetConnections() bool { runLock.Lock() defer runLock.Unlock() resolver.ResetConnection() return true } func handleCloseConnection(connectionId string) bool { runLock.Lock() defer runLock.Unlock() c := statistic.DefaultManager.Get(connectionId) if c == nil { return false } _ = c.Close() return true } func handleGetExternalProviders() string { runLock.Lock() defer runLock.Unlock() externalProviders = getExternalProvidersRaw() eps := make([]ExternalProvider, 0) for _, p := range externalProviders { externalProvider, err := toExternalProvider(p) if err != nil { continue } eps = append(eps, *externalProvider) } sort.Sort(ExternalProviders(eps)) data, err := json.Marshal(eps) if err != nil { return "" } return string(data) } func handleGetExternalProvider(externalProviderName string) string { runLock.Lock() defer runLock.Unlock() externalProvider, exist := externalProviders[externalProviderName] if !exist { return "" } e, err := toExternalProvider(externalProvider) if err != nil { return "" } data, err := json.Marshal(e) if err != nil { return "" } return string(data) } func handleUpdateGeoData(geoType string, geoName string, fn func(value string)) { go func() { path := constant.Path.Resolve(geoName) switch geoType { case "MMDB": err := updater.UpdateMMDBWithPath(path) if err != nil { fn(err.Error()) return } case "ASN": err := updater.UpdateASNWithPath(path) if err != nil { fn(err.Error()) return } case "GEOIP": err := updater.UpdateGeoIpWithPath(path) if err != nil { fn(err.Error()) return } case "GEOSITE": err := updater.UpdateGeoSiteWithPath(path) if err != nil { fn(err.Error()) return } } fn("") }() } func handleUpdateExternalProvider(providerName string, fn func(value string)) { go func() { externalProvider, exist := externalProviders[providerName] if !exist { fn("external provider is not exist") return } err := externalProvider.Update() if err != nil { fn(err.Error()) return } fn("") }() } func handleSideLoadExternalProvider(providerName string, data []byte, fn func(value string)) { go func() { runLock.Lock() defer runLock.Unlock() externalProvider, exist := externalProviders[providerName] if !exist { fn("external provider is not exist") return } err := sideUpdateExternalProvider(externalProvider, data) if err != nil { fn(err.Error()) return } fn("") }() } func handleSuspend(suspended bool) bool { if suspended { tunnel.OnSuspend() } else { tunnel.OnRunning() } return true } func handleStartLog() { if logSubscriber != nil { log.UnSubscribe(logSubscriber) logSubscriber = nil } logSubscriber = log.Subscribe() go func() { for logData := range logSubscriber { if logData.LogLevel < log.Level() { continue } message := &Message{ Type: LogMessage, Data: logData, } sendMessage(*message) } }() } func handleStopLog() { if logSubscriber != nil { log.UnSubscribe(logSubscriber) logSubscriber = nil } } func handleGetCountryCode(ip string, fn func(value string)) { go func() { runLock.Lock() defer runLock.Unlock() codes := mmdb.IPInstance().LookupCode(net.ParseIP(ip)) if len(codes) == 0 { fn("") return } fn(codes[0]) }() } func handleGetMemory(fn func(value string)) { go func() { fn(strconv.FormatUint(statistic.DefaultManager.Memory(), 10)) }() } func handleGetConfig(path string) (*config.RawConfig, error) { bytes, err := readFile(path) if err != nil { return nil, err } prof, err := config.UnmarshalRawConfig(bytes) if err != nil { return nil, err } return prof, nil } func handleCrash() { panic("handle invoke crash") } func handleUpdateConfig(bytes []byte) string { var params = &UpdateParams{} err := json.Unmarshal(bytes, params) if err != nil { return err.Error() } updateConfig(params) return "" } func handleDelFile(path string, result ActionResult) { go func() { fileInfo, err := os.Stat(path) if err != nil { if !os.IsNotExist(err) { result.success(err.Error()) } result.success("") return } if fileInfo.IsDir() { err = os.RemoveAll(path) if err != nil { result.success(err.Error()) return } } else { err = os.Remove(path) if err != nil { result.success(err.Error()) return } } result.success("") }() } func handleSetupConfig(bytes []byte) string { var params = defaultSetupParams() err := UnmarshalJson(bytes, params) if err != nil { log.Errorln("unmarshalRawConfig error %v", err) _ = setupConfig(defaultSetupParams()) return err.Error() } err = setupConfig(params) if err != nil { return err.Error() } return "" } func init() { adapter.UrlTestHook = func(url string, name string, delay uint16) { delayData := &Delay{ Url: url, Name: name, } if delay == 0 { delayData.Value = -1 } else { delayData.Value = int32(delay) } sendMessage(Message{ Type: DelayMessage, Data: delayData, }) } statistic.DefaultRequestNotify = func(c statistic.Tracker) { sendMessage(Message{ Type: RequestMessage, Data: c, }) } executor.DefaultProviderLoadedHook = func(providerName string) { sendMessage(Message{ Type: LoadedMessage, Data: providerName, }) } }