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