github.com/igoogolx/clash@v1.19.8/adapter/outboundgroup/relay.go (about) 1 package outboundgroup 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 8 "github.com/igoogolx/clash/adapter/outbound" 9 "github.com/igoogolx/clash/common/singledo" 10 "github.com/igoogolx/clash/component/dialer" 11 C "github.com/igoogolx/clash/constant" 12 "github.com/igoogolx/clash/constant/provider" 13 ) 14 15 type Relay struct { 16 *outbound.Base 17 single *singledo.Single 18 providers []provider.ProxyProvider 19 } 20 21 // DialContext implements C.ProxyAdapter 22 func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 23 var proxies []C.Proxy 24 for _, proxy := range r.proxies(metadata, true) { 25 if proxy.Type() != C.Direct { 26 proxies = append(proxies, proxy) 27 } 28 } 29 30 switch len(proxies) { 31 case 0: 32 return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) 33 case 1: 34 return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) 35 } 36 37 first := proxies[0] 38 last := proxies[len(proxies)-1] 39 40 c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) 41 if err != nil { 42 return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) 43 } 44 tcpKeepAlive(c) 45 46 var currentMeta *C.Metadata 47 for _, proxy := range proxies[1:] { 48 currentMeta, err = addrToMetadata(proxy.Addr()) 49 if err != nil { 50 return nil, err 51 } 52 53 c, err = first.StreamConn(c, currentMeta) 54 if err != nil { 55 return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) 56 } 57 58 first = proxy 59 } 60 61 c, err = last.StreamConn(c, metadata) 62 if err != nil { 63 return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) 64 } 65 66 return outbound.NewConn(c, r), nil 67 } 68 69 // MarshalJSON implements C.ProxyAdapter 70 func (r *Relay) MarshalJSON() ([]byte, error) { 71 var all []string 72 for _, proxy := range r.rawProxies(false) { 73 all = append(all, proxy.Name()) 74 } 75 return json.Marshal(map[string]any{ 76 "type": r.Type().String(), 77 "all": all, 78 }) 79 } 80 81 func (r *Relay) rawProxies(touch bool) []C.Proxy { 82 elm, _, _ := r.single.Do(func() (any, error) { 83 return getProvidersProxies(r.providers, touch), nil 84 }) 85 86 return elm.([]C.Proxy) 87 } 88 89 func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { 90 proxies := r.rawProxies(touch) 91 92 for n, proxy := range proxies { 93 subproxy := proxy.Unwrap(metadata) 94 for subproxy != nil { 95 proxies[n] = subproxy 96 subproxy = subproxy.Unwrap(metadata) 97 } 98 } 99 100 return proxies 101 } 102 103 func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { 104 return &Relay{ 105 Base: outbound.NewBase(outbound.BaseOption{ 106 Name: option.Name, 107 Type: C.Relay, 108 Interface: option.Interface, 109 RoutingMark: option.RoutingMark, 110 }), 111 single: singledo.NewSingle(defaultGetProxiesDuration), 112 providers: providers, 113 } 114 }