Browse Source

support for http and tls header sniffing

Darien Raymond 8 năm trước cách đây
mục cha
commit
77521029b1

+ 8 - 19
app/dispatcher/config.pb.go

@@ -16,7 +16,6 @@ var _ = math.Inf
 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
 
 type SessionConfig struct {
-	AllowPassiveConnection bool `protobuf:"varint,1,opt,name=allow_passive_connection,json=allowPassiveConnection" json:"allow_passive_connection,omitempty"`
 }
 
 func (m *SessionConfig) Reset()                    { *m = SessionConfig{} }
@@ -24,13 +23,6 @@ func (m *SessionConfig) String() string            { return proto.CompactTextStr
 func (*SessionConfig) ProtoMessage()               {}
 func (*SessionConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 
-func (m *SessionConfig) GetAllowPassiveConnection() bool {
-	if m != nil {
-		return m.AllowPassiveConnection
-	}
-	return false
-}
-
 type Config struct {
 	Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings" json:"settings,omitempty"`
 }
@@ -55,19 +47,16 @@ func init() {
 func init() { proto.RegisterFile("v2ray.com/core/app/dispatcher/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 210 bytes of a gzipped FileDescriptorProto
+	// 176 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2a, 0x33, 0x2a, 0x4a,
 	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0x4f,
 	0xc9, 0x2c, 0x2e, 0x48, 0x2c, 0x49, 0xce, 0x48, 0x2d, 0xd2, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c,
 	0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x84, 0xa9, 0x2d, 0x4a, 0xd5, 0x4b, 0x2c, 0x28,
-	0xd0, 0x43, 0xa8, 0x53, 0xf2, 0xe4, 0xe2, 0x0d, 0x4e, 0x2d, 0x2e, 0xce, 0xcc, 0xcf, 0x73, 0x06,
-	0xeb, 0x10, 0xb2, 0xe0, 0x92, 0x48, 0xcc, 0xc9, 0xc9, 0x2f, 0x8f, 0x2f, 0x48, 0x2c, 0x2e, 0xce,
-	0x2c, 0x4b, 0x8d, 0x4f, 0xce, 0xcf, 0xcb, 0x4b, 0x4d, 0x2e, 0xc9, 0xcc, 0xcf, 0x93, 0x60, 0x54,
-	0x60, 0xd4, 0xe0, 0x08, 0x12, 0x03, 0xcb, 0x07, 0x40, 0xa4, 0x9d, 0xe1, 0xb2, 0x4a, 0x7e, 0x5c,
-	0x6c, 0x50, 0x33, 0x5c, 0xb8, 0x38, 0x8a, 0x53, 0x4b, 0x4a, 0x32, 0xf3, 0xd2, 0x8b, 0xc1, 0x7a,
-	0xb8, 0x8d, 0x34, 0xf4, 0x70, 0x3a, 0x41, 0x0f, 0xc5, 0xfe, 0x20, 0xb8, 0x4e, 0x27, 0x4f, 0x2e,
-	0xd9, 0xe4, 0xfc, 0x5c, 0xdc, 0x1a, 0x03, 0x18, 0xa3, 0xb8, 0x10, 0xbc, 0x55, 0x4c, 0x92, 0x61,
-	0x46, 0x41, 0x89, 0x95, 0x7a, 0xce, 0x20, 0x95, 0x8e, 0x05, 0x05, 0x7a, 0x2e, 0x70, 0xb9, 0x24,
-	0x36, 0x70, 0x38, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x50, 0x1d, 0x4d, 0x35, 0x01,
-	0x00, 0x00,
+	0xd0, 0x43, 0xa8, 0x53, 0x12, 0xe5, 0xe2, 0x0d, 0x4e, 0x2d, 0x2e, 0xce, 0xcc, 0xcf, 0x73, 0x06,
+	0xeb, 0xf0, 0x62, 0xe1, 0x60, 0x14, 0x60, 0x52, 0xf2, 0xe3, 0x62, 0x83, 0xf0, 0x85, 0x5c, 0xb8,
+	0x38, 0x8a, 0x53, 0x4b, 0x4a, 0x32, 0xf3, 0xd2, 0x8b, 0x25, 0x18, 0x15, 0x18, 0x35, 0xb8, 0x8d,
+	0x34, 0xf4, 0x70, 0x1a, 0xa7, 0x87, 0x62, 0x56, 0x10, 0x5c, 0xa7, 0x93, 0x27, 0x97, 0x6c, 0x72,
+	0x7e, 0x2e, 0x6e, 0x8d, 0x01, 0x8c, 0x51, 0x5c, 0x08, 0xde, 0x2a, 0x26, 0xc9, 0x30, 0xa3, 0xa0,
+	0xc4, 0x4a, 0x3d, 0x67, 0x90, 0x4a, 0xc7, 0x82, 0x02, 0x3d, 0x17, 0xb8, 0x5c, 0x12, 0x1b, 0xd8,
+	0x4f, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x47, 0xc5, 0xd6, 0x01, 0x01, 0x00, 0x00,
 }

+ 2 - 1
app/dispatcher/config.proto

@@ -7,7 +7,8 @@ option java_package = "com.v2ray.core.app.dispatcher";
 option java_multiple_files = true;
 
 message SessionConfig {
-  bool allow_passive_connection = 1;
+  reserved 1;
+  
 }
 
 message Config {

+ 167 - 0
app/dispatcher/impl/sniffer.go

@@ -0,0 +1,167 @@
+package impl
+
+import (
+	"bytes"
+	"strings"
+
+	"v2ray.com/core/common/serial"
+)
+
+var (
+	ErrMoreData    = newError("need more data")
+	ErrInvalidData = newError("invalid data")
+)
+
+func ContainsValidHTTPMethod(b []byte) bool {
+	if len(b) == 0 {
+		return false
+	}
+
+	parts := bytes.Split(b, []byte{' '})
+	part0Trimed := strings.ToLower(string(bytes.Trim(parts[0], " ")))
+	return part0Trimed == "get" || part0Trimed == "post" ||
+		part0Trimed == "head" || part0Trimed == "put" ||
+		part0Trimed == "delete" || part0Trimed == "options" || part0Trimed == "connect"
+}
+
+func SniffHTTP(b []byte) (string, error) {
+	if len(b) == 0 {
+		return "", ErrMoreData
+	}
+	headers := bytes.Split(b, []byte{'\n'})
+	if !ContainsValidHTTPMethod(headers[0]) {
+		return "", ErrInvalidData
+	}
+	for i := 1; i < len(headers); i++ {
+		header := headers[i]
+		if len(header) == 0 {
+			return "", ErrInvalidData
+		}
+		parts := bytes.SplitN(header, []byte{':'}, 2)
+		if len(parts) != 2 {
+			return "", ErrInvalidData
+		}
+		key := strings.ToLower(string(parts[0]))
+		value := strings.ToLower(string(bytes.Trim(parts[1], " ")))
+		if key == "host" {
+			return value, nil
+		}
+	}
+	return "", ErrMoreData
+}
+
+func IsValidTLSVersion(major, minor byte) bool {
+	return major == 3
+}
+
+// ReadClientHello returns server name (if any) from TLS client hello message.
+// https://github.com/golang/go/blob/master/src/crypto/tls/handshake_messages.go#L300
+func ReadClientHello(data []byte) (string, error) {
+	if len(data) < 42 {
+		return "", ErrMoreData
+	}
+	sessionIdLen := int(data[38])
+	if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
+		return "", ErrInvalidData
+	}
+	data = data[39+sessionIdLen:]
+	if len(data) < 2 {
+		return "", ErrMoreData
+	}
+	// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
+	// they are uint16s, the number must be even.
+	cipherSuiteLen := int(data[0])<<8 | int(data[1])
+	if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
+		return "", ErrInvalidData
+	}
+	data = data[2+cipherSuiteLen:]
+	if len(data) < 1 {
+		return "", ErrMoreData
+	}
+	compressionMethodsLen := int(data[0])
+	if len(data) < 1+compressionMethodsLen {
+		return "", ErrMoreData
+	}
+	data = data[1+compressionMethodsLen:]
+
+	if len(data) == 0 {
+		return "", ErrInvalidData
+	}
+	if len(data) < 2 {
+		return "", ErrInvalidData
+	}
+
+	extensionsLength := int(data[0])<<8 | int(data[1])
+	data = data[2:]
+	if extensionsLength != len(data) {
+		return "", ErrInvalidData
+	}
+
+	for len(data) != 0 {
+		if len(data) < 4 {
+			return "", ErrInvalidData
+		}
+		extension := uint16(data[0])<<8 | uint16(data[1])
+		length := int(data[2])<<8 | int(data[3])
+		data = data[4:]
+		if len(data) < length {
+			return "", ErrInvalidData
+		}
+
+		switch extension {
+		case 0x00: /* extensionServerName */
+			d := data[:length]
+			if len(d) < 2 {
+				return "", ErrInvalidData
+			}
+			namesLen := int(d[0])<<8 | int(d[1])
+			d = d[2:]
+			if len(d) != namesLen {
+				return "", ErrInvalidData
+			}
+			for len(d) > 0 {
+				if len(d) < 3 {
+					return "", ErrInvalidData
+				}
+				nameType := d[0]
+				nameLen := int(d[1])<<8 | int(d[2])
+				d = d[3:]
+				if len(d) < nameLen {
+					return "", ErrInvalidData
+				}
+				if nameType == 0 {
+					serverName := string(d[:nameLen])
+					// An SNI value may not include a
+					// trailing dot. See
+					// https://tools.ietf.org/html/rfc6066#section-3.
+					if strings.HasSuffix(serverName, ".") {
+						return "", ErrInvalidData
+					}
+					return serverName, nil
+				}
+				d = d[nameLen:]
+			}
+		}
+		data = data[length:]
+	}
+
+	return "", ErrInvalidData
+}
+
+func SniffTLS(b []byte) (string, error) {
+	if len(b) < 5 {
+		return "", ErrMoreData
+	}
+
+	if b[0] != 0x16 /* TLS Handshake */ {
+		return "", ErrInvalidData
+	}
+	if !IsValidTLSVersion(b[1], b[2]) {
+		return "", ErrInvalidData
+	}
+	headerLen := int(serial.BytesToUint16(b[3:5]))
+	if 5+headerLen > len(b) {
+		return "", ErrMoreData
+	}
+	return ReadClientHello(b[5 : 5+headerLen])
+}

+ 186 - 0
app/dispatcher/impl/sniffer_test.go

@@ -0,0 +1,186 @@
+package impl_test
+
+import (
+	"testing"
+
+	. "v2ray.com/core/app/dispatcher/impl"
+	"v2ray.com/core/testing/assert"
+)
+
+func TestHTTPHeaders(t *testing.T) {
+	assert := assert.On(t)
+
+	cases := []struct {
+		input  string
+		domain string
+		err    error
+	}{
+		{
+			input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
+Host: net.tutsplus.com
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
+Pragma: no-cache
+Cache-Control: no-cache`,
+			domain: "net.tutsplus.com",
+			err:    nil,
+		},
+		{
+			input: `POST /foo.php HTTP/1.1
+Host: localhost
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Referer: http://localhost/test.php
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 43
+ 
+first_name=John&last_name=Doe&action=Submit`,
+			domain: "localhost",
+			err:    nil,
+		},
+		{
+			input: `X /foo.php HTTP/1.1
+Host: localhost
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Referer: http://localhost/test.php
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 43
+ 
+first_name=John&last_name=Doe&action=Submit`,
+			domain: "",
+			err:    ErrInvalidData,
+		},
+		{
+			input: `GET /foo.php HTTP/1.1
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+Referer: http://localhost/test.php
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 43
+
+Host: localhost
+first_name=John&last_name=Doe&action=Submit`,
+			domain: "",
+			err:    ErrInvalidData,
+		},
+		{
+			input:  `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1`,
+			domain: "",
+			err:    ErrMoreData,
+		},
+	}
+
+	for _, test := range cases {
+		domain, err := SniffHTTP([]byte(test.input))
+		assert.String(domain).Equals(test.domain)
+		assert.Error(err).Equals(test.err)
+	}
+}
+
+func TestTLSHeaders(t *testing.T) {
+	assert := assert.On(t)
+
+	cases := []struct {
+		input  []byte
+		domain string
+		err    error
+	}{
+		{
+			input: []byte{
+				0x16, 0x03, 0x01, 0x00, 0xc8, 0x01, 0x00, 0x00,
+				0xc4, 0x03, 0x03, 0x1a, 0xac, 0xb2, 0xa8, 0xfe,
+				0xb4, 0x96, 0x04, 0x5b, 0xca, 0xf7, 0xc1, 0xf4,
+				0x2e, 0x53, 0x24, 0x6e, 0x34, 0x0c, 0x58, 0x36,
+				0x71, 0x97, 0x59, 0xe9, 0x41, 0x66, 0xe2, 0x43,
+				0xa0, 0x13, 0xb6, 0x00, 0x00, 0x20, 0x1a, 0x1a,
+				0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+				0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
+				0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
+				0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
+				0x00, 0x7b, 0xba, 0xba, 0x00, 0x00, 0xff, 0x01,
+				0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
+				0x14, 0x00, 0x00, 0x11, 0x63, 0x2e, 0x73, 0x2d,
+				0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,
+				0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00,
+				0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
+				0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04,
+				0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08,
+				0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00,
+				0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
+				0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c,
+				0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70,
+				0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02,
+				0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,
+				0xaa, 0xaa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
+				0xaa, 0xaa, 0x00, 0x01, 0x00,
+			},
+			domain: "c.s-microsoft.com",
+			err:    nil,
+		},
+		{
+			input: []byte{
+				0x16, 0x03, 0x01, 0x00, 0xee, 0x01, 0x00, 0x00,
+				0xea, 0x03, 0x03, 0xe7, 0x91, 0x9e, 0x93, 0xca,
+				0x78, 0x1b, 0x3c, 0xe0, 0x65, 0x25, 0x58, 0xb5,
+				0x93, 0xe1, 0x0f, 0x85, 0xec, 0x9a, 0x66, 0x8e,
+				0x61, 0x82, 0x88, 0xc8, 0xfc, 0xae, 0x1e, 0xca,
+				0xd7, 0xa5, 0x63, 0x20, 0xbd, 0x1c, 0x00, 0x00,
+				0x8b, 0xee, 0x09, 0xe3, 0x47, 0x6a, 0x0e, 0x74,
+				0xb0, 0xbc, 0xa3, 0x02, 0xa7, 0x35, 0xe8, 0x85,
+				0x70, 0x7c, 0x7a, 0xf0, 0x00, 0xdf, 0x4a, 0xea,
+				0x87, 0x01, 0x14, 0x91, 0x00, 0x20, 0xea, 0xea,
+				0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+				0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
+				0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
+				0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
+				0x00, 0x81, 0x9a, 0x9a, 0x00, 0x00, 0xff, 0x01,
+				0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
+				0x16, 0x00, 0x00, 0x13, 0x77, 0x77, 0x77, 0x30,
+				0x37, 0x2e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x74,
+				0x61, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x00,
+				0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+				0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
+				0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,
+				0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00,
+				0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e,
+				0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74,
+				0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x75, 0x50,
+				0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
+				0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x9a, 0x9a,
+				0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x8a, 0x8a,
+				0x00, 0x01, 0x00,
+			},
+			domain: "www07.clicktale.net",
+			err:    nil,
+		},
+	}
+
+	for _, test := range cases {
+		domain, err := SniffTLS(test.input)
+		assert.String(domain).Equals(test.domain)
+		assert.Error(err).Equals(test.err)
+	}
+}

+ 83 - 130
app/proxyman/config.pb.go

@@ -5,7 +5,7 @@ import fmt "fmt"
 import math "math"
 import v2ray_core_common_serial "v2ray.com/core/common/serial"
 import v2ray_core_common_net "v2ray.com/core/common/net"
-import v2ray_core_common_net2 "v2ray.com/core/common/net"
+import _ "v2ray.com/core/common/net"
 import v2ray_core_common_net3 "v2ray.com/core/common/net"
 import v2ray_core_transport_internet "v2ray.com/core/transport/internet"
 
@@ -20,6 +20,27 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
 
+type KnownProtocols int32
+
+const (
+	KnownProtocols_HTTP KnownProtocols = 0
+	KnownProtocols_TLS  KnownProtocols = 1
+)
+
+var KnownProtocols_name = map[int32]string{
+	0: "HTTP",
+	1: "TLS",
+}
+var KnownProtocols_value = map[string]int32{
+	"HTTP": 0,
+	"TLS":  1,
+}
+
+func (x KnownProtocols) String() string {
+	return proto.EnumName(KnownProtocols_name, int32(x))
+}
+func (KnownProtocols) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
 type AllocationStrategy_Type int32
 
 const (
@@ -47,30 +68,6 @@ func (x AllocationStrategy_Type) String() string {
 }
 func (AllocationStrategy_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
 
-type SessionFrame_FrameCommand int32
-
-const (
-	SessionFrame_SessionNew  SessionFrame_FrameCommand = 0
-	SessionFrame_SessionKeep SessionFrame_FrameCommand = 1
-	SessionFrame_SessionEnd  SessionFrame_FrameCommand = 2
-)
-
-var SessionFrame_FrameCommand_name = map[int32]string{
-	0: "SessionNew",
-	1: "SessionKeep",
-	2: "SessionEnd",
-}
-var SessionFrame_FrameCommand_value = map[string]int32{
-	"SessionNew":  0,
-	"SessionKeep": 1,
-	"SessionEnd":  2,
-}
-
-func (x SessionFrame_FrameCommand) String() string {
-	return proto.EnumName(SessionFrame_FrameCommand_name, int32(x))
-}
-func (SessionFrame_FrameCommand) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{8, 0} }
-
 type InboundConfig struct {
 }
 
@@ -165,7 +162,7 @@ type ReceiverConfig struct {
 	AllocationStrategy         *AllocationStrategy                         `protobuf:"bytes,3,opt,name=allocation_strategy,json=allocationStrategy" json:"allocation_strategy,omitempty"`
 	StreamSettings             *v2ray_core_transport_internet.StreamConfig `protobuf:"bytes,4,opt,name=stream_settings,json=streamSettings" json:"stream_settings,omitempty"`
 	ReceiveOriginalDestination bool                                        `protobuf:"varint,5,opt,name=receive_original_destination,json=receiveOriginalDestination" json:"receive_original_destination,omitempty"`
-	AllowPassiveConnection     bool                                        `protobuf:"varint,6,opt,name=allow_passive_connection,json=allowPassiveConnection" json:"allow_passive_connection,omitempty"`
+	DomainOverride             []KnownProtocols                            `protobuf:"varint,7,rep,packed,name=domain_override,json=domainOverride,enum=v2ray.core.app.proxyman.KnownProtocols" json:"domain_override,omitempty"`
 }
 
 func (m *ReceiverConfig) Reset()                    { *m = ReceiverConfig{} }
@@ -208,11 +205,11 @@ func (m *ReceiverConfig) GetReceiveOriginalDestination() bool {
 	return false
 }
 
-func (m *ReceiverConfig) GetAllowPassiveConnection() bool {
+func (m *ReceiverConfig) GetDomainOverride() []KnownProtocols {
 	if m != nil {
-		return m.AllowPassiveConnection
+		return m.DomainOverride
 	}
-	return false
+	return nil
 }
 
 type InboundHandlerConfig struct {
@@ -345,7 +342,9 @@ func (m *OutboundHandlerConfig) GetComment() string {
 }
 
 type MultiplexingConfig struct {
-	Enabled     bool   `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"`
+	// Whether or not Mux is enabled.
+	Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"`
+	// Max number of concurrent connections that one Mux connection can handle.
 	Concurrency uint32 `protobuf:"varint,2,opt,name=concurrency" json:"concurrency,omitempty"`
 }
 
@@ -368,46 +367,6 @@ func (m *MultiplexingConfig) GetConcurrency() uint32 {
 	return 0
 }
 
-type SessionFrame struct {
-	Id      uint32                           `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
-	Command SessionFrame_FrameCommand        `protobuf:"varint,2,opt,name=command,enum=v2ray.core.app.proxyman.SessionFrame_FrameCommand" json:"command,omitempty"`
-	Target  *v2ray_core_common_net2.Endpoint `protobuf:"bytes,3,opt,name=target" json:"target,omitempty"`
-	Payload []byte                           `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"`
-}
-
-func (m *SessionFrame) Reset()                    { *m = SessionFrame{} }
-func (m *SessionFrame) String() string            { return proto.CompactTextString(m) }
-func (*SessionFrame) ProtoMessage()               {}
-func (*SessionFrame) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
-
-func (m *SessionFrame) GetId() uint32 {
-	if m != nil {
-		return m.Id
-	}
-	return 0
-}
-
-func (m *SessionFrame) GetCommand() SessionFrame_FrameCommand {
-	if m != nil {
-		return m.Command
-	}
-	return SessionFrame_SessionNew
-}
-
-func (m *SessionFrame) GetTarget() *v2ray_core_common_net2.Endpoint {
-	if m != nil {
-		return m.Target
-	}
-	return nil
-}
-
-func (m *SessionFrame) GetPayload() []byte {
-	if m != nil {
-		return m.Payload
-	}
-	return nil
-}
-
 func init() {
 	proto.RegisterType((*InboundConfig)(nil), "v2ray.core.app.proxyman.InboundConfig")
 	proto.RegisterType((*AllocationStrategy)(nil), "v2ray.core.app.proxyman.AllocationStrategy")
@@ -419,70 +378,64 @@ func init() {
 	proto.RegisterType((*SenderConfig)(nil), "v2ray.core.app.proxyman.SenderConfig")
 	proto.RegisterType((*OutboundHandlerConfig)(nil), "v2ray.core.app.proxyman.OutboundHandlerConfig")
 	proto.RegisterType((*MultiplexingConfig)(nil), "v2ray.core.app.proxyman.MultiplexingConfig")
-	proto.RegisterType((*SessionFrame)(nil), "v2ray.core.app.proxyman.SessionFrame")
+	proto.RegisterEnum("v2ray.core.app.proxyman.KnownProtocols", KnownProtocols_name, KnownProtocols_value)
 	proto.RegisterEnum("v2ray.core.app.proxyman.AllocationStrategy_Type", AllocationStrategy_Type_name, AllocationStrategy_Type_value)
-	proto.RegisterEnum("v2ray.core.app.proxyman.SessionFrame_FrameCommand", SessionFrame_FrameCommand_name, SessionFrame_FrameCommand_value)
 }
 
 func init() { proto.RegisterFile("v2ray.com/core/app/proxyman/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 899 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x5d, 0x6f, 0x1b, 0x45,
-	0x14, 0xed, 0xda, 0xa9, 0x9b, 0xdc, 0x24, 0x8e, 0x3b, 0x94, 0xd6, 0x18, 0x10, 0xc6, 0x42, 0x10,
-	0x51, 0xb4, 0x2e, 0xae, 0x10, 0x42, 0x42, 0x2a, 0xa9, 0x13, 0x44, 0x04, 0x21, 0x66, 0x5c, 0xf1,
-	0x50, 0x21, 0x59, 0x93, 0xdd, 0xe9, 0x32, 0x62, 0x77, 0x66, 0x34, 0x33, 0x4e, 0xb2, 0x6f, 0xfc,
-	0x1e, 0x7e, 0x05, 0x8f, 0x3c, 0xf0, 0x8f, 0x78, 0x41, 0xf3, 0xb1, 0x8e, 0x53, 0x67, 0x5b, 0x42,
-	0xd4, 0x97, 0x68, 0xc6, 0x39, 0xe7, 0xcc, 0xdc, 0x73, 0xee, 0x1d, 0x1b, 0x76, 0x4f, 0x47, 0x8a,
-	0x94, 0x71, 0x22, 0x8a, 0x61, 0x22, 0x14, 0x1d, 0x12, 0x29, 0x87, 0x52, 0x89, 0xf3, 0xb2, 0x20,
-	0x7c, 0x98, 0x08, 0xfe, 0x82, 0x65, 0xb1, 0x54, 0xc2, 0x08, 0xf4, 0xa0, 0x42, 0x2a, 0x1a, 0x13,
-	0x29, 0xe3, 0x0a, 0xd5, 0x7b, 0xf4, 0x92, 0x44, 0x22, 0x8a, 0x42, 0xf0, 0xa1, 0xa6, 0x8a, 0x91,
-	0x7c, 0x68, 0x4a, 0x49, 0xd3, 0x59, 0x41, 0xb5, 0x26, 0x19, 0xf5, 0x52, 0xbd, 0x4f, 0xae, 0x66,
-	0x70, 0x6a, 0x86, 0x24, 0x4d, 0x15, 0xd5, 0x3a, 0x00, 0x1f, 0xd6, 0x03, 0x53, 0xaa, 0x0d, 0xe3,
-	0xc4, 0x30, 0xc1, 0x03, 0xf8, 0xa3, 0x7a, 0xb0, 0x14, 0xca, 0x04, 0x54, 0xfc, 0x12, 0xca, 0x28,
-	0xc2, 0xb5, 0xfd, 0xff, 0x90, 0x71, 0x43, 0x95, 0x45, 0x2f, 0x97, 0x3d, 0xd8, 0x81, 0xed, 0x43,
-	0x7e, 0x22, 0xe6, 0x3c, 0x1d, 0xbb, 0x8f, 0x07, 0x7f, 0x36, 0x01, 0xed, 0xe5, 0xb9, 0x48, 0xdc,
-	0xd9, 0x53, 0xa3, 0x88, 0xa1, 0x59, 0x89, 0xf6, 0x61, 0xcd, 0x96, 0xda, 0x8d, 0xfa, 0xd1, 0x6e,
-	0x7b, 0xf4, 0x28, 0xae, 0x71, 0x2b, 0x5e, 0xa5, 0xc6, 0xcf, 0x4a, 0x49, 0xb1, 0x63, 0xa3, 0xdf,
-	0x60, 0x33, 0x11, 0x3c, 0x99, 0x2b, 0x45, 0x79, 0x52, 0x76, 0x1b, 0xfd, 0x68, 0x77, 0x73, 0x74,
-	0x78, 0x1d, 0xb1, 0xd5, 0x8f, 0xc6, 0x17, 0x82, 0x78, 0x59, 0x1d, 0xcd, 0xe0, 0x8e, 0xa2, 0x2f,
-	0x14, 0xd5, 0xbf, 0x76, 0x9b, 0xee, 0xa0, 0x83, 0x9b, 0x1d, 0x84, 0xbd, 0x18, 0xae, 0x54, 0x7b,
-	0x5f, 0xc0, 0xfb, 0xaf, 0xbc, 0x0e, 0xba, 0x07, 0xb7, 0x4f, 0x49, 0x3e, 0xf7, 0xae, 0x6d, 0x63,
-	0xbf, 0xe9, 0x7d, 0x0e, 0xef, 0xd4, 0x8a, 0x5f, 0x4d, 0x19, 0x7c, 0x06, 0x6b, 0xd6, 0x45, 0x04,
-	0xd0, 0xda, 0xcb, 0xcf, 0x48, 0xa9, 0x3b, 0xb7, 0xec, 0x1a, 0x13, 0x9e, 0x8a, 0xa2, 0x13, 0xa1,
-	0x2d, 0x58, 0x3f, 0x38, 0xb7, 0xf1, 0x92, 0xbc, 0xd3, 0xb0, 0x11, 0xb6, 0x31, 0x4d, 0x28, 0x3b,
-	0xa5, 0xca, 0xa7, 0x8a, 0x9e, 0x00, 0xd8, 0x26, 0x98, 0x29, 0xc2, 0x33, 0xaf, 0xbd, 0x39, 0xea,
-	0x2f, 0xdb, 0xe1, 0xbb, 0x29, 0xe6, 0xd4, 0xc4, 0x13, 0xa1, 0x0c, 0xb6, 0x38, 0xbc, 0x21, 0xab,
-	0x25, 0xfa, 0x0a, 0x5a, 0x39, 0xd3, 0x86, 0xf2, 0x10, 0xda, 0x87, 0x35, 0xe4, 0xc3, 0xc9, 0xb1,
-	0xda, 0x17, 0x05, 0x61, 0x1c, 0x07, 0x02, 0xfa, 0x05, 0xde, 0x22, 0x8b, 0x7a, 0x67, 0x3a, 0x14,
-	0x1c, 0x32, 0x79, 0x78, 0x8d, 0x4c, 0x30, 0x22, 0xab, 0x8d, 0xf9, 0x0c, 0x76, 0xb4, 0x51, 0x94,
-	0x14, 0x33, 0x4d, 0x8d, 0x61, 0x3c, 0xd3, 0xdd, 0xb5, 0x55, 0xe5, 0xc5, 0x18, 0xc4, 0xd5, 0x18,
-	0xc4, 0x53, 0xc7, 0xf2, 0xfe, 0xe0, 0xb6, 0xd7, 0x98, 0x06, 0x09, 0xf4, 0x0d, 0xbc, 0xa7, 0xbc,
-	0x83, 0x33, 0xa1, 0x58, 0xc6, 0x38, 0xc9, 0x67, 0x4b, 0x23, 0xd9, 0xbd, 0xdd, 0x8f, 0x76, 0xd7,
-	0x71, 0x2f, 0x60, 0x8e, 0x03, 0x64, 0xff, 0x02, 0x81, 0xbe, 0x86, 0xae, 0xbd, 0xed, 0xd9, 0x4c,
-	0x12, 0xad, 0xad, 0x4e, 0x22, 0x38, 0xa7, 0x89, 0x63, 0xb7, 0x2c, 0xfb, 0x69, 0xa3, 0x1b, 0xe1,
-	0xfb, 0x0e, 0x33, 0xf1, 0x90, 0xf1, 0x02, 0x31, 0xf8, 0x3b, 0x82, 0x7b, 0x61, 0x2e, 0xbf, 0x23,
-	0x3c, 0xcd, 0x17, 0x41, 0x76, 0xa0, 0x69, 0x48, 0xe6, 0x12, 0xdc, 0xc0, 0x76, 0x89, 0xa6, 0x70,
-	0x37, 0x5c, 0x43, 0x5d, 0x58, 0xe0, 0x43, 0xfa, 0xf8, 0x8a, 0x90, 0xfc, 0xbb, 0xe5, 0x86, 0x32,
-	0x3d, 0xf2, 0xcf, 0x16, 0xee, 0x54, 0x02, 0x8b, 0xfa, 0x8f, 0xa0, 0xed, 0x82, 0xb8, 0x50, 0x6c,
-	0x5e, 0x4b, 0x71, 0xdb, 0xb1, 0x2b, 0xb9, 0x41, 0x07, 0xda, 0xc7, 0x73, 0xb3, 0xfc, 0xcc, 0xfc,
-	0xd5, 0x80, 0xad, 0x29, 0xe5, 0xe9, 0xa2, 0xb0, 0xc7, 0xd0, 0x3c, 0x65, 0x24, 0xb4, 0xe6, 0x7f,
-	0xe8, 0x2e, 0x8b, 0xbe, 0x2a, 0xfc, 0xc6, 0xcd, 0xc3, 0xff, 0xa9, 0xa6, 0xf8, 0x4f, 0x5f, 0x23,
-	0x3a, 0xb1, 0xa4, 0xa0, 0x79, 0xd9, 0x00, 0xf4, 0x1c, 0x50, 0x31, 0xcf, 0x0d, 0x93, 0x39, 0x3d,
-	0x7f, 0x65, 0xa3, 0x5e, 0x1a, 0x81, 0xa3, 0x8a, 0xc2, 0x78, 0x16, 0x74, 0xef, 0x2e, 0x64, 0x16,
-	0xe6, 0xfe, 0x13, 0xc1, 0xdb, 0x95, 0xbb, 0xaf, 0x6b, 0x96, 0x63, 0xd8, 0xd1, 0xce, 0xf5, 0xff,
-	0xdb, 0x2a, 0x6d, 0x4f, 0x7f, 0x43, 0x8d, 0x82, 0xee, 0x43, 0x8b, 0x9e, 0x4b, 0xa6, 0xa8, 0xf3,
-	0xa6, 0x89, 0xc3, 0x0e, 0x75, 0xe1, 0x8e, 0x15, 0xa1, 0xdc, 0xb8, 0xd1, 0xdb, 0xc0, 0xd5, 0x76,
-	0x30, 0x01, 0xb4, 0x6a, 0x93, 0xc5, 0x53, 0x4e, 0x4e, 0x72, 0x9a, 0xba, 0xea, 0xd7, 0x71, 0xb5,
-	0x45, 0xfd, 0xd5, 0xaf, 0xa0, 0xed, 0x4b, 0xdf, 0x1b, 0x83, 0xdf, 0x5d, 0x6b, 0x6a, 0xcd, 0x04,
-	0xff, 0x56, 0x91, 0x82, 0xa2, 0x36, 0x34, 0x58, 0x1a, 0x1e, 0xe4, 0x06, 0x4b, 0xd1, 0x0f, 0xfe,
-	0x32, 0x84, 0xa7, 0x8e, 0xde, 0x1e, 0x8d, 0x6a, 0x13, 0x5c, 0xd6, 0x89, 0xdd, 0xdf, 0xb1, 0x67,
-	0xe2, 0x4a, 0x02, 0x7d, 0x09, 0x2d, 0x43, 0x54, 0x46, 0x4d, 0x70, 0xee, 0x83, 0x9a, 0xde, 0x3f,
-	0xe0, 0xa9, 0x14, 0x8c, 0x1b, 0x1c, 0xe0, 0xb6, 0x46, 0x49, 0xca, 0x5c, 0x90, 0xd4, 0x99, 0xb5,
-	0x85, 0xab, 0xed, 0xe0, 0x09, 0x6c, 0x2d, 0x9f, 0x85, 0xda, 0x00, 0xe1, 0x22, 0x3f, 0xd2, 0xb3,
-	0xce, 0x2d, 0xb4, 0x03, 0x9b, 0x61, 0xff, 0x3d, 0xa5, 0xb2, 0x13, 0x2d, 0x01, 0x0e, 0x78, 0xda,
-	0x69, 0x3c, 0x1d, 0xc3, 0xbb, 0x89, 0x28, 0xea, 0xaa, 0x9a, 0x44, 0xcf, 0xd7, 0xab, 0xf5, 0x1f,
-	0x8d, 0x07, 0x3f, 0x8f, 0x30, 0x29, 0xe3, 0xb1, 0x45, 0xed, 0x49, 0xe9, 0xa7, 0xa0, 0x20, 0xfc,
-	0xa4, 0xe5, 0x7e, 0x61, 0x3c, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0x70, 0x61, 0x1e, 0xe3, 0x84,
-	0x09, 0x00, 0x00,
+	// 829 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xd1, 0x8e, 0xdb, 0x44,
+	0x14, 0xad, 0xe3, 0x34, 0xc9, 0xde, 0xed, 0x7a, 0xdd, 0xa1, 0xd0, 0x10, 0x40, 0x0a, 0x01, 0xd1,
+	0xa8, 0x45, 0x4e, 0x49, 0xc5, 0x03, 0x4f, 0xb0, 0xec, 0x56, 0xea, 0x02, 0xab, 0x84, 0x49, 0xc4,
+	0x43, 0x85, 0x64, 0xcd, 0xda, 0x53, 0x33, 0xc2, 0x9e, 0xb1, 0x66, 0x26, 0xe9, 0xfa, 0x97, 0xf8,
+	0x0a, 0x1e, 0x79, 0xe0, 0x0b, 0xf8, 0x15, 0x5e, 0x90, 0x3d, 0xe3, 0x6c, 0xb6, 0x59, 0xb7, 0x2c,
+	0x15, 0x6f, 0x33, 0xc9, 0x39, 0xc7, 0x73, 0xcf, 0x3d, 0x77, 0x06, 0xc6, 0xeb, 0xa9, 0x24, 0x45,
+	0x10, 0x89, 0x6c, 0x12, 0x09, 0x49, 0x27, 0x24, 0xcf, 0x27, 0xb9, 0x14, 0x17, 0x45, 0x46, 0xf8,
+	0x24, 0x12, 0xfc, 0x05, 0x4b, 0x82, 0x5c, 0x0a, 0x2d, 0xd0, 0xfd, 0x1a, 0x29, 0x69, 0x40, 0xf2,
+	0x3c, 0xa8, 0x51, 0x83, 0xc7, 0xaf, 0x48, 0x44, 0x22, 0xcb, 0x04, 0x9f, 0x28, 0x2a, 0x19, 0x49,
+	0x27, 0xba, 0xc8, 0x69, 0x1c, 0x66, 0x54, 0x29, 0x92, 0x50, 0x23, 0x35, 0x78, 0x70, 0x3d, 0x83,
+	0x53, 0x3d, 0x21, 0x71, 0x2c, 0xa9, 0x52, 0x16, 0xf8, 0xa8, 0x19, 0x18, 0x53, 0xa5, 0x19, 0x27,
+	0x9a, 0x09, 0x6e, 0xc1, 0x9f, 0x36, 0x83, 0x73, 0x21, 0xb5, 0x45, 0x05, 0xaf, 0xa0, 0xb4, 0x24,
+	0x5c, 0x95, 0xff, 0x4f, 0x18, 0xd7, 0x54, 0x96, 0xe8, 0xed, 0xb2, 0x47, 0x87, 0x70, 0x70, 0xca,
+	0xcf, 0xc5, 0x8a, 0xc7, 0xc7, 0xd5, 0xcf, 0xa3, 0xdf, 0x5d, 0x40, 0x47, 0x69, 0x2a, 0xa2, 0xea,
+	0xdb, 0x0b, 0x2d, 0x89, 0xa6, 0x49, 0x81, 0x4e, 0xa0, 0x5d, 0x96, 0xda, 0x77, 0x86, 0xce, 0xd8,
+	0x9b, 0x3e, 0x0e, 0x1a, 0xdc, 0x0a, 0x76, 0xa9, 0xc1, 0xb2, 0xc8, 0x29, 0xae, 0xd8, 0xe8, 0x57,
+	0xd8, 0x8f, 0x04, 0x8f, 0x56, 0x52, 0x52, 0x1e, 0x15, 0xfd, 0xd6, 0xd0, 0x19, 0xef, 0x4f, 0x4f,
+	0x6f, 0x22, 0xb6, 0xfb, 0xd3, 0xf1, 0xa5, 0x20, 0xde, 0x56, 0x47, 0x21, 0x74, 0x25, 0x7d, 0x21,
+	0xa9, 0xfa, 0xa5, 0xef, 0x56, 0x1f, 0x7a, 0xfa, 0x76, 0x1f, 0xc2, 0x46, 0x0c, 0xd7, 0xaa, 0x83,
+	0x2f, 0xe1, 0xa3, 0xd7, 0x1e, 0x07, 0xdd, 0x83, 0xdb, 0x6b, 0x92, 0xae, 0x8c, 0x6b, 0x07, 0xd8,
+	0x6c, 0x06, 0x5f, 0xc0, 0xfb, 0x8d, 0xe2, 0xd7, 0x53, 0x46, 0x9f, 0x43, 0xbb, 0x74, 0x11, 0x01,
+	0x74, 0x8e, 0xd2, 0x97, 0xa4, 0x50, 0xfe, 0xad, 0x72, 0x8d, 0x09, 0x8f, 0x45, 0xe6, 0x3b, 0xe8,
+	0x0e, 0xf4, 0x9e, 0x5e, 0x94, 0xed, 0x25, 0xa9, 0xdf, 0x1a, 0xfd, 0xe5, 0x82, 0x87, 0x69, 0x44,
+	0xd9, 0x9a, 0x4a, 0xd3, 0x55, 0xf4, 0x35, 0x40, 0x19, 0x82, 0x50, 0x12, 0x9e, 0x18, 0xed, 0xfd,
+	0xe9, 0x70, 0xdb, 0x0e, 0x93, 0xa6, 0x80, 0x53, 0x1d, 0xcc, 0x85, 0xd4, 0xb8, 0xc4, 0xe1, 0xbd,
+	0xbc, 0x5e, 0xa2, 0xaf, 0xa0, 0x93, 0x32, 0xa5, 0x29, 0xb7, 0x4d, 0xfb, 0xb8, 0x81, 0x7c, 0x3a,
+	0x9f, 0xc9, 0x13, 0x91, 0x11, 0xc6, 0xb1, 0x25, 0xa0, 0x9f, 0xe1, 0x1d, 0xb2, 0xa9, 0x37, 0x54,
+	0xb6, 0x60, 0xdb, 0x93, 0x47, 0x37, 0xe8, 0x09, 0x46, 0x64, 0x37, 0x98, 0x4b, 0x38, 0x54, 0x5a,
+	0x52, 0x92, 0x85, 0x8a, 0x6a, 0xcd, 0x78, 0xa2, 0xfa, 0xed, 0x5d, 0xe5, 0xcd, 0x18, 0x04, 0xf5,
+	0x18, 0x04, 0x8b, 0x8a, 0x65, 0xfc, 0xc1, 0x9e, 0xd1, 0x58, 0x58, 0x09, 0xf4, 0x0d, 0x7c, 0x28,
+	0x8d, 0x83, 0xa1, 0x90, 0x2c, 0x61, 0x9c, 0xa4, 0xe1, 0xd6, 0x48, 0xf6, 0x6f, 0x0f, 0x9d, 0x71,
+	0x0f, 0x0f, 0x2c, 0x66, 0x66, 0x21, 0x27, 0x97, 0x08, 0x34, 0x87, 0xc3, 0xb8, 0xf2, 0x21, 0x14,
+	0x6b, 0x2a, 0x25, 0x8b, 0x69, 0xbf, 0x3b, 0x74, 0xc7, 0xde, 0xf4, 0x41, 0x63, 0xc5, 0xdf, 0x73,
+	0xf1, 0x92, 0xcf, 0xcb, 0xb1, 0x8c, 0x44, 0xaa, 0xb0, 0x67, 0xf8, 0x33, 0x4b, 0xff, 0xae, 0xdd,
+	0xeb, 0xf8, 0xdd, 0xd1, 0x9f, 0x0e, 0xdc, 0xb3, 0x13, 0xfb, 0x8c, 0xf0, 0x38, 0xdd, 0xb4, 0xd8,
+	0x07, 0x57, 0x93, 0xa4, 0xea, 0xed, 0x1e, 0x2e, 0x97, 0x68, 0x01, 0x77, 0xed, 0x01, 0xe5, 0xa5,
+	0x39, 0xa6, 0x7d, 0x9f, 0x5d, 0xd3, 0x3e, 0x73, 0xa3, 0x55, 0xe3, 0x1a, 0x9f, 0x99, 0x0b, 0x0d,
+	0xfb, 0xb5, 0xc0, 0xc6, 0x99, 0x33, 0xf0, 0xaa, 0x03, 0x5f, 0x2a, 0xba, 0x37, 0x52, 0x3c, 0xa8,
+	0xd8, 0xb5, 0xdc, 0xc8, 0x07, 0x6f, 0xb6, 0xd2, 0xdb, 0x17, 0xd0, 0x1f, 0x2d, 0xb8, 0xb3, 0xa0,
+	0x3c, 0xde, 0x14, 0xf6, 0x04, 0xdc, 0x35, 0x23, 0x36, 0xb4, 0xff, 0x22, 0x77, 0x25, 0xfa, 0xba,
+	0x58, 0xb4, 0xde, 0x3e, 0x16, 0x3f, 0x36, 0x14, 0xff, 0xf0, 0x0d, 0xa2, 0xf3, 0x92, 0x64, 0x35,
+	0xaf, 0x1a, 0x80, 0x9e, 0x03, 0xca, 0x56, 0xa9, 0x66, 0x79, 0x4a, 0x2f, 0x5e, 0x1b, 0xe1, 0x2b,
+	0x51, 0x39, 0xab, 0x29, 0x8c, 0x27, 0x56, 0xf7, 0xee, 0x46, 0x66, 0x63, 0xee, 0xdf, 0x0e, 0xbc,
+	0x5b, 0xbb, 0xfb, 0xa6, 0xb0, 0xcc, 0xe0, 0x50, 0x55, 0xae, 0xff, 0xd7, 0xa8, 0x78, 0x86, 0xfe,
+	0x3f, 0x05, 0x05, 0xbd, 0x07, 0x1d, 0x7a, 0x91, 0x33, 0x49, 0x2b, 0x6f, 0x5c, 0x6c, 0x77, 0xa8,
+	0x0f, 0xdd, 0x52, 0x84, 0x72, 0x5d, 0x0d, 0xe5, 0x1e, 0xae, 0xb7, 0xa3, 0x39, 0xa0, 0x5d, 0x9b,
+	0x4a, 0x3c, 0xe5, 0xe4, 0x3c, 0xa5, 0x71, 0x55, 0x7d, 0x0f, 0xd7, 0x5b, 0x34, 0xdc, 0x7d, 0x9c,
+	0x0e, 0xae, 0xbc, 0x28, 0x0f, 0x3f, 0x01, 0xef, 0xea, 0x8c, 0xa2, 0x1e, 0xb4, 0x9f, 0x2d, 0x97,
+	0x73, 0xff, 0x16, 0xea, 0x82, 0xbb, 0xfc, 0x61, 0xe1, 0x3b, 0xdf, 0x1e, 0xc3, 0x07, 0x91, 0xc8,
+	0x9a, 0x3a, 0x37, 0x77, 0x9e, 0xf7, 0xea, 0xf5, 0x6f, 0xad, 0xfb, 0x3f, 0x4d, 0x31, 0x29, 0x82,
+	0xe3, 0x12, 0x75, 0x94, 0xe7, 0x26, 0x27, 0x19, 0xe1, 0xe7, 0x9d, 0xea, 0x75, 0x7e, 0xf2, 0x4f,
+	0x00, 0x00, 0x00, 0xff, 0xff, 0x17, 0x26, 0x33, 0xf4, 0xc0, 0x08, 0x00, 0x00,
 }

+ 7 - 2
app/proxyman/config.proto

@@ -8,7 +8,6 @@ option java_multiple_files = true;
 
 import "v2ray.com/core/common/serial/typed_message.proto";
 import "v2ray.com/core/common/net/address.proto";
-import "v2ray.com/core/common/net/destination.proto";
 import "v2ray.com/core/common/net/port.proto";
 import "v2ray.com/core/transport/internet/config.proto";
 
@@ -47,13 +46,19 @@ message AllocationStrategy {
   AllocationStrategyRefresh refresh = 3;
 }
 
+enum KnownProtocols {
+  HTTP = 0;
+  TLS = 1;
+}
+
 message ReceiverConfig {
   v2ray.core.common.net.PortRange port_range = 1;
   v2ray.core.common.net.IPOrDomain listen = 2;
   AllocationStrategy allocation_strategy = 3;
   v2ray.core.transport.internet.StreamConfig stream_settings = 4;
   bool receive_original_destination = 5;
-  bool allow_passive_connection = 6 [deprecated=true];
+  reserved 6;
+  repeated KnownProtocols domain_override = 7;
 }
 
 message InboundHandlerConfig {

+ 1 - 0
app/proxyman/inbound/always.go

@@ -45,6 +45,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 				recvOrigDest: receiverConfig.ReceiveOriginalDestination,
 				tag:          tag,
 				dispatcher:   h.mux,
+				sniffers:     receiverConfig.DomainOverride,
 			}
 			h.workers = append(h.workers, worker)
 		}

+ 1 - 0
app/proxyman/inbound/dynamic.go

@@ -105,6 +105,7 @@ func (h *DynamicInboundHandler) refresh() error {
 				stream:       h.receiverConfig.StreamSettings,
 				recvOrigDest: h.receiverConfig.ReceiveOriginalDestination,
 				dispatcher:   h.mux,
+				sniffers:     h.receiverConfig.DomainOverride,
 			}
 			if err := worker.Start(); err != nil {
 				log.Trace(newError("failed to create TCP worker").Base(err).AtWarning())

+ 5 - 0
app/proxyman/inbound/worker.go

@@ -10,6 +10,7 @@ import (
 
 	"v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/app/log"
+	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common/buf"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
@@ -33,6 +34,7 @@ type tcpWorker struct {
 	recvOrigDest bool
 	tag          string
 	dispatcher   dispatcher.Interface
+	sniffers     []proxyman.KnownProtocols
 
 	ctx    context.Context
 	cancel context.CancelFunc
@@ -55,6 +57,9 @@ func (w *tcpWorker) callback(conn internet.Connection) {
 	}
 	ctx = proxy.ContextWithInboundEntryPoint(ctx, v2net.TCPDestination(w.address, w.port))
 	ctx = proxy.ContextWithSource(ctx, v2net.DestinationFromAddr(conn.RemoteAddr()))
+	if len(w.sniffers) > 0 {
+		ctx = proxyman.ContextWithProtocolSniffers(ctx, w.sniffers)
+	}
 	if err := w.proxy.Process(ctx, v2net.Network_TCP, conn, w.dispatcher); err != nil {
 		log.Trace(newError("connection ends").Base(err))
 	}

+ 17 - 0
app/proxyman/proxyman.go

@@ -50,3 +50,20 @@ func OutboundHandlerManagerFromSpace(space app.Space) OutboundHandlerManager {
 	}
 	return app.(OutboundHandlerManager)
 }
+
+type key int
+
+const (
+	protocolsKey key = iota
+)
+
+func ContextWithProtocolSniffers(ctx context.Context, list []KnownProtocols) context.Context {
+	return context.WithValue(ctx, protocolsKey, list)
+}
+
+func ProtocoSniffersFromContext(ctx context.Context) []KnownProtocols {
+	if list, ok := ctx.Value(protocolsKey).([]KnownProtocols); ok {
+		return list
+	}
+	return nil
+}

+ 2 - 3
testing/scenarios/feature_test.go

@@ -42,9 +42,8 @@ func TestPassiveConnection(t *testing.T) {
 		Inbound: []*proxyman.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
-					PortRange:              v2net.SinglePortRange(serverPort),
-					Listen:                 v2net.NewIPOrDomain(v2net.LocalHostIP),
-					AllowPassiveConnection: true,
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
 				}),
 				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),

+ 8 - 1
transport/ray/direct.go

@@ -39,7 +39,7 @@ func (v *directRay) InboundOutput() InputStream {
 }
 
 type Stream struct {
-	access sync.Mutex
+	access sync.RWMutex
 	data   buf.MultiBuffer
 	ctx    context.Context
 	wakeup chan bool
@@ -75,6 +75,13 @@ func (s *Stream) getData() (buf.MultiBuffer, error) {
 	return nil, nil
 }
 
+func (s *Stream) Peek() buf.MultiBuffer {
+	s.access.RLock()
+	defer s.access.RUnlock()
+
+	return s.data
+}
+
 func (s *Stream) Read() (buf.MultiBuffer, error) {
 	for {
 		mb, err := s.getData()