Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions adapter/outboundgroup/groupbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ type GroupBase struct {
maxFailedTimes int

// for GetProxies
getProxiesMutex sync.Mutex
providerVersions []uint32
providerProxies []C.Proxy
getProxiesMutex sync.Mutex
providerVersions []uint32
providerProxies []C.Proxy
providerProxyOwners []provider.ProxyProvider
}

type GroupBaseOption struct {
Expand Down Expand Up @@ -100,7 +101,7 @@ func (gb *GroupBase) Touch() {
}
}

func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
func (gb *GroupBase) getProxiesInternal(touch bool) ([]C.Proxy, []provider.ProxyProvider) {
providerVersions := make([]uint32, len(gb.providers))
for i, pd := range gb.providers {
if touch { // touch first
Expand All @@ -115,22 +116,32 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {

// return the cached proxies if version not changed
if slices.Equal(providerVersions, gb.providerVersions) {
return gb.providerProxies
return gb.providerProxies, gb.providerProxyOwners
}

var proxies []C.Proxy
var owners []provider.ProxyProvider
if len(gb.filterRegs) == 0 {
for _, pd := range gb.providers {
proxies = append(proxies, pd.Proxies()...)
pds := pd.Proxies()
proxies = append(proxies, pds...)
for range pds {
owners = append(owners, pd)
}
}
} else {
for _, pd := range gb.providers {
if pd.VehicleType() == types.Compatible { // compatible provider unneeded filter
proxies = append(proxies, pd.Proxies()...)
pds := pd.Proxies()
proxies = append(proxies, pds...)
for range pds {
owners = append(owners, pd)
}
continue
}

var newProxies []C.Proxy
var newOwners []provider.ProxyProvider
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range pd.Proxies() {
Expand All @@ -139,11 +150,13 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
newOwners = append(newOwners, pd)
}
}
}
}
proxies = append(proxies, newProxies...)
owners = append(owners, newOwners...)
}
}

Expand All @@ -152,69 +165,89 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
// when there are multiple providers, the array needs to be reordered as a whole.
if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
var newProxies []C.Proxy
var newOwners []provider.ProxyProvider
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
for idx, p := range proxies {
name := p.Name()
if mat, _ := filterReg.MatchString(name); mat {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
newOwners = append(newOwners, owners[idx])
}
}
}
}
for _, p := range proxies { // add not matched proxies at the end
for idx, p := range proxies { // add not matched proxies at the end
name := p.Name()
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
newOwners = append(newOwners, owners[idx])
}
}
proxies = newProxies
owners = newOwners
}

if len(gb.excludeFilterRegs) > 0 {
var newProxies []C.Proxy
var newOwners []provider.ProxyProvider
LOOP1:
for _, p := range proxies {
for idx, p := range proxies {
name := p.Name()
for _, excludeFilterReg := range gb.excludeFilterRegs {
if mat, _ := excludeFilterReg.MatchString(name); mat {
continue LOOP1
}
}
newProxies = append(newProxies, p)
newOwners = append(newOwners, owners[idx])
}
proxies = newProxies
owners = newOwners
}

if gb.excludeTypeArray != nil {
var newProxies []C.Proxy
var newOwners []provider.ProxyProvider
LOOP2:
for _, p := range proxies {
for idx, p := range proxies {
mType := p.Type().String()
for _, excludeType := range gb.excludeTypeArray {
if strings.EqualFold(mType, excludeType) {
continue LOOP2
}
}
newProxies = append(newProxies, p)
newOwners = append(newOwners, owners[idx])
}
proxies = newProxies
owners = newOwners
}

if len(proxies) == 0 {
return []C.Proxy{tunnel.Proxies()["COMPATIBLE"]}
return []C.Proxy{tunnel.Proxies()["COMPATIBLE"]}, nil
}

// only cache when proxies not empty
gb.providerVersions = providerVersions
gb.providerProxies = proxies
gb.providerProxyOwners = owners

return proxies, owners
}

func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
proxies, _ := gb.getProxiesInternal(touch)
return proxies
}

func (gb *GroupBase) GetProxiesWithOwners(touch bool) ([]C.Proxy, []provider.ProxyProvider) {
return gb.getProxiesInternal(touch)
}

func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
var wg sync.WaitGroup
var lock sync.Mutex
Expand Down
2 changes: 1 addition & 1 deletion adapter/outboundgroup/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide

hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)

pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc, 0)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
Expand Down
47 changes: 44 additions & 3 deletions adapter/outboundgroup/urltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"sort"
"time"

