github.com/imannamdari/v2ray-core/v5@v5.0.5/proxy/http/client.go (about) 1 package http 2 3 import ( 4 "bufio" 5 "context" 6 "encoding/base64" 7 "io" 8 "net/http" 9 "net/url" 10 "sync" 11 12 "golang.org/x/net/http2" 13 14 core "github.com/imannamdari/v2ray-core/v5" 15 "github.com/imannamdari/v2ray-core/v5/common" 16 "github.com/imannamdari/v2ray-core/v5/common/buf" 17 "github.com/imannamdari/v2ray-core/v5/common/bytespool" 18 "github.com/imannamdari/v2ray-core/v5/common/net" 19 "github.com/imannamdari/v2ray-core/v5/common/protocol" 20 "github.com/imannamdari/v2ray-core/v5/common/retry" 21 "github.com/imannamdari/v2ray-core/v5/common/session" 22 "github.com/imannamdari/v2ray-core/v5/common/signal" 23 "github.com/imannamdari/v2ray-core/v5/common/task" 24 "github.com/imannamdari/v2ray-core/v5/features/policy" 25 "github.com/imannamdari/v2ray-core/v5/proxy" 26 "github.com/imannamdari/v2ray-core/v5/transport" 27 "github.com/imannamdari/v2ray-core/v5/transport/internet" 28 "github.com/imannamdari/v2ray-core/v5/transport/internet/tls" 29 ) 30 31 type Client struct { 32 serverPicker protocol.ServerPicker 33 policyManager policy.Manager 34 } 35 36 type h2Conn struct { 37 rawConn net.Conn 38 h2Conn *http2.ClientConn 39 } 40 41 var ( 42 cachedH2Mutex sync.Mutex 43 cachedH2Conns map[net.Destination]h2Conn 44 ) 45 46 // NewClient create a new http client based on the given config. 47 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { 48 serverList := protocol.NewServerList() 49 for _, rec := range config.Server { 50 s, err := protocol.NewServerSpecFromPB(rec) 51 if err != nil { 52 return nil, newError("failed to get server spec").Base(err) 53 } 54 serverList.AddServer(s) 55 } 56 if serverList.Size() == 0 { 57 return nil, newError("0 target server") 58 } 59 60 v := core.MustFromContext(ctx) 61 return &Client{ 62 serverPicker: protocol.NewRoundRobinServerPicker(serverList), 63 policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), 64 }, nil 65 } 66 67 // Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel. 68 func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { 69 outbound := session.OutboundFromContext(ctx) 70 if outbound == nil || !outbound.Target.IsValid() { 71 return newError("target not specified.") 72 } 73 target := outbound.Target 74 targetAddr := target.NetAddr() 75 76 if target.Network == net.Network_UDP { 77 return newError("UDP is not supported by HTTP outbound") 78 } 79 80 var user *protocol.MemoryUser 81 var conn internet.Connection 82 83 var firstPayload []byte 84 85 if reader, ok := link.Reader.(buf.TimeoutReader); ok { 86 // 0-RTT optimization for HTTP/2: If the payload comes very soon, it can be 87 // transmitted together. Note we should not get stuck here, as the payload may 88 // not exist (considering to access MySQL database via a HTTP proxy, where the 89 // server sends hello to the client first). 90 if mbuf, _ := reader.ReadMultiBufferTimeout(proxy.FirstPayloadTimeout); mbuf != nil { 91 mlen := mbuf.Len() 92 firstPayload = bytespool.Alloc(mlen) 93 mbuf, _ = buf.SplitBytes(mbuf, firstPayload) 94 firstPayload = firstPayload[:mlen] 95 96 buf.ReleaseMulti(mbuf) 97 defer bytespool.Free(firstPayload) 98 } 99 } 100 101 if err := retry.ExponentialBackoff(5, 100).On(func() error { 102 server := c.serverPicker.PickServer() 103 dest := server.Destination() 104 user = server.PickUser() 105 106 netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload) 107 if netConn != nil { 108 if _, ok := netConn.(*http2Conn); !ok { 109 if _, err := netConn.Write(firstPayload); err != nil { 110 netConn.Close() 111 return err 112 } 113 } 114 conn = internet.Connection(netConn) 115 } 116 return err 117 }); err != nil { 118 return newError("failed to find an available destination").Base(err) 119 } 120 121 defer func() { 122 if err := conn.Close(); err != nil { 123 newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) 124 } 125 }() 126 127 p := c.policyManager.ForLevel(0) 128 if user != nil { 129 p = c.policyManager.ForLevel(user.Level) 130 } 131 132 ctx, cancel := context.WithCancel(ctx) 133 timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) 134 135 requestFunc := func() error { 136 defer timer.SetTimeout(p.Timeouts.DownlinkOnly) 137 return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) 138 } 139 responseFunc := func() error { 140 defer timer.SetTimeout(p.Timeouts.UplinkOnly) 141 return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) 142 } 143 144 responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) 145 if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { 146 return newError("connection ends").Base(err) 147 } 148 149 return nil 150 } 151 152 // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method 153 func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) { 154 req := &http.Request{ 155 Method: http.MethodConnect, 156 URL: &url.URL{Host: target}, 157 Header: make(http.Header), 158 Host: target, 159 } 160 161 if user != nil && user.Account != nil { 162 account := user.Account.(*Account) 163 auth := account.GetUsername() + ":" + account.GetPassword() 164 req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 165 } 166 167 connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { 168 req.Header.Set("Proxy-Connection", "Keep-Alive") 169 170 err := req.Write(rawConn) 171 if err != nil { 172 rawConn.Close() 173 return nil, err 174 } 175 176 resp, err := http.ReadResponse(bufio.NewReader(rawConn), req) 177 if err != nil { 178 rawConn.Close() 179 return nil, err 180 } 181 defer resp.Body.Close() 182 183 if resp.StatusCode != http.StatusOK { 184 rawConn.Close() 185 return nil, newError("Proxy responded with non 200 code: " + resp.Status) 186 } 187 return rawConn, nil 188 } 189 190 connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) { 191 pr, pw := io.Pipe() 192 req.Body = pr 193 194 var pErr error 195 var wg sync.WaitGroup 196 wg.Add(1) 197 198 go func() { 199 _, pErr = pw.Write(firstPayload) 200 wg.Done() 201 }() 202 203 resp, err := h2clientConn.RoundTrip(req) // nolint: bodyclose 204 if err != nil { 205 rawConn.Close() 206 return nil, err 207 } 208 209 wg.Wait() 210 if pErr != nil { 211 rawConn.Close() 212 return nil, pErr 213 } 214 215 if resp.StatusCode != http.StatusOK { 216 rawConn.Close() 217 return nil, newError("Proxy responded with non 200 code: " + resp.Status) 218 } 219 return newHTTP2Conn(rawConn, pw, resp.Body), nil 220 } 221 222 cachedH2Mutex.Lock() 223 cachedConn, cachedConnFound := cachedH2Conns[dest] 224 cachedH2Mutex.Unlock() 225 226 if cachedConnFound { 227 rc, cc := cachedConn.rawConn, cachedConn.h2Conn 228 if cc.CanTakeNewRequest() { 229 proxyConn, err := connectHTTP2(rc, cc) 230 if err != nil { 231 return nil, err 232 } 233 234 return proxyConn, nil 235 } 236 } 237 238 rawConn, err := dialer.Dial(ctx, dest) 239 if err != nil { 240 return nil, err 241 } 242 243 iConn := rawConn 244 if statConn, ok := iConn.(*internet.StatCouterConnection); ok { 245 iConn = statConn.Connection 246 } 247 248 nextProto := "" 249 if tlsConn, ok := iConn.(*tls.Conn); ok { 250 if err := tlsConn.Handshake(); err != nil { 251 rawConn.Close() 252 return nil, err 253 } 254 nextProto = tlsConn.ConnectionState().NegotiatedProtocol 255 } 256 257 switch nextProto { 258 case "", "http/1.1": 259 return connectHTTP1(rawConn) 260 case "h2": 261 t := http2.Transport{} 262 h2clientConn, err := t.NewClientConn(rawConn) 263 if err != nil { 264 rawConn.Close() 265 return nil, err 266 } 267 268 proxyConn, err := connectHTTP2(rawConn, h2clientConn) 269 if err != nil { 270 rawConn.Close() 271 return nil, err 272 } 273 274 cachedH2Mutex.Lock() 275 if cachedH2Conns == nil { 276 cachedH2Conns = make(map[net.Destination]h2Conn) 277 } 278 279 cachedH2Conns[dest] = h2Conn{ 280 rawConn: rawConn, 281 h2Conn: h2clientConn, 282 } 283 cachedH2Mutex.Unlock() 284 285 return proxyConn, err 286 default: 287 return nil, newError("negotiated unsupported application layer protocol: " + nextProto) 288 } 289 } 290 291 func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn { 292 return &http2Conn{Conn: c, in: pipedReqBody, out: respBody} 293 } 294 295 type http2Conn struct { 296 net.Conn 297 in *io.PipeWriter 298 out io.ReadCloser 299 } 300 301 func (h *http2Conn) Read(p []byte) (n int, err error) { 302 return h.out.Read(p) 303 } 304 305 func (h *http2Conn) Write(p []byte) (n int, err error) { 306 return h.in.Write(p) 307 } 308 309 func (h *http2Conn) Close() error { 310 h.in.Close() 311 return h.out.Close() 312 } 313 314 func init() { 315 common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 316 return NewClient(ctx, config.(*ClientConfig)) 317 })) 318 }