github.com/metacubex/mihomo@v1.18.5/adapter/outboundgroup/fallback.go (about)

     1  package outboundgroup
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"time"
     8  
     9  	"github.com/metacubex/mihomo/adapter/outbound"
    10  	"github.com/metacubex/mihomo/common/callback"
    11  	N "github.com/metacubex/mihomo/common/net"
    12  	"github.com/metacubex/mihomo/common/utils"
    13  	"github.com/metacubex/mihomo/component/dialer"
    14  	C "github.com/metacubex/mihomo/constant"
    15  	"github.com/metacubex/mihomo/constant/provider"
    16  )
    17  
    18  type Fallback struct {
    19  	*GroupBase
    20  	disableUDP     bool
    21  	testUrl        string
    22  	selected       string
    23  	expectedStatus string
    24  	Hidden         bool
    25  	Icon           string
    26  }
    27  
    28  func (f *Fallback) Now() string {
    29  	proxy := f.findAliveProxy(false)
    30  	return proxy.Name()
    31  }
    32  
    33  // DialContext implements C.ProxyAdapter
    34  func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    35  	proxy := f.findAliveProxy(true)
    36  	c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
    37  	if err == nil {
    38  		c.AppendToChains(f)
    39  	} else {
    40  		f.onDialFailed(proxy.Type(), err)
    41  	}
    42  
    43  	if N.NeedHandshake(c) {
    44  		c = callback.NewFirstWriteCallBackConn(c, func(err error) {
    45  			if err == nil {
    46  				f.onDialSuccess()
    47  			} else {
    48  				f.onDialFailed(proxy.Type(), err)
    49  			}
    50  		})
    51  	}
    52  
    53  	return c, err
    54  }
    55  
    56  // ListenPacketContext implements C.ProxyAdapter
    57  func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    58  	proxy := f.findAliveProxy(true)
    59  	pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
    60  	if err == nil {
    61  		pc.AppendToChains(f)
    62  	}
    63  
    64  	return pc, err
    65  }
    66  
    67  // SupportUDP implements C.ProxyAdapter
    68  func (f *Fallback) SupportUDP() bool {
    69  	if f.disableUDP {
    70  		return false
    71  	}
    72  
    73  	proxy := f.findAliveProxy(false)
    74  	return proxy.SupportUDP()
    75  }
    76  
    77  // IsL3Protocol implements C.ProxyAdapter
    78  func (f *Fallback) IsL3Protocol(metadata *C.Metadata) bool {
    79  	return f.findAliveProxy(false).IsL3Protocol(metadata)
    80  }
    81  
    82  // MarshalJSON implements C.ProxyAdapter
    83  func (f *Fallback) MarshalJSON() ([]byte, error) {
    84  	all := []string{}
    85  	for _, proxy := range f.GetProxies(false) {
    86  		all = append(all, proxy.Name())
    87  	}
    88  	return json.Marshal(map[string]any{
    89  		"type":           f.Type().String(),
    90  		"now":            f.Now(),
    91  		"all":            all,
    92  		"testUrl":        f.testUrl,
    93  		"expectedStatus": f.expectedStatus,
    94  		"fixed":          f.selected,
    95  		"hidden":         f.Hidden,
    96  		"icon":           f.Icon,
    97  	})
    98  }
    99  
   100  // Unwrap implements C.ProxyAdapter
   101  func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
   102  	proxy := f.findAliveProxy(touch)
   103  	return proxy
   104  }
   105  
   106  func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
   107  	proxies := f.GetProxies(touch)
   108  	for _, proxy := range proxies {
   109  		if len(f.selected) == 0 {
   110  			if proxy.AliveForTestUrl(f.testUrl) {
   111  				return proxy
   112  			}
   113  		} else {
   114  			if proxy.Name() == f.selected {
   115  				if proxy.AliveForTestUrl(f.testUrl) {
   116  					return proxy
   117  				} else {
   118  					f.selected = ""
   119  				}
   120  			}
   121  		}
   122  	}
   123  
   124  	return proxies[0]
   125  }
   126  
   127  func (f *Fallback) Set(name string) error {
   128  	var p C.Proxy
   129  	for _, proxy := range f.GetProxies(false) {
   130  		if proxy.Name() == name {
   131  			p = proxy
   132  			break
   133  		}
   134  	}
   135  
   136  	if p == nil {
   137  		return errors.New("proxy not exist")
   138  	}
   139  
   140  	f.selected = name
   141  	if !p.AliveForTestUrl(f.testUrl) {
   142  		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
   143  		defer cancel()
   144  		expectedStatus, _ := utils.NewUnsignedRanges[uint16](f.expectedStatus)
   145  		_, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  func (f *Fallback) ForceSet(name string) {
   152  	f.selected = name
   153  }
   154  
   155  func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
   156  	return &Fallback{
   157  		GroupBase: NewGroupBase(GroupBaseOption{
   158  			outbound.BaseOption{
   159  				Name:        option.Name,
   160  				Type:        C.Fallback,
   161  				Interface:   option.Interface,
   162  				RoutingMark: option.RoutingMark,
   163  			},
   164  			option.Filter,
   165  			option.ExcludeFilter,
   166  			option.ExcludeType,
   167  			option.TestTimeout,
   168  			option.MaxFailedTimes,
   169  			providers,
   170  		}),
   171  		disableUDP:     option.DisableUDP,
   172  		testUrl:        option.URL,
   173  		expectedStatus: option.ExpectedStatus,
   174  		Hidden:         option.Hidden,
   175  		Icon:           option.Icon,
   176  	}
   177  }