"github.com/metacubex/mihomo/common/callback"
Expand Down Expand Up @@ -108,7 +109,7 @@ func (u *URLTest) healthCheck() {

func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
proxies := u.GetProxies(touch)
proxies, owners := u.GetProxiesWithOwners(touch)
if u.selected != "" {
for _, proxy := range proxies {
if !proxy.AliveForTestUrl(u.testUrl) {
Expand All @@ -121,11 +122,51 @@ func (u *URLTest) fast(touch bool) C.Proxy {
}
}

fast := proxies[0]
var candidates []C.Proxy
if len(proxies) != 0 {
priorityBuckets := map[int][]C.Proxy{}
prioritySet := map[int]struct{}{}
for idx, proxy := range proxies {
var owner provider.ProxyProvider
if owners != nil && idx < len(owners) {
owner = owners[idx]
}
priority := 0
if owner != nil {
priority = owner.Priority()
}
prioritySet[priority] = struct{}{}
if proxy.AliveForTestUrl(u.testUrl) {
priorityBuckets[priority] = append(priorityBuckets[priority], proxy)
}
}

priorities := make([]int, 0, len(prioritySet))
for priority := range prioritySet {
priorities = append(priorities, priority)
}
sort.Sort(sort.Reverse(sort.IntSlice(priorities)))

for _, priority := range priorities {
if bucket := priorityBuckets[priority]; len(bucket) != 0 {
candidates = bucket
break
}
}
}

if len(candidates) == 0 {
candidates = proxies
}
if len(candidates) == 0 {
return nil, errors.New("proxy not exist")
}

fast := candidates[0]
minDelay := fast.LastDelayForTestUrl(u.testUrl)
fastNotExist := true

for _, proxy := range proxies[1:] {
for _, proxy := range candidates[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
Expand Down
5 changes: 3 additions & 2 deletions adapter/provider/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type proxyProviderSchema struct {
DialerProxy string `provider:"dialer-proxy,omitempty"`
SizeLimit int64 `provider:"size-limit,omitempty"`
Payload []map[string]any `provider:"payload,omitempty"`
Priority int `provider:"priority,omitempty"`

HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"`
Expand Down Expand Up @@ -122,12 +123,12 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
}
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
case "inline":
return NewInlineProvider(name, schema.Payload, parser, hc)
return NewInlineProvider(name, schema.Payload, parser, hc, schema.Priority)
default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}

interval := time.Duration(uint(schema.Interval)) * time.Second

return NewProxySetProvider(name, interval, schema.Payload, parser, vehicle, hc)
return NewProxySetProvider(name, interval, schema.Payload, parser, vehicle, hc, schema.Priority)
}
18 changes: 15 additions & 3 deletions adapter/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ type providerForApi struct {
ExpectedStatus string `json:"expectedStatus"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
SubscriptionInfo *SubscriptionInfo `json:"subscriptionInfo,omitempty"`
Priority int `json:"priority"`
}

type baseProvider struct {
name string
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
priority int
}

func (bp *baseProvider) Name() string {
Expand All @@ -57,6 +59,10 @@ func (bp *baseProvider) Version() uint32 {
return bp.version
}

func (bp *baseProvider) Priority() int {
return bp.priority
}

func (bp *baseProvider) Initial() error {
if bp.healthCheck.auto() {
go bp.healthCheck.process()
Expand Down Expand Up @@ -127,6 +133,7 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
ExpectedStatus: pp.healthCheck.expectedStatus.String(),
UpdatedAt: pp.UpdatedAt(),
SubscriptionInfo: pp.subscriptionInfo,
Priority: pp.Priority(),
})
}

Expand Down Expand Up @@ -171,12 +178,13 @@ func (pp *proxySetProvider) Close() error {
return pp.Fetcher.Close()
}

func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck, priority int) (*ProxySetProvider, error) {
pd := &proxySetProvider{
baseProvider: baseProvider{
name: name,
proxies: []C.Proxy{},
healthCheck: hc,
priority: priority,
},
}

Expand Down Expand Up @@ -234,6 +242,7 @@ func (ip *inlineProvider) MarshalJSON() ([]byte, error) {
Proxies: ip.Proxies(),
TestUrl: ip.healthCheck.url,
ExpectedStatus: ip.healthCheck.expectedStatus.String(),
Priority: ip.Priority(),
UpdatedAt: ip.updateAt,
})
}
Expand All @@ -248,7 +257,7 @@ func (ip *inlineProvider) Update() error {
return nil
}

func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck, priority int) (*InlineProvider, error) {
ps := ProxySchema{Proxies: payload}
buf, err := yaml.Marshal(ps)
if err != nil {
Expand All @@ -266,6 +275,7 @@ func NewInlineProvider(name string, payload []map[string]any, parser resource.Pa
name: name,
proxies: proxies,
healthCheck: hc,
priority: priority,
},
updateAt: time.Now(),
}
Expand Down Expand Up @@ -296,6 +306,7 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
Proxies: cp.Proxies(),
TestUrl: cp.healthCheck.url,
ExpectedStatus: cp.healthCheck.expectedStatus.String(),
Priority: cp.Priority(),
})
}

Expand All @@ -307,7 +318,7 @@ func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible
}

func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck, priority int) (*CompatibleProvider, error) {
if len(proxies) == 0 {
return nil, errors.New("provider need one proxy at least")
}
Expand All @@ -317,6 +328,7 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
name: name,
proxies: proxies,
healthCheck: hc,
priority: priority,
},
}

Expand Down
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
ps = append(ps, proxies[v])
}
hc := provider.NewHealthCheck(ps, "", 5000, 0, true, nil)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc, 0)
providersMap[provider.ReservedName] = pd

if !hasGlobal {
Expand Down
1 change: 1 addition & 0 deletions constant/provider/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type ProxyProvider interface {
Version() uint32
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
HealthCheckURL() string
Priority() int
}

// RuleProvider interface
Expand Down
Loading