123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- package main
- import "C"
- import (
- "context"
- "errors"
- "math"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strings"
- "sync"
- "syscall"
- "time"
- "github.com/metacubex/mihomo/adapter"
- "github.com/metacubex/mihomo/adapter/inbound"
- "github.com/metacubex/mihomo/adapter/outboundgroup"
- "github.com/metacubex/mihomo/adapter/provider"
- "github.com/metacubex/mihomo/common/batch"
- "github.com/metacubex/mihomo/component/dialer"
- "github.com/metacubex/mihomo/component/resolver"
- "github.com/metacubex/mihomo/component/sniffer"
- "github.com/metacubex/mihomo/config"
- "github.com/metacubex/mihomo/constant"
- cp "github.com/metacubex/mihomo/constant/provider"
- "github.com/metacubex/mihomo/hub"
- "github.com/metacubex/mihomo/hub/executor"
- "github.com/metacubex/mihomo/hub/route"
- "github.com/metacubex/mihomo/listener"
- "github.com/metacubex/mihomo/log"
- rp "github.com/metacubex/mihomo/rules/provider"
- "github.com/metacubex/mihomo/tunnel"
- )
- type ConfigExtendedParams struct {
- IsPatch bool `json:"is-patch"`
- IsCompatible bool `json:"is-compatible"`
- SelectedMap map[string]string `json:"selected-map"`
- TestURL *string `json:"test-url"`
- }
- type GenerateConfigParams struct {
- ProfileId string `json:"profile-id"`
- Config config.RawConfig `json:"config" `
- Params ConfigExtendedParams `json:"params"`
- }
- type ChangeProxyParams struct {
- GroupName *string `json:"group-name"`
- ProxyName *string `json:"proxy-name"`
- }
- type TestDelayParams struct {
- ProxyName string `json:"proxy-name"`
- Timeout int64 `json:"timeout"`
- }
- type ProcessMapItem struct {
- Id int64 `json:"id"`
- Value string `json:"value"`
- }
- type ExternalProvider struct {
- Name string `json:"name"`
- Type string `json:"type"`
- VehicleType string `json:"vehicle-type"`
- Count int `json:"count"`
- Path string `json:"path"`
- UpdateAt time.Time `json:"update-at"`
- }
- type ExternalProviders []ExternalProvider
- func (a ExternalProviders) Len() int { return len(a) }
- func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
- func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
- var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
- func restartExecutable(execPath string) {
- var err error
- executor.Shutdown()
- if runtime.GOOS == "windows" {
- cmd := exec.Command(execPath, os.Args[1:]...)
- log.Infoln("restarting: %q %q", execPath, os.Args[1:])
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err = cmd.Start()
- if err != nil {
- log.Fatalln("restarting: %s", err)
- }
- os.Exit(0)
- }
- log.Infoln("restarting: %q %q", execPath, os.Args[1:])
- err = syscall.Exec(execPath, os.Args, os.Environ())
- if err != nil {
- log.Fatalln("restarting: %s", err)
- }
- }
- func readFile(path string) ([]byte, error) {
- if _, err := os.Stat(path); os.IsNotExist(err) {
- return nil, err
- }
- data, err := os.ReadFile(path)
- if err != nil {
- return nil, err
- }
- return data, err
- }
- func removeFile(path string) error {
- absPath, err := filepath.Abs(path)
- if err != nil {
- return err
- }
- err = os.Remove(absPath)
- if err != nil {
- return err
- }
- return nil
- }
- func getProfilePath(id string) string {
- return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
- }
- func getProfileProvidersPath(id string) string {
- return filepath.Join(constant.Path.HomeDir(), "providers", id)
- }
- func getRawConfigWithId(id string) *config.RawConfig {
- path := getProfilePath(id)
- bytes, err := readFile(path)
- if err != nil {
- log.Errorln("profile is not exist")
- return config.DefaultRawConfig()
- }
- prof, err := config.UnmarshalRawConfig(bytes)
- if err != nil {
- log.Errorln("unmarshalRawConfig error %v", err)
- return config.DefaultRawConfig()
- }
- for _, mapping := range prof.ProxyProvider {
- value, exist := mapping["path"].(string)
- if !exist {
- continue
- }
- mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
- }
- for _, mapping := range prof.RuleProvider {
- value, exist := mapping["path"].(string)
- if !exist {
- continue
- }
- mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
- }
- return prof
- }
- func getExternalProvidersRaw() map[string]cp.Provider {
- eps := make(map[string]cp.Provider)
- for n, p := range tunnel.Providers() {
- if p.VehicleType() != cp.Compatible {
- eps[n] = p
- }
- }
- for n, p := range tunnel.RuleProviders() {
- if p.VehicleType() != cp.Compatible {
- eps[n] = p
- }
- }
- return eps
- }
- func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
- switch p.(type) {
- case *provider.ProxySetProvider:
- psp := p.(*provider.ProxySetProvider)
- return &ExternalProvider{
- Name: psp.Name(),
- Type: psp.Type().String(),
- VehicleType: psp.VehicleType().String(),
- Count: psp.Count(),
- Path: psp.Vehicle().Path(),
- UpdateAt: psp.UpdatedAt,
- }, nil
- case *rp.RuleSetProvider:
- rsp := p.(*rp.RuleSetProvider)
- return &ExternalProvider{
- Name: rsp.Name(),
- Type: rsp.Type().String(),
- VehicleType: rsp.VehicleType().String(),
- Count: rsp.Count(),
- Path: rsp.Vehicle().Path(),
- UpdateAt: rsp.UpdatedAt,
- }, nil
- default:
- return nil, errors.New("not external provider")
- }
- }
- func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
- switch p.(type) {
- case *provider.ProxySetProvider:
- psp := p.(*provider.ProxySetProvider)
- elm, same, err := psp.SideUpdate(bytes)
- if err == nil && !same {
- psp.OnUpdate(elm)
- }
- return nil
- case rp.RuleSetProvider:
- rsp := p.(*rp.RuleSetProvider)
- elm, same, err := rsp.SideUpdate(bytes)
- if err == nil && !same {
- rsp.OnUpdate(elm)
- }
- return nil
- default:
- return errors.New("not external provider")
- }
- }
- func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
- prof := getRawConfigWithId(profileId)
- overwriteConfig(prof, cfg)
- return prof
- }
- func Reduce[T any, U any](s []T, initVal U, f func(U, T) U) U {
- for _, v := range s {
- initVal = f(initVal, v)
- }
- return initVal
- }
- func Map[T, U any](slice []T, fn func(T) U) []U {
- result := make([]U, len(slice))
- for i, v := range slice {
- result[i] = fn(v)
- }
- return result
- }
- func replaceFromMap(s string, m map[string]string) string {
- for k, v := range m {
- s = strings.ReplaceAll(s, k, v)
- }
- return s
- }
- func removeDuplicateFromSlice[T any](slice []T) []T {
- result := make([]T, 0)
- seen := make(map[any]struct{})
- for _, value := range slice {
- if _, ok := seen[value]; !ok {
- result = append(result, value)
- seen[value] = struct{}{}
- }
- }
- return result
- }
- func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
- var replacements = map[string]string{}
- var selectArr []map[string]any
- var urlTestArr []map[string]any
- var fallbackArr []map[string]any
- for _, group := range *proxyGroup {
- switch group["type"] {
- case "select":
- selectArr = append(selectArr, group)
- replacements[group["name"].(string)] = "Proxy"
- break
- case "url-test":
- urlTestArr = append(urlTestArr, group)
- replacements[group["name"].(string)] = "Auto"
- break
- case "fallback":
- fallbackArr = append(fallbackArr, group)
- replacements[group["name"].(string)] = "Fallback"
- break
- default:
- break
- }
- }
- ProxyProxies := Reduce(selectArr, []string{}, func(res []string, cur map[string]any) []string {
- if cur["proxies"] == nil {
- return res
- }
- for _, proxyName := range cur["proxies"].([]interface{}) {
- if str, ok := proxyName.(string); ok {
- str = replaceFromMap(str, replacements)
- if str != "Proxy" {
- res = append(res, str)
- }
- }
- }
- return res
- })
- ProxyProxies = removeDuplicateFromSlice(ProxyProxies)
- AutoProxies := Reduce(urlTestArr, []string{}, func(res []string, cur map[string]any) []string {
- if cur["proxies"] == nil {
- return res
- }
- for _, proxyName := range cur["proxies"].([]interface{}) {
- if str, ok := proxyName.(string); ok {
- str = replaceFromMap(str, replacements)
- if str != "Auto" {
- res = append(res, str)
- }
- }
- }
- return res
- })
- AutoProxies = removeDuplicateFromSlice(AutoProxies)
- FallbackProxies := Reduce(fallbackArr, []string{}, func(res []string, cur map[string]any) []string {
- if cur["proxies"] == nil {
- return res
- }
- for _, proxyName := range cur["proxies"].([]interface{}) {
- if str, ok := proxyName.(string); ok {
- str = replaceFromMap(str, replacements)
- if str != "Fallback" {
- res = append(res, str)
- }
- }
- }
- return res
- })
- FallbackProxies = removeDuplicateFromSlice(FallbackProxies)
- var computedProxyGroup []map[string]any
- if len(ProxyProxies) > 0 {
- computedProxyGroup = append(computedProxyGroup,
- map[string]any{
- "name": "Proxy",
- "type": "select",
- "proxies": ProxyProxies,
- })
- }
- if len(AutoProxies) > 0 {
- computedProxyGroup = append(computedProxyGroup,
- map[string]any{
- "name": "Auto",
- "type": "url-test",
- "proxies": AutoProxies,
- })
- }
- if len(FallbackProxies) > 0 {
- computedProxyGroup = append(computedProxyGroup,
- map[string]any{
- "name": "Fallback",
- "type": "fallback",
- "proxies": FallbackProxies,
- })
- }
- computedRule := Map(*rule, func(value string) string {
- return replaceFromMap(value, replacements)
- })
- *proxyGroup = computedProxyGroup
- *rule = computedRule
- }
- func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
- targetConfig.ExternalController = patchConfig.ExternalController
- targetConfig.ExternalUI = ""
- targetConfig.Interface = ""
- targetConfig.ExternalUIURL = ""
- targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
- targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
- //targetConfig.GeodataMode = false
- targetConfig.IPv6 = patchConfig.IPv6
- targetConfig.LogLevel = patchConfig.LogLevel
- targetConfig.Port = 0
- targetConfig.SocksPort = 0
- targetConfig.KeepAliveInterval = patchConfig.KeepAliveInterval
- targetConfig.MixedPort = patchConfig.MixedPort
- targetConfig.FindProcessMode = patchConfig.FindProcessMode
- targetConfig.AllowLan = patchConfig.AllowLan
- targetConfig.Mode = patchConfig.Mode
- targetConfig.Tun.Enable = patchConfig.Tun.Enable
- targetConfig.Tun.Device = patchConfig.Tun.Device
- targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
- targetConfig.Tun.Stack = patchConfig.Tun.Stack
- targetConfig.GeodataLoader = patchConfig.GeodataLoader
- targetConfig.Profile.StoreSelected = false
- targetConfig.GeoXUrl = patchConfig.GeoXUrl
- targetConfig.GlobalUA = patchConfig.GlobalUA
- if targetConfig.DNS.Enable == false {
- targetConfig.DNS = patchConfig.DNS
- }
- //if runtime.GOOS == "android" {
- // targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
- //} else if runtime.GOOS == "windows" {
- // targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
- //}
- if configParams.IsCompatible == false {
- targetConfig.ProxyProvider = make(map[string]map[string]any)
- targetConfig.RuleProvider = make(map[string]map[string]any)
- generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
- }
- }
- func patchConfig(general *config.General) {
- log.Infoln("[Apply] patch")
- route.ReStartServer(general.ExternalController)
- if sniffer.Dispatcher != nil {
- tunnel.SetSniffing(general.Sniffing)
- }
- tunnel.SetFindProcessMode(general.FindProcessMode)
- dialer.SetTcpConcurrent(general.TCPConcurrent)
- dialer.DefaultInterface.Store(general.Interface)
- adapter.UnifiedDelay.Store(general.UnifiedDelay)
- tunnel.SetMode(general.Mode)
- log.SetLevel(general.LogLevel)
- resolver.DisableIPv6 = !general.IPv6
- }
- var isRunning = false
- var runLock sync.Mutex
- func updateListeners(general *config.General, listeners map[string]constant.InboundListener) {
- listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
- listener.SetAllowLan(general.AllowLan)
- inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
- inbound.SetAllowedIPs(general.LanAllowedIPs)
- inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
- listener.SetBindAddress(general.BindAddress)
- listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
- listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
- listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
- listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
- listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
- listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
- listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
- listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
- listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
- listener.ReCreateTun(general.Tun, tunnel.Tunnel)
- listener.ReCreateRedirToTun(general.EBpf.RedirectToTun)
- }
- func stopListeners() {
- listener.StopListener()
- }
- func hcCompatibleProvider(proxyProviders map[string]cp.ProxyProvider) {
- wg := sync.WaitGroup{}
- ch := make(chan struct{}, math.MaxInt)
- for _, proxyProvider := range proxyProviders {
- proxyProvider := proxyProvider
- if proxyProvider.VehicleType() == cp.Compatible {
- log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
- wg.Add(1)
- ch <- struct{}{}
- go func() {
- defer func() { <-ch; wg.Done() }()
- if err := proxyProvider.Initial(); err != nil {
- log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
- }
- }()
- }
- }
- }
- func patchSelectGroup() {
- mapping := configParams.SelectedMap
- if mapping == nil {
- return
- }
- for name, proxy := range tunnel.ProxiesWithProviders() {
- outbound, ok := proxy.(*adapter.Proxy)
- if !ok {
- continue
- }
- selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble)
- if !ok {
- continue
- }
- selected, exist := mapping[name]
- if !exist {
- continue
- }
- selector.ForceSet(selected)
- }
- }
- func applyConfig() error {
- cfg, err := config.ParseRawConfig(currentRawConfig)
- if err != nil {
- cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
- }
- if configParams.TestURL != nil {
- constant.DefaultTestURL = *configParams.TestURL
- }
- if configParams.IsPatch {
- patchConfig(cfg.General)
- } else {
- closeConnections()
- runtime.GC()
- hub.UltraApplyConfig(cfg)
- patchSelectGroup()
- }
- if isRunning {
- updateListeners(cfg.General, cfg.Listeners)
- hcCompatibleProvider(cfg.Providers)
- }
- externalProviders = getExternalProvidersRaw()
- return err
- }
|