mirror of https://github.com/XTLS/Xray-core
parent
038f849dd3
commit
c9f517108c
@ -1,67 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
"github.com/xtls/xray-core/proxy/mtproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MTProtoAccount struct {
|
|
||||||
Secret string `json:"secret"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build implements Buildable
|
|
||||||
func (a *MTProtoAccount) Build() (*mtproto.Account, error) {
|
|
||||||
if len(a.Secret) != 32 {
|
|
||||||
return nil, newError("MTProto secret must have 32 chars")
|
|
||||||
}
|
|
||||||
secret, err := hex.DecodeString(a.Secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to decode secret: ", a.Secret).Base(err)
|
|
||||||
}
|
|
||||||
return &mtproto.Account{
|
|
||||||
Secret: secret,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MTProtoServerConfig struct {
|
|
||||||
Users []json.RawMessage `json:"users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MTProtoServerConfig) Build() (proto.Message, error) {
|
|
||||||
config := &mtproto.ServerConfig{}
|
|
||||||
|
|
||||||
if len(c.Users) == 0 {
|
|
||||||
return nil, newError("zero MTProto users configured.")
|
|
||||||
}
|
|
||||||
config.User = make([]*protocol.User, len(c.Users))
|
|
||||||
for idx, rawData := range c.Users {
|
|
||||||
user := new(protocol.User)
|
|
||||||
if err := json.Unmarshal(rawData, user); err != nil {
|
|
||||||
return nil, newError("invalid MTProto user").Base(err)
|
|
||||||
}
|
|
||||||
account := new(MTProtoAccount)
|
|
||||||
if err := json.Unmarshal(rawData, account); err != nil {
|
|
||||||
return nil, newError("invalid MTProto user").Base(err)
|
|
||||||
}
|
|
||||||
accountProto, err := account.Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to parse MTProto user").Base(err)
|
|
||||||
}
|
|
||||||
user.Account = serial.ToTypedMessage(accountProto)
|
|
||||||
config.User[idx] = user
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MTProtoClientConfig struct{}
|
|
||||||
|
|
||||||
func (c *MTProtoClientConfig) Build() (proto.Message, error) {
|
|
||||||
config := new(mtproto.ClientConfig)
|
|
||||||
return config, nil
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package conf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
|
||||||
"github.com/xtls/xray-core/proxy/mtproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMTProtoServerConfig(t *testing.T) {
|
|
||||||
creator := func() Buildable {
|
|
||||||
return new(MTProtoServerConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
runMultiTestCase(t, []TestCase{
|
|
||||||
{
|
|
||||||
Input: `{
|
|
||||||
"users": [{
|
|
||||||
"email": "love@example.com",
|
|
||||||
"level": 1,
|
|
||||||
"secret": "b0cbcef5a486d9636472ac27f8e11a9d"
|
|
||||||
}]
|
|
||||||
}`,
|
|
||||||
Parser: loadJSON(creator),
|
|
||||||
Output: &mtproto.ServerConfig{
|
|
||||||
User: []*protocol.User{
|
|
||||||
{
|
|
||||||
Email: "love@example.com",
|
|
||||||
Level: 1,
|
|
||||||
Account: serial.ToTypedMessage(&mtproto.Account{
|
|
||||||
Secret: []byte{176, 203, 206, 245, 164, 134, 217, 99, 100, 114, 172, 39, 248, 225, 26, 157},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
package mtproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
HeaderSize = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
type SessionContext struct {
|
|
||||||
ConnectionType [4]byte
|
|
||||||
DataCenterID uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultSessionContext() SessionContext {
|
|
||||||
return SessionContext{
|
|
||||||
ConnectionType: [4]byte{0xef, 0xef, 0xef, 0xef},
|
|
||||||
DataCenterID: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type contextKey int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
sessionContextKey contextKey = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
func ContextWithSessionContext(ctx context.Context, c SessionContext) context.Context {
|
|
||||||
return context.WithValue(ctx, sessionContextKey, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SessionContextFromContext(ctx context.Context) SessionContext {
|
|
||||||
if c := ctx.Value(sessionContextKey); c != nil {
|
|
||||||
return c.(SessionContext)
|
|
||||||
}
|
|
||||||
return DefaultSessionContext()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Authentication struct {
|
|
||||||
Header [HeaderSize]byte
|
|
||||||
DecodingKey [32]byte
|
|
||||||
EncodingKey [32]byte
|
|
||||||
DecodingNonce [16]byte
|
|
||||||
EncodingNonce [16]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Authentication) DataCenterID() uint16 {
|
|
||||||
x := ((int16(a.Header[61]) << 8) | int16(a.Header[60]))
|
|
||||||
if x < 0 {
|
|
||||||
x = -x
|
|
||||||
}
|
|
||||||
return uint16(x) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Authentication) ConnectionType() [4]byte {
|
|
||||||
var x [4]byte
|
|
||||||
copy(x[:], a.Header[56:60])
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
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, connType [4]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 (uint32(random[7])<<24)|(uint32(random[6])<<16)|(uint32(random[5])<<8)|uint32(random[4]) == 0x00000000 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(random[56:60], connType[:])
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthentication(sc SessionContext) *Authentication {
|
|
||||||
auth := getAuthenticationObject()
|
|
||||||
random := auth.Header[:]
|
|
||||||
generateRandomBytes(random, sc.ConnectionType)
|
|
||||||
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)
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package mtproto_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
. "github.com/xtls/xray-core/proxy/mtproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInverse(t *testing.T) {
|
|
||||||
const size = 64
|
|
||||||
b := make([]byte, 64)
|
|
||||||
for b[0] == b[size-1] {
|
|
||||||
common.Must2(rand.Read(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
bi := Inverse(b)
|
|
||||||
if b[0] == bi[0] {
|
|
||||||
t.Fatal("seems bytes are not inversed: ", b[0], "vs", bi[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
bii := Inverse(bi)
|
|
||||||
if r := cmp.Diff(bii, b); r != "" {
|
|
||||||
t.Fatal(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthenticationReadWrite(t *testing.T) {
|
|
||||||
a := NewAuthentication(DefaultSessionContext())
|
|
||||||
b := bytes.NewReader(a.Header[:])
|
|
||||||
a2, err := ReadAuthentication(b)
|
|
||||||
common.Must(err)
|
|
||||||
|
|
||||||
if r := cmp.Diff(a.EncodingKey[:], a2.DecodingKey[:]); r != "" {
|
|
||||||
t.Error("decoding key: ", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r := cmp.Diff(a.EncodingNonce[:], a2.DecodingNonce[:]); r != "" {
|
|
||||||
t.Error("decoding nonce: ", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r := cmp.Diff(a.DecodingKey[:], a2.EncodingKey[:]); r != "" {
|
|
||||||
t.Error("encoding key: ", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r := cmp.Diff(a.DecodingNonce[:], a2.EncodingNonce[:]); r != "" {
|
|
||||||
t.Error("encoding nonce: ", r)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package mtproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
|
||||||
"github.com/xtls/xray-core/common/task"
|
|
||||||
"github.com/xtls/xray-core/transport"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct{}
|
|
||||||
|
|
||||||
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
|
|
||||||
return &Client{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
|
||||||
outbound := session.OutboundFromContext(ctx)
|
|
||||||
if outbound == nil || !outbound.Target.IsValid() {
|
|
||||||
return newError("unknown destination.")
|
|
||||||
}
|
|
||||||
dest := outbound.Target
|
|
||||||
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()
|
|
||||||
|
|
||||||
sc := SessionContextFromContext(ctx)
|
|
||||||
auth := NewAuthentication(sc)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
responseDoneAndCloseWriter := task.OnSuccess(response, task.Close(link.Writer))
|
|
||||||
if err := task.Run(ctx, 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))
|
|
||||||
}))
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package mtproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-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
|
|
||||||
}
|
|
@ -1,272 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.28.1
|
|
||||||
// protoc v3.21.12
|
|
||||||
// source: proxy/mtproto/config.proto
|
|
||||||
|
|
||||||
package mtproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
protocol "github.com/xtls/xray-core/common/protocol"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Secret []byte `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Account) Reset() {
|
|
||||||
*x = Account{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_proxy_mtproto_config_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Account) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Account) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Account) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_proxy_mtproto_config_proto_msgTypes[0]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Account) Descriptor() ([]byte, []int) {
|
|
||||||
return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Account) GetSecret() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Secret
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// 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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ServerConfig) Reset() {
|
|
||||||
*x = ServerConfig{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_proxy_mtproto_config_proto_msgTypes[1]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ServerConfig) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ServerConfig) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *ServerConfig) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_proxy_mtproto_config_proto_msgTypes[1]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ServerConfig) Descriptor() ([]byte, []int) {
|
|
||||||
return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ServerConfig) GetUser() []*protocol.User {
|
|
||||||
if x != nil {
|
|
||||||
return x.User
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientConfig struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ClientConfig) Reset() {
|
|
||||||
*x = ClientConfig{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_proxy_mtproto_config_proto_msgTypes[2]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ClientConfig) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ClientConfig) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *ClientConfig) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_proxy_mtproto_config_proto_msgTypes[2]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ClientConfig) Descriptor() ([]byte, []int) {
|
|
||||||
return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{2}
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_proxy_mtproto_config_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_proxy_mtproto_config_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
|
||||||
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x78, 0x72,
|
|
||||||
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
|
||||||
0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x21, 0x0a, 0x07,
|
|
||||||
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65,
|
|
||||||
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22,
|
|
||||||
0x3e, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
|
||||||
0x2e, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
|
||||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
|
||||||
0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22,
|
|
||||||
0x0e, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42,
|
|
||||||
0x58, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78,
|
|
||||||
0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x69, 0x74,
|
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
|
|
||||||
0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70,
|
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78,
|
|
||||||
0x79, 0x2e, 0x4d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_proxy_mtproto_config_proto_rawDescOnce sync.Once
|
|
||||||
file_proxy_mtproto_config_proto_rawDescData = file_proxy_mtproto_config_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_proxy_mtproto_config_proto_rawDescGZIP() []byte {
|
|
||||||
file_proxy_mtproto_config_proto_rawDescOnce.Do(func() {
|
|
||||||
file_proxy_mtproto_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_mtproto_config_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_proxy_mtproto_config_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_proxy_mtproto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
|
||||||
var file_proxy_mtproto_config_proto_goTypes = []interface{}{
|
|
||||||
(*Account)(nil), // 0: xray.proxy.mtproto.Account
|
|
||||||
(*ServerConfig)(nil), // 1: xray.proxy.mtproto.ServerConfig
|
|
||||||
(*ClientConfig)(nil), // 2: xray.proxy.mtproto.ClientConfig
|
|
||||||
(*protocol.User)(nil), // 3: xray.common.protocol.User
|
|
||||||
}
|
|
||||||
var file_proxy_mtproto_config_proto_depIdxs = []int32{
|
|
||||||
3, // 0: xray.proxy.mtproto.ServerConfig.user:type_name -> xray.common.protocol.User
|
|
||||||
1, // [1:1] is the sub-list for method output_type
|
|
||||||
1, // [1:1] is the sub-list for method input_type
|
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
|
||||||
0, // [0:1] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_proxy_mtproto_config_proto_init() }
|
|
||||||
func file_proxy_mtproto_config_proto_init() {
|
|
||||||
if File_proxy_mtproto_config_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_proxy_mtproto_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Account); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_proxy_mtproto_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*ServerConfig); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_proxy_mtproto_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*ClientConfig); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_proxy_mtproto_config_proto_rawDesc,
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 3,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 0,
|
|
||||||
},
|
|
||||||
GoTypes: file_proxy_mtproto_config_proto_goTypes,
|
|
||||||
DependencyIndexes: file_proxy_mtproto_config_proto_depIdxs,
|
|
||||||
MessageInfos: file_proxy_mtproto_config_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_proxy_mtproto_config_proto = out.File
|
|
||||||
file_proxy_mtproto_config_proto_rawDesc = nil
|
|
||||||
file_proxy_mtproto_config_proto_goTypes = nil
|
|
||||||
file_proxy_mtproto_config_proto_depIdxs = nil
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package xray.proxy.mtproto;
|
|
||||||
option csharp_namespace = "Xray.Proxy.Mtproto";
|
|
||||||
option go_package = "github.com/xtls/xray-core/proxy/mtproto";
|
|
||||||
option java_package = "com.xray.proxy.mtproto";
|
|
||||||
option java_multiple_files = true;
|
|
||||||
|
|
||||||
import "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 xray.common.protocol.User user = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ClientConfig {}
|
|
@ -1,9 +0,0 @@
|
|||||||
package mtproto
|
|
||||||
|
|
||||||
import "github.com/xtls/xray-core/common/errors"
|
|
||||||
|
|
||||||
type errPathObjHolder struct{}
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
|
||||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package mtproto
|
|
||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
|
@ -1,160 +0,0 @@
|
|||||||
package mtproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
|
||||||
"github.com/xtls/xray-core/common/signal"
|
|
||||||
"github.com/xtls/xray-core/common/task"
|
|
||||||
"github.com/xtls/xray-core/core"
|
|
||||||
"github.com/xtls/xray-core/features/policy"
|
|
||||||
"github.com/xtls/xray-core/features/routing"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
policy policy.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := core.MustFromContext(ctx)
|
|
||||||
|
|
||||||
return &Server{
|
|
||||||
user: user,
|
|
||||||
account: account,
|
|
||||||
policy: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Network() []net.Network {
|
|
||||||
return []net.Network{net.Network_TCP}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ctype1 = []byte{0xef, 0xef, 0xef, 0xef}
|
|
||||||
ctype2 = []byte{0xee, 0xee, 0xee, 0xee}
|
|
||||||
)
|
|
||||||
|
|
||||||
func isValidConnectionType(c [4]byte) bool {
|
|
||||||
if bytes.Equal(c[:], ctype1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if bytes.Equal(c[:], ctype2) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
|
||||||
sPolicy := s.policy.ForLevel(s.user.Level)
|
|
||||||
|
|
||||||
if err := conn.SetDeadline(time.Now().Add(sPolicy.Timeouts.Handshake)); err != nil {
|
|
||||||
newError("failed to set deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
auth, err := ReadAuthentication(conn)
|
|
||||||
if err != nil {
|
|
||||||
return newError("failed to read authentication header").Base(err)
|
|
||||||
}
|
|
||||||
defer putAuthenticationObject(auth)
|
|
||||||
|
|
||||||
if err := conn.SetDeadline(time.Time{}); err != nil {
|
|
||||||
newError("failed to clear deadline").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
auth.ApplySecret(s.account.Secret)
|
|
||||||
|
|
||||||
decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:])
|
|
||||||
decryptor.XORKeyStream(auth.Header[:], auth.Header[:])
|
|
||||||
|
|
||||||
ct := auth.ConnectionType()
|
|
||||||
if !isValidConnectionType(ct) {
|
|
||||||
return newError("invalid connection type: ", ct)
|
|
||||||
}
|
|
||||||
|
|
||||||
dcID := auth.DataCenterID()
|
|
||||||
if dcID >= uint16(len(dcList)) {
|
|
||||||
return newError("invalid datacenter id: ", dcID)
|
|
||||||
}
|
|
||||||
|
|
||||||
dest := net.Destination{
|
|
||||||
Network: net.Network_TCP,
|
|
||||||
Address: dcList[dcID],
|
|
||||||
Port: net.Port(443),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
timer := signal.CancelAfterInactivity(ctx, cancel, sPolicy.Timeouts.ConnectionIdle)
|
|
||||||
ctx = policy.ContextWithBufferPolicy(ctx, sPolicy.Buffer)
|
|
||||||
|
|
||||||
sc := SessionContext{
|
|
||||||
ConnectionType: ct,
|
|
||||||
DataCenterID: dcID,
|
|
||||||
}
|
|
||||||
ctx = ContextWithSessionContext(ctx, sc)
|
|
||||||
|
|
||||||
link, err := dispatcher.Dispatch(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return newError("failed to dispatch request to: ", dest).Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
request := func() error {
|
|
||||||
defer timer.SetTimeout(sPolicy.Timeouts.DownlinkOnly)
|
|
||||||
|
|
||||||
reader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn))
|
|
||||||
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
|
|
||||||
}
|
|
||||||
|
|
||||||
response := func() error {
|
|
||||||
defer timer.SetTimeout(sPolicy.Timeouts.UplinkOnly)
|
|
||||||
|
|
||||||
encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:])
|
|
||||||
writer := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn))
|
|
||||||
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
|
|
||||||
}
|
|
||||||
|
|
||||||
responseDoneAndCloseWriter := task.OnSuccess(response, task.Close(link.Writer))
|
|
||||||
if err := task.Run(ctx, request, responseDoneAndCloseWriter); err != nil {
|
|
||||||
common.Interrupt(link.Reader)
|
|
||||||
common.Interrupt(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