github.com/yaling888/clash@v1.53.0/adapter/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 "github.com/yaling888/clash/common/convert" 17 "github.com/yaling888/clash/component/dialer" 18 C "github.com/yaling888/clash/constant" 19 ) 20 21 var _ C.ProxyAdapter = (*Http)(nil) 22 23 type Http struct { 24 *Base 25 user string 26 pass string 27 tlsConfig *tls.Config 28 headers http.Header 29 } 30 31 type HttpOption struct { 32 BasicOption 33 Name string `proxy:"name"` 34 Server string `proxy:"server"` 35 Port int `proxy:"port"` 36 UserName string `proxy:"username,omitempty"` 37 Password string `proxy:"password,omitempty"` 38 TLS bool `proxy:"tls,omitempty"` 39 SNI string `proxy:"sni,omitempty"` 40 SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 41 Headers map[string]string `proxy:"headers,omitempty"` 42 RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` 43 } 44 45 // StreamConn implements C.ProxyAdapter 46 func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 47 if h.tlsConfig != nil { 48 cc := tls.Client(c, h.tlsConfig) 49 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) 50 defer cancel() 51 err := cc.HandshakeContext(ctx) 52 c = cc 53 if err != nil { 54 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 55 } 56 } 57 58 if err := h.shakeHand(metadata, c); err != nil { 59 return nil, err 60 } 61 return c, nil 62 } 63 64 // DialContext implements C.ProxyAdapter 65 func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { 66 c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...) 67 if err != nil { 68 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 69 } 70 tcpKeepAlive(c) 71 72 defer func(cc net.Conn, e error) { 73 safeConnClose(cc, e) 74 }(c, err) 75 76 c, err = h.StreamConn(c, metadata) 77 if err != nil { 78 return nil, err 79 } 80 81 return NewConn(c, h), nil 82 } 83 84 func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 85 addr := metadata.RemoteAddress() 86 req := &http.Request{ 87 Method: http.MethodConnect, 88 URL: &url.URL{ 89 Host: addr, 90 }, 91 Host: addr, 92 Header: h.headers.Clone(), 93 } 94 95 req.Header.Add("Proxy-Connection", "Keep-Alive") 96 97 if h.user != "" && h.pass != "" { 98 auth := h.user + ":" + h.pass 99 req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 100 } 101 102 if metadata.Type == C.MITM { 103 req.Header.Set("Origin-Request-Source-Address", metadata.SourceAddress()) 104 req.Header.Set("Origin-Request-Special-Proxy", metadata.SpecialProxy) 105 } 106 107 if err := req.Write(rw); err != nil { 108 return err 109 } 110 111 resp, err := http.ReadResponse(bufio.NewReader(rw), req) 112 if err != nil { 113 return err 114 } 115 116 if resp.StatusCode == http.StatusOK { 117 return nil 118 } 119 120 if resp.StatusCode == http.StatusProxyAuthRequired { 121 return errors.New("HTTP need auth") 122 } 123 124 if resp.StatusCode == http.StatusMethodNotAllowed { 125 return errors.New("CONNECT method not allowed by proxy") 126 } 127 128 if resp.StatusCode >= http.StatusInternalServerError { 129 return errors.New(resp.Status) 130 } 131 132 return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 133 } 134 135 func NewHttp(option HttpOption) *Http { 136 var tlsConfig *tls.Config 137 if option.TLS { 138 sni := option.Server 139 if option.SNI != "" { 140 sni = option.SNI 141 } 142 tlsConfig = &tls.Config{ 143 InsecureSkipVerify: option.SkipCertVerify, 144 ServerName: sni, 145 } 146 } 147 148 headers := http.Header{} 149 for name, value := range option.Headers { 150 headers.Add(name, value) 151 } 152 153 if headers.Get("User-Agent") == "" { 154 headers.Set("User-Agent", convert.RandUserAgent()) 155 } 156 157 return &Http{ 158 Base: &Base{ 159 name: option.Name, 160 addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 161 tp: C.Http, 162 iface: option.Interface, 163 rmark: option.RoutingMark, 164 dns: option.RemoteDnsResolve, 165 }, 166 user: option.UserName, 167 pass: option.Password, 168 tlsConfig: tlsConfig, 169 headers: headers, 170 } 171 }