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