mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-21 17:46:58 -05:00
prototype of mtproto proxy
This commit is contained in:
parent
255090c5c0
commit
0d94d25688
@ -10,15 +10,21 @@ import (
|
|||||||
// NewAesDecryptionStream creates a new AES encryption stream based on given key and IV.
|
// NewAesDecryptionStream creates a new AES encryption stream based on given key and IV.
|
||||||
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
|
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
|
||||||
func NewAesDecryptionStream(key []byte, iv []byte) cipher.Stream {
|
func NewAesDecryptionStream(key []byte, iv []byte) cipher.Stream {
|
||||||
|
return NewAesStreamMethod(key, iv, cipher.NewCFBDecrypter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAesStreamMethod(key []byte, iv []byte, f func(cipher.Block, []byte) cipher.Stream) cipher.Stream {
|
||||||
aesBlock, err := aes.NewCipher(key)
|
aesBlock, err := aes.NewCipher(key)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
return cipher.NewCFBDecrypter(aesBlock, iv)
|
return f(aesBlock, iv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAesEncryptionStream creates a new AES description stream based on given key and IV.
|
// NewAesEncryptionStream creates a new AES description stream based on given key and IV.
|
||||||
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
|
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
|
||||||
func NewAesEncryptionStream(key []byte, iv []byte) cipher.Stream {
|
func NewAesEncryptionStream(key []byte, iv []byte) cipher.Stream {
|
||||||
aesBlock, err := aes.NewCipher(key)
|
return NewAesStreamMethod(key, iv, cipher.NewCFBEncrypter)
|
||||||
common.Must(err)
|
}
|
||||||
return cipher.NewCFBEncrypter(aesBlock, iv)
|
|
||||||
|
func NewAesCTRStream(key []byte, iv []byte) cipher.Stream {
|
||||||
|
return NewAesStreamMethod(key, iv, cipher.NewCTR)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
_ "v2ray.com/core/proxy/dokodemo"
|
_ "v2ray.com/core/proxy/dokodemo"
|
||||||
_ "v2ray.com/core/proxy/freedom"
|
_ "v2ray.com/core/proxy/freedom"
|
||||||
_ "v2ray.com/core/proxy/http"
|
_ "v2ray.com/core/proxy/http"
|
||||||
|
_ "v2ray.com/core/proxy/mtproto"
|
||||||
_ "v2ray.com/core/proxy/shadowsocks"
|
_ "v2ray.com/core/proxy/shadowsocks"
|
||||||
_ "v2ray.com/core/proxy/socks"
|
_ "v2ray.com/core/proxy/socks"
|
||||||
_ "v2ray.com/core/proxy/vmess/inbound"
|
_ "v2ray.com/core/proxy/vmess/inbound"
|
||||||
|
108
proxy/mtproto/auth.go
Normal file
108
proxy/mtproto/auth.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderSize = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authentication struct {
|
||||||
|
Header [HeaderSize]byte
|
||||||
|
DecodingKey [32]byte
|
||||||
|
EncodingKey [32]byte
|
||||||
|
DecodingNonce [16]byte
|
||||||
|
EncodingNonce [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authentication) DataCenterID() uint16 {
|
||||||
|
return (uint16(a.Header[61]) << 8) | uint16(a.Header[60])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authentication) ApplySecret(b []byte) {
|
||||||
|
a.DecodingKey = sha256.Sum256(append(a.DecodingKey[:], b...))
|
||||||
|
a.EncodingKey = sha256.Sum256(append(a.EncodingKey[:], b...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomBytes(random []byte) {
|
||||||
|
for {
|
||||||
|
common.Must2(rand.Read(random[:]))
|
||||||
|
|
||||||
|
if random[0] == 0xef {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := (uint32(random[3]) << 24) | (uint32(random[2]) << 16) | (uint32(random[1]) << 8) | uint32(random[0])
|
||||||
|
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0x00000000 == (uint32(random[7])<<24)|(uint32(random[6])<<16)|(uint32(random[5])<<8)|uint32(random[4]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthentication() *Authentication {
|
||||||
|
auth := getAuthenticationObject()
|
||||||
|
random := auth.Header[:]
|
||||||
|
generateRandomBytes(random)
|
||||||
|
copy(auth.EncodingKey[:], random[8:])
|
||||||
|
copy(auth.EncodingNonce[:], random[8+32:])
|
||||||
|
keyivInverse := Inverse(random[8 : 8+32+16])
|
||||||
|
copy(auth.DecodingKey[:], keyivInverse)
|
||||||
|
copy(auth.DecodingNonce[:], keyivInverse[32:])
|
||||||
|
return auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAuthentication(reader io.Reader) (*Authentication, error) {
|
||||||
|
auth := getAuthenticationObject()
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(reader, auth.Header[:]); err != nil {
|
||||||
|
putAuthenticationObject(auth)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(auth.DecodingKey[:], auth.Header[8:])
|
||||||
|
copy(auth.DecodingNonce[:], auth.Header[8+32:])
|
||||||
|
keyivInverse := Inverse(auth.Header[8 : 8+32+16])
|
||||||
|
copy(auth.EncodingKey[:], keyivInverse)
|
||||||
|
copy(auth.EncodingNonce[:], keyivInverse[32:])
|
||||||
|
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse returns a new byte array. It is a sequence of bytes when the input is read from end to beginning.Inverse
|
||||||
|
// Visible for testing only.
|
||||||
|
func Inverse(b []byte) []byte {
|
||||||
|
lenb := len(b)
|
||||||
|
b2 := make([]byte, lenb)
|
||||||
|
for i, v := range b {
|
||||||
|
b2[lenb-i-1] = v
|
||||||
|
}
|
||||||
|
return b2
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
authPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(Authentication)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getAuthenticationObject() *Authentication {
|
||||||
|
return authPool.Get().(*Authentication)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putAuthenticationObject(auth *Authentication) {
|
||||||
|
authPool.Put(auth)
|
||||||
|
}
|
23
proxy/mtproto/auth_test.go
Normal file
23
proxy/mtproto/auth_test.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package mtproto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
. "v2ray.com/core/proxy/mtproto"
|
||||||
|
. "v2ray.com/ext/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInverse(t *testing.T) {
|
||||||
|
assert := With(t)
|
||||||
|
|
||||||
|
b := make([]byte, 64)
|
||||||
|
common.Must2(rand.Read(b))
|
||||||
|
|
||||||
|
bi := Inverse(b)
|
||||||
|
assert(b[0], NotEquals, bi[0])
|
||||||
|
|
||||||
|
bii := Inverse(bi)
|
||||||
|
assert(bii, Equals, b)
|
||||||
|
}
|
75
proxy/mtproto/client.go
Normal file
75
proxy/mtproto/client.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"v2ray.com/core"
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
"v2ray.com/core/common/buf"
|
||||||
|
"v2ray.com/core/common/crypto"
|
||||||
|
"v2ray.com/core/common/net"
|
||||||
|
"v2ray.com/core/common/task"
|
||||||
|
"v2ray.com/core/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
|
||||||
|
return &Client{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Process(ctx context.Context, link *core.Link, dialer proxy.Dialer) error {
|
||||||
|
dest, ok := proxy.TargetFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return newError("unknown destination.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dest.Network != net.Network_TCP {
|
||||||
|
return newError("not TCP traffic", dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dialer.Dial(ctx, dest)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to dial to ", dest).Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
defer conn.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
auth := NewAuthentication()
|
||||||
|
defer putAuthenticationObject(auth)
|
||||||
|
|
||||||
|
request := func() error {
|
||||||
|
encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:])
|
||||||
|
|
||||||
|
var header [HeaderSize]byte
|
||||||
|
encryptor.XORKeyStream(header[:], auth.Header[:])
|
||||||
|
copy(header[:56], auth.Header[:])
|
||||||
|
|
||||||
|
if _, err := conn.Write(header[:]); err != nil {
|
||||||
|
return newError("failed to write auth header").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connWriter := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn))
|
||||||
|
return buf.Copy(link.Reader, connWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := func() error {
|
||||||
|
decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:])
|
||||||
|
|
||||||
|
connReader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn))
|
||||||
|
return buf.Copy(connReader, link.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseDoneAndCloseWriter = task.Single(response, task.OnSuccess(task.Close(link.Writer)))
|
||||||
|
if err := task.Run(task.WithContext(ctx), task.Parallel(request, responseDoneAndCloseWriter))(); err != nil {
|
||||||
|
return newError("connection ends").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return NewClient(ctx, config.(*ClientConfig))
|
||||||
|
}))
|
||||||
|
}
|
24
proxy/mtproto/config.go
Normal file
24
proxy/mtproto/config.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"v2ray.com/core/common/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Account) Equals(another protocol.Account) bool {
|
||||||
|
aa, ok := another.(*Account)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.Secret) != len(aa.Secret) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range a.Secret {
|
||||||
|
if v != aa.Secret[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
153
proxy/mtproto/config.pb.go
Normal file
153
proxy/mtproto/config.pb.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
import protocol "v2ray.com/core/common/protocol"
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
Secret []byte `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Account) Reset() { *m = Account{} }
|
||||||
|
func (m *Account) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Account) ProtoMessage() {}
|
||||||
|
func (*Account) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_config_32dc1a2aa94bc2a8, []int{0}
|
||||||
|
}
|
||||||
|
func (m *Account) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Account.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Account) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Account.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (dst *Account) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Account.Merge(dst, src)
|
||||||
|
}
|
||||||
|
func (m *Account) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Account.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Account) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Account.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Account proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Account) GetSecret() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.Secret
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
// User is a list of users that allowed to connect to this inbound.
|
||||||
|
// Although this is a repeated field, only the first user is effective for now.
|
||||||
|
User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ServerConfig) Reset() { *m = ServerConfig{} }
|
||||||
|
func (m *ServerConfig) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*ServerConfig) ProtoMessage() {}
|
||||||
|
func (*ServerConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_config_32dc1a2aa94bc2a8, []int{1}
|
||||||
|
}
|
||||||
|
func (m *ServerConfig) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_ServerConfig.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *ServerConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_ServerConfig.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (dst *ServerConfig) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_ServerConfig.Merge(dst, src)
|
||||||
|
}
|
||||||
|
func (m *ServerConfig) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_ServerConfig.Size(m)
|
||||||
|
}
|
||||||
|
func (m *ServerConfig) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_ServerConfig.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_ServerConfig proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *ServerConfig) GetUser() []*protocol.User {
|
||||||
|
if m != nil {
|
||||||
|
return m.User
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientConfig) Reset() { *m = ClientConfig{} }
|
||||||
|
func (m *ClientConfig) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*ClientConfig) ProtoMessage() {}
|
||||||
|
func (*ClientConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_config_32dc1a2aa94bc2a8, []int{2}
|
||||||
|
}
|
||||||
|
func (m *ClientConfig) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_ClientConfig.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *ClientConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_ClientConfig.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (dst *ClientConfig) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_ClientConfig.Merge(dst, src)
|
||||||
|
}
|
||||||
|
func (m *ClientConfig) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_ClientConfig.Size(m)
|
||||||
|
}
|
||||||
|
func (m *ClientConfig) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_ClientConfig.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_ClientConfig proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*Account)(nil), "v2ray.core.proxy.mtproto.Account")
|
||||||
|
proto.RegisterType((*ServerConfig)(nil), "v2ray.core.proxy.mtproto.ServerConfig")
|
||||||
|
proto.RegisterType((*ClientConfig)(nil), "v2ray.core.proxy.mtproto.ClientConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterFile("v2ray.com/core/proxy/mtproto/config.proto", fileDescriptor_config_32dc1a2aa94bc2a8)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileDescriptor_config_32dc1a2aa94bc2a8 = []byte{
|
||||||
|
// 221 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x8f, 0xc1, 0x4a, 0xc4, 0x30,
|
||||||
|
0x10, 0x86, 0x89, 0xca, 0x2e, 0xc4, 0xe2, 0xa1, 0x07, 0x09, 0xe2, 0xa1, 0xf6, 0xb4, 0x5e, 0x26,
|
||||||
|
0x50, 0x7d, 0x01, 0xed, 0x5e, 0x85, 0xa5, 0xa2, 0x07, 0x6f, 0xeb, 0x30, 0xca, 0xc2, 0x26, 0x53,
|
||||||
|
0xa6, 0x69, 0xb1, 0xaf, 0xe4, 0x53, 0x4a, 0x93, 0x16, 0x44, 0xf0, 0x94, 0xfc, 0xfc, 0x1f, 0xdf,
|
||||||
|
0x9f, 0xe8, 0xdb, 0xa1, 0x92, 0xfd, 0x08, 0xc8, 0xce, 0x22, 0x0b, 0xd9, 0x56, 0xf8, 0x6b, 0xb4,
|
||||||
|
0x2e, 0xb4, 0xc2, 0x81, 0x2d, 0xb2, 0xff, 0x38, 0x7c, 0x42, 0x0c, 0xb9, 0x59, 0x50, 0x21, 0x88,
|
||||||
|
0x18, 0xcc, 0xd8, 0xd5, 0x5f, 0x09, 0xb2, 0x73, 0xec, 0x6d, 0x2c, 0x91, 0x8f, 0xb6, 0xef, 0x48,
|
||||||
|
0x92, 0xa4, 0xbc, 0xd1, 0xeb, 0x07, 0x44, 0xee, 0x7d, 0xc8, 0x2f, 0xf5, 0xaa, 0x23, 0x14, 0x0a,
|
||||||
|
0x46, 0x15, 0x6a, 0x93, 0x35, 0x73, 0x2a, 0xb7, 0x3a, 0x7b, 0x26, 0x19, 0x48, 0xea, 0xb8, 0x9e,
|
||||||
|
0xdf, 0xeb, 0xb3, 0x49, 0x60, 0x54, 0x71, 0xba, 0x39, 0xaf, 0x0a, 0xf8, 0xf5, 0x8c, 0x34, 0x04,
|
||||||
|
0xcb, 0x10, 0xbc, 0x74, 0x24, 0x4d, 0xa4, 0xcb, 0x0b, 0x9d, 0xd5, 0xc7, 0x03, 0xf9, 0x90, 0x2c,
|
||||||
|
0x8f, 0x5b, 0x7d, 0x8d, 0xec, 0xe0, 0xbf, 0x3f, 0xec, 0xd4, 0xdb, 0x7a, 0xbe, 0x7e, 0x9f, 0x98,
|
||||||
|
0xd7, 0xaa, 0xd9, 0x8f, 0x50, 0x4f, 0xd4, 0x2e, 0x52, 0x4f, 0xa9, 0x7a, 0x5f, 0xc5, 0xe3, 0xee,
|
||||||
|
0x27, 0x00, 0x00, 0xff, 0xff, 0x54, 0x23, 0xa0, 0xae, 0x37, 0x01, 0x00, 0x00,
|
||||||
|
}
|
23
proxy/mtproto/config.proto
Normal file
23
proxy/mtproto/config.proto
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package v2ray.core.proxy.mtproto;
|
||||||
|
option csharp_namespace = "V2Ray.Core.Proxy.Mtproto";
|
||||||
|
option go_package = "mtproto";
|
||||||
|
option java_package = "com.v2ray.core.proxy.mtproto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "v2ray.com/core/common/protocol/user.proto";
|
||||||
|
|
||||||
|
message Account {
|
||||||
|
bytes secret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerConfig {
|
||||||
|
// User is a list of users that allowed to connect to this inbound.
|
||||||
|
// Although this is a repeated field, only the first user is effective for now.
|
||||||
|
repeated v2ray.core.common.protocol.User user = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientConfig {
|
||||||
|
|
||||||
|
}
|
5
proxy/mtproto/errors.generated.go
Normal file
5
proxy/mtproto/errors.generated.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
import "v2ray.com/core/common/errors"
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("Proxy", "MTProto") }
|
3
proxy/mtproto/mtproto.go
Normal file
3
proxy/mtproto/mtproto.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg mtproto -path Proxy,MTProto
|
110
proxy/mtproto/server.go
Normal file
110
proxy/mtproto/server.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package mtproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"v2ray.com/core"
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
"v2ray.com/core/common/buf"
|
||||||
|
"v2ray.com/core/common/crypto"
|
||||||
|
"v2ray.com/core/common/net"
|
||||||
|
"v2ray.com/core/common/protocol"
|
||||||
|
"v2ray.com/core/common/task"
|
||||||
|
"v2ray.com/core/transport/internet"
|
||||||
|
"v2ray.com/core/transport/pipe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dcList = []net.Address{
|
||||||
|
net.ParseAddress("149.154.175.50"),
|
||||||
|
net.ParseAddress("149.154.167.51"),
|
||||||
|
net.ParseAddress("149.154.175.100"),
|
||||||
|
net.ParseAddress("149.154.167.91"),
|
||||||
|
net.ParseAddress("149.154.171.5"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
user *protocol.User
|
||||||
|
account *Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
||||||
|
if len(config.User) == 0 {
|
||||||
|
return nil, newError("no user configured.")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := config.User[0]
|
||||||
|
rawAccount, err := config.User[0].GetTypedAccount()
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("invalid account").Base(err)
|
||||||
|
}
|
||||||
|
account, ok := rawAccount.(*Account)
|
||||||
|
if !ok {
|
||||||
|
return nil, newError("not a MTProto account")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
user: user,
|
||||||
|
account: account,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Network() net.NetworkList {
|
||||||
|
return net.NetworkList{
|
||||||
|
Network: []net.Network{net.Network_TCP},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error {
|
||||||
|
auth, err := ReadAuthentication(conn)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to read authentication header").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.ApplySecret(s.account.Secret)
|
||||||
|
|
||||||
|
decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:])
|
||||||
|
decryptor.XORKeyStream(auth.Header[:], auth.Header[:])
|
||||||
|
|
||||||
|
dcID := auth.DataCenterID()
|
||||||
|
if dcID >= uint16(len(dcList)) {
|
||||||
|
return newError("invalid data center id: ", dcID)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := net.Destination{
|
||||||
|
Network: net.Network_TCP,
|
||||||
|
Address: dcList[dcID],
|
||||||
|
Port: net.Port(443),
|
||||||
|
}
|
||||||
|
link, err := dispatcher.Dispatch(ctx, dest)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to dispatch request to: ", dest).Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := func() error {
|
||||||
|
reader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn))
|
||||||
|
return buf.Copy(reader, link.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := func() error {
|
||||||
|
encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:])
|
||||||
|
writer := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn))
|
||||||
|
return buf.Copy(link.Reader, writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseDoneAndCloseWriter = task.Single(response, task.OnSuccess(task.Close(link.Writer)))
|
||||||
|
if err := task.Run(task.WithContext(ctx), task.Parallel(request, responseDoneAndCloseWriter))(); err != nil {
|
||||||
|
pipe.CloseError(link.Reader)
|
||||||
|
pipe.CloseError(link.Writer)
|
||||||
|
return newError("connection ends").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return NewServer(ctx, config.(*ServerConfig))
|
||||||
|
}))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user