mirror of https://github.com/v2ray/v2ray-core
commit
6b5d2fed91
|
@ -28,6 +28,25 @@ func RollUint16() uint16 {
|
||||||
return uint16(rand.Intn(65536))
|
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() {
|
func init() {
|
||||||
rand.Seed(time.Now().Unix())
|
rand.Seed(time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ const (
|
||||||
RequestOptionChunkMasking bitmask.Byte = 0x04
|
RequestOptionChunkMasking bitmask.Byte = 0x04
|
||||||
|
|
||||||
RequestOptionGlobalPadding bitmask.Byte = 0x08
|
RequestOptionGlobalPadding bitmask.Byte = 0x08
|
||||||
|
|
||||||
|
RequestOptionEarlyChecksum bitmask.Byte = 0x16
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestHeader struct {
|
type RequestHeader struct {
|
||||||
|
|
2
core.go
2
core.go
|
@ -19,7 +19,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "4.23.3"
|
version = "4.23.4"
|
||||||
build = "Custom"
|
build = "Custom"
|
||||||
codename = "V2Fly, a community-driven edition of V2Ray."
|
codename = "V2Fly, a community-driven edition of V2Ray."
|
||||||
intro = "A unified platform for anti-censorship."
|
intro = "A unified platform for anti-censorship."
|
||||||
|
|
|
@ -113,7 +113,9 @@ func defaultBufferPolicy() Buffer {
|
||||||
func SessionDefault() Session {
|
func SessionDefault() Session {
|
||||||
return Session{
|
return Session{
|
||||||
Timeouts: Timeout{
|
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,
|
ConnectionIdle: time.Second * 300,
|
||||||
UplinkOnly: time.Second * 1,
|
UplinkOnly: time.Second * 1,
|
||||||
DownlinkOnly: time.Second * 1,
|
DownlinkOnly: time.Second * 1,
|
||||||
|
|
|
@ -125,7 +125,29 @@ func parseSecurityType(b byte) protocol.SecurityType {
|
||||||
// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.
|
// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.
|
||||||
func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) {
|
func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) {
|
||||||
buffer := buf.New()
|
buffer := buf.New()
|
||||||
defer buffer.Release()
|
behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed()))
|
||||||
|
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 {
|
||||||
|
//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 DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(err).Base(e)
|
||||||
|
}
|
||||||
|
return newError("connection drained DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(e)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
buffer.Release()
|
||||||
|
}()
|
||||||
|
|
||||||
if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil {
|
if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil {
|
||||||
return nil, newError("failed to read request header").Base(err)
|
return nil, newError("failed to read request header").Base(err)
|
||||||
|
@ -133,7 +155,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
|
||||||
|
|
||||||
user, timestamp, valid := s.userValidator.Get(buffer.Bytes())
|
user, timestamp, valid := s.userValidator.Get(buffer.Bytes())
|
||||||
if !valid {
|
if !valid {
|
||||||
return nil, newError("invalid user")
|
return nil, drainConnection(newError("invalid user"))
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := hashTimestamp(md5.New(), timestamp)
|
iv := hashTimestamp(md5.New(), timestamp)
|
||||||
|
@ -142,6 +164,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
|
||||||
aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv[:])
|
aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv[:])
|
||||||
decryptor := crypto.NewCryptionReader(aesStream, reader)
|
decryptor := crypto.NewCryptionReader(aesStream, reader)
|
||||||
|
|
||||||
|
readSizeRemain -= int(buffer.Len())
|
||||||
buffer.Clear()
|
buffer.Clear()
|
||||||
if _, err := buffer.ReadFullFrom(decryptor, 38); err != nil {
|
if _, err := buffer.ReadFullFrom(decryptor, 38); err != nil {
|
||||||
return nil, newError("failed to read request header").Base(err)
|
return nil, newError("failed to read request header").Base(err)
|
||||||
|
@ -159,7 +182,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
|
||||||
sid.key = s.requestBodyKey
|
sid.key = s.requestBodyKey
|
||||||
sid.nonce = s.requestBodyIV
|
sid.nonce = s.requestBodyIV
|
||||||
if !s.sessionHistory.addIfNotExits(sid) {
|
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
|
s.responseHeader = buffer.Byte(33) // 1 byte
|
||||||
|
@ -197,12 +220,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
|
||||||
|
|
||||||
if actualHash != expectedHash {
|
if actualHash != expectedHash {
|
||||||
//It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523
|
//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
|
return nil, drainConnection(newError("invalid auth"))
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.Address == nil {
|
if request.Address == nil {
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
package vmess
|
package vmess
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"hash/crc64"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"v2ray.com/core/common/dice"
|
||||||
|
|
||||||
"v2ray.com/core/common"
|
"v2ray.com/core/common"
|
||||||
"v2ray.com/core/common/protocol"
|
"v2ray.com/core/common/protocol"
|
||||||
|
@ -31,6 +33,8 @@ type TimedUserValidator struct {
|
||||||
hasher protocol.IDHash
|
hasher protocol.IDHash
|
||||||
baseTime protocol.Timestamp
|
baseTime protocol.Timestamp
|
||||||
task *task.Periodic
|
task *task.Periodic
|
||||||
|
behaviorSeed uint64
|
||||||
|
behaviorFused bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type indexTimePair struct {
|
type indexTimePair struct {
|
||||||
|
@ -124,6 +128,11 @@ func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error {
|
||||||
v.users = append(v.users, uu)
|
v.users = append(v.users, uu)
|
||||||
v.generateNewHashes(protocol.Timestamp(nowSec), uu)
|
v.generateNewHashes(protocol.Timestamp(nowSec), uu)
|
||||||
|
|
||||||
|
if v.behaviorFused == false {
|
||||||
|
account := uu.user.Account.(*MemoryAccount)
|
||||||
|
v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), account.ID.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +140,8 @@ func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protoco
|
||||||
defer v.RUnlock()
|
defer v.RUnlock()
|
||||||
v.RLock()
|
v.RLock()
|
||||||
|
|
||||||
|
v.behaviorFused = true
|
||||||
|
|
||||||
var fixedSizeHash [16]byte
|
var fixedSizeHash [16]byte
|
||||||
copy(fixedSizeHash[:], userHash)
|
copy(fixedSizeHash[:], userHash)
|
||||||
pair, found := v.userHash[fixedSizeHash]
|
pair, found := v.userHash[fixedSizeHash]
|
||||||
|
@ -170,3 +181,13 @@ func (v *TimedUserValidator) Remove(email string) bool {
|
||||||
func (v *TimedUserValidator) Close() error {
|
func (v *TimedUserValidator) Close() error {
|
||||||
return v.task.Close()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -265,7 +266,9 @@ func TestCommanderAddRemoveUser(t *testing.T) {
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer CloseAllServers(servers)
|
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)
|
t.Fatal("expected error: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package http
|
||||||
//go:generate errorgen
|
//go:generate errorgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
@ -28,6 +29,8 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrHeaderToLong = newError("Header too long.")
|
ErrHeaderToLong = newError("Header too long.")
|
||||||
|
|
||||||
|
ErrHeaderMisMatch = newError("Header Mismatch.")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader interface {
|
type Reader interface {
|
||||||
|
@ -51,12 +54,22 @@ func (NoOpWriter) Write(io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderReader struct {
|
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()
|
buffer := buf.New()
|
||||||
totalBytes := int32(0)
|
totalBytes := int32(0)
|
||||||
endingDetected := false
|
endingDetected := false
|
||||||
|
|
||||||
|
var headerBuf bytes.Buffer
|
||||||
|
|
||||||
for totalBytes < maxHeaderLength {
|
for totalBytes < maxHeaderLength {
|
||||||
_, err := buffer.ReadFrom(reader)
|
_, err := buffer.ReadFrom(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -64,6 +77,7 @@ func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if n := bytes.Index(buffer.Bytes(), []byte(ENDING)); n != -1 {
|
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)))
|
buffer.Advance(int32(n + len(ENDING)))
|
||||||
endingDetected = true
|
endingDetected = true
|
||||||
break
|
break
|
||||||
|
@ -71,19 +85,56 @@ func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) {
|
||||||
lenEnding := int32(len(ENDING))
|
lenEnding := int32(len(ENDING))
|
||||||
if buffer.Len() >= lenEnding {
|
if buffer.Len() >= lenEnding {
|
||||||
totalBytes += buffer.Len() - lenEnding
|
totalBytes += buffer.Len() - lenEnding
|
||||||
|
headerBuf.Write(buffer.BytesRange(0, buffer.Len()-lenEnding))
|
||||||
leftover := buffer.BytesFrom(-lenEnding)
|
leftover := buffer.BytesFrom(-lenEnding)
|
||||||
buffer.Clear()
|
buffer.Clear()
|
||||||
copy(buffer.Extend(lenEnding), leftover)
|
copy(buffer.Extend(lenEnding), leftover)
|
||||||
|
|
||||||
|
if _, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes())), false); err != io.ErrUnexpectedEOF {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buffer.IsEmpty() {
|
|
||||||
buffer.Release()
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !endingDetected {
|
if !endingDetected {
|
||||||
buffer.Release()
|
buffer.Release()
|
||||||
return nil, ErrHeaderToLong
|
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
|
return buffer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,14 +165,20 @@ type HttpConn struct {
|
||||||
oneTimeReader Reader
|
oneTimeReader Reader
|
||||||
oneTimeWriter Writer
|
oneTimeWriter Writer
|
||||||
errorWriter 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{
|
return &HttpConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
oneTimeReader: reader,
|
oneTimeReader: reader,
|
||||||
oneTimeWriter: writer,
|
oneTimeWriter: writer,
|
||||||
errorWriter: errorWriter,
|
errorWriter: errorWriter,
|
||||||
|
errorMismatchWriter: errorMismatchWriter,
|
||||||
|
errorTooLongWriter: errorTooLongWriter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +186,7 @@ func (c *HttpConn) Read(b []byte) (int, error) {
|
||||||
if c.oneTimeReader != nil {
|
if c.oneTimeReader != nil {
|
||||||
buffer, err := c.oneTimeReader.Read(c.Conn)
|
buffer, err := c.oneTimeReader.Read(c.Conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.errReason = err
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
c.readBuffer = buffer
|
c.readBuffer = buffer
|
||||||
|
@ -165,8 +223,17 @@ func (c *HttpConn) Close() error {
|
||||||
if c.oneTimeWriter != nil && c.errorWriter != nil {
|
if c.oneTimeWriter != nil && c.errorWriter != nil {
|
||||||
// Connection is being closed but header wasn't sent. This means the client request
|
// 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.
|
// is probably not valid. Sending back a server error header in this case.
|
||||||
|
|
||||||
|
//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)
|
c.errorWriter.Write(c.Conn)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return c.Conn.Close()
|
return c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -230,36 +297,17 @@ func (a HttpAuthenticator) Client(conn net.Conn) net.Conn {
|
||||||
if a.config.Response != nil {
|
if a.config.Response != nil {
|
||||||
writer = a.GetClientWriter()
|
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 {
|
func (a HttpAuthenticator) Server(conn net.Conn) net.Conn {
|
||||||
if a.config.Request == nil && a.config.Response == nil {
|
if a.config.Request == nil && a.config.Response == nil {
|
||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
return NewHttpConn(conn, new(HeaderReader), a.GetServerWriter(), formResponseHeader(&ResponseConfig{
|
return NewHttpConn(conn, new(HeaderReader).ExpectThisRequest(a.config.Request), a.GetServerWriter(),
|
||||||
Version: &Version{
|
formResponseHeader(resp400),
|
||||||
Value: "1.1",
|
formResponseHeader(resp404),
|
||||||
},
|
formResponseHeader(resp400))
|
||||||
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"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpAuthenticator(ctx context.Context, config *Config) (HttpAuthenticator, error) {
|
func NewHttpAuthenticator(ctx context.Context, config *Config) (HttpAuthenticator, error) {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package http_test
|
package http_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -28,10 +30,15 @@ func TestReaderWriter(t *testing.T) {
|
||||||
|
|
||||||
reader := &HeaderReader{}
|
reader := &HeaderReader{}
|
||||||
buffer, err := reader.Read(cache)
|
buffer, err := reader.Read(cache)
|
||||||
common.Must(err)
|
if err != nil && !strings.HasPrefix(err.Error(), "malformed HTTP request") {
|
||||||
|
t.Error("unknown error ", err)
|
||||||
|
}
|
||||||
|
_ = buffer
|
||||||
|
return
|
||||||
|
/*
|
||||||
if buffer.String() != "efg" {
|
if buffer.String() != "efg" {
|
||||||
t.Error("buffer: ", buffer.String())
|
t.Error("buffer: ", buffer.String())
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestHeader(t *testing.T) {
|
func TestRequestHeader(t *testing.T) {
|
||||||
|
@ -65,10 +72,16 @@ func TestLongRequestHeader(t *testing.T) {
|
||||||
|
|
||||||
reader := HeaderReader{}
|
reader := HeaderReader{}
|
||||||
b, err := reader.Read(bytes.NewReader(payload))
|
b, err := reader.Read(bytes.NewReader(payload))
|
||||||
|
|
||||||
|
if err != nil && !(strings.HasPrefix(err.Error(), "invalid") || strings.HasPrefix(err.Error(), "malformed")) {
|
||||||
|
t.Error("unknown error ", err)
|
||||||
|
}
|
||||||
|
_ = b
|
||||||
|
/*
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if b.String() != "abcd" {
|
if b.String() != "abcd" {
|
||||||
t.Error("expect content abcd, but actually ", b.String())
|
t.Error("expect content abcd, but actually ", b.String())
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnection(t *testing.T) {
|
func TestConnection(t *testing.T) {
|
||||||
|
@ -143,3 +156,164 @@ func TestConnection(t *testing.T) {
|
||||||
t.Error("response: ", string(actualResponse[:totalBytes]))
|
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 == nil {
|
||||||
|
t.Error("Error Expected", err)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -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"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in New Issue