github.com/laof/lite-speed-test@v0.0.0-20230930011949-1f39b7037845/request/request.go (about) 1 package request 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "strings" 12 "time" 13 14 "github.com/laof/lite-speed-test/common" 15 "github.com/laof/lite-speed-test/config" 16 C "github.com/laof/lite-speed-test/constant" 17 "github.com/laof/lite-speed-test/dns" 18 "github.com/laof/lite-speed-test/outbound" 19 "github.com/laof/lite-speed-test/transport/resolver" 20 "github.com/laof/lite-speed-test/utils" 21 ) 22 23 const ( 24 remoteHost = "clients3.google.com" 25 generate_204 = "http://clients3.google.com/generate_204" 26 ) 27 28 var ( 29 httpRequest = []byte("GET /generate_204 HTTP/1.1\r\nHost: clients3.google.com\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36\r\n\r\n") 30 tcpTimeout = 2200 * time.Millisecond 31 ) 32 33 type PingOption struct { 34 Attempts int 35 TimeOut time.Duration 36 } 37 38 func PingVmess(vmessOption *outbound.VmessOption) (int64, error) { 39 ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 40 defer cancel() 41 vmess, err := outbound.NewVmess(vmessOption) 42 if err != nil { 43 return 0, err 44 } 45 meta := &C.Metadata{ 46 NetWork: 0, 47 Type: 0, 48 SrcPort: "", 49 DstPort: "80", 50 Host: remoteHost, 51 } 52 remoteConn, err := vmess.DialContext(ctx, meta) 53 if err != nil { 54 return 0, err 55 } 56 return pingInternal(remoteConn) 57 } 58 59 func parseFirstLine(buf []byte) (int, error) { 60 bNext := buf 61 var b []byte 62 var err error 63 for len(b) == 0 { 64 if b, bNext, err = nextLine(bNext); err != nil { 65 return 0, err 66 } 67 } 68 69 // parse protocol 70 n := bytes.IndexByte(b, ' ') 71 if n < 0 { 72 return 0, fmt.Errorf("cannot find whitespace in the first line of response %q", buf) 73 } 74 b = b[n+1:] 75 76 // parse status code 77 statusCode, n, err := parseUintBuf(b) 78 if err != nil { 79 return 0, fmt.Errorf("cannot parse response status code: %s. Response %q", err, buf) 80 } 81 if len(b) > n && b[n] != ' ' { 82 return 0, fmt.Errorf("unexpected char at the end of status code. Response %q", buf) 83 } 84 85 if statusCode == 204 || statusCode == 200 { 86 return len(buf) - len(bNext), nil 87 } 88 return 0, errors.New("Wrong Status Code") 89 } 90 91 func nextLine(b []byte) ([]byte, []byte, error) { 92 nNext := bytes.IndexByte(b, '\n') 93 if nNext < 0 { 94 return nil, nil, errors.New("need more data: cannot find trailing lf") 95 } 96 n := nNext 97 if n > 0 && b[n-1] == '\r' { 98 n-- 99 } 100 return b[:n], b[nNext+1:], nil 101 } 102 103 func parseUintBuf(b []byte) (int, int, error) { 104 n := len(b) 105 if n == 0 { 106 return -1, 0, errors.New("empty integer") 107 } 108 v := 0 109 for i := 0; i < n; i++ { 110 c := b[i] 111 k := c - '0' 112 if k > 9 { 113 if i == 0 { 114 return -1, i, errors.New("unexpected first char found. Expecting 0-9") 115 } 116 return v, i, nil 117 } 118 vNew := 10*v + int(k) 119 // Test for overflow. 120 if vNew < v { 121 return -1, i, errors.New("too long int") 122 } 123 v = vNew 124 } 125 return v, n, nil 126 } 127 128 func PingTrojan(trojanOption *outbound.TrojanOption) (int64, error) { 129 ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 130 defer cancel() 131 trojan, err := outbound.NewTrojan(trojanOption) 132 if err != nil { 133 return 0, err 134 } 135 meta := &C.Metadata{ 136 NetWork: 0, 137 Type: 0, 138 SrcPort: "", 139 DstPort: "80", 140 Host: remoteHost, 141 } 142 remoteConn, err := trojan.DialContext(ctx, meta) 143 if err != nil { 144 return 0, err 145 } 146 return pingInternal(remoteConn) 147 } 148 149 func PingSS(ssOption *outbound.ShadowSocksOption) (int64, error) { 150 ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 151 defer cancel() 152 ss, err := outbound.NewShadowSocks(ssOption) 153 if err != nil { 154 return 0, err 155 } 156 meta := &C.Metadata{ 157 NetWork: 0, 158 Type: 0, 159 SrcPort: "", 160 DstPort: "80", 161 Host: remoteHost, 162 } 163 remoteConn, err := ss.DialContext(ctx, meta) 164 if err != nil { 165 return 0, err 166 } 167 return pingInternal(remoteConn) 168 } 169 170 func PingSSR(ssrOption *outbound.ShadowSocksROption) (int64, error) { 171 ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 172 defer cancel() 173 ssr, err := outbound.NewShadowSocksR(ssrOption) 174 if err != nil { 175 return 0, err 176 } 177 meta := &C.Metadata{ 178 NetWork: 0, 179 Type: 0, 180 SrcPort: "", 181 DstPort: "80", 182 Host: remoteHost, 183 } 184 remoteConn, err := ssr.DialContext(ctx, meta) 185 if err != nil { 186 return 0, err 187 } 188 return pingInternal(remoteConn) 189 } 190 191 type PingResult struct { 192 elapse int64 193 err error 194 } 195 196 func PingLink(link string, attempts int) (int64, error) { 197 opt := PingOption{ 198 Attempts: attempts, 199 TimeOut: tcpTimeout, 200 } 201 return PingLinkInternal(link, opt) 202 } 203 204 func PingLinkInternal(link string, pingOption PingOption) (int64, error) { 205 matches, err := utils.CheckLink(link) 206 if err != nil { 207 return 0, err 208 } 209 var option interface{} 210 switch strings.ToLower(matches[1]) { 211 case "vmess": 212 option, err = config.VmessLinkToVmessOption(link) 213 case "trojan": 214 option, err = config.TrojanLinkToTrojanOption(link) 215 case "http": 216 option, err = config.HttpLinkToHttpOption(link) 217 case "ss": 218 option, err = config.SSLinkToSSOption(link) 219 case "ssr": 220 option, err = config.SSRLinkToSSROption(link) 221 default: 222 return 0, common.NewError("Not Suported Link") 223 } 224 if err != nil { 225 return 0, err 226 } 227 var elapse int64 228 if pingOption.TimeOut > 0 { 229 tcpTimeout = pingOption.TimeOut 230 } 231 err = utils.ExponentialBackoff(pingOption.Attempts, 100).On(func() error { 232 ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 233 defer cancel() 234 pingChan := make(chan PingResult, 1) 235 go func(pingChan chan<- PingResult) { 236 start := time.Now() 237 elp, err := Ping(option) 238 elapse := time.Since(start) 239 if elapse > 2000*time.Second { 240 elp = 0 241 } 242 pingResult := PingResult{elapse: elp, err: err} 243 pingChan <- pingResult 244 }(pingChan) 245 for { 246 select { 247 case pingResult := <-pingChan: 248 { 249 elapse = pingResult.elapse 250 return pingResult.err 251 } 252 case <-ctx.Done(): 253 return fmt.Errorf("ping time out") 254 } 255 } 256 257 }) 258 return elapse, err 259 } 260 261 func PingContext(ctx context.Context, option interface{}) (int64, error) { 262 var d outbound.ContextDialer 263 var err error 264 meta := &C.Metadata{ 265 NetWork: 0, 266 Type: C.TEST, 267 SrcPort: "", 268 DstPort: "80", 269 Host: remoteHost, 270 Timeout: tcpTimeout, 271 } 272 if ssOption, ok := option.(*outbound.ShadowSocksOption); ok { 273 d, err = outbound.NewShadowSocks(ssOption) 274 if err != nil { 275 return 0, err 276 } 277 } 278 if ssrOption, ok := option.(*outbound.ShadowSocksROption); ok { 279 d, err = outbound.NewShadowSocksR(ssrOption) 280 if err != nil { 281 return 0, err 282 } 283 dialerCtx := func(ctx context.Context, network, addr string) (net.Conn, error) { 284 return d.DialContext(ctx, meta) 285 } 286 return pingHTTPClient(ctx, generate_204, tcpTimeout, dialerCtx) 287 } 288 if vmessOption, ok := option.(*outbound.VmessOption); ok { 289 d, err = outbound.NewVmess(vmessOption) 290 if err != nil { 291 return 0, err 292 } 293 } 294 if trojanOption, ok := option.(*outbound.TrojanOption); ok { 295 d, err = outbound.NewTrojan(trojanOption) 296 if err != nil { 297 return 0, err 298 } 299 } 300 if httpOption, ok := option.(*outbound.HttpOption); ok { 301 d = outbound.NewHttp(*httpOption) 302 } 303 if d == nil { 304 return 0, errors.New("not support config") 305 } 306 remoteConn, err := d.DialContext(ctx, meta) 307 if err != nil { 308 return 0, err 309 } 310 return pingInternal(remoteConn) 311 } 312 313 func Ping(option interface{}) (int64, error) { 314 ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 315 defer cancel() 316 return PingContext(ctx, option) 317 } 318 319 func pingHTTPClient(ctx context.Context, url string, timeout time.Duration, dialCtx func(ctx context.Context, network, addr string) (net.Conn, error)) (int64, error) { 320 httpTransport := &http.Transport{} 321 httpClient := &http.Client{Transport: httpTransport, Timeout: timeout} 322 if dialCtx != nil { 323 httpTransport.DialContext = dialCtx 324 } 325 defer httpClient.CloseIdleConnections() 326 req, err := http.NewRequest("GET", url, nil) 327 if err != nil { 328 return 0, err 329 } 330 start := time.Now() 331 response, err := httpClient.Do(req) 332 now := time.Now() 333 if err != nil { 334 return 0, err 335 } 336 elapse := now.Sub(start).Milliseconds() 337 defer response.Body.Close() 338 if response.StatusCode != 204 && response.StatusCode != 200 { 339 return 0, fmt.Errorf("wrong status code %d", response.StatusCode) 340 } 341 return elapse, nil 342 } 343 344 func pingInternal(remoteConn net.Conn) (int64, error) { 345 defer remoteConn.Close() 346 remoteConn.SetDeadline(time.Now().Add(tcpTimeout)) 347 start := time.Now() 348 // httpRequest := "GET /generate_204 HTTP/1.1\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36\r\n\r\n" 349 if _, err := remoteConn.Write(httpRequest); err != nil { 350 return 0, err 351 } 352 buf := make([]byte, 128) 353 _, err := remoteConn.Read(buf) 354 if err != nil && err != io.EOF { 355 return 0, err 356 } 357 _, err = parseFirstLine(buf) 358 if err != nil { 359 return 0, err 360 } 361 elapsed := time.Since(start).Milliseconds() 362 // fmt.Print(string(buf)) 363 // fmt.Printf("server: %s port: %d elapsed: %d\n", vmessOption.Server, vmessOption.Port, elapsed) 364 return elapsed, nil 365 } 366 367 func init() { 368 resolver.DefaultResolver = dns.DefaultResolver() 369 }