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 }