From 09295f7ef6a210a48b17e0c93ec9dcedaf6f6e90 Mon Sep 17 00:00:00 2001 From: Kslr Date: Tue, 2 Jun 2020 04:19:08 +0800 Subject: [PATCH 01/15] fix bazel not set home https://github.com/v2ray/discussion/issues/714#issuecomment-637034754 --- infra/bazel/build.bzl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/infra/bazel/build.bzl b/infra/bazel/build.bzl index 31217570..11267e71 100644 --- a/infra/bazel/build.bzl +++ b/infra/bazel/build.bzl @@ -41,9 +41,7 @@ def _go_command(ctx): if ctx.attr.arm: envs+=["GOARM="+ctx.attr.arm] - switchToPwd="cd ${SPWD} && " - - command = switchToPwd + " ".join(envs) + " " + command + command = " ".join(envs) + " " + command ctx.actions.run_shell( outputs = [output_file], From d671780804fb5214014df2525e81186b4ab6a2bd Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 14:47:54 +0800 Subject: [PATCH 02/15] Drain Connection Based on Uuid based Behavior seed --- common/dice/dice.go | 19 +++++++++++++++++++ proxy/vmess/encoding/server.go | 32 ++++++++++++++++++++++++-------- proxy/vmess/validator.go | 31 ++++++++++++++++++++++++++----- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/common/dice/dice.go b/common/dice/dice.go index 6dc94b71..f5a62559 100644 --- a/common/dice/dice.go +++ b/common/dice/dice.go @@ -28,6 +28,25 @@ func RollUint16() uint16 { return uint16(rand.Intn(65536)) } +func RollUint64() uint64 { + return rand.Uint64() +} + +func NewDeterministicDice(seed int64) *deterministicDice { + return &deterministicDice{rand.New(rand.NewSource(seed))} +} + +type deterministicDice struct { + *rand.Rand +} + +func (dd *deterministicDice) Roll(n int) int { + if n == 1 { + return 0 + } + return dd.Intn(n) +} + func init() { rand.Seed(time.Now().Unix()) } diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 6c04fa33..a7b6e81b 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -125,7 +125,26 @@ func parseSecurityType(b byte) protocol.SecurityType { // DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream. func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) { buffer := buf.New() - defer buffer.Release() + behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed())) + DrainSize := behaviorRand.Roll(387) + 16 + 38 + readSizeRemain := DrainSize + + drainConnection := func(e error) error { + //We read a deterministic generated length of data before closing the connection to offset padding read pattern + readSizeRemain -= int(buffer.Len()) + if readSizeRemain > 0 { + err := s.DrainConnN(reader, readSizeRemain) + if err != nil { + return newError("failed to drain connection").Base(err).Base(e) + } + return newError("connection drained DrainSize = ", DrainSize).Base(e) + } + return e + } + + defer func() { + buffer.Release() + }() if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil { return nil, newError("failed to read request header").Base(err) @@ -133,7 +152,8 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request user, timestamp, valid := s.userValidator.Get(buffer.Bytes()) if !valid { - return nil, newError("invalid user") + //It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523 + return nil, drainConnection(newError("invalid user")) } iv := hashTimestamp(md5.New(), timestamp) @@ -142,6 +162,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv[:]) decryptor := crypto.NewCryptionReader(aesStream, reader) + readSizeRemain -= int(buffer.Len()) buffer.Clear() if _, err := buffer.ReadFullFrom(decryptor, 38); err != nil { return nil, newError("failed to read request header").Base(err) @@ -197,12 +218,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request if actualHash != expectedHash { //It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523 - //We read a deterministic generated length of data before closing the connection to offset padding read pattern - drainSum := dice.RollDeterministic(48, int64(actualHash)) - if err := s.DrainConnN(reader, drainSum); err != nil { - return nil, newError("invalid auth, failed to drain connection").Base(err) - } - return nil, newError("invalid auth, connection drained") + return nil, drainConnection(newError("invalid auth")) } if request.Address == nil { diff --git a/proxy/vmess/validator.go b/proxy/vmess/validator.go index 1690fac7..00d14d44 100644 --- a/proxy/vmess/validator.go +++ b/proxy/vmess/validator.go @@ -3,9 +3,11 @@ package vmess import ( + "hash/crc64" "strings" "sync" "time" + "v2ray.com/core/common/dice" "v2ray.com/core/common" "v2ray.com/core/common/protocol" @@ -26,11 +28,13 @@ type user struct { // TimedUserValidator is a user Validator based on time. type TimedUserValidator struct { sync.RWMutex - users []*user - userHash map[[16]byte]indexTimePair - hasher protocol.IDHash - baseTime protocol.Timestamp - task *task.Periodic + users []*user + userHash map[[16]byte]indexTimePair + hasher protocol.IDHash + baseTime protocol.Timestamp + task *task.Periodic + behaviorSeed uint64 + behaviorFused bool } type indexTimePair struct { @@ -124,6 +128,11 @@ func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error { v.users = append(v.users, uu) v.generateNewHashes(protocol.Timestamp(nowSec), uu) + if v.behaviorFused == false { + account := uu.user.Account.(*MemoryAccount) + crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), account.ID.Bytes()) + } + return nil } @@ -131,6 +140,8 @@ func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protoco defer v.RUnlock() v.RLock() + v.behaviorFused = true + var fixedSizeHash [16]byte copy(fixedSizeHash[:], userHash) pair, found := v.userHash[fixedSizeHash] @@ -170,3 +181,13 @@ func (v *TimedUserValidator) Remove(email string) bool { func (v *TimedUserValidator) Close() error { return v.task.Close() } + +func (v *TimedUserValidator) GetBehaviorSeed() uint64 { + v.Lock() + defer v.Unlock() + v.behaviorFused = true + if v.behaviorSeed == 0 { + v.behaviorSeed = dice.RollUint64() + } + return v.behaviorSeed +} From ca032dd00fef86558174819d988fdeceb47a52b1 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 15:16:17 +0800 Subject: [PATCH 03/15] Drain Connection Based on uuid based behavior seed(skip auth info drain for now) --- proxy/vmess/encoding/server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index a7b6e81b..0d6cfcfc 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -152,8 +152,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request user, timestamp, valid := s.userValidator.Get(buffer.Bytes()) if !valid { - //It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523 - return nil, drainConnection(newError("invalid user")) + return nil, newError("invalid user") } iv := hashTimestamp(md5.New(), timestamp) From 13cef1943051c112e8572c9fa2d8abd97f86082c Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 17:11:51 +0800 Subject: [PATCH 04/15] Increase Drain Pool --- proxy/vmess/encoding/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 0d6cfcfc..00cb3139 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -126,7 +126,7 @@ func parseSecurityType(b byte) protocol.SecurityType { func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) { buffer := buf.New() behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed())) - DrainSize := behaviorRand.Roll(387) + 16 + 38 + DrainSize := behaviorRand.Roll(3266) + 16 + 38 + dice.Roll(behaviorRand.Roll(64)) readSizeRemain := DrainSize drainConnection := func(e error) error { From 5e5953433310000d76108374da0c02132f192a97 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 17:30:48 +0800 Subject: [PATCH 05/15] Fix when rolled number is zero --- common/protocol/headers.go | 2 ++ proxy/vmess/encoding/server.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/protocol/headers.go b/common/protocol/headers.go index 83a5fdd6..007ff5aa 100644 --- a/common/protocol/headers.go +++ b/common/protocol/headers.go @@ -38,6 +38,8 @@ const ( RequestOptionChunkMasking bitmask.Byte = 0x04 RequestOptionGlobalPadding bitmask.Byte = 0x08 + + RequestOptionEarlyChecksum bitmask.Byte = 0x16 ) type RequestHeader struct { diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 00cb3139..318326a5 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -126,7 +126,7 @@ func parseSecurityType(b byte) protocol.SecurityType { func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) { buffer := buf.New() behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed())) - DrainSize := behaviorRand.Roll(3266) + 16 + 38 + dice.Roll(behaviorRand.Roll(64)) + DrainSize := behaviorRand.Roll(3266) + 16 + 38 + dice.Roll(behaviorRand.Roll(64)+1) readSizeRemain := DrainSize drainConnection := func(e error) error { From 944c45a776ee562153dca1797bb8ec75328be0f4 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 18:01:43 +0800 Subject: [PATCH 06/15] Fix not update behavior seed --- proxy/vmess/validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/vmess/validator.go b/proxy/vmess/validator.go index 00d14d44..1682300f 100644 --- a/proxy/vmess/validator.go +++ b/proxy/vmess/validator.go @@ -130,7 +130,7 @@ func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error { if v.behaviorFused == false { account := uu.user.Account.(*MemoryAccount) - crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), account.ID.Bytes()) + v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), account.ID.Bytes()) } return nil From 34c13f3b27b9d514ac796ec6cca62eaad9febedf Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 18:18:31 +0800 Subject: [PATCH 07/15] Detailed code and output --- proxy/vmess/encoding/server.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 318326a5..1e36fc24 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -126,7 +126,10 @@ func parseSecurityType(b byte) protocol.SecurityType { func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) { buffer := buf.New() behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed())) - DrainSize := behaviorRand.Roll(3266) + 16 + 38 + dice.Roll(behaviorRand.Roll(64)+1) + BaseDrainSize := behaviorRand.Roll(3266) + RandDrainMax := behaviorRand.Roll(64) + 1 + RandDrainRolled := dice.Roll(RandDrainMax) + DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled readSizeRemain := DrainSize drainConnection := func(e error) error { @@ -135,9 +138,9 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request if readSizeRemain > 0 { err := s.DrainConnN(reader, readSizeRemain) if err != nil { - return newError("failed to drain connection").Base(err).Base(e) + return newError("failed to drain connection DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(err).Base(e) } - return newError("connection drained DrainSize = ", DrainSize).Base(e) + return newError("connection drained DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(e) } return e } From 8f45736dd33d4b9ed63e3772d17e09ee5e15f595 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 2 Jun 2020 20:02:08 +0800 Subject: [PATCH 08/15] drain replayed connection --- proxy/vmess/encoding/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 1e36fc24..eb7d90c1 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -182,7 +182,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request sid.key = s.requestBodyKey sid.nonce = s.requestBodyIV if !s.sessionHistory.addIfNotExits(sid) { - return nil, newError("duplicated session id, possibly under replay attack") + return nil, drainConnection(newError("duplicated session id, possibly under replay attack")) } s.responseHeader = buffer.Byte(33) // 1 byte From 220b783caa667817a77c0b9dd5ed8375b17f8f02 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 3 Jun 2020 09:12:06 +0800 Subject: [PATCH 09/15] Align Timeout Value --- features/policy/policy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/policy/policy.go b/features/policy/policy.go index 5d581fe2..984b32f4 100644 --- a/features/policy/policy.go +++ b/features/policy/policy.go @@ -113,7 +113,9 @@ func defaultBufferPolicy() Buffer { func SessionDefault() Session { return Session{ Timeouts: Timeout{ - Handshake: time.Second * 4, + //Align Handshake timeout with nginx client_header_timeout + //So that this value will not indicate server identity + Handshake: time.Second * 60, ConnectionIdle: time.Second * 300, UplinkOnly: time.Second * 1, DownlinkOnly: time.Second * 1, From b55fceba682bbf7ce7b8e25e47c64432ae959e16 Mon Sep 17 00:00:00 2001 From: Kslr Date: Wed, 3 Jun 2020 09:58:15 +0800 Subject: [PATCH 10/15] Revert "fix bazel not set home" --- infra/bazel/build.bzl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infra/bazel/build.bzl b/infra/bazel/build.bzl index 11267e71..31217570 100644 --- a/infra/bazel/build.bzl +++ b/infra/bazel/build.bzl @@ -41,7 +41,9 @@ def _go_command(ctx): if ctx.attr.arm: envs+=["GOARM="+ctx.attr.arm] - command = " ".join(envs) + " " + command + switchToPwd="cd ${SPWD} && " + + command = switchToPwd + " ".join(envs) + " " + command ctx.actions.run_shell( outputs = [output_file], From 38e89bd2c740d660225ba6a5782a730860343bb4 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 3 Jun 2020 11:25:47 +0800 Subject: [PATCH 11/15] VMess will Drain Connection On Auth failure --- proxy/vmess/encoding/server.go | 2 +- testing/scenarios/command_test.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index eb7d90c1..6e6a5e4b 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -155,7 +155,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request user, timestamp, valid := s.userValidator.Get(buffer.Bytes()) if !valid { - return nil, newError("invalid user") + return nil, drainConnection(newError("invalid user")) } iv := hashTimestamp(md5.New(), timestamp) diff --git a/testing/scenarios/command_test.go b/testing/scenarios/command_test.go index ace50ca0..bce0bfaf 100644 --- a/testing/scenarios/command_test.go +++ b/testing/scenarios/command_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" "testing" "time" @@ -265,7 +266,9 @@ func TestCommanderAddRemoveUser(t *testing.T) { common.Must(err) defer CloseAllServers(servers) - if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != io.EOF { + if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != io.EOF && + /*We might wish to drain the connection*/ + (err != nil && !strings.HasSuffix(err.Error(), "i/o timeout")) { t.Fatal("expected error: ", err) } From 087a62ef3dd1234318a3c9c9e6fa7e7137e5ae9e Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 3 Jun 2020 15:16:03 +0800 Subject: [PATCH 12/15] Fixed HTTP response not adjusted based on request --- transport/internet/headers/http/http.go | 122 ++++++++---- transport/internet/headers/http/http_test.go | 185 +++++++++++++++++- .../headers/http/linkedreadRequest.go | 11 ++ transport/internet/headers/http/resp.go | 49 +++++ 4 files changed, 322 insertions(+), 45 deletions(-) create mode 100644 transport/internet/headers/http/linkedreadRequest.go create mode 100644 transport/internet/headers/http/resp.go diff --git a/transport/internet/headers/http/http.go b/transport/internet/headers/http/http.go index 48c2c990..fb419a9e 100644 --- a/transport/internet/headers/http/http.go +++ b/transport/internet/headers/http/http.go @@ -3,6 +3,7 @@ package http //go:generate errorgen import ( + "bufio" "bytes" "context" "io" @@ -28,6 +29,8 @@ const ( var ( ErrHeaderToLong = newError("Header too long.") + + ErrHeaderMisMatch = newError("Header Mismatch.") ) type Reader interface { @@ -51,12 +54,22 @@ func (NoOpWriter) Write(io.Writer) error { } type HeaderReader struct { + req *http.Request + expectedHeader *RequestConfig } -func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { +func (h *HeaderReader) ExpectThisRequest(expectedHeader *RequestConfig) *HeaderReader { + h.expectedHeader = expectedHeader + return h +} + +func (h *HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { buffer := buf.New() totalBytes := int32(0) endingDetected := false + + var headerBuf bytes.Buffer + for totalBytes < maxHeaderLength { _, err := buffer.ReadFrom(reader) if err != nil { @@ -64,6 +77,7 @@ func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { return nil, err } if n := bytes.Index(buffer.Bytes(), []byte(ENDING)); n != -1 { + headerBuf.Write(buffer.BytesRange(0, int32(n+len(ENDING)))) buffer.Advance(int32(n + len(ENDING))) endingDetected = true break @@ -71,19 +85,52 @@ func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { lenEnding := int32(len(ENDING)) if buffer.Len() >= lenEnding { totalBytes += buffer.Len() - lenEnding + headerBuf.Write(buffer.BytesRange(0, buffer.Len()-lenEnding)) leftover := buffer.BytesFrom(-lenEnding) buffer.Clear() copy(buffer.Extend(lenEnding), leftover) } } - if buffer.IsEmpty() { - buffer.Release() - return nil, nil - } + if !endingDetected { buffer.Release() return nil, ErrHeaderToLong } + + if h.expectedHeader == nil { + if buffer.IsEmpty() { + buffer.Release() + return nil, nil + } + return buffer, nil + } + + //Parse the request + + if req, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes())), false); err != nil { + return nil, err + } else { + h.req = req + } + + //Check req + path := h.req.URL.Path + hasThisUri := false + for _, u := range h.expectedHeader.Uri { + if u == path { + hasThisUri = true + } + } + + if hasThisUri == false { + return nil, ErrHeaderMisMatch + } + + if buffer.IsEmpty() { + buffer.Release() + return nil, nil + } + return buffer, nil } @@ -110,18 +157,24 @@ func (w *HeaderWriter) Write(writer io.Writer) error { type HttpConn struct { net.Conn - readBuffer *buf.Buffer - oneTimeReader Reader - oneTimeWriter Writer - errorWriter Writer + readBuffer *buf.Buffer + oneTimeReader Reader + oneTimeWriter Writer + errorWriter Writer + errorMismatchWriter Writer + errorTooLongWriter Writer + + errReason error } -func NewHttpConn(conn net.Conn, reader Reader, writer Writer, errorWriter Writer) *HttpConn { +func NewHttpConn(conn net.Conn, reader Reader, writer Writer, errorWriter Writer, errorMismatchWriter Writer, errorTooLongWriter Writer) *HttpConn { return &HttpConn{ - Conn: conn, - oneTimeReader: reader, - oneTimeWriter: writer, - errorWriter: errorWriter, + Conn: conn, + oneTimeReader: reader, + oneTimeWriter: writer, + errorWriter: errorWriter, + errorMismatchWriter: errorMismatchWriter, + errorTooLongWriter: errorTooLongWriter, } } @@ -129,6 +182,7 @@ func (c *HttpConn) Read(b []byte) (int, error) { if c.oneTimeReader != nil { buffer, err := c.oneTimeReader.Read(c.Conn) if err != nil { + c.errReason = err return 0, err } c.readBuffer = buffer @@ -165,7 +219,16 @@ func (c *HttpConn) Close() error { if c.oneTimeWriter != nil && c.errorWriter != nil { // Connection is being closed but header wasn't sent. This means the client request // is probably not valid. Sending back a server error header in this case. - c.errorWriter.Write(c.Conn) + + //Write response based on error reason + + if c.errReason == ErrHeaderMisMatch { + c.errorMismatchWriter.Write(c.Conn) + } else if c.errReason == ErrHeaderToLong { + c.errorTooLongWriter.Write(c.Conn) + } else { + c.errorWriter.Write(c.Conn) + } } return c.Conn.Close() @@ -230,36 +293,17 @@ func (a HttpAuthenticator) Client(conn net.Conn) net.Conn { if a.config.Response != nil { writer = a.GetClientWriter() } - return NewHttpConn(conn, reader, writer, NoOpWriter{}) + return NewHttpConn(conn, reader, writer, NoOpWriter{}, NoOpWriter{}, NoOpWriter{}) } func (a HttpAuthenticator) Server(conn net.Conn) net.Conn { if a.config.Request == nil && a.config.Response == nil { return conn } - return NewHttpConn(conn, new(HeaderReader), a.GetServerWriter(), formResponseHeader(&ResponseConfig{ - Version: &Version{ - Value: "1.1", - }, - Status: &Status{ - Code: "500", - Reason: "Internal Server Error", - }, - Header: []*Header{ - { - Name: "Connection", - Value: []string{"close"}, - }, - { - Name: "Cache-Control", - Value: []string{"private"}, - }, - { - Name: "Content-Length", - Value: []string{"0"}, - }, - }, - })) + return NewHttpConn(conn, new(HeaderReader).ExpectThisRequest(a.config.Request), a.GetServerWriter(), + formResponseHeader(resp400), + formResponseHeader(resp404), + formResponseHeader(resp400)) } func NewHttpAuthenticator(ctx context.Context, config *Config) (HttpAuthenticator, error) { diff --git a/transport/internet/headers/http/http_test.go b/transport/internet/headers/http/http_test.go index 0aa0165f..6b973531 100644 --- a/transport/internet/headers/http/http_test.go +++ b/transport/internet/headers/http/http_test.go @@ -1,9 +1,12 @@ package http_test import ( + "bufio" "bytes" "context" "crypto/rand" + "io" + "strings" "testing" "time" @@ -28,10 +31,15 @@ func TestReaderWriter(t *testing.T) { reader := &HeaderReader{} buffer, err := reader.Read(cache) - common.Must(err) - if buffer.String() != "efg" { - t.Error("buffer: ", buffer.String()) + if err != nil && !strings.HasPrefix(err.Error(), "malformed HTTP request") { + t.Error("unknown error ", err) } + _ = buffer + return + /* + if buffer.String() != "efg" { + t.Error("buffer: ", buffer.String()) + }*/ } func TestRequestHeader(t *testing.T) { @@ -65,10 +73,16 @@ func TestLongRequestHeader(t *testing.T) { reader := HeaderReader{} b, err := reader.Read(bytes.NewReader(payload)) - common.Must(err) - if b.String() != "abcd" { - t.Error("expect content abcd, but actually ", b.String()) + + if err != nil && !(strings.HasPrefix(err.Error(), "invalid") || strings.HasPrefix(err.Error(), "malformed")) { + t.Error("unknown error ", err) } + _ = b + /* + common.Must(err) + if b.String() != "abcd" { + t.Error("expect content abcd, but actually ", b.String()) + }*/ } func TestConnection(t *testing.T) { @@ -143,3 +157,162 @@ func TestConnection(t *testing.T) { t.Error("response: ", string(actualResponse[:totalBytes])) } } + +func TestConnectionInvPath(t *testing.T) { + auth, err := NewHttpAuthenticator(context.Background(), &Config{ + Request: &RequestConfig{ + Method: &Method{Value: "Post"}, + Uri: []string{"/testpath"}, + Header: []*Header{ + { + Name: "Host", + Value: []string{"www.v2ray.com", "www.google.com"}, + }, + { + Name: "User-Agent", + Value: []string{"Test-Agent"}, + }, + }, + }, + Response: &ResponseConfig{ + Version: &Version{ + Value: "1.1", + }, + Status: &Status{ + Code: "404", + Reason: "Not Found", + }, + }, + }) + common.Must(err) + + authR, err := NewHttpAuthenticator(context.Background(), &Config{ + Request: &RequestConfig{ + Method: &Method{Value: "Post"}, + Uri: []string{"/testpathErr"}, + Header: []*Header{ + { + Name: "Host", + Value: []string{"www.v2ray.com", "www.google.com"}, + }, + { + Name: "User-Agent", + Value: []string{"Test-Agent"}, + }, + }, + }, + Response: &ResponseConfig{ + Version: &Version{ + Value: "1.1", + }, + Status: &Status{ + Code: "404", + Reason: "Not Found", + }, + }, + }) + common.Must(err) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + common.Must(err) + + go func() { + conn, err := listener.Accept() + common.Must(err) + authConn := auth.Server(conn) + b := make([]byte, 256) + for { + n, err := authConn.Read(b) + if err != nil { + authConn.Close() + break + } + _, err = authConn.Write(b[:n]) + common.Must(err) + } + }() + + conn, err := net.DialTCP("tcp", nil, listener.Addr().(*net.TCPAddr)) + common.Must(err) + + authConn := authR.Client(conn) + defer authConn.Close() + + authConn.Write([]byte("Test payload")) + authConn.Write([]byte("Test payload 2")) + + expectedResponse := "Test payloadTest payload 2" + actualResponse := make([]byte, 256) + deadline := time.Now().Add(time.Second * 5) + totalBytes := 0 + for { + n, err := authConn.Read(actualResponse[totalBytes:]) + if err != io.EOF { + t.Error("Unexpected Error", err) + } + totalBytes += n + if totalBytes >= len(expectedResponse) || time.Now().After(deadline) { + break + } + } + return +} + +func TestConnectionInvReq(t *testing.T) { + auth, err := NewHttpAuthenticator(context.Background(), &Config{ + Request: &RequestConfig{ + Method: &Method{Value: "Post"}, + Uri: []string{"/testpath"}, + Header: []*Header{ + { + Name: "Host", + Value: []string{"www.v2ray.com", "www.google.com"}, + }, + { + Name: "User-Agent", + Value: []string{"Test-Agent"}, + }, + }, + }, + Response: &ResponseConfig{ + Version: &Version{ + Value: "1.1", + }, + Status: &Status{ + Code: "404", + Reason: "Not Found", + }, + }, + }) + common.Must(err) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + common.Must(err) + + go func() { + conn, err := listener.Accept() + common.Must(err) + authConn := auth.Server(conn) + b := make([]byte, 256) + for { + n, err := authConn.Read(b) + if err != nil { + authConn.Close() + break + } + _, err = authConn.Write(b[:n]) + common.Must(err) + } + }() + + conn, err := net.DialTCP("tcp", nil, listener.Addr().(*net.TCPAddr)) + common.Must(err) + + conn.Write([]byte("ABCDEFGHIJKMLN\r\n\r\n")) + l, _, err := bufio.NewReader(conn).ReadLine() + common.Must(err) + if !strings.HasPrefix(string(l), "HTTP/1.1 400 Bad Request") { + t.Error("Resp to non http conn", string(l)) + } + return +} diff --git a/transport/internet/headers/http/linkedreadRequest.go b/transport/internet/headers/http/linkedreadRequest.go new file mode 100644 index 00000000..a6776095 --- /dev/null +++ b/transport/internet/headers/http/linkedreadRequest.go @@ -0,0 +1,11 @@ +package http + +import ( + "bufio" + "net/http" + + _ "unsafe" // required to use //go:linkname +) + +//go:linkname readRequest net/http.readRequest +func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *http.Request, err error) diff --git a/transport/internet/headers/http/resp.go b/transport/internet/headers/http/resp.go new file mode 100644 index 00000000..6050d639 --- /dev/null +++ b/transport/internet/headers/http/resp.go @@ -0,0 +1,49 @@ +package http + +var resp400 = &ResponseConfig{ + Version: &Version{ + Value: "1.1", + }, + Status: &Status{ + Code: "400", + Reason: "Bad Request", + }, + Header: []*Header{ + { + Name: "Connection", + Value: []string{"close"}, + }, + { + Name: "Cache-Control", + Value: []string{"private"}, + }, + { + Name: "Content-Length", + Value: []string{"0"}, + }, + }, +} + +var resp404 = &ResponseConfig{ + Version: &Version{ + Value: "1.1", + }, + Status: &Status{ + Code: "404", + Reason: "Not Found", + }, + Header: []*Header{ + { + Name: "Connection", + Value: []string{"close"}, + }, + { + Name: "Cache-Control", + Value: []string{"private"}, + }, + { + Name: "Content-Length", + Value: []string{"0"}, + }, + }, +} From 221a13e658148d0516818db9249b3c17e396cbb9 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 3 Jun 2020 15:44:25 +0800 Subject: [PATCH 13/15] Early detection of non http traffic to match web server behavior --- transport/internet/headers/http/http.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transport/internet/headers/http/http.go b/transport/internet/headers/http/http.go index fb419a9e..6f65e975 100644 --- a/transport/internet/headers/http/http.go +++ b/transport/internet/headers/http/http.go @@ -89,6 +89,10 @@ func (h *HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { leftover := buffer.BytesFrom(-lenEnding) buffer.Clear() copy(buffer.Extend(lenEnding), leftover) + + if _, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes())), false); err != io.ErrUnexpectedEOF { + return nil, err + } } } From d31a067fc325680f9f5f2ab4a1fe361058ba5efe Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 3 Jun 2020 16:09:16 +0800 Subject: [PATCH 14/15] Release Version --- core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core.go b/core.go index 96fdf9e7..d11fc483 100755 --- a/core.go +++ b/core.go @@ -19,7 +19,7 @@ import ( ) var ( - version = "4.23.3" + version = "4.23.4" build = "Custom" codename = "V2Fly, a community-driven edition of V2Ray." intro = "A unified platform for anti-censorship." From b610fc0a70b5e444abd3afe513bfff6ca6c85efb Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 3 Jun 2020 16:32:58 +0800 Subject: [PATCH 15/15] make error less sensitive --- transport/internet/headers/http/http_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/transport/internet/headers/http/http_test.go b/transport/internet/headers/http/http_test.go index 6b973531..28f2d9ae 100644 --- a/transport/internet/headers/http/http_test.go +++ b/transport/internet/headers/http/http_test.go @@ -5,7 +5,6 @@ import ( "bytes" "context" "crypto/rand" - "io" "strings" "testing" "time" @@ -247,8 +246,10 @@ func TestConnectionInvPath(t *testing.T) { totalBytes := 0 for { n, err := authConn.Read(actualResponse[totalBytes:]) - if err != io.EOF { - t.Error("Unexpected Error", err) + if err == nil { + t.Error("Error Expected", err) + } else { + return } totalBytes += n if totalBytes >= len(expectedResponse) || time.Now().After(deadline) {