1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-06-10 18:00:43 +00:00

prototype of mtproto proxy

This commit is contained in:
Darien Raymond 2018-07-04 17:48:48 +02:00
parent 255090c5c0
commit 0d94d25688
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
11 changed files with 535 additions and 4 deletions

View File

@ -10,15 +10,21 @@ import (
// 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.
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)
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.
// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.
func NewAesEncryptionStream(key []byte, iv []byte) cipher.Stream {
aesBlock, err := aes.NewCipher(key)
common.Must(err)
return cipher.NewCFBEncrypter(aesBlock, iv)
return NewAesStreamMethod(key, iv, cipher.NewCFBEncrypter)
}
func NewAesCTRStream(key []byte, iv []byte) cipher.Stream {
return NewAesStreamMethod(key, iv, cipher.NewCTR)
}

View File

@ -26,6 +26,7 @@ import (
_ "v2ray.com/core/proxy/dokodemo"
_ "v2ray.com/core/proxy/freedom"
_ "v2ray.com/core/proxy/http"
_ "v2ray.com/core/proxy/mtproto"
_ "v2ray.com/core/proxy/shadowsocks"
_ "v2ray.com/core/proxy/socks"
_ "v2ray.com/core/proxy/vmess/inbound"

108
proxy/mtproto/auth.go Normal file
View 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)
}

View 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
View 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
View 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
View 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,
}

View 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 {
}

View 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
View 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
View 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))
}))
}