github.com/metacubex/mihomo@v1.18.5/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 11 "io" 12 "net" 13 "net/http" 14 "strconv" 15 16 N "github.com/metacubex/mihomo/common/net" 17 "github.com/metacubex/mihomo/component/ca" 18 "github.com/metacubex/mihomo/component/dialer" 19 "github.com/metacubex/mihomo/component/proxydialer" 20 C "github.com/metacubex/mihomo/constant" 21 ) 22 23 type Http struct { 24 *Base 25 user string 26 pass string 27 tlsConfig *tls.Config 28 option *HttpOption 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 Fingerprint string `proxy:"fingerprint,omitempty"` 42 Headers map[string]string `proxy:"headers,omitempty"` 43 } 44 45 // StreamConnContext implements C.ProxyAdapter 46 func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { 47 if h.tlsConfig != nil { 48 cc := tls.Client(c, h.tlsConfig) 49 err := cc.HandshakeContext(ctx) 50 c = cc 51 if err != nil { 52 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 53 } 54 } 55 56 if err := h.shakeHand(metadata, c); err != nil { 57 return nil, err 58 } 59 return c, nil 60 } 61 62 // DialContext implements C.ProxyAdapter 63 func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { 64 return h.DialContextWithDialer(ctx, dialer.NewDialer(h.Base.DialOptions(opts...)...), metadata) 65 } 66 67 // DialContextWithDialer implements C.ProxyAdapter 68 func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { 69 if len(h.option.DialerProxy) > 0 { 70 dialer, err = proxydialer.NewByName(h.option.DialerProxy, dialer) 71 if err != nil { 72 return nil, err 73 } 74 } 75 c, err := dialer.DialContext(ctx, "tcp", h.addr) 76 if err != nil { 77 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 78 } 79 N.TCPKeepAlive(c) 80 81 defer func(c net.Conn) { 82 safeConnClose(c, err) 83 }(c) 84 85 c, err = h.StreamConnContext(ctx, c, metadata) 86 if err != nil { 87 return nil, err 88 } 89 90 return NewConn(c, h), nil 91 } 92 93 // SupportWithDialer implements C.ProxyAdapter 94 func (h *Http) SupportWithDialer() C.NetWork { 95 return C.TCP 96 } 97 98 func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 99 addr := metadata.RemoteAddress() 100 HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n" 101 tempHeaders := map[string]string{ 102 "Host": addr, 103 "User-Agent": "Go-http-client/1.1", 104 "Proxy-Connection": "Keep-Alive", 105 } 106 107 for key, value := range h.option.Headers { 108 tempHeaders[key] = value 109 } 110 111 if h.user != "" && h.pass != "" { 112 auth := h.user + ":" + h.pass 113 tempHeaders["Proxy-Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 114 } 115 116 for key, value := range tempHeaders { 117 HeaderString += key + ": " + value + "\r\n" 118 } 119 120 HeaderString += "\r\n" 121 122 _, err := rw.Write([]byte(HeaderString)) 123 124 if err != nil { 125 return err 126 } 127 128 resp, err := http.ReadResponse(bufio.NewReader(rw), nil) 129 130 if err != nil { 131 return err 132 } 133 134 if resp.StatusCode == http.StatusOK { 135 return nil 136 } 137 138 if resp.StatusCode == http.StatusProxyAuthRequired { 139 return errors.New("HTTP need auth") 140 } 141 142 if resp.StatusCode == http.StatusMethodNotAllowed { 143 return errors.New("CONNECT method not allowed by proxy") 144 } 145 146 if resp.StatusCode >= http.StatusInternalServerError { 147 return errors.New(resp.Status) 148 } 149 150 return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 151 } 152 153 func NewHttp(option HttpOption) (*Http, error) { 154 var tlsConfig *tls.Config 155 if option.TLS { 156 sni := option.Server 157 if option.SNI != "" { 158 sni = option.SNI 159 } 160 var err error 161 tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{ 162 InsecureSkipVerify: option.SkipCertVerify, 163 ServerName: sni, 164 }, option.Fingerprint) 165 if err != nil { 166 return nil, err 167 } 168 } 169 170 return &Http{ 171 Base: &Base{ 172 name: option.Name, 173 addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 174 tp: C.Http, 175 tfo: option.TFO, 176 mpTcp: option.MPTCP, 177 iface: option.Interface, 178 rmark: option.RoutingMark, 179 prefer: C.NewDNSPrefer(option.IPVersion), 180 }, 181 user: option.UserName, 182 pass: option.Password, 183 tlsConfig: tlsConfig, 184 option: &option, 185 }, nil 186 }