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