github.com/moqsien/xraycore@v1.8.5/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/moqsien/xraycore/common" 15 "github.com/moqsien/xraycore/common/buf" 16 "github.com/moqsien/xraycore/common/bytespool" 17 "github.com/moqsien/xraycore/common/net" 18 "github.com/moqsien/xraycore/common/protocol" 19 "github.com/moqsien/xraycore/common/retry" 20 "github.com/moqsien/xraycore/common/session" 21 "github.com/moqsien/xraycore/common/signal" 22 "github.com/moqsien/xraycore/common/task" 23 "github.com/moqsien/xraycore/core" 24 "github.com/moqsien/xraycore/features/policy" 25 "github.com/moqsien/xraycore/transport" 26 "github.com/moqsien/xraycore/transport/internet" 27 "github.com/moqsien/xraycore/transport/internet/stat" 28 "github.com/moqsien/xraycore/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 if inbound == nil || outbound == nil { 176 return nil, newError("missing inbound or outbound metadata from context") 177 } 178 179 data := struct { 180 Source net.Destination 181 Target net.Destination 182 }{ 183 Source: inbound.Source, 184 Target: outbound.Target, 185 } 186 187 filled := make([]*Header, len(header)) 188 for i, h := range header { 189 tmpl, err := template.New(h.Key).Parse(h.Value) 190 if err != nil { 191 return nil, err 192 } 193 var buf bytes.Buffer 194 195 if err = tmpl.Execute(&buf, data); err != nil { 196 return nil, err 197 } 198 filled[i] = &Header{Key: h.Key, Value: buf.String()} 199 } 200 201 return filled, nil 202 } 203 204 // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method 205 func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, header []*Header, firstPayload []byte) (net.Conn, error) { 206 req := &http.Request{ 207 Method: http.MethodConnect, 208 URL: &url.URL{Host: target}, 209 Header: make(http.Header), 210 Host: target, 211 } 212 213 if user != nil && user.Account != nil { 214 account := user.Account.(*Account) 215 auth := account.GetUsername() + ":" + account.GetPassword() 216 req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 217 } 218 219 for _, h := range header { 220 req.Header.Set(h.Key, h.Value) 221 } 222 223 connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { 224 req.Header.Set("Proxy-Connection", "Keep-Alive") 225 226 err := req.Write(rawConn) 227 if err != nil { 228 rawConn.Close() 229 return nil, err 230 } 231 232 resp, err := http.ReadResponse(bufio.NewReader(rawConn), req) 233 if err != nil { 234 rawConn.Close() 235 return nil, err 236 } 237 defer resp.Body.Close() 238 239 if resp.StatusCode != http.StatusOK { 240 rawConn.Close() 241 return nil, newError("Proxy responded with non 200 code: " + resp.Status) 242 } 243 return rawConn, nil 244 } 245 246 connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) { 247 pr, pw := io.Pipe() 248 req.Body = pr 249 250 var pErr error 251 var wg sync.WaitGroup 252 wg.Add(1) 253 254 go func() { 255 _, pErr = pw.Write(firstPayload) 256 wg.Done() 257 }() 258 259 resp, err := h2clientConn.RoundTrip(req) 260 if err != nil { 261 rawConn.Close() 262 return nil, err 263 } 264 265 wg.Wait() 266 if pErr != nil { 267 rawConn.Close() 268 return nil, pErr 269 } 270 271 if resp.StatusCode != http.StatusOK { 272 rawConn.Close() 273 return nil, newError("Proxy responded with non 200 code: " + resp.Status) 274 } 275 return newHTTP2Conn(rawConn, pw, resp.Body), nil 276 } 277 278 cachedH2Mutex.Lock() 279 cachedConn, cachedConnFound := cachedH2Conns[dest] 280 cachedH2Mutex.Unlock() 281 282 if cachedConnFound { 283 rc, cc := cachedConn.rawConn, cachedConn.h2Conn 284 if cc.CanTakeNewRequest() { 285 proxyConn, err := connectHTTP2(rc, cc) 286 if err != nil { 287 return nil, err 288 } 289 290 return proxyConn, nil 291 } 292 } 293 294 rawConn, err := dialer.Dial(ctx, dest) 295 if err != nil { 296 return nil, err 297 } 298 299 iConn := rawConn 300 if statConn, ok := iConn.(*stat.CounterConnection); ok { 301 iConn = statConn.Connection 302 } 303 304 nextProto := "" 305 if tlsConn, ok := iConn.(*tls.Conn); ok { 306 if err := tlsConn.Handshake(); err != nil { 307 rawConn.Close() 308 return nil, err 309 } 310 nextProto = tlsConn.ConnectionState().NegotiatedProtocol 311 } 312 313 switch nextProto { 314 case "", "http/1.1": 315 return connectHTTP1(rawConn) 316 case "h2": 317 t := http2.Transport{} 318 h2clientConn, err := t.NewClientConn(rawConn) 319 if err != nil { 320 rawConn.Close() 321 return nil, err 322 } 323 324 proxyConn, err := connectHTTP2(rawConn, h2clientConn) 325 if err != nil { 326 rawConn.Close() 327 return nil, err 328 } 329 330 cachedH2Mutex.Lock() 331 if cachedH2Conns == nil { 332 cachedH2Conns = make(map[net.Destination]h2Conn) 333 } 334 335 cachedH2Conns[dest] = h2Conn{ 336 rawConn: rawConn, 337 h2Conn: h2clientConn, 338 } 339 cachedH2Mutex.Unlock() 340 341 return proxyConn, err 342 default: 343 return nil, newError("negotiated unsupported application layer protocol: " + nextProto) 344 } 345 } 346 347 func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn { 348 return &http2Conn{Conn: c, in: pipedReqBody, out: respBody} 349 } 350 351 type http2Conn struct { 352 net.Conn 353 in *io.PipeWriter 354 out io.ReadCloser 355 } 356 357 func (h *http2Conn) Read(p []byte) (n int, err error) { 358 return h.out.Read(p) 359 } 360 361 func (h *http2Conn) Write(p []byte) (n int, err error) { 362 return h.in.Write(p) 363 } 364 365 func (h *http2Conn) Close() error { 366 h.in.Close() 367 return h.out.Close() 368 } 369 370 func init() { 371 common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 372 return NewClient(ctx, config.(*ClientConfig)) 373 })) 374 }