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