Support core status check and force restart Optimize proxies page and access page Update flutter and pub dependencies Optimize more details
529 lines
10 KiB
Go
529 lines
10 KiB
Go
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(bytes []byte) string {
|
|
_, err := config.UnmarshalRawConfig(bytes)
|
|
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,
|
|
})
|
|
}
|
|
}
|