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  }