From 222b6cd6ad62f38de71522ab74ac90a8f587f4b1 Mon Sep 17 00:00:00 2001 From: Anonymous-Someneese Date: Sat, 14 Dec 2019 00:04:33 +0800 Subject: [PATCH 1/5] Reduce http proxy client overhead by 1RTT --- proxy/http/client.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index 51dca746..55839150 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -3,9 +3,11 @@ package http import ( + "bufio" "context" "encoding/base64" "io" + "net/http" "strings" "v2ray.com/core" @@ -90,7 +92,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } - if err := setUpHttpTunnel(conn, conn, &destination, user); err != nil { + if err := setUpHttpTunnel(conn, &destination, user); err != nil { return err } @@ -103,7 +105,15 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } responseFunc := func() error { defer timer.SetTimeout(p.Timeouts.UplinkOnly) - return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) + bc := bufio.NewReader(conn) + resp, err := http.ReadResponse(bc, nil) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return newError(resp.Status) + } + return buf.Copy(buf.NewReader(bc), link.Writer, buf.UpdateActivity(timer)) } var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer)) @@ -115,7 +125,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } // setUpHttpTunnel will create a socket tunnel via HTTP CONNECT method -func setUpHttpTunnel(reader io.Reader, writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error { +func setUpHttpTunnel(writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error { var headers []string destNetAddr := destination.NetAddr() headers = append(headers, "CONNECT "+destNetAddr+" HTTP/1.1") @@ -133,11 +143,6 @@ func setUpHttpTunnel(reader io.Reader, writer io.Writer, destination *net.Destin return err } - b.Clear() - if _, err := b.ReadFrom(reader); err != nil { - return err - } - return nil } From a9ff0a2b921a9e9e32b65d12924d7e1e5425035e Mon Sep 17 00:00:00 2001 From: Anonymous-Someneese Date: Sat, 14 Dec 2019 01:22:28 +0800 Subject: [PATCH 2/5] Fix code quality --- proxy/http/client.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index 55839150..740865a7 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -24,6 +24,7 @@ import ( "v2ray.com/core/transport/internet" ) +// Client is a inbound handler for HTTP protocol type Client struct { serverPicker protocol.ServerPicker policyManager policy.Manager @@ -92,7 +93,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } - if err := setUpHttpTunnel(conn, &destination, user); err != nil { + if err := setUpHTTPTunnel(conn, &destination, user); err != nil { return err } @@ -124,8 +125,8 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return nil } -// setUpHttpTunnel will create a socket tunnel via HTTP CONNECT method -func setUpHttpTunnel(writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error { +// setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method +func setUpHTTPTunnel(writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error { var headers []string destNetAddr := destination.NetAddr() headers = append(headers, "CONNECT "+destNetAddr+" HTTP/1.1") From 5e40291e69facd03e36e8ec69f9d4dc8996a8df1 Mon Sep 17 00:00:00 2001 From: Anonymous-Someneese Date: Thu, 2 Jan 2020 21:09:33 +0800 Subject: [PATCH 3/5] Optimize HTTP tunnel setup in TFO environment --- proxy/http/client.go | 51 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index 740865a7..2115572a 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -4,9 +4,9 @@ package http import ( "bufio" + "io" "context" "encoding/base64" - "io" "net/http" "strings" @@ -93,9 +93,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } - if err := setUpHTTPTunnel(conn, &destination, user); err != nil { - return err - } + conn = setUpHTTPTunnel(conn, &destination, user) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) @@ -125,8 +123,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return nil } +type tunnelConn struct { + internet.Connection + header *buf.Buffer +} + // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method -func setUpHTTPTunnel(writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error { +func setUpHTTPTunnel(conn internet.Connection, destination *net.Destination, user *protocol.MemoryUser) *tunnelConn { var headers []string destNetAddr := destination.NetAddr() headers = append(headers, "CONNECT "+destNetAddr+" HTTP/1.1") @@ -140,11 +143,43 @@ func setUpHTTPTunnel(writer io.Writer, destination *net.Destination, user *proto b := buf.New() b.WriteString(strings.Join(headers, "\r\n") + "\r\n\r\n") - if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil { - return err + return &tunnelConn { + Connection: conn, + header: b, } +} - return nil +func (c *tunnelConn) Write(b []byte) (n int, err error) { + if c.header == nil { + return c.Connection.Write(b) + } + buffer := c.header + lenheader := c.header.Len() + // Concate header and b + _, err = buffer.Write(b) + if err != nil { + c.header.Resize(0, lenheader) + return 0, err + } + // Write buffer + nc, err := io.Copy(c.Connection, buffer) + if int32(nc) < lenheader { + c.header.Resize(int32(nc), lenheader) + return 0, err + } + c.header.Release() + c.header = nil + n = int(nc) - int(lenheader) + if err != nil { + return n, err + } + // Write trailing bytes + if n < len(b) { + var nw int + nw, err = c.Connection.Write(b[:n]) + n += nw + } + return n, err } func init() { From e2b6080b4e1435742f098508cec9454174d93291 Mon Sep 17 00:00:00 2001 From: Anonymous-Someneese Date: Sun, 5 Jan 2020 07:11:54 +0800 Subject: [PATCH 4/5] Fix reverse proxy with the HTTP optimization And any other protocol that require the connection initializer to send first, E.g. freedom with TFO. --- app/reverse/bridge.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/reverse/bridge.go b/app/reverse/bridge.go index 50274548..9dc4a178 100644 --- a/app/reverse/bridge.go +++ b/app/reverse/bridge.go @@ -7,6 +7,7 @@ import ( "time" "github.com/golang/protobuf/proto" + "v2ray.com/core/common/buf" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/session" @@ -119,6 +120,13 @@ func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWo tag: tag, } + // Initialize the connection by sending a Keepalive frame + keepalive := buf.New() + mux.FrameMetadata{SessionStatus: mux.SessionStatusKeepAlive}.WriteTo(keepalive) + err = link.Writer.WriteMultiBuffer(buf.MultiBuffer{keepalive}) + if err != nil { + return nil, err + } worker, err := mux.NewServerWorker(context.Background(), w, link) if err != nil { return nil, err From 06431ae21a69d2d7d6369cd6db53253e87fb5f31 Mon Sep 17 00:00:00 2001 From: Anonymous-Someneese Date: Tue, 7 Jan 2020 18:16:22 +0800 Subject: [PATCH 5/5] http: send HTTP Connect if no content is written after 5ms. --- proxy/http/client.go | 86 ++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/proxy/http/client.go b/proxy/http/client.go index 2115572a..318ff332 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -4,11 +4,13 @@ package http import ( "bufio" - "io" "context" "encoding/base64" + "io" "net/http" "strings" + "sync" + "time" "v2ray.com/core" "v2ray.com/core/common" @@ -123,13 +125,8 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return nil } -type tunnelConn struct { - internet.Connection - header *buf.Buffer -} - // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method -func setUpHTTPTunnel(conn internet.Connection, destination *net.Destination, user *protocol.MemoryUser) *tunnelConn { +func setUpHTTPTunnel(conn internet.Connection, destination *net.Destination, user *protocol.MemoryUser) *tunConn { var headers []string destNetAddr := destination.NetAddr() headers = append(headers, "CONNECT "+destNetAddr+" HTTP/1.1") @@ -143,40 +140,59 @@ func setUpHTTPTunnel(conn internet.Connection, destination *net.Destination, use b := buf.New() b.WriteString(strings.Join(headers, "\r\n") + "\r\n\r\n") - return &tunnelConn { - Connection: conn, - header: b, - } + return newTunConn(conn, b, 5 * time.Millisecond) } -func (c *tunnelConn) Write(b []byte) (n int, err error) { +// tunConn is a connection that writes header before content, +// the header will be written during the next Write call or after +// specified delay. +type tunConn struct { + internet.Connection + header *buf.Buffer + once sync.Once + timer *time.Timer +} + +func newTunConn(conn internet.Connection, header *buf.Buffer, delay time.Duration) *tunConn { + tc := &tunConn{ + Connection: conn, + header: header, + } + if delay > 0 { + tc.timer = time.AfterFunc(delay, func() { + tc.Write([]byte{}) + }) + } + return tc +} + +func (c *tunConn) Write(b []byte) (n int, err error) { + // fallback to normal write if header is sent if c.header == nil { return c.Connection.Write(b) } - buffer := c.header - lenheader := c.header.Len() - // Concate header and b - _, err = buffer.Write(b) - if err != nil { - c.header.Resize(0, lenheader) - return 0, err - } - // Write buffer - nc, err := io.Copy(c.Connection, buffer) - if int32(nc) < lenheader { - c.header.Resize(int32(nc), lenheader) - return 0, err - } - c.header.Release() - c.header = nil - n = int(nc) - int(lenheader) - if err != nil { - return n, err - } - // Write trailing bytes - if n < len(b) { + // Prevent timer and writer race condition + c.once.Do(func() { + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + lenheader := c.header.Len() + // Concate header and b + common.Must2(c.header.Write(b)) + // Write buffer + var nc int64 + nc, err = io.Copy(c.Connection, c.header) + c.header.Release() + c.header = nil + n = int(nc) - int(lenheader) + if n < 0 { n = 0 } + b = b[n:] + }) + // Write Trailing bytes + if len(b) > 0 && err == nil { var nw int - nw, err = c.Connection.Write(b[:n]) + nw, err = c.Connection.Write(b) n += nw } return n, err