github.com/igoogolx/clash@v1.19.8/adapter/adapter.go (about) 1 package adapter 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net" 8 "net/http" 9 "net/url" 10 "strconv" 11 "time" 12 13 "github.com/igoogolx/clash/common/queue" 14 "github.com/igoogolx/clash/component/dialer" 15 C "github.com/igoogolx/clash/constant" 16 17 "go.uber.org/atomic" 18 ) 19 20 type Proxy struct { 21 C.ProxyAdapter 22 history *queue.Queue 23 alive *atomic.Bool 24 } 25 26 // Alive implements C.Proxy 27 func (p *Proxy) Alive() bool { 28 return p.alive.Load() 29 } 30 31 // Dial implements C.Proxy 32 func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { 33 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) 34 defer cancel() 35 return p.DialContext(ctx, metadata) 36 } 37 38 // DialContext implements C.ProxyAdapter 39 func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 40 conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) 41 p.alive.Store(err == nil) 42 return conn, err 43 } 44 45 // DialUDP implements C.ProxyAdapter 46 func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 47 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) 48 defer cancel() 49 return p.ListenPacketContext(ctx, metadata) 50 } 51 52 // ListenPacketContext implements C.ProxyAdapter 53 func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 54 pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) 55 p.alive.Store(err == nil) 56 return pc, err 57 } 58 59 // DelayHistory implements C.Proxy 60 func (p *Proxy) DelayHistory() []C.DelayHistory { 61 queue := p.history.Copy() 62 histories := []C.DelayHistory{} 63 for _, item := range queue { 64 histories = append(histories, item.(C.DelayHistory)) 65 } 66 return histories 67 } 68 69 // LastDelay return last history record. if proxy is not alive, return the max value of uint16. 70 // implements C.Proxy 71 func (p *Proxy) LastDelay() (delay uint16) { 72 var max uint16 = 0xffff 73 if !p.alive.Load() { 74 return max 75 } 76 77 last := p.history.Last() 78 if last == nil { 79 return max 80 } 81 history := last.(C.DelayHistory) 82 if history.Delay == 0 { 83 return max 84 } 85 return history.Delay 86 } 87 88 // MarshalJSON implements C.ProxyAdapter 89 func (p *Proxy) MarshalJSON() ([]byte, error) { 90 inner, err := p.ProxyAdapter.MarshalJSON() 91 if err != nil { 92 return inner, err 93 } 94 95 mapping := map[string]any{} 96 json.Unmarshal(inner, &mapping) 97 mapping["history"] = p.DelayHistory() 98 mapping["alive"] = p.Alive() 99 mapping["name"] = p.Name() 100 mapping["udp"] = p.SupportUDP() 101 return json.Marshal(mapping) 102 } 103 104 // URLTest get the delay for the specified URL 105 // implements C.Proxy 106 func (p *Proxy) URLTest(ctx context.Context, url string) (delay, meanDelay uint16, err error) { 107 defer func() { 108 p.alive.Store(err == nil) 109 record := C.DelayHistory{Time: time.Now()} 110 if err == nil { 111 record.Delay = delay 112 record.MeanDelay = meanDelay 113 } 114 p.history.Put(record) 115 if p.history.Len() > 10 { 116 p.history.Pop() 117 } 118 }() 119 120 addr, err := urlToMetadata(url) 121 if err != nil { 122 return 123 } 124 125 start := time.Now() 126 instance, err := p.DialContext(ctx, &addr) 127 if err != nil { 128 return 129 } 130 defer instance.Close() 131 132 req, err := http.NewRequest(http.MethodHead, url, nil) 133 if err != nil { 134 return 135 } 136 req = req.WithContext(ctx) 137 138 transport := &http.Transport{ 139 Dial: func(string, string) (net.Conn, error) { 140 return instance, nil 141 }, 142 // from http.DefaultTransport 143 MaxIdleConns: 100, 144 IdleConnTimeout: 90 * time.Second, 145 TLSHandshakeTimeout: 10 * time.Second, 146 ExpectContinueTimeout: 1 * time.Second, 147 } 148 149 client := http.Client{ 150 Transport: transport, 151 CheckRedirect: func(req *http.Request, via []*http.Request) error { 152 return http.ErrUseLastResponse 153 }, 154 } 155 defer client.CloseIdleConnections() 156 157 resp, err := client.Do(req) 158 if err != nil { 159 return 160 } 161 resp.Body.Close() 162 delay = uint16(time.Since(start) / time.Millisecond) 163 164 resp, err = client.Do(req) 165 if err != nil { 166 // ignore error because some server will hijack the connection and close immediately 167 return delay, 0, nil 168 } 169 resp.Body.Close() 170 meanDelay = uint16(time.Since(start) / time.Millisecond / 2) 171 172 return 173 } 174 175 func NewProxy(adapter C.ProxyAdapter) *Proxy { 176 return &Proxy{adapter, queue.New(10), atomic.NewBool(true)} 177 } 178 179 func urlToMetadata(rawURL string) (addr C.Metadata, err error) { 180 u, err := url.Parse(rawURL) 181 if err != nil { 182 return 183 } 184 185 port := u.Port() 186 if port == "" { 187 switch u.Scheme { 188 case "https": 189 port = "443" 190 case "http": 191 port = "80" 192 default: 193 err = fmt.Errorf("%s scheme not Support", rawURL) 194 return 195 } 196 } 197 198 p, _ := strconv.ParseUint(port, 10, 16) 199 200 addr = C.Metadata{ 201 Host: u.Hostname(), 202 DstIP: nil, 203 DstPort: C.Port(p), 204 } 205 return 206 }