Support proxies search Support svg display Optimize config persistence Add some scenes auto close connections Update core Optimize more details
497 lines
9.9 KiB
Go
497 lines
9.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"core/state"
|
|
"encoding/json"
|
|
"fmt"
|
|
"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"
|
|
"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()
|
|
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() string {
|
|
up, down := statistic.DefaultManager.Current(state.CurrentState.OnlyStatisticsProxy)
|
|
traffic := map[string]int64{
|
|
"up": up,
|
|
"down": down,
|
|
}
|
|
data, err := json.Marshal(traffic)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
return ""
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func handleGetTotalTraffic() string {
|
|
up, down := statistic.DefaultManager.Total(state.CurrentState.OnlyStatisticsProxy)
|
|
traffic := map[string]int64{
|
|
"up": up,
|
|
"down": down,
|
|
}
|
|
data, err := json.Marshal(traffic)
|
|
if err != nil {
|
|
fmt.Println("Error:", 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 {
|
|
fmt.Println("Error:", 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 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 handleSetState(params string) {
|
|
_ = json.Unmarshal([]byte(params), state.CurrentState)
|
|
}
|
|
|
|
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 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,
|
|
})
|
|
}
|
|
}
|