github.com/laof/lite-speed-test@v0.0.0-20230930011949-1f39b7037845/outbound/http.go (about) 1 package outbound 2 3 import ( 4 "bufio" 5 "context" 6 "crypto/tls" 7 "encoding/base64" 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "net/http" 13 "net/url" 14 "strconv" 15 16 C "github.com/laof/lite-speed-test/constant" 17 "github.com/laof/lite-speed-test/transport/dialer" 18 ) 19 20 type Http struct { 21 *Base 22 user string 23 pass string 24 tlsConfig *tls.Config 25 } 26 27 type HttpOption struct { 28 BasicOption 29 Name string `proxy:"name"` 30 Remarks string `proxy:"remarks,omitempty"` 31 Server string `proxy:"server"` 32 Port int `proxy:"port"` 33 UserName string `proxy:"username,omitempty"` 34 Password string `proxy:"password,omitempty"` 35 TLS bool `proxy:"tls,omitempty"` 36 SNI string `proxy:"sni,omitempty"` 37 SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 38 } 39 40 func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 41 if h.tlsConfig != nil { 42 cc := tls.Client(c, h.tlsConfig) 43 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) 44 defer cancel() 45 err := cc.HandshakeContext(ctx) 46 c = cc 47 if err != nil { 48 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 49 } 50 } 51 52 if err := h.shakeHand(metadata, c); err != nil { 53 return nil, err 54 } 55 return c, nil 56 } 57 58 // DialContext implements C.ProxyAdapter 59 func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) { 60 c, err := dialer.DialContext(ctx, "tcp", h.addr) 61 if err != nil { 62 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 63 } 64 tcpKeepAlive(c) 65 66 defer c.Close() 67 68 return h.StreamConn(c, metadata) 69 } 70 71 func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 72 addr := metadata.RemoteAddress() 73 req := &http.Request{ 74 Method: http.MethodConnect, 75 URL: &url.URL{ 76 Host: addr, 77 }, 78 Host: addr, 79 Header: http.Header{ 80 "Proxy-Connection": []string{"Keep-Alive"}, 81 }, 82 } 83 84 if h.user != "" && h.pass != "" { 85 auth := h.user + ":" + h.pass 86 req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 87 } 88 89 if err := req.Write(rw); err != nil { 90 return err 91 } 92 93 resp, err := http.ReadResponse(bufio.NewReader(rw), req) 94 if err != nil { 95 return err 96 } 97 98 if resp.StatusCode == http.StatusOK { 99 return nil 100 } 101 102 if resp.StatusCode == http.StatusProxyAuthRequired { 103 return errors.New("HTTP need auth") 104 } 105 106 if resp.StatusCode == http.StatusMethodNotAllowed { 107 return errors.New("CONNECT method not allowed by proxy") 108 } 109 110 if resp.StatusCode >= http.StatusInternalServerError { 111 return errors.New(resp.Status) 112 } 113 114 return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 115 } 116 117 func (h *Http) DialUDP(metadata *C.Metadata) (net.PacketConn, error) { 118 return nil, errors.New("not support udp") 119 } 120 121 func NewHttp(option HttpOption) *Http { 122 var tlsConfig *tls.Config 123 if option.TLS { 124 sni := option.Server 125 if option.SNI != "" { 126 sni = option.SNI 127 } 128 tlsConfig = &tls.Config{ 129 InsecureSkipVerify: option.SkipCertVerify, 130 ServerName: sni, 131 } 132 } 133 134 return &Http{ 135 Base: &Base{ 136 name: option.Name, 137 addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 138 }, 139 user: option.UserName, 140 pass: option.Password, 141 tlsConfig: tlsConfig, 142 } 143 }