github.com/yaling888/clash@v1.53.0/adapter/adapter.go (about) 1 package adapter 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net" 9 "net/http" 10 "net/netip" 11 "net/url" 12 "os" 13 "strconv" 14 "sync" 15 "time" 16 17 "go.uber.org/atomic" 18 19 "github.com/yaling888/clash/common/queue" 20 "github.com/yaling888/clash/component/dialer" 21 "github.com/yaling888/clash/component/resolver" 22 C "github.com/yaling888/clash/constant" 23 ) 24 25 type Proxy struct { 26 C.ProxyAdapter 27 history *queue.Queue[C.DelayHistory] 28 alive *atomic.Bool 29 hasV6 *atomic.Bool 30 v6Mux sync.Mutex 31 } 32 33 // Alive implements C.Proxy 34 func (p *Proxy) Alive() bool { 35 return p.alive.Load() 36 } 37 38 // HasV6 implements C.Proxy 39 func (p *Proxy) HasV6() bool { 40 if p.hasV6 == nil { 41 return false 42 } 43 return p.hasV6.Load() 44 } 45 46 // Dial implements C.Proxy 47 func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { 48 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) 49 defer cancel() 50 return p.DialContext(ctx, metadata) 51 } 52 53 // DialContext implements C.ProxyAdapter 54 func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 55 conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) 56 if !errors.Is(err, context.Canceled) { 57 p.alive.Store(err == nil) 58 } 59 return conn, err 60 } 61 62 // DialUDP implements C.ProxyAdapter 63 func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 64 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) 65 defer cancel() 66 return p.ListenPacketContext(ctx, metadata) 67 } 68 69 // ListenPacketContext implements C.ProxyAdapter 70 func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 71 pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) 72 if !errors.Is(err, context.Canceled) { 73 p.alive.Store(err == nil) 74 } 75 return pc, err 76 } 77 78 // DelayHistory implements C.Proxy 79 func (p *Proxy) DelayHistory() []C.DelayHistory { 80 queueM := p.history.Copy() 81 histories := []C.DelayHistory{} 82 histories = append(histories, queueM...) 83 return histories 84 } 85 86 // LastDelay return last history record. if proxy is not alive, return the max value of uint16. 87 // implements C.Proxy 88 func (p *Proxy) LastDelay() (delay uint16) { 89 var maxDelay uint16 = 0xffff 90 if !p.alive.Load() { 91 return maxDelay 92 } 93 94 history := p.history.Last() 95 if history.Delay == 0 { 96 return maxDelay 97 } 98 return history.Delay 99 } 100 101 // MarshalJSON implements C.ProxyAdapter 102 func (p *Proxy) MarshalJSON() ([]byte, error) { 103 inner, err := p.ProxyAdapter.MarshalJSON() 104 if err != nil { 105 return inner, err 106 } 107 108 mapping := map[string]any{} 109 _ = json.Unmarshal(inner, &mapping) 110 mapping["history"] = p.DelayHistory() 111 mapping["alive"] = p.Alive() 112 mapping["name"] = p.Name() 113 mapping["udp"] = p.SupportUDP() 114 return json.Marshal(mapping) 115 } 116 117 // URLTest get the delay for the specified URL 118 // implements C.Proxy 119 func (p *Proxy) URLTest(ctx context.Context, url string) (delay, avgDelay uint16, err error) { 120 defer func() { 121 alive := err == nil 122 p.alive.Store(alive) 123 record := C.DelayHistory{Time: time.Now()} 124 if alive { 125 record.Delay = delay 126 record.AvgDelay = avgDelay 127 if p.hasV6 == nil && resolver.RemoteDnsResolve && !p.ProxyAdapter.DisableDnsResolve() { 128 go p.v6Test(url) 129 } 130 } 131 p.history.Put(record) 132 if p.history.Len() > 10 { 133 p.history.Pop() 134 } 135 }() 136 137 addr, err := urlToMetadata(url) 138 if err != nil { 139 return 140 } 141 142 start := time.Now() 143 instance, err := p.DialContext(ctx, &addr) 144 if err != nil { 145 return 146 } 147 defer func() { 148 _ = instance.Close() 149 }() 150 151 req, err := http.NewRequest(http.MethodHead, url, nil) 152 if err != nil { 153 return 154 } 155 req = req.WithContext(ctx) 156 157 transport := &http.Transport{ 158 DialContext: func(context.Context, string, string) (net.Conn, error) { 159 return instance, nil 160 }, 161 // from http.DefaultTransport 162 MaxIdleConns: 100, 163 IdleConnTimeout: 90 * time.Second, 164 TLSHandshakeTimeout: 10 * time.Second, 165 ExpectContinueTimeout: 1 * time.Second, 166 } 167 168 client := http.Client{ 169 Transport: transport, 170 CheckRedirect: func(req *http.Request, via []*http.Request) error { 171 return http.ErrUseLastResponse 172 }, 173 } 174 defer client.CloseIdleConnections() 175 176 resp, err := client.Do(req) 177 if err != nil { 178 return 179 } 180 _ = resp.Body.Close() 181 delay = uint16(time.Since(start) / time.Millisecond) 182 183 resp, err = client.Do(req) 184 if err != nil { 185 avgDelay = 0 186 err = nil 187 return 188 } 189 _ = resp.Body.Close() 190 avgDelay = uint16(time.Since(start) / time.Millisecond / 2) 191 192 return 193 } 194 195 func (p *Proxy) v6Test(url string) { 196 p.v6Mux.Lock() 197 if p.hasV6 != nil { 198 return 199 } 200 201 var ( 202 resolved bool 203 err error 204 ) 205 206 defer func() { 207 if resolved { 208 p.hasV6 = atomic.NewBool(err == nil) 209 } 210 p.v6Mux.Unlock() 211 }() 212 213 addr, err := urlToMetadata(url) 214 if err != nil { 215 return 216 } 217 218 ips, err := resolver.LookupIPv6ByProxy(context.Background(), addr.Host, p.Name()) 219 if err != nil { 220 if os.IsTimeout(err) || errors.Is(err, resolver.ErrIPNotFound) || errors.Is(err, resolver.ErrIPVersion) { 221 resolved = true 222 } 223 return 224 } 225 addr.DstIP = ips[0] 226 resolved = true 227 228 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 229 defer cancel() 230 instance, err := p.DialContext(ctx, &addr) 231 if err != nil { 232 return 233 } 234 defer func() { 235 _ = instance.Close() 236 }() 237 238 req, err := http.NewRequest(http.MethodGet, url, nil) 239 if err != nil { 240 return 241 } 242 req = req.WithContext(ctx) 243 244 transport := &http.Transport{ 245 DialContext: func(context.Context, string, string) (net.Conn, error) { 246 return instance, nil 247 }, 248 // from http.DefaultTransport 249 MaxIdleConns: 100, 250 IdleConnTimeout: 90 * time.Second, 251 TLSHandshakeTimeout: 10 * time.Second, 252 ExpectContinueTimeout: 1 * time.Second, 253 } 254 255 client := http.Client{ 256 Transport: transport, 257 CheckRedirect: func(req *http.Request, via []*http.Request) error { 258 return http.ErrUseLastResponse 259 }, 260 } 261 defer client.CloseIdleConnections() 262 263 resp, err := client.Do(req) 264 if err != nil { 265 return 266 } 267 _ = resp.Body.Close() 268 } 269 270 func NewProxy(adapter C.ProxyAdapter) *Proxy { 271 return &Proxy{ 272 ProxyAdapter: adapter, 273 history: queue.New[C.DelayHistory](10), 274 alive: atomic.NewBool(true), 275 } 276 } 277 278 func urlToMetadata(rawURL string) (addr C.Metadata, err error) { 279 u, err := url.Parse(rawURL) 280 if err != nil { 281 return 282 } 283 284 port := u.Port() 285 if port == "" { 286 switch u.Scheme { 287 case "https": 288 port = "443" 289 case "http": 290 port = "80" 291 default: 292 err = fmt.Errorf("%s scheme not Support", rawURL) 293 return 294 } 295 } 296 297 p, _ := strconv.ParseUint(port, 10, 16) 298 299 addr = C.Metadata{ 300 Host: u.Hostname(), 301 DstIP: netip.Addr{}, 302 DstPort: C.Port(p), 303 } 304 return 305 }