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