2024-04-30 23:38:49 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2024-08-04 08:21:14 +08:00
|
|
|
"context"
|
2024-12-03 21:47:12 +08:00
|
|
|
"encoding/json"
|
2024-08-05 19:25:35 +08:00
|
|
|
"errors"
|
2024-10-14 10:03:23 +08:00
|
|
|
"fmt"
|
2024-06-13 23:43:42 +08:00
|
|
|
"github.com/metacubex/mihomo/adapter"
|
2024-04-30 23:38:49 +08:00
|
|
|
"github.com/metacubex/mihomo/adapter/inbound"
|
2024-07-02 08:08:31 +08:00
|
|
|
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
2024-08-04 08:21:14 +08:00
|
|
|
"github.com/metacubex/mihomo/adapter/provider"
|
|
|
|
|
"github.com/metacubex/mihomo/common/batch"
|
2024-04-30 23:38:49 +08:00
|
|
|
"github.com/metacubex/mihomo/component/dialer"
|
|
|
|
|
"github.com/metacubex/mihomo/component/resolver"
|
|
|
|
|
"github.com/metacubex/mihomo/config"
|
2024-06-13 23:43:42 +08:00
|
|
|
"github.com/metacubex/mihomo/constant"
|
2024-12-03 21:47:12 +08:00
|
|
|
"github.com/metacubex/mihomo/constant/features"
|
2024-08-04 08:21:14 +08:00
|
|
|
cp "github.com/metacubex/mihomo/constant/provider"
|
2024-06-12 19:28:58 +08:00
|
|
|
"github.com/metacubex/mihomo/hub"
|
2024-12-03 21:47:12 +08:00
|
|
|
"github.com/metacubex/mihomo/hub/route"
|
2024-04-30 23:38:49 +08:00
|
|
|
"github.com/metacubex/mihomo/listener"
|
|
|
|
|
"github.com/metacubex/mihomo/log"
|
2024-08-04 08:21:14 +08:00
|
|
|
rp "github.com/metacubex/mihomo/rules/provider"
|
2024-04-30 23:38:49 +08:00
|
|
|
"github.com/metacubex/mihomo/tunnel"
|
2024-12-03 21:47:12 +08:00
|
|
|
"github.com/samber/lo"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"runtime"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
2024-04-30 23:38:49 +08:00
|
|
|
)
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
var (
|
|
|
|
|
isRunning = false
|
|
|
|
|
runLock sync.Mutex
|
2024-12-09 01:40:39 +08:00
|
|
|
ips = []string{"ipwho.is", "ifconfig.me", "icanhazip.com", "api.ip.sb", "ipinfo.io"}
|
2024-12-03 21:47:12 +08:00
|
|
|
b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
|
|
|
|
)
|
2024-06-03 18:02:05 +08:00
|
|
|
|
2024-08-05 19:25:35 +08:00
|
|
|
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] }
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
func (message *Message) Json() (string, error) {
|
|
|
|
|
data, err := json.Marshal(message)
|
|
|
|
|
return string(data), err
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 08:21:14 +08:00
|
|
|
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")
|
2024-04-30 23:38:49 +08:00
|
|
|
return config.DefaultRawConfig()
|
2024-08-04 08:21:14 +08:00
|
|
|
}
|
|
|
|
|
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
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-08-04 08:21:14 +08:00
|
|
|
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
2024-10-14 10:03:23 +08:00
|
|
|
if configParams.TestURL != nil {
|
2024-10-26 16:52:10 +08:00
|
|
|
if mapping["health-check"] != nil {
|
|
|
|
|
hc := mapping["health-check"].(map[string]any)
|
|
|
|
|
if hc != nil {
|
|
|
|
|
if hc["url"] != nil {
|
|
|
|
|
hc["url"] = *configParams.TestURL
|
|
|
|
|
}
|
2024-10-14 10:03:23 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-04 08:21:14 +08:00
|
|
|
}
|
|
|
|
|
for _, mapping := range prof.RuleProvider {
|
|
|
|
|
value, exist := mapping["path"].(string)
|
|
|
|
|
if !exist {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
|
|
|
|
}
|
|
|
|
|
return prof
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-05 19:25:35 +08:00
|
|
|
func getExternalProvidersRaw() map[string]cp.Provider {
|
|
|
|
|
eps := make(map[string]cp.Provider)
|
2024-08-04 08:21:14 +08:00
|
|
|
for n, p := range tunnel.Providers() {
|
|
|
|
|
if p.VehicleType() != cp.Compatible {
|
2024-08-05 19:25:35 +08:00
|
|
|
eps[n] = p
|
2024-08-04 08:21:14 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for n, p := range tunnel.RuleProviders() {
|
|
|
|
|
if p.VehicleType() != cp.Compatible {
|
2024-08-05 19:25:35 +08:00
|
|
|
eps[n] = p
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return eps
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
|
|
|
|
|
switch p.(type) {
|
|
|
|
|
case *provider.ProxySetProvider:
|
|
|
|
|
psp := p.(*provider.ProxySetProvider)
|
|
|
|
|
return &ExternalProvider{
|
2024-11-09 20:17:57 +08:00
|
|
|
Name: psp.Name(),
|
|
|
|
|
Type: psp.Type().String(),
|
|
|
|
|
VehicleType: psp.VehicleType().String(),
|
|
|
|
|
Count: psp.Count(),
|
|
|
|
|
UpdateAt: psp.UpdatedAt(),
|
|
|
|
|
Path: psp.Vehicle().Path(),
|
|
|
|
|
SubscriptionInfo: psp.GetSubscriptionInfo(),
|
2024-08-05 19:25:35 +08:00
|
|
|
}, nil
|
|
|
|
|
case *rp.RuleSetProvider:
|
|
|
|
|
rsp := p.(*rp.RuleSetProvider)
|
|
|
|
|
return &ExternalProvider{
|
|
|
|
|
Name: rsp.Name(),
|
|
|
|
|
Type: rsp.Type().String(),
|
|
|
|
|
VehicleType: rsp.VehicleType().String(),
|
|
|
|
|
Count: rsp.Count(),
|
2024-09-05 08:59:45 +08:00
|
|
|
UpdateAt: rsp.UpdatedAt(),
|
2024-11-09 20:17:57 +08:00
|
|
|
Path: rsp.Vehicle().Path(),
|
2024-08-05 19:25:35 +08:00
|
|
|
}, 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)
|
2024-10-14 10:03:23 +08:00
|
|
|
_, _, err := psp.SideUpdate(bytes)
|
|
|
|
|
if err == nil {
|
|
|
|
|
return err
|
2024-08-05 19:25:35 +08:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
case rp.RuleSetProvider:
|
|
|
|
|
rsp := p.(*rp.RuleSetProvider)
|
2024-10-14 10:03:23 +08:00
|
|
|
_, _, err := rsp.SideUpdate(bytes)
|
|
|
|
|
if err == nil {
|
|
|
|
|
return err
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-08-05 19:25:35 +08:00
|
|
|
return nil
|
|
|
|
|
default:
|
|
|
|
|
return errors.New("not external provider")
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 08:21:14 +08:00
|
|
|
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
|
|
|
|
|
prof := getRawConfigWithId(profileId)
|
2024-07-02 08:08:31 +08:00
|
|
|
overwriteConfig(prof, cfg)
|
2024-04-30 23:38:49 +08:00
|
|
|
return prof
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-26 20:44:30 +08:00
|
|
|
func genHosts(hosts, patchHosts map[string]any) {
|
|
|
|
|
for k, v := range patchHosts {
|
|
|
|
|
hosts[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 10:03:23 +08:00
|
|
|
func trimArr(arr []string) (r []string) {
|
|
|
|
|
for _, e := range arr {
|
|
|
|
|
r = append(r, strings.Trim(e, " "))
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func overrideRules(rules *[]string) {
|
|
|
|
|
var target = ""
|
|
|
|
|
for _, line := range *rules {
|
|
|
|
|
rule := trimArr(strings.Split(line, ","))
|
|
|
|
|
l := len(rule)
|
|
|
|
|
if l != 2 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if strings.ToUpper(rule[0]) == "MATCH" {
|
|
|
|
|
target = rule[1]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if target == "" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var rulesExt = lo.Map(ips, func(ip string, index int) string {
|
|
|
|
|
return fmt.Sprintf("DOMAIN %s %s", ip, target)
|
|
|
|
|
})
|
|
|
|
|
*rules = append(rulesExt, *rules...)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-02 08:08:31 +08:00
|
|
|
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
|
2024-06-11 20:50:57 +08:00
|
|
|
targetConfig.ExternalController = patchConfig.ExternalController
|
2024-04-30 23:38:49 +08:00
|
|
|
targetConfig.ExternalUI = ""
|
|
|
|
|
targetConfig.Interface = ""
|
|
|
|
|
targetConfig.ExternalUIURL = ""
|
2024-06-12 19:28:58 +08:00
|
|
|
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
|
|
|
|
|
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
|
2024-06-11 20:50:57 +08:00
|
|
|
targetConfig.IPv6 = patchConfig.IPv6
|
2024-04-30 23:38:49 +08:00
|
|
|
targetConfig.LogLevel = patchConfig.LogLevel
|
2024-07-15 13:47:06 +08:00
|
|
|
targetConfig.Port = 0
|
|
|
|
|
targetConfig.SocksPort = 0
|
2024-08-04 08:21:14 +08:00
|
|
|
targetConfig.KeepAliveInterval = patchConfig.KeepAliveInterval
|
2024-06-06 10:13:03 +08:00
|
|
|
targetConfig.MixedPort = patchConfig.MixedPort
|
2024-06-13 23:43:42 +08:00
|
|
|
targetConfig.FindProcessMode = patchConfig.FindProcessMode
|
2024-04-30 23:38:49 +08:00
|
|
|
targetConfig.AllowLan = patchConfig.AllowLan
|
|
|
|
|
targetConfig.Mode = patchConfig.Mode
|
|
|
|
|
targetConfig.Tun.Enable = patchConfig.Tun.Enable
|
|
|
|
|
targetConfig.Tun.Device = patchConfig.Tun.Device
|
2024-06-29 21:42:00 +08:00
|
|
|
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
|
|
|
|
|
targetConfig.Tun.Stack = patchConfig.Tun.Stack
|
2024-06-12 19:28:58 +08:00
|
|
|
targetConfig.GeodataLoader = patchConfig.GeodataLoader
|
2024-05-05 16:12:00 +08:00
|
|
|
targetConfig.Profile.StoreSelected = false
|
2024-07-13 16:36:08 +08:00
|
|
|
targetConfig.GeoXUrl = patchConfig.GeoXUrl
|
|
|
|
|
targetConfig.GlobalUA = patchConfig.GlobalUA
|
2024-10-08 17:28:18 +08:00
|
|
|
if configParams.TestURL != nil {
|
|
|
|
|
constant.DefaultTestURL = *configParams.TestURL
|
|
|
|
|
}
|
|
|
|
|
for idx := range targetConfig.ProxyGroup {
|
|
|
|
|
targetConfig.ProxyGroup[idx]["url"] = ""
|
|
|
|
|
}
|
2024-08-26 20:44:30 +08:00
|
|
|
genHosts(targetConfig.Hosts, patchConfig.Hosts)
|
|
|
|
|
if configParams.OverrideDns {
|
2024-04-30 23:38:49 +08:00
|
|
|
targetConfig.DNS = patchConfig.DNS
|
2024-09-05 08:59:45 +08:00
|
|
|
} else {
|
|
|
|
|
if targetConfig.DNS.Enable == false {
|
|
|
|
|
targetConfig.DNS.Enable = true
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-10-14 10:03:23 +08:00
|
|
|
overrideRules(&targetConfig.Rule)
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
func patchConfig() {
|
2024-04-30 23:38:49 +08:00
|
|
|
log.Infoln("[Apply] patch")
|
2024-12-03 21:47:12 +08:00
|
|
|
general := currentConfig.General
|
|
|
|
|
controller := currentConfig.Controller
|
|
|
|
|
tls := currentConfig.TLS
|
2024-09-05 08:59:45 +08:00
|
|
|
tunnel.SetSniffing(general.Sniffing)
|
2024-06-13 23:43:42 +08:00
|
|
|
tunnel.SetFindProcessMode(general.FindProcessMode)
|
2024-04-30 23:38:49 +08:00
|
|
|
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
|
|
|
|
dialer.DefaultInterface.Store(general.Interface)
|
2024-06-13 23:43:42 +08:00
|
|
|
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
2024-08-15 16:18:00 +08:00
|
|
|
tunnel.SetMode(general.Mode)
|
|
|
|
|
log.SetLevel(general.LogLevel)
|
|
|
|
|
resolver.DisableIPv6 = !general.IPv6
|
2024-10-14 10:03:23 +08:00
|
|
|
|
|
|
|
|
route.ReCreateServer(&route.Config{
|
|
|
|
|
Addr: controller.ExternalController,
|
|
|
|
|
TLSAddr: controller.ExternalControllerTLS,
|
|
|
|
|
UnixAddr: controller.ExternalControllerUnix,
|
|
|
|
|
PipeAddr: controller.ExternalControllerPipe,
|
|
|
|
|
Secret: controller.Secret,
|
|
|
|
|
Certificate: tls.Certificate,
|
|
|
|
|
PrivateKey: tls.PrivateKey,
|
|
|
|
|
DohServer: controller.ExternalDohServer,
|
|
|
|
|
IsDebug: false,
|
|
|
|
|
Cors: route.Cors{
|
|
|
|
|
AllowOrigins: controller.Cors.AllowOrigins,
|
|
|
|
|
AllowPrivateNetwork: controller.Cors.AllowPrivateNetwork,
|
|
|
|
|
},
|
|
|
|
|
})
|
2024-08-15 16:18:00 +08:00
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
func updateListeners(force bool) {
|
2024-08-26 20:44:30 +08:00
|
|
|
if !isRunning {
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
general := currentConfig.General
|
|
|
|
|
listeners := currentConfig.Listeners
|
|
|
|
|
if force == true {
|
|
|
|
|
stopListeners()
|
|
|
|
|
}
|
2024-08-15 16:18:00 +08:00
|
|
|
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)
|
2024-04-30 23:38:49 +08:00
|
|
|
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
|
|
|
|
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
|
|
|
|
listener.ReCreateRedir(general.RedirPort, 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)
|
2024-09-26 14:29:04 +08:00
|
|
|
if !features.Android {
|
|
|
|
|
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
|
|
|
|
}
|
2024-08-15 16:18:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stopListeners() {
|
|
|
|
|
listener.StopListener()
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-02 08:08:31 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
func applyConfig(rawConfig *config.RawConfig) error {
|
|
|
|
|
runLock.Lock()
|
|
|
|
|
defer runLock.Unlock()
|
|
|
|
|
var err error
|
|
|
|
|
currentConfig, err = config.ParseRawConfig(rawConfig)
|
2024-04-30 23:38:49 +08:00
|
|
|
if err != nil {
|
2024-12-03 21:47:12 +08:00
|
|
|
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-07-02 08:08:31 +08:00
|
|
|
if configParams.IsPatch {
|
2024-12-03 21:47:12 +08:00
|
|
|
patchConfig()
|
2024-04-30 23:38:49 +08:00
|
|
|
} else {
|
2024-12-03 21:47:12 +08:00
|
|
|
handleCloseConnectionsUnLock()
|
2024-06-19 13:13:31 +08:00
|
|
|
runtime.GC()
|
2024-12-03 21:47:12 +08:00
|
|
|
hub.ApplyConfig(currentConfig)
|
2024-07-02 08:08:31 +08:00
|
|
|
patchSelectGroup()
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
updateListeners(false)
|
2024-07-26 08:05:22 +08:00
|
|
|
return err
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|