123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- package outboundgroup
- import (
- "context"
- "encoding/json"
- "errors"
- "time"
- "github.com/metacubex/mihomo/adapter/outbound"
- "github.com/metacubex/mihomo/common/callback"
- N "github.com/metacubex/mihomo/common/net"
- "github.com/metacubex/mihomo/common/utils"
- "github.com/metacubex/mihomo/component/dialer"
- C "github.com/metacubex/mihomo/constant"
- "github.com/metacubex/mihomo/constant/provider"
- )
- type Fallback struct {
- *GroupBase
- disableUDP bool
- testUrl string
- selected string
- expectedStatus string
- Hidden bool
- Icon string
- }
- func (f *Fallback) Now() string {
- proxy := f.findAliveProxy(false)
- return proxy.Name()
- }
- // DialContext implements C.ProxyAdapter
- func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
- proxy := f.findAliveProxy(true)
- c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
- if err == nil {
- c.AppendToChains(f)
- } else {
- f.onDialFailed(proxy.Type(), err)
- }
- if N.NeedHandshake(c) {
- c = callback.NewFirstWriteCallBackConn(c, func(err error) {
- if err == nil {
- f.onDialSuccess()
- } else {
- f.onDialFailed(proxy.Type(), err)
- }
- })
- }
- return c, err
- }
- // ListenPacketContext implements C.ProxyAdapter
- func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
- proxy := f.findAliveProxy(true)
- pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
- if err == nil {
- pc.AppendToChains(f)
- }
- return pc, err
- }
- // SupportUDP implements C.ProxyAdapter
- func (f *Fallback) SupportUDP() bool {
- if f.disableUDP {
- return false
- }
- proxy := f.findAliveProxy(false)
- return proxy.SupportUDP()
- }
- // IsL3Protocol implements C.ProxyAdapter
- func (f *Fallback) IsL3Protocol(metadata *C.Metadata) bool {
- return f.findAliveProxy(false).IsL3Protocol(metadata)
- }
- // MarshalJSON implements C.ProxyAdapter
- func (f *Fallback) MarshalJSON() ([]byte, error) {
- all := []string{}
- for _, proxy := range f.GetProxies(false) {
- all = append(all, proxy.Name())
- }
- return json.Marshal(map[string]any{
- "type": f.Type().String(),
- "now": f.Now(),
- "all": all,
- "testUrl": f.testUrl,
- "expectedStatus": f.expectedStatus,
- "fixed": f.selected,
- "hidden": f.Hidden,
- "icon": f.Icon,
- })
- }
- // Unwrap implements C.ProxyAdapter
- func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
- proxy := f.findAliveProxy(touch)
- return proxy
- }
- func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
- proxies := f.GetProxies(touch)
- for _, proxy := range proxies {
- if len(f.selected) == 0 {
- if proxy.AliveForTestUrl(f.testUrl) {
- return proxy
- }
- } else {
- if proxy.Name() == f.selected {
- if proxy.AliveForTestUrl(f.testUrl) {
- return proxy
- } else {
- f.selected = ""
- }
- }
- }
- }
- return proxies[0]
- }
- func (f *Fallback) Set(name string) error {
- var p C.Proxy
- for _, proxy := range f.GetProxies(false) {
- if proxy.Name() == name {
- p = proxy
- break
- }
- }
- if p == nil {
- return errors.New("proxy not exist")
- }
- f.selected = name
- if !p.AliveForTestUrl(f.testUrl) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
- defer cancel()
- expectedStatus, _ := utils.NewUnsignedRanges[uint16](f.expectedStatus)
- _, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
- }
- return nil
- }
- func (f *Fallback) ForceSet(name string) {
- f.selected = name
- }
- func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
- return &Fallback{
- GroupBase: NewGroupBase(GroupBaseOption{
- outbound.BaseOption{
- Name: option.Name,
- Type: C.Fallback,
- Interface: option.Interface,
- RoutingMark: option.RoutingMark,
- },
- option.Filter,
- option.ExcludeFilter,
- option.ExcludeType,
- option.TestTimeout,
- option.MaxFailedTimes,
- providers,
- }),
- disableUDP: option.DisableUDP,
- testUrl: option.URL,
- expectedStatus: option.ExpectedStatus,
- Hidden: option.Hidden,
- Icon: option.Icon,
- }
- }
|