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  }