diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 79a752a65..2fe9633c7 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -1,11 +1,9 @@ package provider import ( - "context" "encoding/json" "errors" "fmt" - "net/http" "reflect" "runtime" "strings" @@ -14,7 +12,7 @@ import ( "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/common/convert" "github.com/metacubex/mihomo/common/utils" - mihomoHttp "github.com/metacubex/mihomo/component/http" + "github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/resource" C "github.com/metacubex/mihomo/constant" types "github.com/metacubex/mihomo/constant/provider" @@ -80,7 +78,9 @@ func (pp *proxySetProvider) Initial() error { if err != nil { return err } - pp.getSubscriptionInfo() + if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" { + pp.SetSubscriptionInfo(subscriptionInfo) + } pp.closeAllConnections() return nil } @@ -117,35 +117,14 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { } } -func (pp *proxySetProvider) getSubscriptionInfo() { - if pp.VehicleType() != types.HTTP { - return - } - go func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) - defer cancel() - resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(), - http.MethodGet, nil, nil, pp.Vehicle().Proxy()) - if err != nil { - return - } - defer resp.Body.Close() +func (pp *proxySetProvider) SetSubscriptionInfo(userInfo string) { + pp.subscriptionInfo = NewSubscriptionInfo(userInfo) +} - userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) - if userInfoStr == "" { - resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(), - http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy()) - if err != nil { - return - } - defer resp2.Body.Close() - userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo")) - if userInfoStr == "" { - return - } - } - pp.subscriptionInfo = NewSubscriptionInfo(userInfoStr) - }() +func (pp *proxySetProvider) SetProvider(provider types.ProxyProvider) { + if httpVehicle, ok := pp.Vehicle().(*resource.HTTPVehicle); ok { + httpVehicle.SetProvider(provider) + } } func (pp *proxySetProvider) closeAllConnections() { @@ -196,6 +175,9 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd)) pd.Fetcher = fetcher wrapper := &ProxySetProvider{pd} + if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok { + httpVehicle.SetProvider(wrapper) + } runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close) return wrapper, nil } @@ -205,16 +187,21 @@ func (pp *ProxySetProvider) Close() error { return pp.proxySetProvider.Close() } +func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) { + pp.proxySetProvider.SetProvider(provider) +} + // CompatibleProvider for auto gc type CompatibleProvider struct { *compatibleProvider } type compatibleProvider struct { - name string - healthCheck *HealthCheck - proxies []C.Proxy - version uint32 + name string + healthCheck *HealthCheck + subscriptionInfo *SubscriptionInfo + proxies []C.Proxy + version uint32 } func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { @@ -284,6 +271,10 @@ func (cp *compatibleProvider) Close() error { return nil } +func (cp *compatibleProvider) SetSubscriptionInfo(userInfo string) { + cp.subscriptionInfo = NewSubscriptionInfo(userInfo) +} + func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { if len(proxies) == 0 { return nil, errors.New("provider need one proxy at least") @@ -313,7 +304,6 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { return func(elm []C.Proxy) { pd.setProxies(elm) pd.version += 1 - pd.getSubscriptionInfo() } } diff --git a/adapter/provider/subscription_info.go b/adapter/provider/subscription_info.go index b72c7b616..412b4342f 100644 --- a/adapter/provider/subscription_info.go +++ b/adapter/provider/subscription_info.go @@ -16,8 +16,7 @@ type SubscriptionInfo struct { } func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) { - userinfo = strings.ToLower(userinfo) - userinfo = strings.ReplaceAll(userinfo, " ", "") + userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "") si = new(SubscriptionInfo) for _, field := range strings.Split(userinfo, ";") { diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index f5101f5bb..7b4cdfc2a 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -17,9 +17,10 @@ var ( fileMode os.FileMode = 0o666 defaultCache *CacheFile - bucketSelected = []byte("selected") - bucketFakeip = []byte("fakeip") - bucketETag = []byte("etag") + bucketSelected = []byte("selected") + bucketFakeip = []byte("fakeip") + bucketETag = []byte("etag") + bucketSubscriptionInfo = []byte("subscriptioninfo") ) // CacheFile store and update the cache file diff --git a/component/profile/cachefile/subscriptioninfo.go b/component/profile/cachefile/subscriptioninfo.go new file mode 100644 index 000000000..c68f92ebc --- /dev/null +++ b/component/profile/cachefile/subscriptioninfo.go @@ -0,0 +1,41 @@ +package cachefile + +import ( + "github.com/metacubex/mihomo/log" + + "github.com/metacubex/bbolt" +) + +func (c *CacheFile) SetSubscriptionInfo(name string, userInfo string) { + if c.DB == nil { + return + } + + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketSubscriptionInfo) + if err != nil { + return err + } + + return bucket.Put([]byte(name), []byte(userInfo)) + }) + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) + return + } +} +func (c *CacheFile) GetSubscriptionInfo(name string) (userInfo string) { + if c.DB == nil { + return + } + c.DB.View(func(t *bbolt.Tx) error { + if bucket := t.Bucket(bucketSubscriptionInfo); bucket != nil { + if v := bucket.Get([]byte(name)); v != nil { + userInfo = string(v) + } + } + return nil + }) + + return +} diff --git a/component/resource/vehicle.go b/component/resource/vehicle.go index a9382329f..7c3cb1c2a 100644 --- a/component/resource/vehicle.go +++ b/component/resource/vehicle.go @@ -84,11 +84,12 @@ func NewFileVehicle(path string) *FileVehicle { } type HTTPVehicle struct { - url string - path string - proxy string - header http.Header - timeout time.Duration + url string + path string + proxy string + header http.Header + timeout time.Duration + provider types.ProxyProvider } func (h *HTTPVehicle) Url() string { @@ -111,6 +112,10 @@ func (h *HTTPVehicle) Write(buf []byte) error { return safeWrite(h.path, buf) } +func (h *HTTPVehicle) SetProvider(provider types.ProxyProvider) { + h.provider = provider +} + func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) { ctx, cancel := context.WithTimeout(ctx, h.timeout) defer cancel() @@ -133,6 +138,12 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b return } defer resp.Body.Close() + + if subscriptionInfo := resp.Header.Get("subscription-userinfo"); h.provider != nil && subscriptionInfo != "" { + cachefile.Cache().SetSubscriptionInfo(h.provider.Name(), subscriptionInfo) + h.provider.SetSubscriptionInfo(subscriptionInfo) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { if setIfNoneMatch && resp.StatusCode == http.StatusNotModified { return nil, oldHash, nil diff --git a/constant/adapters.go b/constant/adapters.go index b303eb846..c7b73a069 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -213,6 +213,8 @@ func (at AdapterType) String() string { return "WireGuard" case Tuic: return "Tuic" + case Ssh: + return "Ssh" case Relay: return "Relay" @@ -224,8 +226,6 @@ func (at AdapterType) String() string { return "URLTest" case LoadBalance: return "LoadBalance" - case Ssh: - return "Ssh" default: return "Unknown" } diff --git a/constant/provider/interface.go b/constant/provider/interface.go index 065b801ae..925c17346 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -81,6 +81,7 @@ type ProxyProvider interface { Version() uint32 RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) HealthCheckURL() string + SetSubscriptionInfo(userInfo string) } // RuleProvider interface