From e445d21f4ba42e3e4abeeb81164d9bccb79ff26d Mon Sep 17 00:00:00 2001 From: maskedeken <52683904+maskedeken@users.noreply.github.com> Date: Sat, 26 Sep 2020 23:31:24 +0800 Subject: [PATCH] Add trojan protocol support (#181) * Add trojan protocol support Co-authored-by: Eken Chan Co-authored-by: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Co-authored-by: GitHub Action --- infra/conf/trojan.go | 135 +++++++++++ infra/conf/v2ray.go | 2 + main/distro/all/all.go | 1 + proxy/trojan/client.go | 146 ++++++++++++ proxy/trojan/config.go | 50 ++++ proxy/trojan/config.pb.go | 376 +++++++++++++++++++++++++++++++ proxy/trojan/config.proto | 28 +++ proxy/trojan/errors.generated.go | 9 + proxy/trojan/protocol.go | 282 +++++++++++++++++++++++ proxy/trojan/protocol_test.go | 91 ++++++++ proxy/trojan/server.go | 290 ++++++++++++++++++++++++ proxy/trojan/trojan.go | 1 + proxy/trojan/validator.go | 28 +++ 13 files changed, 1439 insertions(+) create mode 100644 infra/conf/trojan.go create mode 100644 proxy/trojan/client.go create mode 100644 proxy/trojan/config.go create mode 100644 proxy/trojan/config.pb.go create mode 100644 proxy/trojan/config.proto create mode 100644 proxy/trojan/errors.generated.go create mode 100644 proxy/trojan/protocol.go create mode 100644 proxy/trojan/protocol_test.go create mode 100644 proxy/trojan/server.go create mode 100644 proxy/trojan/trojan.go create mode 100644 proxy/trojan/validator.go diff --git a/infra/conf/trojan.go b/infra/conf/trojan.go new file mode 100644 index 000000000..4d0c15d89 --- /dev/null +++ b/infra/conf/trojan.go @@ -0,0 +1,135 @@ +package conf + +import ( + "strconv" + + "github.com/golang/protobuf/proto" // nolint: staticcheck + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/serial" + "v2ray.com/core/proxy/trojan" +) + +// TrojanServerTarget is configuration of a single trojan server +type TrojanServerTarget struct { + Address *Address `json:"address"` + Port uint16 `json:"port"` + Password string `json:"password"` + Email string `json:"email"` + Level byte `json:"level"` +} + +// TrojanClientConfig is configuration of trojan servers +type TrojanClientConfig struct { + Servers []*TrojanServerTarget `json:"servers"` +} + +// Build implements Buildable +func (c *TrojanClientConfig) Build() (proto.Message, error) { + config := new(trojan.ClientConfig) + + if len(c.Servers) == 0 { + return nil, newError("0 Trojan server configured.") + } + + serverSpecs := make([]*protocol.ServerEndpoint, len(c.Servers)) + for idx, rec := range c.Servers { + if rec.Address == nil { + return nil, newError("Trojan server address is not set.") + } + if rec.Port == 0 { + return nil, newError("Invalid Trojan port.") + } + if rec.Password == "" { + return nil, newError("Trojan password is not specified.") + } + account := &trojan.Account{ + Password: rec.Password, + } + trojan := &protocol.ServerEndpoint{ + Address: rec.Address.Build(), + Port: uint32(rec.Port), + User: []*protocol.User{ + { + Level: uint32(rec.Level), + Email: rec.Email, + Account: serial.ToTypedMessage(account), + }, + }, + } + + serverSpecs[idx] = trojan + } + + config.Server = serverSpecs + + return config, nil +} + +// TrojanInboundFallback is fallback configuration +type TrojanInboundFallback struct { + Type string `json:"type"` + Dest string `json:"dest"` +} + +// TrojanUserConfig is user configuration +type TrojanUserConfig struct { + Password string `json:"password"` + Level byte `json:"level"` + Email string `json:"email"` +} + +// TrojanServerConfig is Inbound configuration +type TrojanServerConfig struct { + Clients []*TrojanUserConfig `json:"clients"` + Fallback *TrojanInboundFallback `json:"fallback"` +} + +// Build implements Buildable +func (c *TrojanServerConfig) Build() (proto.Message, error) { + config := new(trojan.ServerConfig) + + if len(c.Clients) == 0 { + return nil, newError("No trojan user settings.") + } + + config.Users = make([]*protocol.User, len(c.Clients)) + for idx, rawUser := range c.Clients { + user := new(protocol.User) + account := &trojan.Account{ + Password: rawUser.Password, + } + + user.Email = rawUser.Email + user.Level = uint32(rawUser.Level) + user.Account = serial.ToTypedMessage(account) + config.Users[idx] = user + } + + if c.Fallback != nil { + fb := &trojan.Fallback{ + Dest: c.Fallback.Dest, + } + + if fb.Type == "" && fb.Dest != "" { + switch fb.Dest[0] { + case '@', '/': + fb.Type = "unix" + default: + if _, err := strconv.Atoi(fb.Dest); err == nil { + fb.Dest = "127.0.0.1:" + fb.Dest + } + if _, _, err := net.SplitHostPort(fb.Dest); err == nil { + fb.Type = "tcp" + } + } + } + if fb.Type == "" { + return nil, newError("please fill in a valid value for trojan fallback type") + } + + config.Fallback = fb + } + + return config, nil +} diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index 70eb2b616..8625f1f53 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -22,6 +22,7 @@ var ( "socks": func() interface{} { return new(SocksServerConfig) }, "vless": func() interface{} { return new(VLessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) }, + "trojan": func() interface{} { return new(TrojanServerConfig) }, "mtproto": func() interface{} { return new(MTProtoServerConfig) }, }, "protocol", "settings") @@ -33,6 +34,7 @@ var ( "socks": func() interface{} { return new(SocksClientConfig) }, "vless": func() interface{} { return new(VLessOutboundConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) }, + "trojan": func() interface{} { return new(TrojanClientConfig) }, "mtproto": func() interface{} { return new(MTProtoClientConfig) }, "dns": func() interface{} { return new(DnsOutboundConfig) }, }, "protocol", "settings") diff --git a/main/distro/all/all.go b/main/distro/all/all.go index a71fdf5da..8c3914c72 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -31,6 +31,7 @@ import ( _ "v2ray.com/core/proxy/mtproto" _ "v2ray.com/core/proxy/shadowsocks" _ "v2ray.com/core/proxy/socks" + _ "v2ray.com/core/proxy/trojan" _ "v2ray.com/core/proxy/vless/inbound" _ "v2ray.com/core/proxy/vless/outbound" _ "v2ray.com/core/proxy/vmess/inbound" diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go new file mode 100644 index 000000000..bd2758b15 --- /dev/null +++ b/proxy/trojan/client.go @@ -0,0 +1,146 @@ +// +build !confonly + +package trojan + +import ( + "context" + "time" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/retry" + "v2ray.com/core/common/session" + "v2ray.com/core/common/signal" + "v2ray.com/core/common/task" + "v2ray.com/core/features/policy" + "v2ray.com/core/transport" + "v2ray.com/core/transport/internet" +) + +// Client is a inbound handler for trojan protocol +type Client struct { + serverPicker protocol.ServerPicker + policyManager policy.Manager +} + +// NewClient create a new trojan client. +func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { + serverList := protocol.NewServerList() + for _, rec := range config.Server { + s, err := protocol.NewServerSpecFromPB(rec) + if err != nil { + return nil, newError("failed to parse server spec").Base(err) + } + serverList.AddServer(s) + } + if serverList.Size() == 0 { + return nil, newError("0 server") + } + + v := core.MustFromContext(ctx) + client := &Client{ + serverPicker: protocol.NewRoundRobinServerPicker(serverList), + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + } + return client, nil +} + +// Process implements OutboundHandler.Process(). +func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { // nolint: funlen + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified") + } + destination := outbound.Target + network := destination.Network + + var server *protocol.ServerSpec + var conn internet.Connection + + err := retry.ExponentialBackoff(5, 100).On(func() error { // nolint: gomnd + server = c.serverPicker.PickServer() + rawConn, err := dialer.Dial(ctx, server.Destination()) + if err != nil { + return err + } + + conn = rawConn + return nil + }) + if err != nil { + return newError("failed to find an available destination").AtWarning().Base(err) + } + newError("tunneling request to ", destination, " via ", server.Destination()).WriteToLog(session.ExportIDToError(ctx)) + + defer conn.Close() + + user := server.PickUser() + account, ok := user.Account.(*MemoryAccount) + if !ok { + return newError("user account is not valid") + } + + sessionPolicy := c.policyManager.ForLevel(user.Level) + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + + postRequest := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + var bodyWriter buf.Writer + bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) + connWriter := &ConnWriter{Writer: bufferWriter, Target: destination, Account: account} + + if destination.Network == net.Network_UDP { + bodyWriter = &PacketWriter{Writer: connWriter, Target: destination} + } else { + bodyWriter = connWriter + } + + // write some request payload to buffer + if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { // nolint: lll,gomnd + return newError("failed to write A reqeust payload").Base(err).AtWarning() + } + + // Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer + if err = bufferWriter.SetBuffered(false); err != nil { + return newError("failed to flush payload").Base(err).AtWarning() + } + + if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer request payload").Base(err).AtInfo() + } + + return nil + } + + getResponse := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + + var reader buf.Reader + if network == net.Network_UDP { + reader = &PacketReader{ + Reader: conn, + } + } else { + reader = buf.NewReader(conn) + } + return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) + } + + var responseDoneAndCloseWriter = task.OnSuccess(getResponse, task.Close(link.Writer)) + if err := task.Run(ctx, postRequest, 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) { // nolint: lll + return NewClient(ctx, config.(*ClientConfig)) + })) +} diff --git a/proxy/trojan/config.go b/proxy/trojan/config.go new file mode 100644 index 000000000..817358d4f --- /dev/null +++ b/proxy/trojan/config.go @@ -0,0 +1,50 @@ +package trojan + +import ( + "crypto/sha256" + "encoding/hex" + fmt "fmt" + + "v2ray.com/core/common" + "v2ray.com/core/common/protocol" +) + +// MemoryAccount is an account type converted from Account. +type MemoryAccount struct { + Password string + Key []byte +} + +// AsAccount implements protocol.AsAccount. +func (a *Account) AsAccount() (protocol.Account, error) { + password := a.GetPassword() + key := hexSha224(password) + return &MemoryAccount{ + Password: password, + Key: key, + }, nil +} + +// Equals implements protocol.Account.Equals(). +func (a *MemoryAccount) Equals(another protocol.Account) bool { + if account, ok := another.(*MemoryAccount); ok { + return a.Password == account.Password + } + return false +} + +func hexSha224(password string) []byte { + buf := make([]byte, 56) + hash := sha256.New224() + common.Must2(hash.Write([]byte(password))) + hex.Encode(buf, hash.Sum(nil)) + return buf +} + +func hexString(data []byte) string { + str := "" + for _, v := range data { + str += fmt.Sprintf("%02x", v) + } + return str +} diff --git a/proxy/trojan/config.pb.go b/proxy/trojan/config.pb.go new file mode 100644 index 000000000..2d012ff94 --- /dev/null +++ b/proxy/trojan/config.pb.go @@ -0,0 +1,376 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.13.0 +// source: proxy/trojan/config.proto + +package trojan + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + protocol "v2ray.com/core/common/protocol" +) + +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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Account struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *Account) Reset() { + *x = Account{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_trojan_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_trojan_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_trojan_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Account) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type Fallback struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"` +} + +func (x *Fallback) Reset() { + *x = Fallback{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_trojan_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Fallback) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Fallback) ProtoMessage() {} + +func (x *Fallback) ProtoReflect() protoreflect.Message { + mi := &file_proxy_trojan_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 Fallback.ProtoReflect.Descriptor instead. +func (*Fallback) Descriptor() ([]byte, []int) { + return file_proxy_trojan_config_proto_rawDescGZIP(), []int{1} +} + +func (x *Fallback) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Fallback) GetDest() string { + if x != nil { + return x.Dest + } + return "" +} + +type ClientConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` +} + +func (x *ClientConfig) Reset() { + *x = ClientConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_trojan_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_trojan_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_trojan_config_proto_rawDescGZIP(), []int{2} +} + +func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { + if x != nil { + return x.Server + } + return nil +} + +type ServerConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` + Fallback *Fallback `protobuf:"bytes,2,opt,name=fallback,proto3" json:"fallback,omitempty"` +} + +func (x *ServerConfig) Reset() { + *x = ServerConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_trojan_config_proto_msgTypes[3] + 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_trojan_config_proto_msgTypes[3] + 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_trojan_config_proto_rawDescGZIP(), []int{3} +} + +func (x *ServerConfig) GetUsers() []*protocol.User { + if x != nil { + return x.Users + } + return nil +} + +func (x *ServerConfig) GetFallback() *Fallback { + if x != nil { + return x.Fallback + } + return nil +} + +var File_proxy_trojan_config_proto protoreflect.FileDescriptor + +var file_proxy_trojan_config_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2f, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x76, 0x32, 0x72, + 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, + 0x6f, 0x6a, 0x61, 0x6e, 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, + 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x25, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x32, 0x0a, 0x08, 0x46, 0x61, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, + 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x22, 0x52, + 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, + 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, + 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x22, 0x85, 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x08, 0x66, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x52, 0x08, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x56, 0x0a, 0x1b, 0x63, 0x6f, + 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, 0x32, 0x72, + 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, 0x61, 0x79, + 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, + 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proxy_trojan_config_proto_rawDescOnce sync.Once + file_proxy_trojan_config_proto_rawDescData = file_proxy_trojan_config_proto_rawDesc +) + +func file_proxy_trojan_config_proto_rawDescGZIP() []byte { + file_proxy_trojan_config_proto_rawDescOnce.Do(func() { + file_proxy_trojan_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_trojan_config_proto_rawDescData) + }) + return file_proxy_trojan_config_proto_rawDescData +} + +var file_proxy_trojan_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_proxy_trojan_config_proto_goTypes = []interface{}{ + (*Account)(nil), // 0: v2ray.core.proxy.trojan.Account + (*Fallback)(nil), // 1: v2ray.core.proxy.trojan.Fallback + (*ClientConfig)(nil), // 2: v2ray.core.proxy.trojan.ClientConfig + (*ServerConfig)(nil), // 3: v2ray.core.proxy.trojan.ServerConfig + (*protocol.ServerEndpoint)(nil), // 4: v2ray.core.common.protocol.ServerEndpoint + (*protocol.User)(nil), // 5: v2ray.core.common.protocol.User +} +var file_proxy_trojan_config_proto_depIdxs = []int32{ + 4, // 0: v2ray.core.proxy.trojan.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint + 5, // 1: v2ray.core.proxy.trojan.ServerConfig.users:type_name -> v2ray.core.common.protocol.User + 1, // 2: v2ray.core.proxy.trojan.ServerConfig.fallback:type_name -> v2ray.core.proxy.trojan.Fallback + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_proxy_trojan_config_proto_init() } +func file_proxy_trojan_config_proto_init() { + if File_proxy_trojan_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proxy_trojan_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_trojan_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Fallback); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proxy_trojan_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 + } + } + file_proxy_trojan_config_proto_msgTypes[3].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 + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proxy_trojan_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proxy_trojan_config_proto_goTypes, + DependencyIndexes: file_proxy_trojan_config_proto_depIdxs, + MessageInfos: file_proxy_trojan_config_proto_msgTypes, + }.Build() + File_proxy_trojan_config_proto = out.File + file_proxy_trojan_config_proto_rawDesc = nil + file_proxy_trojan_config_proto_goTypes = nil + file_proxy_trojan_config_proto_depIdxs = nil +} diff --git a/proxy/trojan/config.proto b/proxy/trojan/config.proto new file mode 100644 index 000000000..74092b1eb --- /dev/null +++ b/proxy/trojan/config.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package v2ray.core.proxy.trojan; +option csharp_namespace = "V2Ray.Core.Proxy.Trojan"; +option go_package = "v2ray.com/core/proxy/trojan"; +option java_package = "com.v2ray.core.proxy.trojan"; +option java_multiple_files = true; + +import "common/protocol/user.proto"; +import "common/protocol/server_spec.proto"; + +message Account { + string password = 1; +} + +message Fallback { + string type = 1; + string dest = 2; +} + +message ClientConfig { + repeated v2ray.core.common.protocol.ServerEndpoint server = 1; +} + +message ServerConfig { + repeated v2ray.core.common.protocol.User users = 1; + Fallback fallback = 2; +} diff --git a/proxy/trojan/errors.generated.go b/proxy/trojan/errors.generated.go new file mode 100644 index 000000000..d15be699e --- /dev/null +++ b/proxy/trojan/errors.generated.go @@ -0,0 +1,9 @@ +package trojan + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/trojan/protocol.go b/proxy/trojan/protocol.go new file mode 100644 index 000000000..d8007563d --- /dev/null +++ b/proxy/trojan/protocol.go @@ -0,0 +1,282 @@ +package trojan + +import ( + "encoding/binary" + "io" + + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" +) + +var ( + crlf = []byte{'\r', '\n'} + + addrParser = protocol.NewAddressParser( + protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4), // nolint: gomnd + protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), // nolint: gomnd + protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), // nolint: gomnd + ) +) + +const ( + maxLength = 8192 + + commandTCP byte = 1 + commandUDP byte = 3 +) + +// ConnWriter is TCP Connection Writer Wrapper for trojan protocol +type ConnWriter struct { + io.Writer + Target net.Destination + Account *MemoryAccount + headerSent bool +} + +// Write implements io.Writer +func (c *ConnWriter) Write(p []byte) (n int, err error) { + if !c.headerSent { + if err := c.writeHeader(); err != nil { + return 0, newError("failed to write request header").Base(err) + } + } + + return c.Writer.Write(p) +} + +// WriteMultiBuffer implements buf.Writer +func (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + defer buf.ReleaseMulti(mb) + + for _, b := range mb { + if !b.IsEmpty() { + if _, err := c.Write(b.Bytes()); err != nil { + return err + } + } + } + + return nil +} + +func (c *ConnWriter) writeHeader() error { + buffer := buf.StackNew() + defer buffer.Release() + + command := commandTCP + if c.Target.Network == net.Network_UDP { + command = commandUDP + } + + if _, err := buffer.Write(c.Account.Key); err != nil { + return err + } + if _, err := buffer.Write(crlf); err != nil { + return err + } + if err := buffer.WriteByte(command); err != nil { + return err + } + if err := addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port); err != nil { + return err + } + if _, err := buffer.Write(crlf); err != nil { + return err + } + + _, err := c.Writer.Write(buffer.Bytes()) + if err == nil { + c.headerSent = true + } + + return err +} + +// PacketWriter UDP Connection Writer Wrapper for trojan protocol +type PacketWriter struct { + io.Writer + Target net.Destination +} + +// WriteMultiBuffer implements buf.Writer +func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + b := make([]byte, maxLength) + for !mb.IsEmpty() { + var length int + mb, length = buf.SplitBytes(mb, b) + if _, err := w.writePacket(b[:length], w.Target); err != nil { + buf.ReleaseMulti(mb) + return err + } + } + + return nil +} + +// WriteMultiBufferWithMetadata writes udp packet with destination specified +func (w *PacketWriter) WriteMultiBufferWithMetadata(mb buf.MultiBuffer, dest net.Destination) error { + b := make([]byte, maxLength) + for !mb.IsEmpty() { + var length int + mb, length = buf.SplitBytes(mb, b) + if _, err := w.writePacket(b[:length], dest); err != nil { + buf.ReleaseMulti(mb) + return err + } + } + + return nil +} + +func (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) { // nolint: unparam + buffer := buf.StackNew() + defer buffer.Release() + + length := len(payload) + lengthBuf := [2]byte{} + binary.BigEndian.PutUint16(lengthBuf[:], uint16(length)) + if err := addrParser.WriteAddressPort(&buffer, dest.Address, dest.Port); err != nil { + return 0, err + } + if _, err := buffer.Write(lengthBuf[:]); err != nil { + return 0, err + } + if _, err := buffer.Write(crlf); err != nil { + return 0, err + } + if _, err := buffer.Write(payload); err != nil { + return 0, err + } + _, err := w.Write(buffer.Bytes()) + if err != nil { + return 0, err + } + + return length, nil +} + +// ConnReader is TCP Connection Reader Wrapper for trojan protocol +type ConnReader struct { + io.Reader + Target net.Destination + headerParsed bool +} + +// ParseHeader parses the trojan protocol header +func (c *ConnReader) ParseHeader() error { + var crlf [2]byte + var command [1]byte + var hash [56]byte + if _, err := io.ReadFull(c.Reader, hash[:]); err != nil { + return newError("failed to read user hash").Base(err) + } + + if _, err := io.ReadFull(c.Reader, crlf[:]); err != nil { + return newError("failed to read crlf").Base(err) + } + + if _, err := io.ReadFull(c.Reader, command[:]); err != nil { + return newError("failed to read command").Base(err) + } + + network := net.Network_TCP + if command[0] == commandUDP { + network = net.Network_UDP + } + + addr, port, err := addrParser.ReadAddressPort(nil, c.Reader) + if err != nil { + return newError("failed to read address and port").Base(err) + } + c.Target = net.Destination{Network: network, Address: addr, Port: port} + + if _, err := io.ReadFull(c.Reader, crlf[:]); err != nil { + return newError("failed to read crlf").Base(err) + } + + c.headerParsed = true + return nil +} + +// Read implements io.Reader +func (c *ConnReader) Read(p []byte) (int, error) { + if !c.headerParsed { + if err := c.ParseHeader(); err != nil { + return 0, err + } + } + + return c.Reader.Read(p) +} + +// ReadMultiBuffer implements buf.Reader +func (c *ConnReader) ReadMultiBuffer() (buf.MultiBuffer, error) { + b := buf.New() + _, err := b.ReadFrom(c) + return buf.MultiBuffer{b}, err +} + +// PacketPayload combines udp payload and destination +type PacketPayload struct { + Target net.Destination + Buffer buf.MultiBuffer +} + +// PacketReader is UDP Connection Reader Wrapper for trojan protocol +type PacketReader struct { + io.Reader +} + +// ReadMultiBuffer implements buf.Reader +func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { + p, err := r.ReadMultiBufferWithMetadata() + if p != nil { + return p.Buffer, err + } + return nil, err +} + +// ReadMultiBufferWithMetadata reads udp packet with destination +func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) { + addr, port, err := addrParser.ReadAddressPort(nil, r) + if err != nil { + return nil, newError("failed to read address and port").Base(err) + } + + var lengthBuf [2]byte + if _, err := io.ReadFull(r, lengthBuf[:]); err != nil { + return nil, newError("failed to read payload length").Base(err) + } + + remain := int(binary.BigEndian.Uint16(lengthBuf[:])) + if remain > maxLength { + return nil, newError("oversize payload") + } + + var crlf [2]byte + if _, err := io.ReadFull(r, crlf[:]); err != nil { + return nil, newError("failed to read crlf").Base(err) + } + + dest := net.UDPDestination(addr, port) + var mb buf.MultiBuffer + for remain > 0 { + length := buf.Size + if remain < length { + length = remain + } + + b := buf.New() + mb = append(mb, b) + n, err := b.ReadFullFrom(r, int32(length)) + if err != nil { + buf.ReleaseMulti(mb) + return nil, newError("failed to read payload").Base(err) + } + + remain -= int(n) + } + + return &PacketPayload{Target: dest, Buffer: mb}, nil +} diff --git a/proxy/trojan/protocol_test.go b/proxy/trojan/protocol_test.go new file mode 100644 index 000000000..c30eabef6 --- /dev/null +++ b/proxy/trojan/protocol_test.go @@ -0,0 +1,91 @@ +package trojan_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + . "v2ray.com/core/proxy/trojan" +) + +func toAccount(a *Account) protocol.Account { + account, err := a.AsAccount() + common.Must(err) + return account +} + +func TestTCPRequest(t *testing.T) { + user := &protocol.MemoryUser{ + Email: "love@v2ray.com", + Account: toAccount(&Account{ + Password: "password", + }), + } + payload := []byte("test string") + data := buf.New() + common.Must2(data.Write(payload)) + + buffer := buf.New() + defer buffer.Release() + + destination := net.Destination{Network: net.Network_TCP, Address: net.LocalHostIP, Port: 1234} + writer := &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)} + common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data})) + + reader := &ConnReader{Reader: buffer} + common.Must(reader.ParseHeader()) + + if r := cmp.Diff(reader.Target, destination); r != "" { + t.Error("destination: ", r) + } + + decodedData, err := reader.ReadMultiBuffer() + common.Must(err) + if r := cmp.Diff(decodedData[0].Bytes(), payload); r != "" { + t.Error("data: ", r) + } +} + +func TestUDPRequest(t *testing.T) { + user := &protocol.MemoryUser{ + Email: "love@v2ray.com", + Account: toAccount(&Account{ + Password: "password", + }), + } + payload := []byte("test string") + data := buf.New() + common.Must2(data.Write(payload)) + + buffer := buf.New() + defer buffer.Release() + + destination := net.Destination{Network: net.Network_UDP, Address: net.LocalHostIP, Port: 1234} + writer := &PacketWriter{Writer: &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}, Target: destination} + common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data})) + + connReader := &ConnReader{Reader: buffer} + common.Must(connReader.ParseHeader()) + + packetReader := &PacketReader{Reader: connReader} + p, err := packetReader.ReadMultiBufferWithMetadata() + common.Must(err) + + if p.Buffer.IsEmpty() { + t.Error("no request data") + } + + if r := cmp.Diff(p.Target, destination); r != "" { + t.Error("destination: ", r) + } + + mb, decoded := buf.SplitFirst(p.Buffer) + buf.ReleaseMulti(mb) + + if r := cmp.Diff(decoded.Bytes(), payload); r != "" { + t.Error("data: ", r) + } +} diff --git a/proxy/trojan/server.go b/proxy/trojan/server.go new file mode 100644 index 000000000..577828c3a --- /dev/null +++ b/proxy/trojan/server.go @@ -0,0 +1,290 @@ +// +build !confonly + +package trojan + +import ( + "context" + "io" + "time" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/errors" + "v2ray.com/core/common/log" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + udp_proto "v2ray.com/core/common/protocol/udp" + "v2ray.com/core/common/retry" + "v2ray.com/core/common/session" + "v2ray.com/core/common/signal" + "v2ray.com/core/common/task" + "v2ray.com/core/features/policy" + "v2ray.com/core/features/routing" + "v2ray.com/core/transport/internet" + "v2ray.com/core/transport/internet/udp" +) + +func init() { + common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { // nolint: lll + return NewServer(ctx, config.(*ServerConfig)) + })) +} + +// Server is an inbound connection handler that handles messages in trojan protocol. +type Server struct { + validator *Validator + policyManager policy.Manager + config *ServerConfig +} + +// NewServer creates a new trojan inbound handler. +func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { + validator := new(Validator) + for _, user := range config.Users { + u, err := user.ToMemoryUser() + if err != nil { + return nil, newError("failed to get trojan user").Base(err).AtError() + } + + if err := validator.Add(u); err != nil { + return nil, newError("failed to add user").Base(err).AtError() + } + } + + v := core.MustFromContext(ctx) + server := &Server{ + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + validator: validator, + config: config, + } + + return server, nil +} + +// Network implements proxy.Inbound.Network(). +func (s *Server) Network() []net.Network { + return []net.Network{net.Network_TCP} +} + +// Process implements proxy.Inbound.Process(). +func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { // nolint: funlen,lll + sessionPolicy := s.policyManager.ForLevel(0) + if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { + return newError("unable to set read deadline").Base(err).AtWarning() + } + + buffer := buf.New() + defer buffer.Release() + + n, err := buffer.ReadFrom(conn) + if err != nil { + return newError("failed to read first request").Base(err) + } + + bufferedReader := &buf.BufferedReader{ + Reader: buf.NewReader(conn), + Buffer: buf.MultiBuffer{buffer}, + } + + var user *protocol.MemoryUser + fallbackEnabled := s.config.Fallback != nil + shouldFallback := false + if n < 56 { // nolint: gomnd + // invalid protocol + log.Record(&log.AccessMessage{ + From: conn.RemoteAddr(), + To: "", + Status: log.AccessRejected, + Reason: newError("not trojan protocol"), + }) + + shouldFallback = true + } else { + user = s.validator.Get(hexString(buffer.BytesTo(56))) // nolint: gomnd + if user == nil { + // invalid user, let's fallback + log.Record(&log.AccessMessage{ + From: conn.RemoteAddr(), + To: "", + Status: log.AccessRejected, + Reason: newError("not a valid user"), + }) + + shouldFallback = true + } + } + + if fallbackEnabled && shouldFallback { + return s.fallback(ctx, sessionPolicy, bufferedReader, buf.NewWriter(conn)) + } else if shouldFallback { + return newError("invalid protocol or invalid user") + } + + clientReader := &ConnReader{Reader: bufferedReader} + if err := clientReader.ParseHeader(); err != nil { + log.Record(&log.AccessMessage{ + From: conn.RemoteAddr(), + To: "", + Status: log.AccessRejected, + Reason: err, + }) + return newError("failed to create request from: ", conn.RemoteAddr()).Base(err) + } + + destination := clientReader.Target + if err := conn.SetReadDeadline(time.Time{}); err != nil { + return newError("unable to set read deadline").Base(err).AtWarning() + } + + inbound := session.InboundFromContext(ctx) + if inbound == nil { + panic("no inbound metadata") + } + inbound.User = user + sessionPolicy = s.policyManager.ForLevel(user.Level) + + if destination.Network == net.Network_UDP { // handle udp request + return s.handleUDPPayload(ctx, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher) + } + + // handle tcp request + + log.ContextWithAccessMessage(ctx, &log.AccessMessage{ + From: conn.RemoteAddr(), + To: destination, + Status: log.AccessAccepted, + Reason: "", + Email: user.Email, + }) + + newError("received request for ", destination).WriteToLog(session.ExportIDToError(ctx)) + return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher) +} + +func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error { // nolint: lll + udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) { + common.Must(clientWriter.WriteMultiBufferWithMetadata(buf.MultiBuffer{packet.Payload}, packet.Source)) + }) + + inbound := session.InboundFromContext(ctx) + user := inbound.User + + for { + select { + case <-ctx.Done(): + return nil + default: + p, err := clientReader.ReadMultiBufferWithMetadata() + if err != nil { + if errors.Cause(err) != io.EOF { + return newError("unexpected EOF").Base(err) + } + return nil + } + + log.ContextWithAccessMessage(ctx, &log.AccessMessage{ + From: inbound.Source, + To: p.Target, + Status: log.AccessAccepted, + Reason: "", + Email: user.Email, + }) + newError("tunnelling request to ", p.Target).WriteToLog(session.ExportIDToError(ctx)) + + for _, b := range p.Buffer { + udpServer.Dispatch(ctx, p.Target, b) + } + } + } +} + +func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session, + destination net.Destination, + clientReader buf.Reader, + clientWriter buf.Writer, dispatcher routing.Dispatcher) error { + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) + + link, err := dispatcher.Dispatch(ctx, destination) + if err != nil { + return newError("failed to dispatch request to ", destination).Base(err) + } + + requestDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + if err := buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer request").Base(err) + } + return nil + } + + responseDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + + if err := buf.Copy(link.Reader, clientWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to write response").Base(err) + } + return nil + } + + var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer)) + if err := task.Run(ctx, requestDonePost, responseDone); err != nil { + common.Must(common.Interrupt(link.Reader)) + common.Must(common.Interrupt(link.Writer)) + return newError("connection ends").Base(err) + } + + return nil +} + +func (s *Server) fallback(ctx context.Context, sessionPolicy policy.Session, requestReader buf.Reader, responseWriter buf.Writer) error { // nolint: lll + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) + + var conn net.Conn + var err error + fb := s.config.Fallback + if err := retry.ExponentialBackoff(5, 100).On(func() error { // nolint: gomnd + var dialer net.Dialer + conn, err = dialer.DialContext(ctx, fb.Type, fb.Dest) + if err != nil { + return err + } + return nil + }); err != nil { + return newError("failed to dial to " + fb.Dest).Base(err).AtWarning() + } + defer conn.Close() + + serverReader := buf.NewReader(conn) + serverWriter := buf.NewWriter(conn) + + requestDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + if err := buf.Copy(requestReader, serverWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to fallback request payload").Base(err).AtInfo() + } + return nil + } + + responseDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + if err := buf.Copy(serverReader, responseWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to deliver response payload").Base(err).AtInfo() + } + return nil + } + + if err := task.Run(ctx, task.OnSuccess(requestDone, task.Close(serverWriter)), task.OnSuccess(responseDone, task.Close(responseWriter))); err != nil { // nolint: lll + common.Must(common.Interrupt(serverReader)) + common.Must(common.Interrupt(serverWriter)) + return newError("fallback ends").Base(err).AtInfo() + } + + return nil +} diff --git a/proxy/trojan/trojan.go b/proxy/trojan/trojan.go new file mode 100644 index 000000000..73b3154fb --- /dev/null +++ b/proxy/trojan/trojan.go @@ -0,0 +1 @@ +package trojan diff --git a/proxy/trojan/validator.go b/proxy/trojan/validator.go new file mode 100644 index 000000000..1c7926e3a --- /dev/null +++ b/proxy/trojan/validator.go @@ -0,0 +1,28 @@ +package trojan + +import ( + "sync" + + "v2ray.com/core/common/protocol" +) + +// Validator stores valid trojan users +type Validator struct { + users sync.Map +} + +// Add a trojan user +func (v *Validator) Add(u *protocol.MemoryUser) error { + user := u.Account.(*MemoryAccount) + v.users.Store(hexString(user.Key), u) + return nil +} + +// Get user with hashed key, nil if user doesn't exist. +func (v *Validator) Get(hash string) *protocol.MemoryUser { + u, _ := v.users.Load(hash) + if u != nil { + return u.(*protocol.MemoryUser) + } + return nil +}