From 3c869bfb7736c00b3396b08b7323161e9b2d7b1a Mon Sep 17 00:00:00 2001 From: Page Fault Date: Tue, 9 Jun 2020 19:07:05 +0000 Subject: [PATCH] refactoring --- api/control/control.go | 181 --- api/control/control_test.go | 7 - api/service/api.pb.go | 1457 ----------------------- api/service/api.proto | 88 -- api/service/client.go | 93 -- api/service/client_test.go | 46 - api/service/gen.sh | 1 - api/service/server.go | 206 ---- api/service/server_test.go | 126 -- build/api.go | 8 - build/base.go | 9 - build/cert.go | 7 - build/client.go | 8 - build/mysql.go | 7 - build/other.go | 9 - build/redis.go | 7 - build/relay.go | 7 - build/router.go | 7 - build/server.go | 8 - cert/cert.go | 207 ---- cert/cert_test.go | 21 - cert/cli.go | 133 --- cert/option.go | 49 - common/error.go | 6 + common/io.go | 58 +- common/io_test.go | 36 - common/net.go | 181 +-- conf/conf.go | 197 --- conf/parse.go | 540 --------- conf/parse_test.go | 96 -- config/config.go | 82 ++ config/config_test.go | 47 + daemon/deamon.go | 49 - easy/easy.go | 142 --- go.mod | 21 +- go.sum | 533 +-------- main.go | 13 +- {common => option}/option.go | 6 +- protocol/direct/outbound.go | 210 ---- protocol/direct/outbound_test.go | 135 --- protocol/http/inbound.go | 142 --- protocol/protocol.go | 134 --- protocol/simplesocks/simplesocks.go | 74 -- protocol/socks/inbound.go | 233 ---- protocol/tproxy/inbound.go | 151 --- protocol/trojan/inbound.go | 148 --- protocol/trojan/outbound.go | 74 -- protocol/trojan/packet.go | 70 -- protocol/trojan/websocket.go | 328 ----- proxy/client/app.go | 55 - proxy/client/client.go | 400 +------ proxy/client/config.go | 22 + proxy/client/forward.go | 212 ---- proxy/client/mux.go | 256 ---- proxy/client/nat.go | 168 --- proxy/client/tls.go | 458 ------- proxy/config.go | 15 + proxy/forward/forward.go | 35 + proxy/nat/nat.go | 35 + proxy/option.go | 77 +- proxy/proxy.go | 310 ++--- proxy/relay/relay.go | 69 -- proxy/server/server.go | 346 +----- proxy/stack.go | 73 ++ redirector/redirector.go | 77 ++ router/mixed/geo.go | 189 --- router/mixed/geo_test.go | 65 - router/mixed/list.go | 128 -- router/mixed/mixed.go | 121 -- router/mixed/mixed_test.go | 116 -- router/router.go | 41 - shadow/shadow.go | 89 -- sockopt/darwin.go | 35 - sockopt/linux.go | 37 - sockopt/other.go | 17 - sockopt/sockopt.go | 35 - sockopt/windows.go | 24 - stat/memory/memory_test.go | 103 -- stat/mysql/mysql_test.go | 50 - stat/redis/redis.go | 104 -- statistic/memory/config.go | 15 + {stat => statistic}/memory/memory.go | 78 +- statistic/mysql/config.go | 30 + {stat => statistic}/mysql/mysql.go | 42 +- stat/stat.go => statistic/statistics.go | 21 +- test/api_test.go | 91 -- test/proxy_test.go | 622 ---------- test/real_test.go | 44 - test/senario/proxy_test.go | 116 ++ test/target.go | 147 --- test/util/util.go | 102 ++ tunnel/dokodemo/config.go | 19 + tunnel/dokodemo/conn.go | 70 ++ tunnel/dokodemo/dokodemo_test.go | 81 ++ tunnel/dokodemo/server.go | 144 +++ tunnel/dokodemo/tunnel.go | 27 + tunnel/metadata.go | 213 ++++ tunnel/mux/client.go | 176 +++ tunnel/mux/config.go | 26 + tunnel/mux/conn.go | 99 ++ tunnel/mux/mux_test.go | 52 + tunnel/mux/server.go | 90 ++ tunnel/mux/tunnel.go | 27 + tunnel/raw/client.go | 48 + tunnel/raw/config.go | 21 + tunnel/raw/conn.go | 52 + tunnel/raw/raw_test.go | 50 + tunnel/raw/server.go | 47 + tunnel/raw/tunnel.go | 41 + tunnel/simplesocks/client.go | 65 + tunnel/simplesocks/conn.go | 42 + tunnel/simplesocks/server.go | 93 ++ tunnel/simplesocks/simplesocks_test.go | 60 + tunnel/simplesocks/tunnel.go | 27 + tunnel/socks/client.go | 9 + tunnel/socks/config.go | 14 + tunnel/socks/conn.go | 82 ++ tunnel/socks/server.go | 184 +++ tunnel/socks/socks_test.go | 49 + tunnel/socks/tunnel.go | 25 + tunnel/tproxy/config.go | 17 + tunnel/tproxy/conn.go | 20 + tunnel/tproxy/server.go | 160 +++ {protocol => tunnel}/tproxy/tproxy.go | 4 +- tunnel/tproxy/tunnel.go | 22 + tunnel/transport/client.go | 106 ++ tunnel/transport/config.go | 59 + tunnel/transport/conn.go | 17 + tunnel/transport/server.go | 248 ++++ tunnel/transport/transport_test.go | 114 ++ tunnel/transport/tunnel.go | 27 + tunnel/trojan/client.go | 154 +++ tunnel/trojan/config.go | 16 + tunnel/trojan/packet.go | 83 ++ tunnel/trojan/server.go | 212 ++++ tunnel/trojan/trojan_test.go | 103 ++ tunnel/trojan/tunnel.go | 27 + tunnel/tunnel.go | 89 ++ tunnel/websocket/client.go | 58 + tunnel/websocket/config.go | 21 + tunnel/websocket/conn.go | 26 + tunnel/websocket/server.go | 159 +++ tunnel/websocket/tunnel.go | 27 + tunnel/websocket/websocket_test.go | 45 + version/version.go | 3 +- 145 files changed, 4813 insertions(+), 10405 deletions(-) delete mode 100644 api/control/control.go delete mode 100644 api/control/control_test.go delete mode 100644 api/service/api.pb.go delete mode 100644 api/service/api.proto delete mode 100644 api/service/client.go delete mode 100644 api/service/client_test.go delete mode 100755 api/service/gen.sh delete mode 100644 api/service/server.go delete mode 100644 api/service/server_test.go delete mode 100644 build/api.go delete mode 100644 build/base.go delete mode 100644 build/cert.go delete mode 100644 build/client.go delete mode 100644 build/mysql.go delete mode 100644 build/other.go delete mode 100644 build/redis.go delete mode 100644 build/relay.go delete mode 100644 build/router.go delete mode 100644 build/server.go delete mode 100644 cert/cert.go delete mode 100644 cert/cert_test.go delete mode 100644 cert/cli.go delete mode 100644 cert/option.go delete mode 100644 common/io_test.go delete mode 100644 conf/conf.go delete mode 100644 conf/parse.go delete mode 100644 conf/parse_test.go create mode 100644 config/config.go create mode 100644 config/config_test.go delete mode 100644 daemon/deamon.go delete mode 100644 easy/easy.go rename {common => option}/option.go (81%) delete mode 100644 protocol/direct/outbound.go delete mode 100644 protocol/direct/outbound_test.go delete mode 100644 protocol/http/inbound.go delete mode 100644 protocol/protocol.go delete mode 100644 protocol/simplesocks/simplesocks.go delete mode 100644 protocol/socks/inbound.go delete mode 100644 protocol/tproxy/inbound.go delete mode 100644 protocol/trojan/inbound.go delete mode 100644 protocol/trojan/outbound.go delete mode 100644 protocol/trojan/packet.go delete mode 100644 protocol/trojan/websocket.go delete mode 100644 proxy/client/app.go create mode 100644 proxy/client/config.go delete mode 100644 proxy/client/forward.go delete mode 100644 proxy/client/mux.go delete mode 100644 proxy/client/nat.go delete mode 100644 proxy/client/tls.go create mode 100644 proxy/config.go create mode 100644 proxy/forward/forward.go create mode 100644 proxy/nat/nat.go delete mode 100644 proxy/relay/relay.go create mode 100644 proxy/stack.go create mode 100644 redirector/redirector.go delete mode 100644 router/mixed/geo.go delete mode 100644 router/mixed/geo_test.go delete mode 100644 router/mixed/list.go delete mode 100644 router/mixed/mixed.go delete mode 100644 router/mixed/mixed_test.go delete mode 100644 router/router.go delete mode 100644 shadow/shadow.go delete mode 100644 sockopt/darwin.go delete mode 100644 sockopt/linux.go delete mode 100644 sockopt/other.go delete mode 100644 sockopt/sockopt.go delete mode 100644 sockopt/windows.go delete mode 100644 stat/memory/memory_test.go delete mode 100644 stat/mysql/mysql_test.go delete mode 100644 stat/redis/redis.go create mode 100644 statistic/memory/config.go rename {stat => statistic}/memory/memory.go (68%) create mode 100644 statistic/mysql/config.go rename {stat => statistic}/mysql/mysql.go (72%) rename stat/stat.go => statistic/statistics.go (65%) delete mode 100644 test/api_test.go delete mode 100644 test/proxy_test.go delete mode 100644 test/real_test.go create mode 100644 test/senario/proxy_test.go delete mode 100644 test/target.go create mode 100644 test/util/util.go create mode 100644 tunnel/dokodemo/config.go create mode 100644 tunnel/dokodemo/conn.go create mode 100644 tunnel/dokodemo/dokodemo_test.go create mode 100644 tunnel/dokodemo/server.go create mode 100644 tunnel/dokodemo/tunnel.go create mode 100644 tunnel/metadata.go create mode 100644 tunnel/mux/client.go create mode 100644 tunnel/mux/config.go create mode 100644 tunnel/mux/conn.go create mode 100644 tunnel/mux/mux_test.go create mode 100644 tunnel/mux/server.go create mode 100644 tunnel/mux/tunnel.go create mode 100644 tunnel/raw/client.go create mode 100644 tunnel/raw/config.go create mode 100644 tunnel/raw/conn.go create mode 100644 tunnel/raw/raw_test.go create mode 100644 tunnel/raw/server.go create mode 100644 tunnel/raw/tunnel.go create mode 100644 tunnel/simplesocks/client.go create mode 100644 tunnel/simplesocks/conn.go create mode 100644 tunnel/simplesocks/server.go create mode 100644 tunnel/simplesocks/simplesocks_test.go create mode 100644 tunnel/simplesocks/tunnel.go create mode 100644 tunnel/socks/client.go create mode 100644 tunnel/socks/config.go create mode 100644 tunnel/socks/conn.go create mode 100644 tunnel/socks/server.go create mode 100644 tunnel/socks/socks_test.go create mode 100644 tunnel/socks/tunnel.go create mode 100644 tunnel/tproxy/config.go create mode 100644 tunnel/tproxy/conn.go create mode 100644 tunnel/tproxy/server.go rename {protocol => tunnel}/tproxy/tproxy.go (98%) create mode 100644 tunnel/tproxy/tunnel.go create mode 100644 tunnel/transport/client.go create mode 100644 tunnel/transport/config.go create mode 100644 tunnel/transport/conn.go create mode 100644 tunnel/transport/server.go create mode 100644 tunnel/transport/transport_test.go create mode 100644 tunnel/transport/tunnel.go create mode 100644 tunnel/trojan/client.go create mode 100644 tunnel/trojan/config.go create mode 100644 tunnel/trojan/packet.go create mode 100644 tunnel/trojan/server.go create mode 100644 tunnel/trojan/trojan_test.go create mode 100644 tunnel/trojan/tunnel.go create mode 100644 tunnel/tunnel.go create mode 100644 tunnel/websocket/client.go create mode 100644 tunnel/websocket/config.go create mode 100644 tunnel/websocket/conn.go create mode 100644 tunnel/websocket/server.go create mode 100644 tunnel/websocket/tunnel.go create mode 100644 tunnel/websocket/websocket_test.go diff --git a/api/control/control.go b/api/control/control.go deleted file mode 100644 index 5311096..0000000 --- a/api/control/control.go +++ /dev/null @@ -1,181 +0,0 @@ -package control - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "io" - - "github.com/p4gefau1t/trojan-go/api/service" - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "google.golang.org/grpc" -) - -type apiOption struct { - address *string - key *string - hash *string - cert *string - - cmd *string - password *string - add *bool - delete *bool - modify *bool - list *bool - uploadSpeedLimit *int - downloadSpeedLimit *int - iplimit *int - ctx context.Context -} - -func (apiOption) Name() string { - return "api" -} - -func (o *apiOption) listUsers(apiClient service.TrojanServerServiceClient) error { - stream, err := apiClient.ListUsers(o.ctx, &service.ListUsersRequest{}) - if err != nil { - return err - } - defer stream.CloseSend() - result := []service.ListUsersResponse{} - for { - resp, err := stream.Recv() - if err != nil { - if err == io.EOF { - break - } - return err - } - result = append(result, *resp) - } - data, err := json.Marshal(result) - common.Must(err) - fmt.Println(string(data)) - return nil -} - -func (o *apiOption) getUsers(apiClient service.TrojanServerServiceClient) error { - stream, err := apiClient.GetUsers(o.ctx) - if err != nil { - return err - } - defer stream.CloseSend() - err = stream.Send(&service.GetUsersRequest{ - User: &service.User{ - Password: *o.password, - Hash: *o.hash, - }, - }) - if err != nil { - return err - } - resp, err := stream.Recv() - if err != nil { - return err - } - data, err := json.Marshal(resp) - common.Must(err) - fmt.Print(string(data)) - return nil -} - -func (o *apiOption) setUsers(apiClient service.TrojanServerServiceClient) error { - stream, err := apiClient.SetUsers(o.ctx) - if err != nil { - return err - } - defer stream.CloseSend() - - req := &service.SetUsersRequest{ - User: &service.User{ - Password: *o.password, - Hash: *o.hash, - }, - IpLimit: int32(*o.iplimit), - SpeedLimit: &service.Speed{ - UploadSpeed: uint64(*o.uploadSpeedLimit), - DownloadSpeed: uint64(*o.downloadSpeedLimit), - }, - } - if *o.add { - req.Operation = service.SetUsersRequest_Add - } else if *o.modify { - req.Operation = service.SetUsersRequest_Modify - } else if *o.delete { - req.Operation = service.SetUsersRequest_Delete - } else { - return common.NewError("Invalid operation") - } - - err = stream.Send(req) - if err != nil { - return err - } - resp, err := stream.Recv() - if err != nil { - return err - } - if resp.Success { - fmt.Println("Done") - } else { - fmt.Println("Failed: " + resp.Info) - } - return nil -} - -func (o *apiOption) Handle() error { - if *o.cmd == "" { - return common.NewError("") - } - conn, err := grpc.Dial(*o.address, grpc.WithInsecure()) - if err != nil { - log.Error(err) - return nil - } - defer conn.Close() - apiClient := service.NewTrojanServerServiceClient(conn) - switch *o.cmd { - case "list": - err := o.listUsers(apiClient) - if err != nil { - log.Error(err) - } - case "get": - err := o.getUsers(apiClient) - if err != nil { - log.Error(err) - } - case "set": - err := o.setUsers(apiClient) - if err != nil { - log.Error(err) - } - default: - log.Error("Unknown command " + *o.cmd) - } - return nil -} - -func (o *apiOption) Priority() int { - return 50 -} - -func init() { - common.RegisterOptionHandler(&apiOption{ - cmd: flag.String("api", "", "Connect to a Trojan-Go API service. \"-api add/get/list\""), - address: flag.String("api-addr", "127.0.0.1:10000", "Address of Trojan-Go API service"), - password: flag.String("target-password", "", "Password of the target user"), - hash: flag.String("target-hash", "", "Hash of the target user"), - add: flag.Bool("add-profile", false, "Add a new profile with API"), - delete: flag.Bool("delete-profile", false, "Delete an existing profile with API"), - modify: flag.Bool("modify-profile", false, "Modify an existing profile with API"), - uploadSpeedLimit: flag.Int("upload-speed-limit", 0, "Limit the upload speed with API"), - downloadSpeedLimit: flag.Int("download-speed-limit", 0, "Limit the download speed with API"), - iplimit: flag.Int("ip-limit", 0, "Limit the number of IP with API"), - ctx: context.Background(), - }) -} diff --git a/api/control/control_test.go b/api/control/control_test.go deleted file mode 100644 index fcde102..0000000 --- a/api/control/control_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package control - -import "testing" - -func TestControl(t *testing.T) { - -} diff --git a/api/service/api.pb.go b/api/service/api.pb.go deleted file mode 100644 index 9e07af7..0000000 --- a/api/service/api.pb.go +++ /dev/null @@ -1,1457 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.23.0 -// protoc v3.11.4 -// source: api.proto - -package service - -import ( - context "context" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - 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) -) - -// 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 SetUsersRequest_Operation int32 - -const ( - SetUsersRequest_Add SetUsersRequest_Operation = 0 - SetUsersRequest_Delete SetUsersRequest_Operation = 1 - SetUsersRequest_Modify SetUsersRequest_Operation = 2 -) - -// Enum value maps for SetUsersRequest_Operation. -var ( - SetUsersRequest_Operation_name = map[int32]string{ - 0: "Add", - 1: "Delete", - 2: "Modify", - } - SetUsersRequest_Operation_value = map[string]int32{ - "Add": 0, - "Delete": 1, - "Modify": 2, - } -) - -func (x SetUsersRequest_Operation) Enum() *SetUsersRequest_Operation { - p := new(SetUsersRequest_Operation) - *p = x - return p -} - -func (x SetUsersRequest_Operation) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (SetUsersRequest_Operation) Descriptor() protoreflect.EnumDescriptor { - return file_api_proto_enumTypes[0].Descriptor() -} - -func (SetUsersRequest_Operation) Type() protoreflect.EnumType { - return &file_api_proto_enumTypes[0] -} - -func (x SetUsersRequest_Operation) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use SetUsersRequest_Operation.Descriptor instead. -func (SetUsersRequest_Operation) EnumDescriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{10, 0} -} - -type Traffic struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UploadTraffic uint64 `protobuf:"varint,1,opt,name=upload_traffic,json=uploadTraffic,proto3" json:"upload_traffic,omitempty"` - DownloadTraffic uint64 `protobuf:"varint,2,opt,name=download_traffic,json=downloadTraffic,proto3" json:"download_traffic,omitempty"` -} - -func (x *Traffic) Reset() { - *x = Traffic{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Traffic) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Traffic) ProtoMessage() {} - -func (x *Traffic) ProtoReflect() protoreflect.Message { - mi := &file_api_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 Traffic.ProtoReflect.Descriptor instead. -func (*Traffic) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{0} -} - -func (x *Traffic) GetUploadTraffic() uint64 { - if x != nil { - return x.UploadTraffic - } - return 0 -} - -func (x *Traffic) GetDownloadTraffic() uint64 { - if x != nil { - return x.DownloadTraffic - } - return 0 -} - -type Speed struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UploadSpeed uint64 `protobuf:"varint,1,opt,name=upload_speed,json=uploadSpeed,proto3" json:"upload_speed,omitempty"` - DownloadSpeed uint64 `protobuf:"varint,2,opt,name=download_speed,json=downloadSpeed,proto3" json:"download_speed,omitempty"` -} - -func (x *Speed) Reset() { - *x = Speed{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Speed) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Speed) ProtoMessage() {} - -func (x *Speed) ProtoReflect() protoreflect.Message { - mi := &file_api_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 Speed.ProtoReflect.Descriptor instead. -func (*Speed) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{1} -} - -func (x *Speed) GetUploadSpeed() uint64 { - if x != nil { - return x.UploadSpeed - } - return 0 -} - -func (x *Speed) GetDownloadSpeed() uint64 { - if x != nil { - return x.DownloadSpeed - } - return 0 -} - -type User struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` - Hash string `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` //optional -} - -func (x *User) Reset() { - *x = User{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *User) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*User) ProtoMessage() {} - -func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_api_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 User.ProtoReflect.Descriptor instead. -func (*User) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{2} -} - -func (x *User) GetPassword() string { - if x != nil { - return x.Password - } - return "" -} - -func (x *User) GetHash() string { - if x != nil { - return x.Hash - } - return "" -} - -type UserStatus struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` - TrafficTotal *Traffic `protobuf:"bytes,2,opt,name=traffic_total,json=trafficTotal,proto3" json:"traffic_total,omitempty"` - SpeedCurrent *Speed `protobuf:"bytes,3,opt,name=speed_current,json=speedCurrent,proto3" json:"speed_current,omitempty"` - SpeedLimit *Speed `protobuf:"bytes,4,opt,name=speed_limit,json=speedLimit,proto3" json:"speed_limit,omitempty"` - IpCurrent int32 `protobuf:"varint,5,opt,name=ip_current,json=ipCurrent,proto3" json:"ip_current,omitempty"` - IpLimit int32 `protobuf:"varint,6,opt,name=ip_limit,json=ipLimit,proto3" json:"ip_limit,omitempty"` -} - -func (x *UserStatus) Reset() { - *x = UserStatus{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *UserStatus) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UserStatus) ProtoMessage() {} - -func (x *UserStatus) ProtoReflect() protoreflect.Message { - mi := &file_api_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 UserStatus.ProtoReflect.Descriptor instead. -func (*UserStatus) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} -} - -func (x *UserStatus) GetUser() *User { - if x != nil { - return x.User - } - return nil -} - -func (x *UserStatus) GetTrafficTotal() *Traffic { - if x != nil { - return x.TrafficTotal - } - return nil -} - -func (x *UserStatus) GetSpeedCurrent() *Speed { - if x != nil { - return x.SpeedCurrent - } - return nil -} - -func (x *UserStatus) GetSpeedLimit() *Speed { - if x != nil { - return x.SpeedLimit - } - return nil -} - -func (x *UserStatus) GetIpCurrent() int32 { - if x != nil { - return x.IpCurrent - } - return 0 -} - -func (x *UserStatus) GetIpLimit() int32 { - if x != nil { - return x.IpLimit - } - return 0 -} - -type GetTrafficRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` -} - -func (x *GetTrafficRequest) Reset() { - *x = GetTrafficRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetTrafficRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetTrafficRequest) ProtoMessage() {} - -func (x *GetTrafficRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] - 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 GetTrafficRequest.ProtoReflect.Descriptor instead. -func (*GetTrafficRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} -} - -func (x *GetTrafficRequest) GetUser() *User { - if x != nil { - return x.User - } - return nil -} - -type GetTrafficResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` - Info string `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` - TrafficTotal *Traffic `protobuf:"bytes,3,opt,name=traffic_total,json=trafficTotal,proto3" json:"traffic_total,omitempty"` - SpeedCurrent *Speed `protobuf:"bytes,4,opt,name=speed_current,json=speedCurrent,proto3" json:"speed_current,omitempty"` -} - -func (x *GetTrafficResponse) Reset() { - *x = GetTrafficResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetTrafficResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetTrafficResponse) ProtoMessage() {} - -func (x *GetTrafficResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[5] - 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 GetTrafficResponse.ProtoReflect.Descriptor instead. -func (*GetTrafficResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5} -} - -func (x *GetTrafficResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *GetTrafficResponse) GetInfo() string { - if x != nil { - return x.Info - } - return "" -} - -func (x *GetTrafficResponse) GetTrafficTotal() *Traffic { - if x != nil { - return x.TrafficTotal - } - return nil -} - -func (x *GetTrafficResponse) GetSpeedCurrent() *Speed { - if x != nil { - return x.SpeedCurrent - } - return nil -} - -type ListUsersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *ListUsersRequest) Reset() { - *x = ListUsersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListUsersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListUsersRequest) ProtoMessage() {} - -func (x *ListUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[6] - 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 ListUsersRequest.ProtoReflect.Descriptor instead. -func (*ListUsersRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{6} -} - -type ListUsersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` - Status *UserStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` -} - -func (x *ListUsersResponse) Reset() { - *x = ListUsersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListUsersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListUsersResponse) ProtoMessage() {} - -func (x *ListUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[7] - 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 ListUsersResponse.ProtoReflect.Descriptor instead. -func (*ListUsersResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{7} -} - -func (x *ListUsersResponse) GetUser() *User { - if x != nil { - return x.User - } - return nil -} - -func (x *ListUsersResponse) GetStatus() *UserStatus { - if x != nil { - return x.Status - } - return nil -} - -type GetUsersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` -} - -func (x *GetUsersRequest) Reset() { - *x = GetUsersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetUsersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetUsersRequest) ProtoMessage() {} - -func (x *GetUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[8] - 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 GetUsersRequest.ProtoReflect.Descriptor instead. -func (*GetUsersRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{8} -} - -func (x *GetUsersRequest) GetUser() *User { - if x != nil { - return x.User - } - return nil -} - -type GetUsersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` - Info string `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` - User *User `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"` - Status *UserStatus `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` -} - -func (x *GetUsersResponse) Reset() { - *x = GetUsersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetUsersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetUsersResponse) ProtoMessage() {} - -func (x *GetUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[9] - 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 GetUsersResponse.ProtoReflect.Descriptor instead. -func (*GetUsersResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{9} -} - -func (x *GetUsersResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *GetUsersResponse) GetInfo() string { - if x != nil { - return x.Info - } - return "" -} - -func (x *GetUsersResponse) GetUser() *User { - if x != nil { - return x.User - } - return nil -} - -func (x *GetUsersResponse) GetStatus() *UserStatus { - if x != nil { - return x.Status - } - return nil -} - -type SetUsersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` - Operation SetUsersRequest_Operation `protobuf:"varint,2,opt,name=operation,proto3,enum=trojan.api.SetUsersRequest_Operation" json:"operation,omitempty"` - SpeedLimit *Speed `protobuf:"bytes,3,opt,name=speed_limit,json=speedLimit,proto3" json:"speed_limit,omitempty"` - IpLimit int32 `protobuf:"varint,4,opt,name=ip_limit,json=ipLimit,proto3" json:"ip_limit,omitempty"` -} - -func (x *SetUsersRequest) Reset() { - *x = SetUsersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetUsersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetUsersRequest) ProtoMessage() {} - -func (x *SetUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[10] - 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 SetUsersRequest.ProtoReflect.Descriptor instead. -func (*SetUsersRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{10} -} - -func (x *SetUsersRequest) GetUser() *User { - if x != nil { - return x.User - } - return nil -} - -func (x *SetUsersRequest) GetOperation() SetUsersRequest_Operation { - if x != nil { - return x.Operation - } - return SetUsersRequest_Add -} - -func (x *SetUsersRequest) GetSpeedLimit() *Speed { - if x != nil { - return x.SpeedLimit - } - return nil -} - -func (x *SetUsersRequest) GetIpLimit() int32 { - if x != nil { - return x.IpLimit - } - return 0 -} - -type SetUsersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` - Info string `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` -} - -func (x *SetUsersResponse) Reset() { - *x = SetUsersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetUsersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetUsersResponse) ProtoMessage() {} - -func (x *SetUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[11] - 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 SetUsersResponse.ProtoReflect.Descriptor instead. -func (*SetUsersResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{11} -} - -func (x *SetUsersResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *SetUsersResponse) GetInfo() string { - if x != nil { - return x.Info - } - return "" -} - -var File_api_proto protoreflect.FileDescriptor - -var file_api_proto_rawDesc = []byte{ - 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x74, 0x72, 0x6f, - 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x22, 0x5b, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x66, 0x66, - 0x69, 0x63, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x72, 0x61, - 0x66, 0x66, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x75, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x72, 0x61, - 0x66, 0x66, 0x69, 0x63, 0x22, 0x51, 0x0a, 0x05, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0b, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x70, 0x65, 0x65, 0x64, - 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65, - 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, - 0x61, 0x64, 0x53, 0x70, 0x65, 0x65, 0x64, 0x22, 0x36, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 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, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, - 0x92, 0x02, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, - 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, - 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, - 0x75, 0x73, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, - 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, - 0x52, 0x0c, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x36, - 0x0a, 0x0d, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0c, 0x73, 0x70, 0x65, 0x65, 0x64, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, - 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0a, - 0x73, 0x70, 0x65, 0x65, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x70, - 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, - 0x69, 0x70, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x70, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x69, 0x70, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x22, 0x39, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, - 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x75, 0x73, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, - 0xb4, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, - 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, - 0x52, 0x0c, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x36, - 0x0a, 0x0d, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0c, 0x73, 0x70, 0x65, 0x65, 0x64, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x69, 0x0a, 0x11, 0x4c, 0x69, - 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x24, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x37, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x96, - 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, - 0x6f, 0x12, 0x24, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xf9, 0x01, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x75, - 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x6f, 0x6a, - 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, - 0x72, 0x12, 0x43, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, - 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0a, - 0x73, 0x70, 0x65, 0x65, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x70, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x69, 0x70, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x69, 0x66, - 0x79, 0x10, 0x02, 0x22, 0x40, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x32, 0x64, 0x0a, 0x13, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x0a, - 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x6f, - 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, - 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x6f, 0x6a, - 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, - 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xfd, 0x01, 0x0a, 0x13, - 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, - 0x12, 0x1c, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, - 0x01, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x2e, - 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x74, 0x72, 0x6f, - 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4b, - 0x0a, 0x08, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x2e, 0x74, 0x72, 0x6f, - 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x0b, 0x5a, 0x09, 0x2e, - 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_api_proto_rawDescOnce sync.Once - file_api_proto_rawDescData = file_api_proto_rawDesc -) - -func file_api_proto_rawDescGZIP() []byte { - file_api_proto_rawDescOnce.Do(func() { - file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) - }) - return file_api_proto_rawDescData -} - -var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 12) -var file_api_proto_goTypes = []interface{}{ - (SetUsersRequest_Operation)(0), // 0: trojan.api.SetUsersRequest.Operation - (*Traffic)(nil), // 1: trojan.api.Traffic - (*Speed)(nil), // 2: trojan.api.Speed - (*User)(nil), // 3: trojan.api.User - (*UserStatus)(nil), // 4: trojan.api.UserStatus - (*GetTrafficRequest)(nil), // 5: trojan.api.GetTrafficRequest - (*GetTrafficResponse)(nil), // 6: trojan.api.GetTrafficResponse - (*ListUsersRequest)(nil), // 7: trojan.api.ListUsersRequest - (*ListUsersResponse)(nil), // 8: trojan.api.ListUsersResponse - (*GetUsersRequest)(nil), // 9: trojan.api.GetUsersRequest - (*GetUsersResponse)(nil), // 10: trojan.api.GetUsersResponse - (*SetUsersRequest)(nil), // 11: trojan.api.SetUsersRequest - (*SetUsersResponse)(nil), // 12: trojan.api.SetUsersResponse -} -var file_api_proto_depIdxs = []int32{ - 3, // 0: trojan.api.UserStatus.user:type_name -> trojan.api.User - 1, // 1: trojan.api.UserStatus.traffic_total:type_name -> trojan.api.Traffic - 2, // 2: trojan.api.UserStatus.speed_current:type_name -> trojan.api.Speed - 2, // 3: trojan.api.UserStatus.speed_limit:type_name -> trojan.api.Speed - 3, // 4: trojan.api.GetTrafficRequest.user:type_name -> trojan.api.User - 1, // 5: trojan.api.GetTrafficResponse.traffic_total:type_name -> trojan.api.Traffic - 2, // 6: trojan.api.GetTrafficResponse.speed_current:type_name -> trojan.api.Speed - 3, // 7: trojan.api.ListUsersResponse.user:type_name -> trojan.api.User - 4, // 8: trojan.api.ListUsersResponse.status:type_name -> trojan.api.UserStatus - 3, // 9: trojan.api.GetUsersRequest.user:type_name -> trojan.api.User - 3, // 10: trojan.api.GetUsersResponse.user:type_name -> trojan.api.User - 4, // 11: trojan.api.GetUsersResponse.status:type_name -> trojan.api.UserStatus - 3, // 12: trojan.api.SetUsersRequest.user:type_name -> trojan.api.User - 0, // 13: trojan.api.SetUsersRequest.operation:type_name -> trojan.api.SetUsersRequest.Operation - 2, // 14: trojan.api.SetUsersRequest.speed_limit:type_name -> trojan.api.Speed - 5, // 15: trojan.api.TrojanClientService.GetTraffic:input_type -> trojan.api.GetTrafficRequest - 7, // 16: trojan.api.TrojanServerService.ListUsers:input_type -> trojan.api.ListUsersRequest - 9, // 17: trojan.api.TrojanServerService.GetUsers:input_type -> trojan.api.GetUsersRequest - 11, // 18: trojan.api.TrojanServerService.SetUsers:input_type -> trojan.api.SetUsersRequest - 6, // 19: trojan.api.TrojanClientService.GetTraffic:output_type -> trojan.api.GetTrafficResponse - 8, // 20: trojan.api.TrojanServerService.ListUsers:output_type -> trojan.api.ListUsersResponse - 10, // 21: trojan.api.TrojanServerService.GetUsers:output_type -> trojan.api.GetUsersResponse - 12, // 22: trojan.api.TrojanServerService.SetUsers:output_type -> trojan.api.SetUsersResponse - 19, // [19:23] is the sub-list for method output_type - 15, // [15:19] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name -} - -func init() { file_api_proto_init() } -func file_api_proto_init() { - if File_api_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Traffic); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Speed); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*User); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserStatus); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTrafficRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTrafficResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListUsersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListUsersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUsersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUsersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetUsersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetUsersResponse); 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_api_proto_rawDesc, - NumEnums: 1, - NumMessages: 12, - NumExtensions: 0, - NumServices: 2, - }, - GoTypes: file_api_proto_goTypes, - DependencyIndexes: file_api_proto_depIdxs, - EnumInfos: file_api_proto_enumTypes, - MessageInfos: file_api_proto_msgTypes, - }.Build() - File_api_proto = out.File - file_api_proto_rawDesc = nil - file_api_proto_goTypes = nil - file_api_proto_depIdxs = nil -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConnInterface - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// TrojanClientServiceClient is the client API for TrojanClientService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type TrojanClientServiceClient interface { - GetTraffic(ctx context.Context, in *GetTrafficRequest, opts ...grpc.CallOption) (*GetTrafficResponse, error) -} - -type trojanClientServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewTrojanClientServiceClient(cc grpc.ClientConnInterface) TrojanClientServiceClient { - return &trojanClientServiceClient{cc} -} - -func (c *trojanClientServiceClient) GetTraffic(ctx context.Context, in *GetTrafficRequest, opts ...grpc.CallOption) (*GetTrafficResponse, error) { - out := new(GetTrafficResponse) - err := c.cc.Invoke(ctx, "/trojan.api.TrojanClientService/GetTraffic", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// TrojanClientServiceServer is the server API for TrojanClientService service. -type TrojanClientServiceServer interface { - GetTraffic(context.Context, *GetTrafficRequest) (*GetTrafficResponse, error) -} - -// UnimplementedTrojanClientServiceServer can be embedded to have forward compatible implementations. -type UnimplementedTrojanClientServiceServer struct { -} - -func (*UnimplementedTrojanClientServiceServer) GetTraffic(context.Context, *GetTrafficRequest) (*GetTrafficResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTraffic not implemented") -} - -func RegisterTrojanClientServiceServer(s *grpc.Server, srv TrojanClientServiceServer) { - s.RegisterService(&_TrojanClientService_serviceDesc, srv) -} - -func _TrojanClientService_GetTraffic_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetTrafficRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TrojanClientServiceServer).GetTraffic(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/trojan.api.TrojanClientService/GetTraffic", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TrojanClientServiceServer).GetTraffic(ctx, req.(*GetTrafficRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _TrojanClientService_serviceDesc = grpc.ServiceDesc{ - ServiceName: "trojan.api.TrojanClientService", - HandlerType: (*TrojanClientServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetTraffic", - Handler: _TrojanClientService_GetTraffic_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "api.proto", -} - -// TrojanServerServiceClient is the client API for TrojanServerService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type TrojanServerServiceClient interface { - // list all users - ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (TrojanServerService_ListUsersClient, error) - // obtain specified user's info - GetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_GetUsersClient, error) - // setup exsisting users' config - SetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_SetUsersClient, error) -} - -type trojanServerServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewTrojanServerServiceClient(cc grpc.ClientConnInterface) TrojanServerServiceClient { - return &trojanServerServiceClient{cc} -} - -func (c *trojanServerServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (TrojanServerService_ListUsersClient, error) { - stream, err := c.cc.NewStream(ctx, &_TrojanServerService_serviceDesc.Streams[0], "/trojan.api.TrojanServerService/ListUsers", opts...) - if err != nil { - return nil, err - } - x := &trojanServerServiceListUsersClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type TrojanServerService_ListUsersClient interface { - Recv() (*ListUsersResponse, error) - grpc.ClientStream -} - -type trojanServerServiceListUsersClient struct { - grpc.ClientStream -} - -func (x *trojanServerServiceListUsersClient) Recv() (*ListUsersResponse, error) { - m := new(ListUsersResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *trojanServerServiceClient) GetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_GetUsersClient, error) { - stream, err := c.cc.NewStream(ctx, &_TrojanServerService_serviceDesc.Streams[1], "/trojan.api.TrojanServerService/GetUsers", opts...) - if err != nil { - return nil, err - } - x := &trojanServerServiceGetUsersClient{stream} - return x, nil -} - -type TrojanServerService_GetUsersClient interface { - Send(*GetUsersRequest) error - Recv() (*GetUsersResponse, error) - grpc.ClientStream -} - -type trojanServerServiceGetUsersClient struct { - grpc.ClientStream -} - -func (x *trojanServerServiceGetUsersClient) Send(m *GetUsersRequest) error { - return x.ClientStream.SendMsg(m) -} - -func (x *trojanServerServiceGetUsersClient) Recv() (*GetUsersResponse, error) { - m := new(GetUsersResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *trojanServerServiceClient) SetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_SetUsersClient, error) { - stream, err := c.cc.NewStream(ctx, &_TrojanServerService_serviceDesc.Streams[2], "/trojan.api.TrojanServerService/SetUsers", opts...) - if err != nil { - return nil, err - } - x := &trojanServerServiceSetUsersClient{stream} - return x, nil -} - -type TrojanServerService_SetUsersClient interface { - Send(*SetUsersRequest) error - Recv() (*SetUsersResponse, error) - grpc.ClientStream -} - -type trojanServerServiceSetUsersClient struct { - grpc.ClientStream -} - -func (x *trojanServerServiceSetUsersClient) Send(m *SetUsersRequest) error { - return x.ClientStream.SendMsg(m) -} - -func (x *trojanServerServiceSetUsersClient) Recv() (*SetUsersResponse, error) { - m := new(SetUsersResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// TrojanServerServiceServer is the server API for TrojanServerService service. -type TrojanServerServiceServer interface { - // list all users - ListUsers(*ListUsersRequest, TrojanServerService_ListUsersServer) error - // obtain specified user's info - GetUsers(TrojanServerService_GetUsersServer) error - // setup exsisting users' config - SetUsers(TrojanServerService_SetUsersServer) error -} - -// UnimplementedTrojanServerServiceServer can be embedded to have forward compatible implementations. -type UnimplementedTrojanServerServiceServer struct { -} - -func (*UnimplementedTrojanServerServiceServer) ListUsers(*ListUsersRequest, TrojanServerService_ListUsersServer) error { - return status.Errorf(codes.Unimplemented, "method ListUsers not implemented") -} -func (*UnimplementedTrojanServerServiceServer) GetUsers(TrojanServerService_GetUsersServer) error { - return status.Errorf(codes.Unimplemented, "method GetUsers not implemented") -} -func (*UnimplementedTrojanServerServiceServer) SetUsers(TrojanServerService_SetUsersServer) error { - return status.Errorf(codes.Unimplemented, "method SetUsers not implemented") -} - -func RegisterTrojanServerServiceServer(s *grpc.Server, srv TrojanServerServiceServer) { - s.RegisterService(&_TrojanServerService_serviceDesc, srv) -} - -func _TrojanServerService_ListUsers_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(ListUsersRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(TrojanServerServiceServer).ListUsers(m, &trojanServerServiceListUsersServer{stream}) -} - -type TrojanServerService_ListUsersServer interface { - Send(*ListUsersResponse) error - grpc.ServerStream -} - -type trojanServerServiceListUsersServer struct { - grpc.ServerStream -} - -func (x *trojanServerServiceListUsersServer) Send(m *ListUsersResponse) error { - return x.ServerStream.SendMsg(m) -} - -func _TrojanServerService_GetUsers_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(TrojanServerServiceServer).GetUsers(&trojanServerServiceGetUsersServer{stream}) -} - -type TrojanServerService_GetUsersServer interface { - Send(*GetUsersResponse) error - Recv() (*GetUsersRequest, error) - grpc.ServerStream -} - -type trojanServerServiceGetUsersServer struct { - grpc.ServerStream -} - -func (x *trojanServerServiceGetUsersServer) Send(m *GetUsersResponse) error { - return x.ServerStream.SendMsg(m) -} - -func (x *trojanServerServiceGetUsersServer) Recv() (*GetUsersRequest, error) { - m := new(GetUsersRequest) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func _TrojanServerService_SetUsers_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(TrojanServerServiceServer).SetUsers(&trojanServerServiceSetUsersServer{stream}) -} - -type TrojanServerService_SetUsersServer interface { - Send(*SetUsersResponse) error - Recv() (*SetUsersRequest, error) - grpc.ServerStream -} - -type trojanServerServiceSetUsersServer struct { - grpc.ServerStream -} - -func (x *trojanServerServiceSetUsersServer) Send(m *SetUsersResponse) error { - return x.ServerStream.SendMsg(m) -} - -func (x *trojanServerServiceSetUsersServer) Recv() (*SetUsersRequest, error) { - m := new(SetUsersRequest) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -var _TrojanServerService_serviceDesc = grpc.ServiceDesc{ - ServiceName: "trojan.api.TrojanServerService", - HandlerType: (*TrojanServerServiceServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{ - { - StreamName: "ListUsers", - Handler: _TrojanServerService_ListUsers_Handler, - ServerStreams: true, - }, - { - StreamName: "GetUsers", - Handler: _TrojanServerService_GetUsers_Handler, - ServerStreams: true, - ClientStreams: true, - }, - { - StreamName: "SetUsers", - Handler: _TrojanServerService_SetUsers_Handler, - ServerStreams: true, - ClientStreams: true, - }, - }, - Metadata: "api.proto", -} diff --git a/api/service/api.proto b/api/service/api.proto deleted file mode 100644 index c42af2a..0000000 --- a/api/service/api.proto +++ /dev/null @@ -1,88 +0,0 @@ -syntax = "proto3"; -package trojan.api; -option go_package = ".;service"; - -message Traffic { - uint64 upload_traffic = 1; - uint64 download_traffic = 2; -} - -message Speed { - uint64 upload_speed = 1; - uint64 download_speed = 2; -} - -message User { - string password = 1; - string hash = 2; //optional -} - -message UserStatus { - User user = 1; - Traffic traffic_total = 2; - Speed speed_current = 3; - Speed speed_limit = 4; - int32 ip_current = 5; - int32 ip_limit = 6; -} - -message GetTrafficRequest { - User user = 1; -} - -message GetTrafficResponse { - bool success = 1; - string info = 2; - Traffic traffic_total = 3; - Speed speed_current = 4; -} - -message ListUsersRequest { - -} - -message ListUsersResponse { - User user = 1; - UserStatus status = 2; -} - -message GetUsersRequest { - User user = 1; -} - -message GetUsersResponse { - bool success = 1; - string info = 2; - User user = 3; - UserStatus status = 4; -} - -message SetUsersRequest { - User user = 1; - enum Operation { - Add = 0; - Delete = 1; - Modify = 2; - } - Operation operation = 2; - Speed speed_limit = 3; - int32 ip_limit = 4; -} - -message SetUsersResponse { - bool success = 1; - string info = 2; -} - -service TrojanClientService { - rpc GetTraffic(GetTrafficRequest) returns(GetTrafficResponse){} -} - -service TrojanServerService { - // list all users - rpc ListUsers(ListUsersRequest) returns(stream ListUsersResponse){} - // obtain specified user's info - rpc GetUsers(stream GetUsersRequest) returns(stream GetUsersResponse){} - // setup exsisting users' config - rpc SetUsers(stream SetUsersRequest) returns(stream SetUsersResponse){} -} diff --git a/api/service/client.go b/api/service/client.go deleted file mode 100644 index 037326c..0000000 --- a/api/service/client.go +++ /dev/null @@ -1,93 +0,0 @@ -package service - -import ( - "context" - "crypto/tls" - "net" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/proxy" - "github.com/p4gefau1t/trojan-go/stat" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" -) - -type ClientAPI struct { - TrojanClientServiceServer - - auth stat.Authenticator - ctx context.Context - uploadSpeed uint64 - downloadSpeed uint64 - lastSent uint64 - lastRecv uint64 -} - -func (s *ClientAPI) GetTraffic(ctx context.Context, req *GetTrafficRequest) (*GetTrafficResponse, error) { - log.Debug("API: GetTraffic") - if req.User == nil { - return nil, common.NewError("User is unspecified") - } - if req.User.Hash == "" { - req.User.Hash = common.SHA224String(req.User.Password) - } - valid, user := s.auth.AuthUser(req.User.Hash) - if !valid { - return nil, common.NewError("User " + req.User.Hash + " not found") - } - sent, recv := user.GetTraffic() - sentSpeed, recvSpeed := user.GetSpeed() - resp := &GetTrafficResponse{ - Success: true, - TrafficTotal: &Traffic{ - UploadTraffic: sent, - DownloadTraffic: recv, - }, - SpeedCurrent: &Speed{ - UploadSpeed: sentSpeed, - DownloadSpeed: recvSpeed, - }, - } - return resp, nil -} - -func RunClientAPI(ctx context.Context, config *conf.GlobalConfig, auth stat.Authenticator) error { - var server *grpc.Server - if config.API.APITLS { - creds := credentials.NewTLS(&tls.Config{ - ClientAuth: tls.RequireAndVerifyClientCert, - Certificates: config.TLS.KeyPair, - ClientCAs: config.TLS.ClientCertPool, - }) - server = grpc.NewServer(grpc.Creds(creds)) - } else { - server = grpc.NewServer() - } - service := &ClientAPI{ - ctx: ctx, - auth: auth, - } - RegisterTrojanClientServiceServer(server, service) - listener, err := net.Listen("tcp", config.API.APIAddress.String()) - if err != nil { - return err - } - log.Info("Trojan-Go client-side API service is listening on", config.API.APIAddress) - errChan := make(chan error, 1) - go func() { - errChan <- server.Serve(listener) - }() - select { - case err := <-errChan: - return err - case <-ctx.Done(): - server.Stop() - return nil - } -} - -func init() { - proxy.RegisterAPI(conf.Client, RunClientAPI) -} diff --git a/api/service/client_test.go b/api/service/client_test.go deleted file mode 100644 index 325d73a..0000000 --- a/api/service/client_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package service - -import ( - "context" - "testing" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - _ "github.com/p4gefau1t/trojan-go/log/golog" - "github.com/p4gefau1t/trojan-go/stat/memory" - "google.golang.org/grpc" -) - -func TestClientAPI(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - auth, err := memory.NewMemoryAuth(ctx, &conf.GlobalConfig{}) - common.Must(err) - go RunClientAPI(ctx, &conf.GlobalConfig{ - API: conf.APIConfig{ - APIAddress: common.NewAddress("127.0.0.1", 10000, "tcp"), - }, - }, auth) - common.Must(auth.AddUser("hash1234")) - valid, user := auth.AuthUser("hash1234") - if !valid { - t.Fail() - } - user.AddTraffic(1234, 5678) - time.Sleep(time.Second) - conn, err := grpc.Dial("127.0.0.1:10000", grpc.WithInsecure()) - common.Must(err) - client := NewTrojanClientServiceClient(conn) - resp, err := client.GetTraffic(ctx, &GetTrafficRequest{User: &User{ - Hash: "hash1234", - }}) - common.Must(err) - if resp.TrafficTotal.DownloadTraffic != 5678 || resp.TrafficTotal.UploadTraffic != 1234 { - t.Fail() - } - resp, err = client.GetTraffic(ctx, &GetTrafficRequest{}) - if err == nil { - t.Fail() - } - cancel() -} diff --git a/api/service/gen.sh b/api/service/gen.sh deleted file mode 100755 index a2ac96e..0000000 --- a/api/service/gen.sh +++ /dev/null @@ -1 +0,0 @@ -protoc ./api.proto --go_out=plugins=grpc:. diff --git a/api/service/server.go b/api/service/server.go deleted file mode 100644 index bbb3ece..0000000 --- a/api/service/server.go +++ /dev/null @@ -1,206 +0,0 @@ -package service - -import ( - "context" - "crypto/tls" - "io" - "net" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/proxy" - "github.com/p4gefau1t/trojan-go/stat" - grpc "google.golang.org/grpc" - "google.golang.org/grpc/credentials" -) - -type ServerAPI struct { - TrojanServerServiceServer - auth stat.Authenticator -} - -func (s *ServerAPI) GetUsers(stream TrojanServerService_GetUsersServer) error { - log.Debug("API: GetUsers") - for { - req, err := stream.Recv() - if err == io.EOF { - return nil - } - if err != nil { - return err - } - if req.User == nil { - return common.NewError("User is unspecified") - } - if req.User.Hash == "" { - req.User.Hash = common.SHA224String(req.User.Password) - } - valid, user := s.auth.AuthUser(req.User.Hash) - if !valid { - stream.Send(&GetUsersResponse{ - Success: false, - Info: "Invalid user: " + req.User.Hash, - }) - continue - } - downloadTraffic, uploadTraffic := user.GetTraffic() - downloadSpeed, uploadSpeed := user.GetSpeed() - downloadSpeedLimit, uploadSpeedLimit := user.GetSpeedLimit() - ipLimit := user.GetIPLimit() - ipCurrent := user.GetIP() - err = stream.Send(&GetUsersResponse{ - Success: true, - Status: &UserStatus{ - User: req.User, - TrafficTotal: &Traffic{ - UploadTraffic: uploadTraffic, - DownloadTraffic: downloadTraffic, - }, - SpeedCurrent: &Speed{ - DownloadSpeed: downloadSpeed, - UploadSpeed: uploadSpeed, - }, - SpeedLimit: &Speed{ - DownloadSpeed: uint64(downloadSpeedLimit), - UploadSpeed: uint64(uploadSpeedLimit), - }, - IpCurrent: int32(ipCurrent), - IpLimit: int32(ipLimit), - }, - }) - if err != nil { - return err - } - } -} - -func (s *ServerAPI) SetUsers(stream TrojanServerService_SetUsersServer) error { - log.Debug("API: SetUsers") - for { - req, err := stream.Recv() - if err == io.EOF { - return nil - } - if err != nil { - return err - } - if req.User == nil { - return common.NewError("User is unspecified") - } - if req.User.Hash == "" { - req.User.Hash = common.SHA224String(req.User.Password) - } - switch req.Operation { - case SetUsersRequest_Add: - err = s.auth.AddUser(req.User.Hash) - if req.SpeedLimit != nil { - valid, user := s.auth.AuthUser(req.User.Hash) - if !valid { - return common.NewError("Failed to add new user") - } - user.SetSpeedLimit(int(req.SpeedLimit.DownloadSpeed), int(req.SpeedLimit.UploadSpeed)) - } - case SetUsersRequest_Delete: - err = s.auth.DelUser(req.User.Hash) - case SetUsersRequest_Modify: - valid, user := s.auth.AuthUser(req.User.Hash) - if !valid { - err = common.NewError("Invalid user " + req.User.Hash) - } else { - if req.SpeedLimit.DownloadSpeed > 0 || req.SpeedLimit.UploadSpeed > 0 { - user.SetSpeedLimit(int(req.SpeedLimit.DownloadSpeed), int(req.SpeedLimit.UploadSpeed)) - } - if req.IpLimit > 0 { - user.SetIPLimit(int(req.IpLimit)) - } - } - } - if err != nil { - stream.Send(&SetUsersResponse{ - Success: false, - Info: err.Error(), - }) - continue - } - stream.Send(&SetUsersResponse{ - Success: true, - }) - } -} - -func (s *ServerAPI) ListUsers(req *ListUsersRequest, stream TrojanServerService_ListUsersServer) error { - log.Debug("API: ListUsers") - users := s.auth.ListUsers() - for _, user := range users { - downloadTraffic, uploadTraffic := user.GetTraffic() - downloadSpeed, uploadSpeed := user.GetSpeed() - downloadSpeedLimit, uploadSpeedLimit := user.GetSpeedLimit() - ipLimit := user.GetIPLimit() - ipCurrent := user.GetIP() - err := stream.Send(&ListUsersResponse{ - User: &User{ - Hash: user.Hash(), - }, - Status: &UserStatus{ - TrafficTotal: &Traffic{ - DownloadTraffic: downloadTraffic, - UploadTraffic: uploadTraffic, - }, - SpeedCurrent: &Speed{ - DownloadSpeed: downloadSpeed, - UploadSpeed: uploadSpeed, - }, - SpeedLimit: &Speed{ - DownloadSpeed: uint64(downloadSpeedLimit), - UploadSpeed: uint64(uploadSpeedLimit), - }, - IpLimit: int32(ipLimit), - IpCurrent: int32(ipCurrent), - }, - }) - if err != nil { - return err - } - } - return nil -} - -func RunServerAPI(ctx context.Context, config *conf.GlobalConfig, auth stat.Authenticator) error { - var server *grpc.Server - if config.API.APITLS { - creds := credentials.NewTLS(&tls.Config{ - ClientAuth: tls.RequireAndVerifyClientCert, - Certificates: config.TLS.KeyPair, - ClientCAs: config.TLS.ClientCertPool, - }) - server = grpc.NewServer(grpc.Creds(creds)) - } else { - server = grpc.NewServer() - log.Warn("Using insecure API service. Please set \"api_tls\" to enable TLS-based gRPC service.") - } - service := &ServerAPI{ - auth: auth, - } - RegisterTrojanServerServiceServer(server, service) - listener, err := net.Listen("tcp", config.API.APIAddress.String()) - if err != nil { - return err - } - log.Info("Trojan-Go server-side API service is listening on", config.API.APIAddress) - errChan := make(chan error, 1) - go func() { - errChan <- server.Serve(listener) - }() - select { - case err := <-errChan: - return err - case <-ctx.Done(): - server.Stop() - return nil - } -} - -func init() { - proxy.RegisterAPI(conf.Server, RunServerAPI) -} diff --git a/api/service/server_test.go b/api/service/server_test.go deleted file mode 100644 index 14508e3..0000000 --- a/api/service/server_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package service - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - _ "github.com/p4gefau1t/trojan-go/log/golog" - "github.com/p4gefau1t/trojan-go/stat/memory" - "google.golang.org/grpc" -) - -func TestServerAPI(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - auth, err := memory.NewMemoryAuth(ctx, &conf.GlobalConfig{}) - common.Must(err) - go RunServerAPI(ctx, &conf.GlobalConfig{ - API: conf.APIConfig{ - APIAddress: common.NewAddress("127.0.0.1", 10000, "tcp"), - }, - }, auth) - common.Must(auth.AddUser("hash1234")) - _, user := auth.AuthUser("hash1234") - conn, err := grpc.Dial("127.0.0.1:10000", grpc.WithInsecure()) - common.Must(err) - server := NewTrojanServerServiceClient(conn) - stream1, err := server.ListUsers(ctx, &ListUsersRequest{}) - common.Must(err) - for { - resp, err := stream1.Recv() - if err != nil { - break - } - fmt.Println(resp.User.Hash) - if resp.User.Hash != "hash1234" { - t.Fail() - } - fmt.Println(resp.Status.SpeedCurrent) - fmt.Println(resp.Status.SpeedLimit) - } - stream1.CloseSend() - user.AddTraffic(1234, 5678) - time.Sleep(time.Millisecond * 1000) - stream2, err := server.GetUsers(ctx) - common.Must(err) - stream2.Send(&GetUsersRequest{ - User: &User{ - Hash: "hash1234", - }, - }) - resp2, err := stream2.Recv() - common.Must(err) - if resp2.Status.TrafficTotal.DownloadTraffic != 1234 || resp2.Status.TrafficTotal.UploadTraffic != 5678 { - t.Fail() - } - if resp2.Status.SpeedCurrent.DownloadSpeed != 1234 || resp2.Status.TrafficTotal.UploadTraffic != 5678 { - t.Fail() - } - - stream3, err := server.SetUsers(ctx) - stream3.Send(&SetUsersRequest{ - User: &User{ - Hash: "hash1234", - }, - Operation: SetUsersRequest_Delete, - }) - resp3, err := stream3.Recv() - if err != nil || !resp3.Success { - t.Fail() - } - valid, _ := auth.AuthUser("hash1234") - if valid { - t.Fail() - } - stream3.Send(&SetUsersRequest{ - User: &User{ - Hash: "newhash", - }, - Operation: SetUsersRequest_Add, - }) - resp3, err = stream3.Recv() - if err != nil || !resp3.Success { - t.Fail() - } - valid, user = auth.AuthUser("newhash") - if !valid { - t.Fail() - } - stream3.Send(&SetUsersRequest{ - User: &User{ - Hash: "newhash", - }, - Operation: SetUsersRequest_Modify, - SpeedLimit: &Speed{ - DownloadSpeed: 5000, - UploadSpeed: 3000, - }, - }) - go func() { - for { - user.AddTraffic(200, 0) - } - }() - go func() { - for { - user.AddTraffic(0, 300) - } - }() - time.Sleep(time.Second * 3) - for i := 0; i < 3; i++ { - stream2.Send(&GetUsersRequest{ - User: &User{ - Hash: "newhash", - }, - }) - resp2, err = stream2.Recv() - fmt.Println(resp2.Status.SpeedCurrent) - fmt.Println(resp2.Status.SpeedLimit) - time.Sleep(time.Second) - } - stream2.CloseSend() - cancel() -} diff --git a/build/api.go b/build/api.go deleted file mode 100644 index 9512546..0000000 --- a/build/api.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build api full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/api/control" - _ "github.com/p4gefau1t/trojan-go/api/service" -) diff --git a/build/base.go b/build/base.go deleted file mode 100644 index 972e1d5..0000000 --- a/build/base.go +++ /dev/null @@ -1,9 +0,0 @@ -package build - -import ( - //required modules - _ "github.com/p4gefau1t/trojan-go/common" - _ "github.com/p4gefau1t/trojan-go/log" - _ "github.com/p4gefau1t/trojan-go/log/golog" - //_ "github.com/p4gefau1t/trojan-go/log/simplelog" //for android -) diff --git a/build/cert.go b/build/cert.go deleted file mode 100644 index 7ac84e5..0000000 --- a/build/cert.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build cert full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/cert" -) diff --git a/build/client.go b/build/client.go deleted file mode 100644 index d205d7a..0000000 --- a/build/client.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build client full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/proxy/client" - _ "github.com/p4gefau1t/trojan-go/stat/memory" -) diff --git a/build/mysql.go b/build/mysql.go deleted file mode 100644 index 20acb72..0000000 --- a/build/mysql.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build auth_mysql full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/stat/mysql" -) diff --git a/build/other.go b/build/other.go deleted file mode 100644 index 9b85993..0000000 --- a/build/other.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build full other - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/daemon" - _ "github.com/p4gefau1t/trojan-go/easy" - _ "github.com/p4gefau1t/trojan-go/version" -) diff --git a/build/redis.go b/build/redis.go deleted file mode 100644 index 83488c6..0000000 --- a/build/redis.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build auth_redis full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/stat/redis" -) diff --git a/build/relay.go b/build/relay.go deleted file mode 100644 index 3643225..0000000 --- a/build/relay.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build relay full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/proxy/relay" -) diff --git a/build/router.go b/build/router.go deleted file mode 100644 index 365d161..0000000 --- a/build/router.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build router full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/router/mixed" -) diff --git a/build/server.go b/build/server.go deleted file mode 100644 index c800448..0000000 --- a/build/server.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build server full - -package build - -import ( - _ "github.com/p4gefau1t/trojan-go/proxy/server" - _ "github.com/p4gefau1t/trojan-go/stat/memory" -) diff --git a/cert/cert.go b/cert/cert.go deleted file mode 100644 index 58fb952..0000000 --- a/cert/cert.go +++ /dev/null @@ -1,207 +0,0 @@ -package cert - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "io/ioutil" - "os" - - "github.com/go-acme/lego/v3/certcrypto" - "github.com/go-acme/lego/v3/certificate" - "github.com/go-acme/lego/v3/challenge/http01" - "github.com/go-acme/lego/v3/challenge/tlsalpn01" - "github.com/go-acme/lego/v3/lego" - "github.com/go-acme/lego/v3/registration" - "github.com/p4gefau1t/trojan-go/common" -) - -var caDir string = "https://acme-v02.api.letsencrypt.org/directory" -var tlsPort string = "443" -var httpPort string = "80" - -type User struct { - Email string - Registration *registration.Resource - Key crypto.PrivateKey -} - -func (u *User) GetEmail() string { - return u.Email -} - -func (u User) GetRegistration() *registration.Resource { - return u.Registration -} - -func (u *User) GetPrivateKey() crypto.PrivateKey { - return u.Key -} - -func createAndSaveUserKey() (*ecdsa.PrivateKey, error) { - _, err := os.Stat("user.key") - if os.IsExist(err) { - return nil, common.NewError("user.key exists, unable to create new user") - } - userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - common.Must(err) - userKeyFile, err := os.Create("user.key") - if err != nil { - return nil, common.NewError("Failed to create user key file").Base(err) - } - defer userKeyFile.Close() - - x509Encoded, _ := x509.MarshalECPrivateKey(userKey) - pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded}) - userKeyFile.Write(pemEncoded) - return userKey, nil -} - -func loadUserKey() (*ecdsa.PrivateKey, error) { - pemEncoded, err := ioutil.ReadFile("user.key") - if err != nil { - return nil, common.NewError("Failed to load user's key").Base(err) - } - block, _ := pem.Decode([]byte(pemEncoded)) - if block == nil { - return nil, common.NewError("Failed to parse user's key").Base(err) - } - x509Encoded := block.Bytes - return x509.ParseECPrivateKey(x509Encoded) -} - -func saveServerKeyAndCert(cert *certificate.Resource) error { - ioutil.WriteFile("server.key", cert.PrivateKey, os.ModePerm) - ioutil.WriteFile("server.crt", cert.Certificate, os.ModePerm) - return nil -} - -func loadServerKey() (*rsa.PrivateKey, error) { - keyBytes, err := ioutil.ReadFile("server.key") - if err != nil { - return nil, err - } - block, _ := pem.Decode(keyBytes) - serverKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - return serverKey, nil -} - -func obtainCertificate(domain, email string, userKey *ecdsa.PrivateKey, serverKey crypto.PrivateKey) (*certificate.Resource, error) { - // Create a user. New accounts need an email and private key to start. - user := User{ - Email: email, - Key: userKey, - } - - config := lego.NewConfig(&user) - - // This CA URL is configured for a local dev instance of Boulder running in Docker in a VM. - config.CADirURL = caDir - config.Certificate.KeyType = certcrypto.RSA2048 - - // A client facilitates communication with the CA server. - client, err := lego.NewClient(config) - if err != nil { - return nil, err - } - - // We specify an http port of 5002 and an tls port of 5001 on all interfaces - // because we aren't running as root and can't bind a listener to port 80 and 443 - // (used later when we attempt to pass challenges). Keep in mind that you still - // need to proxy challenge traffic to port 5002 and 5001. - err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", httpPort)) - if err != nil { - return nil, err - } - err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", tlsPort)) - if err != nil { - return nil, err - } - - reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - if err != nil { - return nil, err - } - user.Registration = reg - - request := certificate.ObtainRequest{ - Domains: []string{domain}, - Bundle: false, - PrivateKey: serverKey, - } - certificates, err := client.Certificate.Obtain(request) - if err != nil { - return nil, err - } - - // Each certificate comes back with the cert bytes, the bytes of the client's - // private key, and a certificate URL. SAVE THESE TO DISK. - fmt.Println("certificates obtained for:", certificates.Domain) - - return certificates, nil -} - -func isFilesExist(nameList []string) bool { - fileInfo, err := ioutil.ReadDir("./") - common.Must(err) - for _, v := range fileInfo { - name := v.Name() - for _, u := range nameList { - if name == u { - return true - } - } - } - return false -} - -func RequestCert(domain, email string) error { - if isFilesExist([]string{"server.key", "server.crt"}) { - return common.NewError("Cert files(server.key, server.crt) already exist") - } - userKey, err := loadUserKey() - if err != nil { - fmt.Println("Failed to load user key, trying to create one..") - userKey, err = createAndSaveUserKey() - if err != nil { - return err - } - } else { - fmt.Println("Found user.key, using exist user key") - } - cert, err := obtainCertificate(domain, email, userKey, nil) - if err != nil { - return err - } - if err := saveServerKeyAndCert(cert); err != nil { - return common.NewError("Failed to save cert").Base(err) - } - return nil -} - -func RenewCert(domain, email string) error { - serverKey, err := loadServerKey() - if err != nil { - return err - } - userKey, err := loadUserKey() - if err != nil { - return err - } - cert, err := obtainCertificate(domain, email, userKey, serverKey) - if err != nil { - return err - } - if err := saveServerKeyAndCert(cert); err != nil { - return common.NewError("Failed to save cert").Base(err) - } - return nil -} diff --git a/cert/cert_test.go b/cert/cert_test.go deleted file mode 100644 index b5e9c48..0000000 --- a/cert/cert_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package cert - -import ( - "testing" - - "github.com/p4gefau1t/trojan-go/common" -) - -func TestCreate(t *testing.T) { - caDir = "https://127.0.0.1:14000/dir" - tlsPort = "5001" - httpPort = "5002" - common.Must(RequestCert("localhost", "test@email.com")) -} - -func TestRenew(t *testing.T) { - caDir = "https://127.0.0.1:14000/dir" - tlsPort = "5001" - httpPort = "5002" - common.Must(RenewCert("localhost", "test@email.com")) -} diff --git a/cert/cli.go b/cert/cli.go deleted file mode 100644 index d0a65af..0000000 --- a/cert/cli.go +++ /dev/null @@ -1,133 +0,0 @@ -package cert - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" -) - -type domainInfo struct { - Domain string - Email string -} - -func posString(slice []string, element string) int { - for index, elem := range slice { - if elem == element { - return index - } - } - return -1 -} - -func containsString(slice []string, element string) bool { - return !(posString(slice, element) == -1) -} - -func askForConfirmation() bool { - var response string - _, err := fmt.Scanln(&response) - if err != nil { - log.Fatal(err) - } - okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} - nokayResponses := []string{"n", "N", "no", "No", "NO"} - if containsString(okayResponses, response) { - return true - } else if containsString(nokayResponses, response) { - return false - } else { - fmt.Println("Please type yes or no and then press enter:") - return askForConfirmation() - } -} - -func RequestCertGuide() { - log.Info("Guide mode: request cert") - - log.Warn("To perform a ACME challenge, trojan-go need the ROOT PRIVILEGE to bind port 80 and 443") - log.Warn("Please make sure you HAVE sudo this program, and port 80/443 is NOT used by other process at this moment") - log.Info("Continue? (y/n)") - - if !askForConfirmation() { - return - } - - data, err := ioutil.ReadFile("domain_info.json") - info := &domainInfo{} - - if err != nil { - fmt.Println("domain_info.json not found, creating one") - fmt.Println("Enter your domain name:") - fmt.Scanf("%s", &info.Domain) - fmt.Println("Enter your email address:") - fmt.Scanf("%s", &info.Email) - } else { - log.Info("domain_info.json found") - if err := json.Unmarshal(data, info); err != nil { - log.Error(common.NewError("Failed to parse domain_info.json").Base(err)) - return - } - } - - fmt.Printf("Domain: %s, Email: %s\n", info.Domain, info.Email) - fmt.Println("Is that correct? (y/n)") - - if !askForConfirmation() { - return - } - - data, err = json.Marshal(info) - common.Must(err) - ioutil.WriteFile("domain_info.json", data, os.ModePerm) - - if err := RequestCert(info.Domain, info.Email); err != nil { - log.Error(common.NewError("Failed to create cert").Base(err)) - return - } - - log.Info("All done. Certificates have been saved to server.crt and server.key") - log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Warn("BACKUP DOMAIN_INFO.JSON, SERVER.KEY, SERVER.CRT AND USER.KEY TO A SAFE PLACE") - log.Warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") -} - -func RenewCertGuide() { - log.Info("Guide mode: renew cert") - - log.Warn("To perform a ACME challenge, trojan-go need the ROOT PRIVILEGE to bind port 80 and 443") - log.Warn("Please make sure you HAVE sudo this program, and port 80/443 is NOT used by other process at this moment") - log.Info("Continue? (y/n)") - - if !askForConfirmation() { - return - } - - data, err := ioutil.ReadFile("domain_info.json") - if err != nil { - log.Error(err) - return - } - - info := &domainInfo{} - if err := json.Unmarshal(data, info); err != nil { - log.Error(err) - } - - fmt.Printf("Domain: %s, Email: %s\n", info.Domain, info.Email) - fmt.Println("Is that correct? (y/n)") - - if !askForConfirmation() { - return - } - - if err := RenewCert(info.Domain, info.Email); err != nil { - log.Error(common.NewError("Failed to renew cert").Base(err)) - return - } - log.Info("All done") -} diff --git a/cert/option.go b/cert/option.go deleted file mode 100644 index af79683..0000000 --- a/cert/option.go +++ /dev/null @@ -1,49 +0,0 @@ -package cert - -import ( - "flag" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" -) - -type certOption struct { - mode *string - httpPort *string - tlsPort *string -} - -func (*certOption) Name() string { - return "cert" -} - -func (*certOption) Priority() int { - return 10 -} - -func (c *certOption) Handle() error { - tlsPort = *c.tlsPort - httpPort = *c.httpPort - switch *c.mode { - case "request": - RequestCertGuide() - return nil - case "renew": - RenewCertGuide() - return nil - case "INVALID": - return common.NewError("Not specified") - default: - err := common.NewError("Invalid args " + *c.mode) - log.Error(err) - return common.NewError("Invalid args") - } -} - -func init() { - common.RegisterOptionHandler(&certOption{ - mode: flag.String("autocert", "INVALID", "Simple letsencrpyt cert ACME client. Use \"-autocert request\" to request a cert or \"-autocert renew\" to renew a cert"), - tlsPort: flag.String("autocert-tls-port", "443", "autocert TLS acme challenge port"), - httpPort: flag.String("autocert-http-port", "80", "autocert HTTP acme challenge port"), - }) -} diff --git a/common/error.go b/common/error.go index 49de924..13819ed 100644 --- a/common/error.go +++ b/common/error.go @@ -1,5 +1,9 @@ package common +import ( + "fmt" +) + type Error struct { info string } @@ -23,12 +27,14 @@ func NewError(info string) *Error { func Must(err error) { if err != nil { + fmt.Println(err) panic(err) } } func Must2(_ interface{}, err error) { if err != nil { + fmt.Println(err) panic(err) } } diff --git a/common/io.go b/common/io.go index 90afd56..168a94b 100644 --- a/common/io.go +++ b/common/io.go @@ -12,7 +12,7 @@ type RewindReader struct { buf []byte bufReadIdx int rewinded bool - buffered bool + buffering bool bufferSize int } @@ -23,10 +23,10 @@ func (r *RewindReader) Read(p []byte) (int, error) { r.bufReadIdx += n return n, nil } - r.rewinded = false //all buffered content has been read + r.rewinded = false //all buffering content has been read } n, err := r.rawReader.Read(p) - if r.buffered { + if r.buffering { r.buf = append(r.buf, p[:n]...) if len(r.buf) > r.bufferSize*2 { log.Debug("read too many bytes!") @@ -67,74 +67,44 @@ func (r *RewindReader) Rewind() { } func (r *RewindReader) StopBuffering() { - r.buffered = false + r.buffering = false } func (r *RewindReader) SetBufferSize(size int) { if size == 0 { //disable buffering - if !r.buffered { + if !r.buffering { panic("reader is already disabled") } - r.buffered = false + r.buffering = false r.buf = nil r.bufReadIdx = 0 r.bufferSize = 0 } else { - if r.buffered { + if r.buffering { panic("reader is already buffering") } - r.buffered = true + r.buffering = true r.bufReadIdx = 0 r.bufferSize = size r.buf = make([]byte, 0, size) } } -func NewRewindReader(r io.Reader) *RewindReader { - return &RewindReader{ - rawReader: r, - } -} - -type RewindReadWriteCloser struct { - rawRWC io.ReadWriteCloser +type RewindConn struct { + net.Conn *RewindReader } -func (rwc *RewindReadWriteCloser) Write(p []byte) (int, error) { - return rwc.rawRWC.Write(p) -} - -func (rwc *RewindReadWriteCloser) Close() error { - return rwc.rawRWC.Close() -} - -func NewRewindReadWriteCloser(rwc io.ReadWriteCloser) *RewindReadWriteCloser { - return &RewindReadWriteCloser{ - rawRWC: rwc, - RewindReader: NewRewindReader(rwc), - } -} - -func ReadByte(r io.Reader) (byte, error) { - buf := [1]byte{} - _, err := r.Read(buf[:]) - return buf[0], err -} - -type RewindConn struct { - net.Conn - R *RewindReader -} - func (c *RewindConn) Read(p []byte) (int, error) { - return c.R.Read(p) + return c.RewindReader.Read(p) } func NewRewindConn(conn net.Conn) *RewindConn { return &RewindConn{ Conn: conn, - R: NewRewindReader(conn), + RewindReader: &RewindReader{ + rawReader: conn, + }, } } diff --git a/common/io_test.go b/common/io_test.go deleted file mode 100644 index 7f84dfd..0000000 --- a/common/io_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package common_test - -import ( - "bytes" - "testing" - - "github.com/p4gefau1t/trojan-go/common" - _ "github.com/p4gefau1t/trojan-go/log/golog" - "github.com/p4gefau1t/trojan-go/test" -) - -func TestBufferedReader(t *testing.T) { - payload := test.GeneratePayload(1024) - rawReader := bytes.NewBuffer(payload) - r := common.NewRewindReader(rawReader) - r.SetBufferSize(2048) - buf1 := make([]byte, 512) - buf2 := make([]byte, 512) - common.Must2(r.Read(buf1)) - r.Rewind() - common.Must2(r.Read(buf2)) - if !bytes.Equal(buf1, buf2) { - t.Fail() - } - buf3 := make([]byte, 512) - common.Must2(r.Read(buf3)) - if !bytes.Equal(buf3, payload[512:]) { - t.Fail() - } - r.Rewind() - buf4 := make([]byte, 1024) - common.Must2(r.Read(buf4)) - if !bytes.Equal(payload, buf4) { - t.Fail() - } -} diff --git a/common/net.go b/common/net.go index dcad5ec..82aa89c 100644 --- a/common/net.go +++ b/common/net.go @@ -1,162 +1,11 @@ package common import ( - "encoding/binary" "fmt" - "io" "net" "strconv" ) -type AddressType byte - -const ( - IPv4 AddressType = 1 - DomainName AddressType = 3 - IPv6 AddressType = 4 -) - -type Address struct { - DomainName string - Port int - NetworkType string - net.IP - AddressType -} - -func (a *Address) String() string { - switch a.AddressType { - case IPv4: - return fmt.Sprintf("%s:%d", a.IP.String(), a.Port) - case IPv6: - return fmt.Sprintf("[%s]:%d", a.IP.String(), a.Port) - case DomainName: - return fmt.Sprintf("%s:%d", a.DomainName, a.Port) - default: - return "INVALID_ADDRESS_TYPE" - } -} - -func (a *Address) Network() string { - return a.NetworkType -} - -func (a *Address) ResolveIP() (net.IP, error) { - if a.AddressType == IPv4 || a.AddressType == IPv6 { - return a.IP, nil - } - if a.IP != nil { - return a.IP, nil - } - addr, err := net.ResolveIPAddr("ip", a.DomainName) - if err != nil { - return nil, err - } - a.IP = addr.IP - return addr.IP, nil -} - -func NewAddress(host string, port int, network string) *Address { - if ip := net.ParseIP(host); ip != nil { - if ip.To4() != nil { - return &Address{ - IP: ip, - Port: port, - AddressType: IPv4, - NetworkType: network, - } - } - return &Address{ - IP: ip, - Port: port, - AddressType: IPv6, - NetworkType: network, - } - } - return &Address{ - DomainName: host, - Port: port, - AddressType: DomainName, - NetworkType: network, - } -} - -func (a *Address) Marshal(r io.Reader) error { - byteBuf := [1]byte{} - _, err := io.ReadFull(r, byteBuf[:]) - if err != nil { - return NewError("Unable to read ATYPE").Base(err) - } - a.AddressType = AddressType(byteBuf[0]) - switch a.AddressType { - case IPv4: - var buf [6]byte - _, err := io.ReadFull(r, buf[:]) - if err != nil { - return NewError("Failed to read IPv4").Base(err) - } - a.IP = buf[0:4] - a.Port = int(binary.BigEndian.Uint16(buf[4:6])) - case IPv6: - var buf [18]byte - _, err := io.ReadFull(r, buf[:]) - if err != nil { - return NewError("Failed to read IPv6").Base(err) - } - a.IP = buf[0:16] - a.Port = int(binary.BigEndian.Uint16(buf[16:18])) - case DomainName: - _, err := io.ReadFull(r, byteBuf[:]) - length := byteBuf[0] - if err != nil { - return NewError("Failed to read domain name length") - } - buf := make([]byte, length+2) - _, err = io.ReadFull(r, buf) - if err != nil { - return NewError("Failed to read domain name") - } - //the fucking browser uses IP as a domain name sometimes - host := buf[0:length] - if ip := net.ParseIP(string(host)); ip != nil { - a.IP = ip - if ip.To4() != nil { - a.AddressType = IPv4 - } else { - a.AddressType = IPv6 - } - } else { - a.DomainName = string(host) - } - a.Port = int(binary.BigEndian.Uint16(buf[length : length+2])) - default: - return NewError("Invalid ATYPE " + strconv.FormatInt(int64(a.AddressType), 10)) - } - return nil -} - -func (a *Address) Unmarshal(w io.Writer) error { - _, err := w.Write([]byte{byte(a.AddressType)}) - switch a.AddressType { - case DomainName: - w.Write([]byte{byte((len(a.DomainName)))}) - _, err = w.Write([]byte(a.DomainName)) - case IPv4: - _, err = w.Write(a.IP.To4()) - case IPv6: - _, err = w.Write(a.IP.To16()) - default: - return NewError("Invalid ATYPE " + strconv.FormatInt(int64(a.AddressType), 10)) - } - if err != nil { - return err - } - port := [2]byte{} - binary.BigEndian.PutUint16(port[:], uint16(a.Port)) - _, err = w.Write(port[:]) - return err -} - const ( KiB = 1024 MiB = KiB * 1024 @@ -177,12 +26,26 @@ func HumanFriendlyTraffic(bytes uint64) string { } func PickPort(network string, host string) int { - l, err := net.Listen(network, host+":0") - Must(err) - defer l.Close() - _, port, err := net.SplitHostPort(l.Addr().String()) - Must(err) - p, err := strconv.ParseInt(port, 10, 32) - Must(err) - return int(p) + switch network { + case "tcp": + l, err := net.Listen("tcp", host+":0") + Must(err) + defer l.Close() + _, port, err := net.SplitHostPort(l.Addr().String()) + Must(err) + p, err := strconv.ParseInt(port, 10, 32) + Must(err) + return int(p) + case "udp": + conn, err := net.ListenPacket("udp", host+":0") + Must(err) + defer conn.Close() + _, port, err := net.SplitHostPort(conn.LocalAddr().String()) + Must(err) + p, err := strconv.ParseInt(port, 10, 32) + Must(err) + return int(p) + default: + return 0 + } } diff --git a/conf/conf.go b/conf/conf.go deleted file mode 100644 index d92151c..0000000 --- a/conf/conf.go +++ /dev/null @@ -1,197 +0,0 @@ -package conf - -import ( - "crypto/tls" - "crypto/x509" - "io" - "os/exec" - - "github.com/p4gefau1t/trojan-go/common" - utls "github.com/refraction-networking/utls" -) - -type RunType string - -const ( - Client RunType = "client" - Server RunType = "server" - NAT RunType = "nat" - Forward RunType = "forward" - Relay RunType = "relay" -) - -type DNSType string - -const ( - UDP DNSType = "udp" - DOH DNSType = "https" - DOT DNSType = "dot" - TCP DNSType = "tcp" -) - -type TLSConfig struct { - Verify bool `json:"verify"` - VerifyHostName bool `json:"verify_hostname"` - CertPath string `json:"cert"` - KeyPath string `json:"key"` - ClientCertPath []string `json:"client_cert"` - KeyPassword string `json:"key_password"` - Cipher string `json:"cipher"` - CipherTLS13 string `json:"cipher_tls13"` - PreferServerCipher bool `json:"prefer_server_cipher"` - SNI string `json:"sni"` - HTTPResponseFileName string `json:"plain_http_response"` - FallbackHost string `json:"fallback_addr"` - FallbackPort int `json:"fallback_port"` - ReuseSession bool `json:"reuse_session"` - ALPN []string `json:"alpn"` - Curves string `json:"curves"` - Fingerprint string `json:"fingerprint"` - KeyLogPath string `json:"key_log"` - - ClientHelloID *utls.ClientHelloID - FallbackAddress *common.Address - CertPool *x509.CertPool - ClientCertPool *x509.CertPool - KeyPair []tls.Certificate - HTTPResponse []byte - CipherSuites []uint16 - CipherSuiteTLS13 []uint16 - SessionTicket bool - CurvePreferences []tls.CurveID - KeyLogger io.Writer -} - -type TCPConfig struct { - PreferIPV4 bool `json:"prefer_ipv4"` - KeepAlive bool `json:"keep_alive"` - FastOpen bool `json:"fast_open"` - FastOpenQLen int `json:"fast_open_qlen"` - ReusePort bool `json:"reuse_port"` - NoDelay bool `json:"no_delay"` -} - -type MuxConfig struct { - Enabled bool `json:"enabled"` - IdleTimeout int `json:"idle_timeout"` - Concurrency int `json:"concurrency"` -} - -type MySQLConfig struct { - Enabled bool `json:"enabled"` - ServerHost string `json:"server_addr"` - ServerPort int `json:"server_port"` - Database string `json:"database"` - Username string `json:"username"` - Password string `json:"password"` - CheckRate int `json:"check_rate"` -} - -type RedisConfig struct { - Enabled bool `json:"enabled"` - ServerHost string `json:"server_addr"` - ServerPort int `json:"server_port"` - Password string `json:"password"` -} - -type ForwardProxyConfig struct { - Enabled bool `json:"enabled"` - ProxyHost string `json:"proxy_addr"` - ProxyPort int `json:"proxy_port"` - Username string `json:"username"` - Password string `json:"password"` - - ProxyAddress *common.Address -} - -type CompressionConfig struct { - Enabled bool `json:"enabled"` -} - -type RouterConfig struct { - Enabled bool `json:"enabled"` - Bypass []string `json:"bypass"` - Proxy []string `json:"proxy"` - Block []string `json:"block"` - DomainStrategy string `json:"domain_strategy"` - DefaultPolicy string `json:"default_policy"` - GeoIPFilename string `json:"geoip"` - GeoSiteFilename string `json:"geosite"` - - BypassList []byte - ProxyList []byte - BlockList []byte - - GeoIP []byte - BypassIPCode []string - ProxyIPCode []string - BlockIPCode []string - GeoSite []byte - BypassSiteCode []string - ProxySiteCode []string - BlockSiteCode []string -} - -type WebsocketConfig struct { - Enabled bool `json:"enabled"` - HostName string `json:"hostname"` - Path string `json:"path"` - ObfuscationPassword string `json:"obfuscation_password"` - DoubleTLS bool `json:"double_tls"` - TLS TLSConfig `json:"ssl"` - - ObfuscationKey []byte -} - -type APIConfig struct { - Enabled bool `json:"enabled"` - APIHost string `json:"api_addr"` - APIPort int `json:"api_port"` - APITLS bool `json:"api_tls"` - TLS TLSConfig `json:"ssl"` - - APIAddress *common.Address -} - -type TransportPluginConfig struct { - Enabled bool `json:"enabled"` - Type string `json:"type"` - Command string `json:"command"` - PluginOption string `json:"plugin_option"` - Arg []string `json:"arg"` - Env []string `json:"env"` - - Cmd *exec.Cmd -} - -type GlobalConfig struct { - RunType RunType `json:"run_type"` - LogLevel int `json:"log_level"` - LogFile string `json:"log_file"` - LocalHost string `json:"local_addr"` - LocalPort int `json:"local_port"` - TargetHost string `json:"target_addr"` - TargetPort int `json:"target_port"` - RemoteHost string `json:"remote_addr"` - RemotePort int `json:"remote_port"` - BufferSize int `json:"buffer_size"` - DisableHTTPCheck bool `json:"disable_http_check"` - Passwords []string `json:"password"` - DNS []string `json:"dns"` - TLS TLSConfig `json:"ssl"` - TCP TCPConfig `json:"tcp"` - MySQL MySQLConfig `json:"mysql"` - Redis RedisConfig `json:"redis"` - Mux MuxConfig `json:"mux"` - Router RouterConfig `json:"router"` - Websocket WebsocketConfig `json:"websocket"` - API APIConfig `json:"api"` - ForwardProxy ForwardProxyConfig `json:"forward_proxy"` - Compression CompressionConfig `json:"compression"` - TransportPlugin TransportPluginConfig `json:"transport_plugin"` - - LocalAddress *common.Address - RemoteAddress *common.Address - TargetAddress *common.Address - Hash map[string]string -} diff --git a/conf/parse.go b/conf/parse.go deleted file mode 100644 index 51f3bbf..0000000 --- a/conf/parse.go +++ /dev/null @@ -1,540 +0,0 @@ -package conf - -import ( - "crypto/aes" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "encoding/json" - "encoding/pem" - "io/ioutil" - "net" - "net/http" - "os" - "os/exec" - "strconv" - "strings" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "golang.org/x/crypto/pbkdf2" -) - -func setKeyLogger(tlsConfig *TLSConfig) error { - if tlsConfig.KeyLogPath != "" { - log.Warn("TLS key logging activated. USE OF KEY LOGGING COMPROMISES SECURITY. IT SHOULD ONLY BE USED FOR DEBUGGING.") - file, err := os.OpenFile(tlsConfig.KeyLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - return common.NewError("Failed to open key log file").Base(err) - } - tlsConfig.KeyLogger = file - } - return nil -} - -func loadCert(tlsConfig *TLSConfig) error { - err := setKeyLogger(tlsConfig) - if err != nil { - return err - } - if tlsConfig.CertPath == "" { - log.Info("Cert of the remote server is unspecified. Using default CA list") - } else { - caCertByte, err := ioutil.ReadFile(tlsConfig.CertPath) - if err != nil { - return common.NewError("failed to load cert file").Base(err) - } - tlsConfig.CertPool = x509.NewCertPool() - ok := tlsConfig.CertPool.AppendCertsFromPEM(caCertByte) - if !ok { - log.Warn("Invalid CA cert list") - } - log.Info("Using custom CA list") - - //show info abount the cert - pemCerts := caCertByte - for len(pemCerts) > 0 { - var block *pem.Block - block, pemCerts = pem.Decode(pemCerts) - if block == nil { - break - } - if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { - continue - } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - continue - } - log.Trace("Issuer:", cert.Issuer, "Subject:", cert.Subject) - } - } - return nil -} - -func loadCertAndKey(tlsConfig *TLSConfig) error { - err := setKeyLogger(tlsConfig) - if err != nil { - return err - } - if tlsConfig.KeyPassword != "" { - keyFile, err := ioutil.ReadFile(tlsConfig.KeyPath) - if err != nil { - return common.NewError("Failed to load key file").Base(err) - } - keyBlock, _ := pem.Decode(keyFile) - if keyBlock == nil { - return common.NewError("Failed to decode key file").Base(err) - } - decryptedKey, err := x509.DecryptPEMBlock(keyBlock, []byte(tlsConfig.KeyPassword)) - if err == nil { - return common.NewError("Failed to decrypt key").Base(err) - } - - certFile, err := ioutil.ReadFile(tlsConfig.CertPath) - certBlock, _ := pem.Decode(certFile) - if certBlock == nil { - return common.NewError("Failed to decode cert file").Base(err) - } - - keyPair, err := tls.X509KeyPair(certBlock.Bytes, decryptedKey) - if err != nil { - return err - } - - tlsConfig.KeyPair = []tls.Certificate{keyPair} - } else { - keyPair, err := tls.LoadX509KeyPair(tlsConfig.CertPath, tlsConfig.KeyPath) - if err != nil { - return common.NewError("Failed to load key pair").Base(err) - } - tlsConfig.KeyPair = []tls.Certificate{keyPair} - } - - tlsConfig.ClientCertPool = x509.NewCertPool() - for _, path := range tlsConfig.ClientCertPath { - log.Debug("Loading client cert: " + path) - certBytes, err := ioutil.ReadFile(path) - if err != nil { - return common.NewError("Failed to load cert file").Base(err) - } - ok := tlsConfig.ClientCertPool.AppendCertsFromPEM(certBytes) - if !ok { - return common.NewError("Invalid client cert") - } - } - return nil -} - -func loadCommonConfig(config *GlobalConfig) error { - //log settigns - log.SetLogLevel(log.LogLevel(config.LogLevel)) - if config.LogFile != "" { - log.Info("Log will be written to", config.LogFile) - file, err := os.OpenFile(config.LogFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return common.NewError("Failed to access the log file").Base(err) - } - log.SetOutput(file) - } - - //buffer size, 4KiB - 16MiB - if config.BufferSize < 4 || config.BufferSize > 16384 { - return common.NewError("Invalid buffer size, 4 KiB < buffer_size < 16384 KiB") - } - - config.BufferSize *= 1024 - - //password settings - if len(config.Passwords) == 0 { - switch config.RunType { - case Client, NAT, Forward: - return common.NewError("No password found") - default: - log.Warn("Password is unspecified in config file") - } - } - config.Hash = make(map[string]string) - for _, password := range config.Passwords { - config.Hash[common.SHA224String(password)] = password - } - - //address settings - config.LocalAddress = common.NewAddress(config.LocalHost, config.LocalPort, "tcp") - config.RemoteAddress = common.NewAddress(config.RemoteHost, config.RemotePort, "tcp") - config.TargetAddress = common.NewAddress(config.TargetHost, config.TargetPort, "tcp") - - if config.TLS.FallbackPort != 0 { - if config.TLS.FallbackHost == "" { - config.TLS.FallbackAddress = common.NewAddress(config.RemoteHost, config.TLS.FallbackPort, "tcp") - } else { - config.TLS.FallbackAddress = common.NewAddress(config.TLS.FallbackHost, config.TLS.FallbackPort, "tcp") - } - } - - //api settings - if config.API.Enabled { - config.API.APIAddress = common.NewAddress(config.API.APIHost, config.API.APIPort, "tcp") - if config.API.APITLS { - if err := loadCertAndKey(&config.API.TLS); err != nil { - return err - } - } - } - - //tls settings - if config.TLS.Cipher != "" || config.TLS.CipherTLS13 != "" { - specifiedSuites := strings.Split(config.TLS.Cipher+":"+config.TLS.CipherTLS13, ":") - supportedSuites := tls.CipherSuites() - invalid := false - for _, specified := range specifiedSuites { - found := false - if specified == "" { - continue - } - for _, supported := range supportedSuites { - if supported.Name == specified { - config.TLS.CipherSuites = append(config.TLS.CipherSuites, supported.ID) - found = true - break - } - } - if !found { - invalid = true - log.Warn("Found invalid cipher ", specified) - break - } - } - if invalid && len(supportedSuites) >= 1 { - log.Warn("\"cipher_suite\" contains invalid cipher name, ignored") - log.Warn("Here is a list of supported ciphers:") - list := "" - for _, c := range supportedSuites { - list += c.Name + ":" - } - log.Warn(list[:len(list)-1]) - config.TLS.CipherSuites = nil - } - } else { - config.TLS.CipherSuites = nil - } - - //websocket settings - if config.Websocket.Enabled { - log.Info("Websocket enabled") - if config.Websocket.Path == "" { - return common.NewError("Websocket path is empty") - } - if config.Websocket.Path[0] != '/' { - return common.NewError("Websocket path must start with \"/\"") - } - if config.Websocket.HostName == "" { - log.Warn("Websocket hostname is unspecified. Using remote_addr \"", config.RemoteHost, "\" as hostname") - config.Websocket.HostName = config.RemoteHost - if ip := net.ParseIP(config.RemoteHost); ip != nil && ip.To4() == nil { //ipv6 address - config.Websocket.HostName = "[" + config.RemoteHost + "]" - } - } - if config.Websocket.ObfuscationPassword != "" { - log.Info("Websocket obfuscation enabled") - password := []byte(config.Websocket.ObfuscationPassword) - //hardcoded salt - salt := []byte{48, 149, 6, 18, 13, 193, 247, 116, 197, 135, 236, 175, 190, 209, 146, 48} - config.Websocket.ObfuscationKey = pbkdf2.Key(password, salt, 32, aes.BlockSize, sha256.New) - } - } - - //router settings - config.Router.BlockList = []byte{} - config.Router.ProxyList = []byte{} - config.Router.BypassList = []byte{} - - for _, s := range config.Router.Block { - if strings.HasPrefix(s, "geoip:") { - config.Router.BlockIPCode = append(config.Router.BlockIPCode, s[len("geoip:"):]) - continue - } - if strings.HasPrefix(s, "geosite:") { - config.Router.BlockSiteCode = append(config.Router.BlockSiteCode, s[len("geosite:"):]) - continue - } - data, err := ioutil.ReadFile(s) - if err != nil { - return err - } - config.Router.BlockList = append(config.Router.BlockList, data...) - config.Router.BlockList = append(config.Router.BlockList, byte('\n')) - } - - for _, s := range config.Router.Bypass { - if strings.HasPrefix(s, "geoip:") { - config.Router.BypassIPCode = append(config.Router.BypassIPCode, s[len("geoip:"):]) - continue - } - if strings.HasPrefix(s, "geosite:") { - config.Router.BypassSiteCode = append(config.Router.BypassSiteCode, s[len("geosite:"):]) - continue - } - data, err := ioutil.ReadFile(s) - if err != nil { - return err - } - config.Router.BypassList = append(config.Router.BypassList, data...) - config.Router.BypassList = append(config.Router.BypassList, byte('\n')) - } - - for _, s := range config.Router.Proxy { - if strings.HasPrefix(s, "geoip:") { - config.Router.ProxyIPCode = append(config.Router.ProxyIPCode, s[len("geoip:"):]) - continue - } - if strings.HasPrefix(s, "geosite:") { - config.Router.ProxySiteCode = append(config.Router.ProxySiteCode, s[len("geosite:"):]) - continue - } - data, err := ioutil.ReadFile(s) - if err != nil { - return err - } - config.Router.ProxyList = append(config.Router.ProxyList, data...) - config.Router.ProxyList = append(config.Router.ProxyList, byte('\n')) - } - - var err error - config.Router.GeoIP, err = ioutil.ReadFile(config.Router.GeoIPFilename) - if err != nil { - config.Router.GeoIP = []byte{} - log.Warn(err) - } - config.Router.GeoSite, err = ioutil.ReadFile(config.Router.GeoSiteFilename) - if err != nil { - config.Router.GeoSite = []byte{} - log.Warn(err) - } - return nil -} - -func loadClientConfig(config *GlobalConfig) error { - //forward proxy settings - if config.ForwardProxy.Enabled { - log.Info("Forward proxy enabled") - config.ForwardProxy.ProxyAddress = common.NewAddress(config.ForwardProxy.ProxyHost, config.ForwardProxy.ProxyPort, "tcp") - log.Debug("Forward proxy", config.ForwardProxy.ProxyAddress.String()) - } - - if config.TransportPlugin.Enabled { - log.Warn("Trojan-Go will use transport plugin and work in plain text mode") - switch config.TransportPlugin.Type { - case "plaintext": - // do nothing - case "shadowsocks": - pluginHost := "127.0.0.1" - pluginPort := common.PickPort("tcp", pluginHost) - config.TransportPlugin.Env = append( - config.TransportPlugin.Env, - "SS_LOCAL_HOST="+pluginHost, - "SS_LOCAL_PORT="+strconv.FormatInt(int64(pluginPort), 10), - "SS_REMOTE_HOST="+config.RemoteHost, - "SS_REMOTE_PORT="+strconv.FormatInt(int64(config.RemotePort), 10), - "SS_PLUGIN_OPTIONS="+config.TransportPlugin.PluginOption, - ) - config.RemoteHost = pluginHost - config.RemotePort = pluginPort - config.RemoteAddress = common.NewAddress(config.RemoteHost, config.RemotePort, "tcp") - log.Debug("New remote address", config.RemoteAddress.String()) - log.Debug("Plugin env", config.TransportPlugin.Env) - - cmd := exec.Command(config.TransportPlugin.Command, config.TransportPlugin.Arg...) - cmd.Env = append(cmd.Env, config.TransportPlugin.Env...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - config.TransportPlugin.Cmd = cmd - case "other": - cmd := exec.Command(config.TransportPlugin.Command, config.TransportPlugin.Arg...) - cmd.Env = append(cmd.Env, config.TransportPlugin.Env...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - config.TransportPlugin.Cmd = cmd - default: - return common.NewError("Invalid plugin type: " + config.TransportPlugin.Type) - } - } else { - if config.TLS.SNI == "" { - log.Warn("SNI is unspecified, using remote_addr as SNI") - config.TLS.SNI = config.RemoteHost - } - if err := loadCert(&config.TLS); err != nil { - return err - } - if config.Websocket.Enabled && config.Websocket.DoubleTLS { - if config.Websocket.TLS.CertPath == "" { - log.Warn("Empty double TLS settings, using default ssl settings") - config.Websocket.TLS = config.TLS - } else { - if err := loadCert(&config.Websocket.TLS); err != nil { - return err - } - } - } - } - - return nil -} - -func loadServerConfig(config *GlobalConfig) error { - //check web server - if !config.DisableHTTPCheck { - resp, err := http.Get("http://" + config.RemoteAddress.String()) - if err != nil { - return common.NewError(config.RemoteAddress.String() + " is not a valid web server").Base(err) - } - buf := [128]byte{} - _, err = resp.Body.Read(buf[:]) - log.Debug("body:\n" + string(buf[:])) - resp.Body.Close() - } - - // transport plugin settings - if config.TransportPlugin.Enabled { - log.Warn("Trojan-Go will use transport plugin and work in plain text mode") - switch config.TransportPlugin.Type { - case "shadowsocks": - trojanHost := "127.0.0.1" - trojanPort := common.PickPort("tcp", trojanHost) - config.TransportPlugin.Env = append( - config.TransportPlugin.Env, - "SS_REMOTE_HOST="+config.LocalHost, - "SS_REMOTE_PORT="+strconv.FormatInt(int64(config.LocalPort), 10), - "SS_LOCAL_HOST="+trojanHost, - "SS_LOCAL_PORT="+strconv.FormatInt(int64(trojanPort), 10), - "SS_PLUGIN_OPTIONS="+config.TransportPlugin.PluginOption, - ) - - config.LocalHost = trojanHost - config.LocalPort = trojanPort - config.LocalAddress = common.NewAddress(config.LocalHost, config.LocalPort, "tcp") - log.Debug("New local address", config.RemoteAddress.String()) - log.Debug("Plugin env", config.TransportPlugin.Env) - - cmd := exec.Command(config.TransportPlugin.Command, config.TransportPlugin.Arg...) - cmd.Env = append(cmd.Env, config.TransportPlugin.Env...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - config.TransportPlugin.Cmd = cmd - case "other": - cmd := exec.Command(config.TransportPlugin.Command, config.TransportPlugin.Arg...) - cmd.Env = append(cmd.Env, config.TransportPlugin.Env...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - config.TransportPlugin.Cmd = cmd - case "plaintext": - // do nothing - default: - return common.NewError("Invalid plugin type: " + config.TransportPlugin.Type) - } - } else { - if err := loadCertAndKey(&config.TLS); err != nil { - return err - } - // tls settings - if config.TLS.SNI == "" { - log.Warn("Empty SNI field. Server will not verify the SNI in client hello request") - config.TLS.VerifyHostName = false - } - - if config.TLS.HTTPResponseFileName != "" { - payload, err := ioutil.ReadFile(config.TLS.HTTPResponseFileName) - if err != nil { - return common.NewError("Failed to load http response file").Base(err) - } - config.TLS.HTTPResponse = payload - } - - if config.Websocket.Enabled && config.Websocket.DoubleTLS { - if config.Websocket.TLS.CertPath == "" { - log.Warn("Empty double TLS settings, using global TLS settings") - config.Websocket.TLS = config.TLS - } - if err := loadCertAndKey(&config.Websocket.TLS); err != nil { - return err - } - } - } - - return nil -} - -func ParseJSON(data []byte) (*GlobalConfig, error) { - //default settings - config := &GlobalConfig{ - LogLevel: 1, - BufferSize: 32, - TCP: TCPConfig{ - FastOpenQLen: 20, - NoDelay: true, - KeepAlive: true, - }, - TLS: TLSConfig{ - Verify: true, - SessionTicket: true, - ReuseSession: true, - ALPN: []string{ - "http/1.1", - }, - Fingerprint: "firefox", - }, - Mux: MuxConfig{ - IdleTimeout: 60, - Concurrency: 8, - }, - Websocket: WebsocketConfig{ - DoubleTLS: true, - TLS: TLSConfig{ - Verify: true, - VerifyHostName: true, - SessionTicket: true, - ReuseSession: true, - }, - }, - MySQL: MySQLConfig{ - CheckRate: 60, - ServerHost: "localhost", - ServerPort: 3306, - }, - Router: RouterConfig{ - DefaultPolicy: "proxy", - DomainStrategy: "as_is", - GeoIPFilename: common.GetProgramDir() + "/geoip.dat", - GeoSiteFilename: common.GetProgramDir() + "/geosite.dat", - }, - Redis: RedisConfig{ - ServerHost: "localhost", - ServerPort: 6379, - }, - } - - err := json.Unmarshal(data, config) - if err != nil { - return nil, err - } - - if err := loadCommonConfig(config); err != nil { - return nil, err - } - - switch config.RunType { - case Client, NAT, Forward: - if err := loadClientConfig(config); err != nil { - return nil, err - } - case Server: - if err := loadServerConfig(config); err != nil { - return nil, err - } - case Relay: - default: - return nil, common.NewError("Invalid run type:" + string(config.RunType)) - } - - return config, nil -} diff --git a/conf/parse_test.go b/conf/parse_test.go deleted file mode 100644 index f886925..0000000 --- a/conf/parse_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package conf - -import ( - "testing" - - "github.com/p4gefau1t/trojan-go/common" -) - -func TestParseJSON(t *testing.T) { - data := ` - { - "run_type": "client", - "local_addr": "127.0.0.1", - "local_port": 1080, - "remote_addr": "baidu.com", - "remote_port": 443, - "password": [ - "password1" - ], - "log_level": 1, - "ssl": { - "verify": true, - "verify_hostname": true, - "cert": "server.crt", - "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA", - "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", - "sni": "", - "alpn": [ - "h2", - "http/1.1" - ], - "reuse_session": true, - "session_ticket": false, - "curves": "" - }, - "tcp": { - "no_delay": true, - "keep_alive": true, - "reuse_port": false, - "fast_open": false, - "fast_open_qlen": 20 - } -} - ` - _, err := ParseJSON([]byte(data)) - common.Must(err) - - data = ` -{ - "run_type": "server", - "local_addr": "0.0.0.0", - "local_port": 4445, - "remote_addr": "127.0.0.1", - "remote_port": 80, - "password": [ - "pass123123" - ], - "log_level": 2, - "ssl": { - "verify": false, - "verify_hostname": false, - "cert": "pass.crt", - "key": "pass.key", - "key_password": "", - "cipher_tls13":"TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", - "prefer_server_cipher": true, - "alpn": [ - "h2", - "http/1.1" - ], - "reuse_session": true, - "session_ticket": false, - "session_timeout": 600, - "plain_http_response": "", - "curves": "", - "dhparam": "" - }, - "tcp": { - "no_delay": true, - "keep_alive": true, - "fast_open": false, - "fast_open_qlen": 20 - }, - "mysql": { - "enabled": true, - "server_addr": "127.0.0.1", - "server_port": 3306, - "database": "trojan", - "username": "root", - "password": "password" - } -} - ` - _, err = ParseJSON([]byte(data)) - common.Must(err) -} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..1c57bb3 --- /dev/null +++ b/config/config.go @@ -0,0 +1,82 @@ +package config + +import ( + "context" + "encoding/json" + + "gopkg.in/yaml.v2" +) + +//KeyType is the name of a config +type KeyType string + +var creators = make(map[KeyType]Creator) + +// Creator creates default config struct for a module +type Creator func() interface{} + +// RegisterConfigCreator registers a config structs for parsing +func RegisterConfigCreator(name KeyType, creator Creator) { + name += "_CONFIG" + creators[name] = creator +} + +func parseJSON(data []byte) (map[KeyType]interface{}, error) { + result := make(map[KeyType]interface{}) + for name, creator := range creators { + config := creator() + if err := json.Unmarshal(data, config); err != nil { + return nil, err + } + result[name] = config + } + return result, nil +} + +func parseYAML(data []byte) (map[KeyType]interface{}, error) { + result := make(map[KeyType]interface{}) + for name, creator := range creators { + config := creator() + if err := yaml.Unmarshal(data, config); err != nil { + return nil, err + } + result[name] = config + } + return result, nil +} + +func WithJSONConfig(ctx context.Context, data []byte) (context.Context, error) { + var configs map[KeyType]interface{} + var err error + configs, err = parseJSON(data) + if err != nil { + return ctx, err + } + for name, config := range configs { + ctx = context.WithValue(ctx, name, config) + } + return ctx, nil +} + +func WithYAMLConfig(ctx context.Context, data []byte) (context.Context, error) { + var configs map[KeyType]interface{} + var err error + configs, err = parseYAML(data) + if err != nil { + return ctx, err + } + for name, config := range configs { + ctx = context.WithValue(ctx, name, config) + } + return ctx, nil +} + +func WithConfig(ctx context.Context, name KeyType, cfg interface{}) context.Context { + name += "_CONFIG" + return context.WithValue(ctx, name, cfg) +} + +// FromContext extracts config from a context +func FromContext(ctx context.Context, name KeyType) interface{} { + return ctx.Value(name + "_CONFIG") +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..3f1ffc0 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,47 @@ +package config + +import ( + "context" + "testing" + + "github.com/p4gefau1t/trojan-go/common" +) + +type TestStruct struct { + Field1 string `json,yaml:"field1"` + Field2 bool `json:"field2" yaml:"field2"` +} + +func creator() interface{} { + return &TestStruct{} +} + +func TestJSONConfig(t *testing.T) { + RegisterConfigCreator("test", creator) + data := []byte(` + { + "Field1": "test1", + "Field2": true + } + `) + ctx, err := WithJSONConfig(context.Background(), data) + common.Must(err) + c := FromContext(ctx, "test").(*TestStruct) + if c.Field1 != "test1" || c.Field2 != true { + t.Fail() + } +} + +func TestYAMLConfig(t *testing.T) { + RegisterConfigCreator("test", creator) + data := []byte(` +field1: 012345678 +field2: true +`) + ctx, err := WithYAMLConfig(context.Background(), data) + common.Must(err) + c := FromContext(ctx, "test").(*TestStruct) + if c.Field1 != "012345678" || c.Field2 != true { + t.Fail() + } +} diff --git a/daemon/deamon.go b/daemon/deamon.go deleted file mode 100644 index 875703b..0000000 --- a/daemon/deamon.go +++ /dev/null @@ -1,49 +0,0 @@ -package deamon - -import ( - "flag" - "fmt" - "os" - "os/exec" - "strings" - - "github.com/p4gefau1t/trojan-go/common" -) - -type DaemonOption struct { - daemon *bool - common.OptionHandler -} - -func (*DaemonOption) Name() string { - return "daemon" -} - -func (*DaemonOption) Priority() int { - return 1000 -} - -func (o *DaemonOption) Handle() error { - if !*o.daemon { - return common.NewError("not set") - } - args := os.Args[1:] - i := 0 - for ; i < len(args); i++ { - if strings.Contains(args[i], "-daemon") { - args[i] = "-daemon=false" - } - } - cmd := exec.Command(os.Args[0], args...) - cmd.Start() - fmt.Println("Trojan-Go is running in the background...") - fmt.Println("[PID]", cmd.Process.Pid) - os.Exit(0) - return nil -} - -func init() { - common.RegisterOptionHandler(&DaemonOption{ - daemon: flag.Bool("daemon", false, "run trojan-go as a daemon with -daemon"), - }) -} diff --git a/easy/easy.go b/easy/easy.go deleted file mode 100644 index c977805..0000000 --- a/easy/easy.go +++ /dev/null @@ -1,142 +0,0 @@ -package easy - -import ( - "flag" - "fmt" - "net" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/proxy" -) - -type EasyOption struct { - server *bool - client *bool - password *string - local *string - remote *string - cert *string - key *string -} - -func (o *EasyOption) Name() string { - return "easy" -} - -func (o *EasyOption) Handle() error { - if !*o.server && !*o.client { - return common.NewError("empty") - } - if *o.password == "" { - log.Fatal("Empty password is not allowed") - } - log.Info("Easy mode enabled, trojan-go will NOT use the config file") - if *o.client { - clientConfigFormat := ` -{ - "run_type": "client", - "local_addr": "%s", - "local_port": %s, - "remote_addr": "%s", - "remote_port": %s, - "password": [ - "%s" - ] -} - ` - if *o.local == "" { - log.Warn("Client local addr is unspecified, using 127.0.0.1:1080") - *o.local = "127.0.0.1:1080" - } - localHost, localPort, err := net.SplitHostPort(*o.local) - if err != nil { - log.Fatal(common.NewError("Invalid local addr format:" + *o.local).Base(err)) - } - remoteHost, remotePort, err := net.SplitHostPort(*o.remote) - if err != nil { - log.Fatal(common.NewError("Invalid remote addr format:" + *o.remote).Base(err)) - } - clientConfigJSON := fmt.Sprintf(clientConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password) - log.Info("Generated config:") - log.Info(clientConfigJSON) - config, err := conf.ParseJSON([]byte(clientConfigJSON)) - if err != nil { - log.Fatal(config) - } - client, err := proxy.NewProxy(config) - if err != nil { - log.Fatal(config) - } - err = client.Run() - if err != nil { - log.Fatal(err) - } - } else if *o.server { - serverConfigFormat := ` -{ - "run_type": "server", - "local_addr": "%s", - "local_port": %s, - "remote_addr": "%s", - "remote_port": %s, - "password": [ - "%s" - ], - "ssl": { - "cert": "%s", - "key": "%s" - } -} - ` - if *o.remote == "" { - log.Warn("Server remote addr is unspecified, using 127.0.0.1:80") - *o.remote = "127.0.0.1:80" - } - if *o.local == "" { - log.Warn("Server local addr is unspecified, using 0.0.0.0:443") - *o.local = "0.0.0.0:443" - } - localHost, localPort, err := net.SplitHostPort(*o.local) - if err != nil { - log.Fatal(common.NewError("Invalid local addr format:" + *o.local).Base(err)) - } - remoteHost, remotePort, err := net.SplitHostPort(*o.remote) - if err != nil { - log.Fatal(common.NewError("Invalid remote addr format:" + *o.remote).Base(err)) - } - serverConfigJSON := fmt.Sprintf(serverConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password, *o.cert, *o.key) - log.Info("Generated config:") - log.Info(serverConfigJSON) - config, err := conf.ParseJSON([]byte(serverConfigJSON)) - if err != nil { - log.Fatal(err) - } - server, err := proxy.NewProxy(config) - if err != nil { - log.Fatal(err) - } - err = server.Run() - if err != nil { - log.Fatal(err) - } - } - return nil -} - -func (o *EasyOption) Priority() int { - return 50 -} - -func init() { - common.RegisterOptionHandler(&EasyOption{ - server: flag.Bool("server", false, "Run a trojan-go server"), - client: flag.Bool("client", false, "Run a trojan-go client"), - password: flag.String("password", "", "Password for authentication"), - remote: flag.String("remote", "", "Remote address, e.g. 127.0.0.1:12345"), - local: flag.String("local", "", "Local address, e.g. 127.0.0.1:12345"), - key: flag.String("key", "server.key", "Key of the server"), - cert: flag.String("cert", "server.crt", "Certificates of the server"), - }) -} diff --git a/go.mod b/go.mod index fbf0bf0..6e55876 100644 --- a/go.mod +++ b/go.mod @@ -4,31 +4,16 @@ go 1.14 require ( github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed - github.com/cenkalti/backoff/v4 v4.0.2 // indirect - github.com/go-acme/lego/v3 v3.5.0 github.com/go-sql-driver/mysql v1.5.0 - github.com/golang/protobuf v1.4.2 - github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect - github.com/mediocregopher/radix/v3 v3.5.1 - github.com/miekg/dns v1.1.29 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/onsi/ginkgo v1.12.3 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/proullon/ramsql v0.0.0-20181213202341-817cee58a244 - github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0 github.com/smartystreets/goconvey v1.6.4 github.com/xtaci/smux v1.5.15-0.20200523091831-637399ad4398 - github.com/ziutek/mymysql v1.5.4 // indirect - go.starlark.net v0.0.0-20200519165436-0aa95694c768 // indirect golang.org/x/crypto v0.0.0-20200602180216-279210d13fed - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 + golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 - google.golang.org/grpc v1.29.1 - google.golang.org/protobuf v1.24.0 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/square/go-jose.v2 v2.5.1 // indirect - v2ray.com/core v0.0.0-20190603071532-16e9d39fff74 + gopkg.in/yaml.v2 v2.3.0 ) replace v2ray.com/core => github.com/v2ray/v2ray-core v0.0.0-20200603100350-6b5d2fed91c0 diff --git a/go.sum b/go.sum index bb3400c..046a3ef 100644 --- a/go.sum +++ b/go.sum @@ -1,569 +1,38 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= -github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw= -github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= -github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= -github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM= -github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= -github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4= github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ= -github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/lego/v3 v3.5.0 h1:/0+NJQK+hNwRznhCi+19lbEa4xufhe7wJZOVd5j486s= -github.com/go-acme/lego/v3 v3.5.0/go.mod h1:TXodhTGOiWEqXDdgrzBoCtJ5R4L9lfOE68CTM0KGkT0= -github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= -github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= -github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= -github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= -github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mediocregopher/radix/v3 v3.5.1 h1:IOYgQUMA380N4khaL5eNT4v/P2LnHa8b0wnVdwZMFsY= -github.com/mediocregopher/radix/v3 v3.5.1/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= -github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= -github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= -github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ= -github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ= -github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= -github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/proullon/ramsql v0.0.0-20181213202341-817cee58a244 h1:fdX2U+a2Rmc4BjRYcOKzjYXtYTE4ga1B2lb8i7BlefU= -github.com/proullon/ramsql v0.0.0-20181213202341-817cee58a244/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= -github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= -github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0 h1:vIkvetWOJZSADSKCF9MLTsQNW2httdBmYz47dQQteP8= -github.com/refraction-networking/utls v0.0.0-20200601200209-ada0bb9b38a0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= -github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY= -github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/v2ray/v2ray-core v0.0.0-20200603100350-6b5d2fed91c0 h1:beJRvss6cKPj/Qy8RLI/O8EKYaxaKlsXBjsXgmNqSUQ= -github.com/v2ray/v2ray-core v0.0.0-20200603100350-6b5d2fed91c0/go.mod h1:6qvbJidjCnQWxyTc9SBD/cLCtN4qLs2neS/VzwSTnTY= -github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xtaci/smux v1.5.15-0.20200523091831-637399ad4398 h1:1nJafFt4SJPzJ5RbWBP2OUJ7Xcx7pdjyjldEdFrLfKs= github.com/xtaci/smux v1.5.15-0.20200523091831-637399ad4398/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.starlark.net v0.0.0-20190919145610-979af19b165c/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= -go.starlark.net v0.0.0-20200519165436-0aa95694c768 h1:p1NBjkIS2bHXntFxS9zhyFmZ9VKtazqNnsn5r7okSTo= -go.starlark.net v0.0.0-20200519165436-0aa95694c768/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= -golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed h1:g4KENRiCMEx58Q7/ecwfT0N2o8z35Fnbsjig/Alf2T4= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= -gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= -gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -h12.io/socks v1.0.0/go.mod h1:MdYbo5/eB9ka7u5dzW2Qh0iSyJENwB3KI5H5ngenFGA= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index f70bb7c..4ccf948 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,22 @@ package main import ( "flag" + "github.com/p4gefau1t/trojan-go/option" - "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/log" - - _ "github.com/p4gefau1t/trojan-go/build" + _ "github.com/p4gefau1t/trojan-go/log/golog" + _ "github.com/p4gefau1t/trojan-go/proxy/client" + _ "github.com/p4gefau1t/trojan-go/proxy/forward" + _ "github.com/p4gefau1t/trojan-go/proxy/nat" + _ "github.com/p4gefau1t/trojan-go/proxy/server" + _ "github.com/p4gefau1t/trojan-go/statistic/memory" + _ "github.com/p4gefau1t/trojan-go/statistic/mysql" ) func main() { flag.Parse() for { - h, err := common.PopOptionHandler() + h, err := option.PopOptionHandler() if err != nil { log.Fatal("invalid options") } diff --git a/common/option.go b/option/option.go similarity index 81% rename from common/option.go rename to option/option.go index 02a9eb6..0854f57 100644 --- a/common/option.go +++ b/option/option.go @@ -1,4 +1,6 @@ -package common +package option + +import "github.com/p4gefau1t/trojan-go/common" type OptionHandler interface { Name() string @@ -20,7 +22,7 @@ func PopOptionHandler() (OptionHandler, error) { } } if maxHandler == nil { - return nil, NewError("No options left") + return nil, common.NewError("No option left") } delete(handlers, maxHandler.Name()) return maxHandler, nil diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go deleted file mode 100644 index 39837cc..0000000 --- a/protocol/direct/outbound.go +++ /dev/null @@ -1,210 +0,0 @@ -package direct - -import ( - "context" - "crypto/tls" - "io" - "net" - "net/url" - "strconv" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/patrickmn/go-cache" -) - -var dnsCache = cache.New(5*time.Minute, 1*time.Minute) - -type DirectOutboundConnSession struct { - protocol.ConnSession - conn io.ReadWriteCloser - request *protocol.Request -} - -func (o *DirectOutboundConnSession) Read(p []byte) (int, error) { - return o.conn.Read(p) -} - -func (o *DirectOutboundConnSession) Write(p []byte) (int, error) { - return o.conn.Write(p) -} - -func (o *DirectOutboundConnSession) Close() error { - return o.conn.Close() -} - -func NewOutboundConnSession(ctx context.Context, req *protocol.Request, config *conf.GlobalConfig) (protocol.ConnSession, error) { - var newConn net.Conn - var err error - //look up the domain name in cache first - if req.AddressType == common.DomainName && len(config.DNS) != 0 { //customized dns server - ip, found := dnsCache.Get(req.DomainName) - if found { - log.Trace("DNS cache hit:", req.DomainName, "->", ip.(net.IP).String()) - newConn, err = net.DialTCP("tcp", nil, &net.TCPAddr{ - IP: ip.(net.IP), - Port: req.Port, - }) - if err != nil { - return nil, err - } - goto done - } - log.Trace("DNS cache missed:", req.DomainName) - //find a avaliable dns server - for _, s := range config.DNS { - var dnsType conf.DNSType - var dnsAddr string - var dnsHost, dnsPort string - var err error - dnsURL, err := url.Parse(s) - if err != nil || dnsURL.Scheme == "" { - dnsType = conf.UDP - dnsAddr = s - } else { - dnsType = conf.DNSType(dnsURL.Scheme) - dnsAddr = dnsURL.Host - } - - dnsHost, dnsPort, err = net.SplitHostPort(dnsAddr) - if err != nil { //port not specifiet - dnsHost = dnsAddr - switch dnsType { - case conf.DOT: - dnsPort = "853" - case conf.TCP, conf.UDP: - dnsPort = "53" - } - } - - resolver := &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - switch dnsType { - case conf.UDP, conf.TCP: - d := net.Dialer{ - Timeout: time.Second * time.Duration(protocol.UDPTimeout), - } - conn, err := d.DialContext(ctx, string(dnsType), dnsHost+":"+dnsPort) - if err != nil { - return nil, err - } - return conn, nil - case conf.DOT: - tlsConn, err := tls.Dial("tcp", dnsHost+":"+dnsPort, nil) - if err != nil { - return nil, err - } - return tlsConn, nil - } - return nil, common.NewError("Invalid dns type :" + string(dnsType)) - }, - } - d := net.Dialer{ - Resolver: resolver, - } - newConn, err = d.Dial("tcp", req.DomainName+":"+strconv.FormatInt(int64(req.Port), 10)) - if err != nil { - log.Error(err) - continue - } - addr, _, err := net.SplitHostPort(newConn.RemoteAddr().String()) - if err != nil { - log.Warn(err) - } else { - if ip := net.ParseIP(addr); ip != nil { - log.Trace("DNS cache set", req.DomainName, "->", addr) - dnsCache.Set(req.DomainName, ip, cache.DefaultExpiration) - } else { - log.Warn("Invalid resolved addr", addr) - } - } - break - } - if newConn == nil { - return nil, common.NewError("All dns servers down") - } - } else { - //default resolver - var err error - newConn, err = net.Dial("tcp", req.String()) - if err != nil { - return nil, err - } - } -done: - o := &DirectOutboundConnSession{ - request: req, - conn: newConn, - } - return o, nil -} - -type packetInfo struct { - request *protocol.Request - packet []byte -} - -type DirectOutboundPacketSession struct { - protocol.PacketSession - packetChan chan *packetInfo - ctx context.Context - cancel context.CancelFunc -} - -func (o *DirectOutboundPacketSession) listenConn(req *protocol.Request, conn net.PacketConn) { - defer conn.Close() - for { - buf := make([]byte, protocol.MaxUDPPacketSize) - conn.SetReadDeadline(time.Now().Add(protocol.UDPTimeout)) - n, addr, err := conn.ReadFrom(buf) - conn.SetReadDeadline(time.Time{}) - if err != nil { - log.Debug(common.NewError("Packet session ends").Base(err)) - return - } - log.Debug("UDP response from", addr) - info := &packetInfo{ - request: req, - packet: buf[0:n], - } - o.packetChan <- info - } -} - -func (o *DirectOutboundPacketSession) Close() error { - o.cancel() - return nil -} - -func (o *DirectOutboundPacketSession) ReadPacket() (*protocol.Request, []byte, error) { - select { - case info := <-o.packetChan: - return info.request, info.packet, nil - case <-o.ctx.Done(): - return nil, nil, common.NewError("Session closed") - } -} - -func (o *DirectOutboundPacketSession) WritePacket(req *protocol.Request, packet []byte) (int, error) { - conn, err := net.Dial("udp", req.Address.String()) - if err != nil { - return 0, common.NewError("Failed to dial UDP").Base(err) - } - log.Debug("UDP directly dialing to", req) - go o.listenConn(req, conn.(net.PacketConn)) - n, err := conn.Write(packet) - return n, err -} - -func NewOutboundPacketSession(ctx context.Context) (protocol.PacketSession, error) { - ctx, cancel := context.WithCancel(ctx) - return &DirectOutboundPacketSession{ - ctx: ctx, - cancel: cancel, - packetChan: make(chan *packetInfo, 256), - }, nil -} diff --git a/protocol/direct/outbound_test.go b/protocol/direct/outbound_test.go deleted file mode 100644 index 533ff67..0000000 --- a/protocol/direct/outbound_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package direct - -import ( - "context" - "fmt" - "math/rand" - "net" - "net/http" - "testing" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/test" -) - -func TestUDPDirectOutbound(t *testing.T) { - go test.RunMultipleUDPEchoServer(context.Background()) - outbound, _ := NewOutboundPacketSession(context.Background()) - go func() { - for i := 0; i < 5; i++ { - req, buf, err := outbound.ReadPacket() - fmt.Println(req, string(buf), err) - } - }() - for i := 0; i < 5; i++ { - req := &protocol.Request{ - Address: &common.Address{ - IP: net.ParseIP("127.0.0.1"), - Port: 6000 + rand.Intn(10), - AddressType: common.IPv4, - }, - } - req.Port += rand.Intn(10) - packet := []byte(fmt.Sprintf("hello motherfucker %d, port=%d", i, req.Port)) - _, err := outbound.WritePacket(req, packet) - common.Must(err) - } - time.Sleep(time.Second * 5) -} - -func TestDNS(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - DNS: []string{"114.114.114.114:53"}, - } - req := &protocol.Request{ - Address: &common.Address{ - DomainName: "www.baidu.com", - Port: 80, - AddressType: common.DomainName, - NetworkType: "tcp", - }, - } - conn, err := NewOutboundConnSession(ctx, req, config) - common.Must(err) - httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) - common.Must(err) - httpReq.Write(conn) - buf := [128]byte{} - conn.Read(buf[:]) - fmt.Println(string(buf[:])) - cancel() -} - -func TestDOT(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - DNS: []string{"dot://223.5.5.5:853"}, - } - req := &protocol.Request{ - Address: &common.Address{ - DomainName: "www.baidu.com", - Port: 80, - AddressType: common.DomainName, - NetworkType: "tcp", - }, - } - conn, err := NewOutboundConnSession(ctx, req, config) - common.Must(err) - httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) - common.Must(err) - httpReq.Write(conn) - buf := [128]byte{} - conn.Read(buf[:]) - fmt.Println(string(buf[:])) - cancel() -} - -func TestDOH(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - DNS: []string{"https://223.5.5.5:443"}, - } - req := &protocol.Request{ - Address: &common.Address{ - DomainName: "www.baidu.com", - Port: 80, - AddressType: common.DomainName, - NetworkType: "tcp", - }, - } - conn, err := NewOutboundConnSession(ctx, req, config) - common.Must(err) - httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) - common.Must(err) - httpReq.Write(conn) - buf := [128]byte{} - conn.Read(buf[:]) - fmt.Println(string(buf[:])) - cancel() -} - -func TestCache(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - DNS: []string{"223.5.5.5:53"}, - } - req := &protocol.Request{ - Address: &common.Address{ - DomainName: "www.baidu.com", - Port: 80, - AddressType: common.DomainName, - NetworkType: "tcp", - }, - } - conn, err := NewOutboundConnSession(ctx, req, config) - common.Must(err) - conn.Close() - conn, err = NewOutboundConnSession(ctx, req, config) - common.Must(err) - conn.Close() - cancel() -} diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go deleted file mode 100644 index 00c8db3..0000000 --- a/protocol/http/inbound.go +++ /dev/null @@ -1,142 +0,0 @@ -package http - -import ( - "bufio" - "bytes" - "fmt" - "io" - "net" - "net/http" - "strconv" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/protocol" -) - -func parseHTTPRequest(httpRequest *http.Request) *protocol.Request { - request := &protocol.Request{ - Address: &common.Address{ - NetworkType: "tcp", - Port: 80, - }, - Command: protocol.Connect, - } - host, port, err := net.SplitHostPort(httpRequest.Host) - if err != nil { - if ip := net.ParseIP(httpRequest.Host); ip != nil { - request.IP = ip - if ip.To4() != nil { - request.AddressType = common.IPv4 - } else { - request.AddressType = common.IPv6 - } - } else { - request.DomainName = httpRequest.Host - request.AddressType = common.DomainName - } - } else { - request.DomainName = host - request.AddressType = common.DomainName - n, err := strconv.ParseUint(port, 10, 16) - common.Must(err) - request.Port = int(n) - } - return request -} - -type HTTPInboundTunnelConnSession struct { - request *protocol.Request - httpRequest *http.Request - bufReader *bufio.Reader - rwc io.ReadWriteCloser - bodyReader io.Reader -} - -func (i *HTTPInboundTunnelConnSession) Read(p []byte) (int, error) { - return i.bufReader.Read(p) -} - -func (i *HTTPInboundTunnelConnSession) Write(p []byte) (int, error) { - return i.rwc.Write(p) -} - -func (i *HTTPInboundTunnelConnSession) Close() error { - return i.rwc.Close() -} - -func (i *HTTPInboundTunnelConnSession) Respond() error { - payload := fmt.Sprintf("HTTP/%d.%d 200 Connection established\r\n\r\n", i.httpRequest.ProtoMajor, i.httpRequest.ProtoMinor) - _, err := i.Write([]byte(payload)) - return err -} - -func (i *HTTPInboundTunnelConnSession) parseRequest() (bool, error) { - httpRequest, err := http.ReadRequest(i.bufReader) - if err != nil { - return false, err - } - if httpRequest.Method != "CONNECT" { - return true, common.NewError("Not a CONNECT request") - } - i.bodyReader = httpRequest.Body - i.httpRequest = httpRequest - i.request = parseHTTPRequest(httpRequest) - return true, nil -} - -type HTTPInboundPacketSession struct { - protocol.PacketSession - - rwc io.ReadWriteCloser - bufReader *bufio.Reader - request *protocol.Request - httpRequest *http.Request -} - -func (i *HTTPInboundPacketSession) Close() error { - return i.rwc.Close() -} - -func (i *HTTPInboundPacketSession) ReadPacket() (*protocol.Request, []byte, error) { - httpRequest, err := http.ReadRequest(i.bufReader) - if err != nil { - return nil, nil, err - } - request := parseHTTPRequest(httpRequest) - buf := bytes.NewBuffer([]byte{}) - err = httpRequest.Write(buf) - common.Must(err) - return request, buf.Bytes(), nil -} - -func (i *HTTPInboundPacketSession) WritePacket(req *protocol.Request, packet []byte) (int, error) { - n, err := i.rwc.Write(packet) - return n, err -} - -func NewHTTPInbound(rwc *common.RewindReadWriteCloser) (protocol.ConnSession, *protocol.Request, protocol.PacketSession, error) { - connSession := &HTTPInboundTunnelConnSession{ - rwc: rwc, - bufReader: bufio.NewReader(rwc), - } - rwc.SetBufferSize(512) - defer rwc.StopBuffering() - isHTTP, err := connSession.parseRequest() - if !isHTTP { - //invalid http format - rwc.SetBufferSize(0) - return nil, nil, nil, common.NewError("Failed to parse http header").Base(err) - } - if err == nil { - //http tunnel - rwc.SetBufferSize(0) - return connSession, connSession.request, nil, nil - } - rwc.Rewind() - packetSession := &HTTPInboundPacketSession{ - rwc: rwc, - bufReader: bufio.NewReader(rwc), - } - // TODO release the read buffer - return nil, nil, packetSession, nil -} diff --git a/protocol/protocol.go b/protocol/protocol.go deleted file mode 100644 index 1931576..0000000 --- a/protocol/protocol.go +++ /dev/null @@ -1,134 +0,0 @@ -package protocol - -import ( - "bytes" - "fmt" - "io" - "math/rand" - "net" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/stat" -) - -type Command byte - -const ( - Connect Command = 1 - Bind Command = 2 - Associate Command = 3 - Mux Command = 0x7f -) - -const ( - MaxUDPPacketSize = 1024 * 4 - UDPTimeout = time.Second * 5 - TCPTimeout = time.Second * 5 -) - -type Request struct { - Command - *common.Address -} - -func (r *Request) Marshal(rr io.Reader) error { - byteBuf := [1]byte{} - _, err := io.ReadFull(rr, byteBuf[:]) - if err != nil { - return err - } - r.Command = Command(byteBuf[0]) - switch r.Command { - case Connect, Bind, Associate, Mux: - r.Address = new(common.Address) - err := r.Address.Marshal(rr) - if err != nil { - return common.NewError("Failed to marshal address").Base(err) - } - default: - return common.NewError(fmt.Sprintf("Invalid command %d", r.Command)) - } - return nil -} - -func (r *Request) Unmarshal(w io.Writer) error { - buf := bytes.NewBuffer(make([]byte, 0, 64)) - buf.WriteByte(byte(r.Command)) - if err := r.Address.Unmarshal(buf); err != nil { - return err - } - //use tcp by default - r.Address.NetworkType = "tcp" - _, err := w.Write(buf.Bytes()) - return err -} - -func (r *Request) Network() string { - if r.Address != nil { - return r.Address.Network() - } - return "empty" -} - -func (r *Request) String() string { - return r.Address.String() -} - -type HasHash interface { - GetHash() string -} - -type NeedRespond interface { - Respond() error -} - -type PacketReader interface { - ReadPacket() (req *Request, payload []byte, err error) -} - -type PacketWriter interface { - WritePacket(req *Request, payload []byte) (n int, err error) -} - -type PacketReadWriter interface { - PacketReader - PacketWriter -} - -type NeedConfig interface { - SetConfig(config *conf.GlobalConfig) -} - -type NeedAuth interface { - SetAuth(auth stat.Authenticator) -} - -type ConnSession interface { - io.ReadWriteCloser -} - -type PacketSession interface { - PacketReadWriter - io.Closer -} - -var timeout time.Duration - -func GetRandomTimeoutDuration() time.Duration { - offset := time.Duration(rand.Intn(3000)) * time.Millisecond - return timeout + offset -} - -func SetRandomizedTimeout(conn net.Conn) { - conn.SetDeadline(time.Now().Add(GetRandomTimeoutDuration())) -} - -func CancelTimeout(conn net.Conn) { - conn.SetDeadline(time.Time{}) -} - -func init() { - timeout = time.Duration(rand.Intn(20))*time.Second + TCPTimeout -} diff --git a/protocol/simplesocks/simplesocks.go b/protocol/simplesocks/simplesocks.go deleted file mode 100644 index af584d4..0000000 --- a/protocol/simplesocks/simplesocks.go +++ /dev/null @@ -1,74 +0,0 @@ -package simplesocks - -import ( - "bytes" - "io" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" -) - -type SimpleSocksConnSession struct { - request *protocol.Request - rwc io.ReadWriteCloser - recv uint64 - sent uint64 - header []byte -} - -func (m *SimpleSocksConnSession) Read(p []byte) (int, error) { - n, err := m.rwc.Read(p) - m.recv += uint64(n) - return n, err -} - -func (m *SimpleSocksConnSession) Write(p []byte) (int, error) { - if m.header != nil { - _, err := m.rwc.Write(append(m.header, p...)) - m.header = nil - return len(p), err - } - n, err := m.rwc.Write(p) - m.sent += uint64(n) - return n, err -} - -func (m *SimpleSocksConnSession) Close() error { - log.Info("SimpleSocks conn to", m.request, "closed", "sent:", common.HumanFriendlyTraffic(m.sent), "recv:", common.HumanFriendlyTraffic(m.recv)) - return m.rwc.Close() -} - -func (m *SimpleSocksConnSession) GetRequest() *protocol.Request { - return m.request -} - -func (m *SimpleSocksConnSession) parseRequest() error { - m.request = new(protocol.Request) - return m.request.Marshal(m.rwc) -} - -func (m *SimpleSocksConnSession) writeRequest(req *protocol.Request) { - m.request = req - buf := bytes.NewBuffer(make([]byte, 0, 128)) - m.request.Unmarshal(buf) - m.header = buf.Bytes() -} - -func NewInboundConnSession(conn io.ReadWriteCloser) (protocol.ConnSession, *protocol.Request, error) { - m := &SimpleSocksConnSession{ - rwc: conn, - } - if err := m.parseRequest(); err != nil { - return nil, nil, common.NewError("Failed to parse mux request").Base(err) - } - return m, m.request, nil -} - -func NewOutboundConnSession(req *protocol.Request, conn io.ReadWriteCloser) (protocol.ConnSession, error) { - m := &SimpleSocksConnSession{ - rwc: conn, - } - m.writeRequest(req) - return m, nil -} diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go deleted file mode 100644 index 6ae3c31..0000000 --- a/protocol/socks/inbound.go +++ /dev/null @@ -1,233 +0,0 @@ -package socks - -import ( - "bytes" - "context" - "io" - "net" - "sync" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" -) - -type SocksConnInboundSession struct { - request *protocol.Request - rwc *common.RewindReadWriteCloser -} - -func (i *SocksConnInboundSession) checkVersion() error { - version, err := i.rwc.ReadByte() - if err != nil { - return err - } - if version != 0x5 { - return common.NewError("Unsupported socks version") - } - return nil -} - -func (i *SocksConnInboundSession) auth() error { - if err := i.checkVersion(); err != nil { - return err - } - nmethods, err := i.rwc.ReadByte() - if err != nil { - return err - } - i.rwc.Discard(int(nmethods)) - i.rwc.Write([]byte{0x5, 0x0}) - return nil -} - -func (i *SocksConnInboundSession) parseRequest() error { - if err := i.checkVersion(); err != nil { - return err - } - cmd, err := i.rwc.ReadByte() - if err != nil { - return common.NewError("Cannot read cmd").Base(err) - } - i.rwc.Discard(1) - - switch protocol.Command(cmd) { - case protocol.Connect, protocol.Associate: - default: - return common.NewError("Invalid command") - } - addr := &common.Address{ - NetworkType: "tcp", - } - if err := addr.Marshal(i.rwc); err != nil { - return common.NewError("Cannot read request").Base(err) - } - request := &protocol.Request{ - Address: addr, - Command: protocol.Command(cmd), - } - i.request = request - return nil -} - -func (i *SocksConnInboundSession) Respond() error { - if i.request.Command == protocol.Connect { - i.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - return nil - } - //associate - resp := bytes.NewBuffer([]byte{0x05, 0x00, 0x00}) - common.Must(i.request.Address.Unmarshal(resp)) - _, err := i.Write(resp.Bytes()) - return err -} - -func (i *SocksConnInboundSession) Read(p []byte) (int, error) { - return i.rwc.Read(p) -} - -func (i *SocksConnInboundSession) Write(p []byte) (int, error) { - return i.rwc.Write(p) -} - -func (i *SocksConnInboundSession) Close() error { - return i.rwc.Close() -} - -func NewInboundConnSession(rwc *common.RewindReadWriteCloser) (protocol.ConnSession, *protocol.Request, error) { - i := &SocksConnInboundSession{ - rwc: rwc, - } - if err := i.auth(); err != nil { - return nil, nil, err - } - if err := i.parseRequest(); err != nil { - return nil, nil, err - } - return i, i.request, nil -} - -type udpSession struct { - src *net.UDPAddr - req *protocol.Request - expire time.Time -} - -type SocksInboundPacketSession struct { - conn *net.UDPConn - sessionTable map[string]*udpSession - tableMutex sync.Mutex - ctx context.Context - cancel context.CancelFunc -} - -func (i *SocksInboundPacketSession) parsePacket(rawPacket []byte) (*protocol.Request, []byte, error) { - if len(rawPacket) <= 4 { - return nil, nil, common.NewError("Malformed socks5 packet") - } - buf := bytes.NewBuffer(rawPacket) - buf.Next(2) - frag, _ := buf.ReadByte() - if frag != 0 { - return nil, nil, common.NewError("Fragment is not supported") - } - addr := &common.Address{ - NetworkType: "udp", - } - if err := addr.Marshal(buf); err != nil { - return nil, nil, common.NewError("cannot parse udp request").Base(err) - } - //command makes no sense here - request := &protocol.Request{ - Address: addr, - } - return request, buf.Bytes(), nil -} - -func (i *SocksInboundPacketSession) writePacketHeader(w io.Writer, req *protocol.Request) error { - w.Write([]byte{0, 0, 0}) - if err := req.Address.Unmarshal(w); err != nil { - return err - } - return nil -} - -func (i *SocksInboundPacketSession) cleanExpiredSession() { - for { - i.tableMutex.Lock() - now := time.Now() - for k, v := range i.sessionTable { - if now.After(v.expire) { - log.Debug("deleting expired session", v.src, "req:", v.req) - delete(i.sessionTable, k) - } - } - i.tableMutex.Unlock() - select { - case <-time.After(protocol.UDPTimeout): - case <-i.ctx.Done(): - i.conn.Close() - return - } - } -} - -func (i *SocksInboundPacketSession) ReadPacket() (*protocol.Request, []byte, error) { - buf := make([]byte, protocol.MaxUDPPacketSize) - i.conn.SetDeadline(time.Now().Add(protocol.UDPTimeout)) - n, src, err := i.conn.ReadFromUDP(buf) - i.conn.SetDeadline(time.Time{}) - if err != nil { - return nil, nil, err - } - req, payload, err := i.parsePacket(buf[0:n]) - if err != nil { - return nil, nil, err - } - session := &udpSession{ - src: src, - req: req, - expire: time.Now().Add(protocol.UDPTimeout), - } - i.tableMutex.Lock() - i.sessionTable[req.String()] = session - i.tableMutex.Unlock() - log.Debug("udp read from", src, "req", req) - return req, payload, err -} - -func (i *SocksInboundPacketSession) WritePacket(req *protocol.Request, packet []byte) (int, error) { - w := bytes.NewBuffer(make([]byte, 0)) - if err := i.writePacketHeader(w, req); err != nil { - return 0, err - } - w.Write(packet) - i.tableMutex.Lock() - defer i.tableMutex.Unlock() - client, found := i.sessionTable[req.String()] - if !found { - return 0, common.NewError("Session not found: " + req.String()) - } - client.expire = time.Now().Add(protocol.UDPTimeout) - log.Debug("udp write to", client.src, "req", req) - return i.conn.WriteToUDP(w.Bytes(), client.src) -} - -func (i *SocksInboundPacketSession) Close() error { - i.cancel() - return i.conn.Close() -} - -func NewInboundPacketSession(ctx context.Context, conn *net.UDPConn) (*SocksInboundPacketSession, error) { - ctx, cancel := context.WithCancel(ctx) - conn.SetWriteBuffer(0) - i := &SocksInboundPacketSession{ - ctx: ctx, - cancel: cancel, - sessionTable: make(map[string]*udpSession), - conn: conn, - } - go i.cleanExpiredSession() - return i, nil -} diff --git a/protocol/tproxy/inbound.go b/protocol/tproxy/inbound.go deleted file mode 100644 index d0e9dfb..0000000 --- a/protocol/tproxy/inbound.go +++ /dev/null @@ -1,151 +0,0 @@ -// +build linux - -package tproxy - -import ( - "context" - "net" - "sync" - "time" - - "github.com/LiamHaworth/go-tproxy" - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" -) - -type TProxyInboundConnSession struct { - reqeust *protocol.Request - net.Conn -} - -func (i *TProxyInboundConnSession) parseRequest() error { - tcpConn := i.Conn.(*tproxy.Conn).TCPConn - addr, err := getOriginalTCPDest(tcpConn) - if err != nil { - return common.NewError("Failed to get original dst").Base(err) - } - req := &protocol.Request{ - Address: &common.Address{ - IP: addr.IP, - Port: addr.Port, - }, - Command: protocol.Connect, - } - if addr.IP.To4() != nil { - req.AddressType = common.IPv4 - } else { - req.AddressType = common.IPv6 - } - i.reqeust = req - return nil -} - -func NewInboundConnSession(conn net.Conn) (protocol.ConnSession, *protocol.Request, error) { - i := &TProxyInboundConnSession{ - Conn: conn, - } - if err := i.parseRequest(); err != nil { - return nil, nil, common.NewError("Failed to parse request").Base(err) - } - return i, i.reqeust, nil -} - -type udpSession struct { - src *net.UDPAddr - dst *net.UDPAddr - expire time.Time -} - -type TProxyInboundPacketSession struct { - request *protocol.Request - conn *net.UDPConn - tableMutex sync.Mutex - sessionTable map[string]*udpSession - ctx context.Context - cancel context.CancelFunc -} - -func (i *TProxyInboundPacketSession) cleanExpiredSession() { - for { - i.tableMutex.Lock() - now := time.Now() - for k, v := range i.sessionTable { - if now.After(v.expire) { - delete(i.sessionTable, k) - } - } - i.tableMutex.Unlock() - select { - case <-time.After(protocol.UDPTimeout): - case <-i.ctx.Done(): - i.conn.Close() - return - } - } -} - -func (i *TProxyInboundPacketSession) WritePacket(req *protocol.Request, packet []byte) (int, error) { - i.tableMutex.Lock() - defer i.tableMutex.Unlock() - session, found := i.sessionTable[req.String()] - if !found { - return 0, common.NewError("Session not found " + req.String()) - } - conn, err := tproxy.DialUDP("udp", session.dst, session.src) - if err != nil { - return 0, common.NewError("Cannot dial to source").Base(err) - } - defer conn.Close() - return conn.Write(packet) -} - -func (i *TProxyInboundPacketSession) ReadPacket() (*protocol.Request, []byte, error) { - buf := [protocol.MaxUDPPacketSize]byte{} - n, src, dst, err := tproxy.ReadFromUDP(i.conn, buf[:]) - if err != nil { - return nil, nil, err - } - - if err != nil { - return nil, nil, err - } - i.tableMutex.Lock() - i.sessionTable[dst.String()] = &udpSession{ - src: src, - dst: dst, - expire: time.Now().Add(protocol.UDPTimeout), - } - i.tableMutex.Unlock() - log.Debug("tproxy udp packet from", src, "to", dst) - req := &protocol.Request{ - Address: &common.Address{ - IP: dst.IP, - Port: dst.Port, - NetworkType: "udp", - }, - } - if dst.IP.To4() != nil { - req.AddressType = common.IPv4 - } else { - req.AddressType = common.IPv6 - } - return req, buf[0:n], nil -} - -func (i *TProxyInboundPacketSession) Close() error { - i.cancel() - return nil -} - -func NewInboundPacketSession(ctx context.Context, conn *net.UDPConn) (protocol.PacketSession, error) { - ctx, cancel := context.WithCancel(ctx) - i := &TProxyInboundPacketSession{ - conn: conn, - sessionTable: make(map[string]*udpSession, 1024), - ctx: ctx, - cancel: cancel, - } - go i.cleanExpiredSession() - return i, nil -} diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go deleted file mode 100644 index 04089cd..0000000 --- a/protocol/trojan/inbound.go +++ /dev/null @@ -1,148 +0,0 @@ -package trojan - -import ( - "context" - "io" - "net" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/shadow" - "github.com/p4gefau1t/trojan-go/stat" -) - -type TrojanInboundConnSession struct { - rwc io.ReadWriteCloser - ctx context.Context - config *conf.GlobalConfig - request *protocol.Request - auth stat.Authenticator - user stat.User - ip string - sent uint64 - recv uint64 - passwordHash string - cancel context.CancelFunc -} - -func (i *TrojanInboundConnSession) Write(p []byte) (int, error) { - n, err := i.rwc.Write(p) - i.sent += uint64(n) - i.user.AddTraffic(n, 0) - return n, err -} - -func (i *TrojanInboundConnSession) Read(p []byte) (int, error) { - n, err := i.rwc.Read(p) - i.recv += uint64(n) - i.user.AddTraffic(0, n) - return n, err -} - -func (i *TrojanInboundConnSession) Close() error { - log.Info("User", i.passwordHash, "to", i.request, "closed", "sent:", common.HumanFriendlyTraffic(i.sent), "recv:", common.HumanFriendlyTraffic(i.recv)) - i.cancel() - i.user.DelIP(i.ip) - return i.rwc.Close() -} - -func (i *TrojanInboundConnSession) parseRequest(r *common.RewindReader) error { - userHash := [56]byte{} - - n, err := r.Read(userHash[:]) - if err != nil || n != 56 { - return common.NewError("Failed to read hash").Base(err) - } - - valid, user := i.auth.AuthUser(string(userHash[:])) - if !valid { - return common.NewError("Invalid hash:" + string(userHash[:])) - } - i.passwordHash = string(userHash[:]) - i.user = user - - ok := user.AddIP(i.ip) - if !ok { - return common.NewError("IP limit reached") - } - - crlf := [2]byte{} - _, err = io.ReadFull(r, crlf[:]) - if err != nil { - return err - } - - i.request = new(protocol.Request) - if err := i.request.Marshal(r); err != nil { - return err - } - - _, err = io.ReadFull(r, crlf[:]) - if err != nil { - return err - } - return nil -} - -func NewInboundConnSession(ctx context.Context, conn net.Conn, config *conf.GlobalConfig, auth stat.Authenticator, shadowMan *shadow.ShadowManager) (protocol.ConnSession, *protocol.Request, error) { - ctx, cancel := context.WithCancel(ctx) - rewindConn := common.NewRewindConn(conn) - ip, _, err := net.SplitHostPort(conn.RemoteAddr().String()) - common.Must(err) - i := &TrojanInboundConnSession{ - config: config, - auth: auth, - passwordHash: "INVALID_HASH", - ctx: ctx, - cancel: cancel, - rwc: rewindConn, - ip: ip, - } - - //start buffering - rewindConn.R.SetBufferSize(512) - defer rewindConn.R.StopBuffering() - - if i.config.Websocket.Enabled { - //try to treat it as a websocket connection first - ws, err := NewInboundWebsocket(i.ctx, rewindConn, config, shadowMan) - if err != nil { - return nil, nil, common.NewError("Invalid websocket request").Base(err) - } - if ws != nil { - //a websocket conn, try to verify it - log.Debug("Incoming websocket conn") - //disable the current read buffer, use ws as the new transport layer - rewindConn.R.SetBufferSize(0) - newTrapsport := common.NewRewindReadWriteCloser(ws) - i.rwc = newTrapsport - //parse it with trojan protocol format - if err := i.parseRequest(newTrapsport.RewindReader); err != nil { - //invalid ws, just simply close it - ws.Close() - return nil, nil, common.NewError("Invalid trojan header over websocket conn").Base(err) - } - return i, i.request, nil - } - //not a websocket conn, it might be a normal trojan conn - rewindConn.R.Rewind() - } - - //normal trojan conn - if err := i.parseRequest(rewindConn.R); err != nil { - //not a valid trojan request, proxy it to the remote_addr - rewindConn.R.Rewind() - err := common.NewError("Invalid trojan header from " + conn.RemoteAddr().String()).Base(err) - shadowMan.SubmitScapegoat(&shadow.Scapegoat{ - Conn: rewindConn, - ShadowAddress: i.config.RemoteAddress, - Info: err.Error(), - }) - return nil, nil, err - } - //release the buffer - rewindConn.R.SetBufferSize(0) - return i, i.request, nil -} diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go deleted file mode 100644 index 620b518..0000000 --- a/protocol/trojan/outbound.go +++ /dev/null @@ -1,74 +0,0 @@ -package trojan - -import ( - "bytes" - "io" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/stat" -) - -type TrojanOutboundConnSession struct { - config *conf.GlobalConfig - rwc io.ReadWriteCloser - request *protocol.Request - sent uint64 - recv uint64 - auth stat.Authenticator - meter stat.TrafficMeter - header []byte -} - -func (o *TrojanOutboundConnSession) Write(p []byte) (int, error) { - if o.header != nil { - //send the payload after the trojan request header - _, err := o.rwc.Write(append(o.header, p...)) - o.meter.AddTraffic(len(p)+len(o.header), 0) - o.sent += uint64(len(p) + len(o.header)) - o.header = nil - return len(p), err - } - n, err := o.rwc.Write(p) - o.meter.AddTraffic(n, 0) - o.sent += uint64(n) - return n, err -} - -func (o *TrojanOutboundConnSession) Read(p []byte) (int, error) { - n, err := o.rwc.Read(p) - o.meter.AddTraffic(0, n) - o.recv += uint64(n) - return n, err -} - -func (o *TrojanOutboundConnSession) Close() error { - log.Info("Conn to", o.request, "closed", "sent:", common.HumanFriendlyTraffic(o.sent), "recv:", common.HumanFriendlyTraffic(o.recv)) - return o.rwc.Close() -} - -func (o *TrojanOutboundConnSession) writeRequest() { - user := o.auth.ListUsers()[0] - hash := user.Hash() - o.meter = user - buf := bytes.NewBuffer(make([]byte, 0, 128)) - crlf := []byte{0x0d, 0x0a} - buf.Write([]byte(hash)) - buf.Write(crlf) - o.request.Unmarshal(buf) - buf.Write(crlf) - o.header = buf.Bytes() -} - -func NewOutboundConnSession(req *protocol.Request, rwc io.ReadWriteCloser, config *conf.GlobalConfig, auth stat.Authenticator) (protocol.ConnSession, error) { - o := &TrojanOutboundConnSession{ - request: req, - config: config, - rwc: rwc, - auth: auth, - } - o.writeRequest() - return o, nil -} diff --git a/protocol/trojan/packet.go b/protocol/trojan/packet.go deleted file mode 100644 index 38cc1a1..0000000 --- a/protocol/trojan/packet.go +++ /dev/null @@ -1,70 +0,0 @@ -package trojan - -import ( - "bytes" - "encoding/binary" - "io" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/protocol" -) - -type TrojanPacketSession struct { - conn io.ReadWriteCloser -} - -func (i *TrojanPacketSession) ReadPacket() (*protocol.Request, []byte, error) { - addr := &common.Address{ - NetworkType: "udp", - } - if err := addr.Marshal(i.conn); err != nil { - return nil, nil, common.NewError("Failed to parse addr").Base(err) - } - req := &protocol.Request{ - Address: addr, - } - lengthBuf := [2]byte{} - _, err := io.ReadFull(i.conn, lengthBuf[:]) - if err != nil { - return req, nil, common.NewError("Failed to read length") - } - length := binary.BigEndian.Uint16(lengthBuf[:]) - - crlf := [2]byte{} - io.ReadFull(i.conn, crlf[:]) - - packet := make([]byte, length) - _, err = io.ReadFull(i.conn, packet) - if err != nil { - return req, nil, common.NewError("Failed to read payload") - } - return req, packet[:], err -} - -func (i *TrojanPacketSession) WritePacket(req *protocol.Request, packet []byte) (int, error) { - buf := bytes.NewBuffer(make([]byte, 0, len(packet)+32)) - common.Must(req.Address.Unmarshal(buf)) - - length := len(packet) - lengthBuf := [2]byte{} - binary.BigEndian.PutUint16(lengthBuf[:], uint16(length)) - buf.Write(lengthBuf[:]) - - crlf := [2]byte{0x0d, 0x0a} - buf.Write(crlf[:]) - - buf.Write(packet) - - return i.conn.Write(buf.Bytes()) -} - -func (i *TrojanPacketSession) Close() error { - return i.conn.Close() -} - -func NewPacketSession(conn io.ReadWriteCloser) (protocol.PacketSession, error) { - i := &TrojanPacketSession{ - conn: conn, - } - return i, nil -} diff --git a/protocol/trojan/websocket.go b/protocol/trojan/websocket.go deleted file mode 100644 index 73d8551..0000000 --- a/protocol/trojan/websocket.go +++ /dev/null @@ -1,328 +0,0 @@ -package trojan - -import ( - "bufio" - "context" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/tls" - "io" - "net" - "net/http" - "strings" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/shadow" - "golang.org/x/net/websocket" -) - -//this AES layer is used for obfuscation purpose only -type obfReadWriteCloser struct { - net.Conn - r cipher.StreamReader - w cipher.StreamWriter - bufrw *bufio.ReadWriter -} - -func (rwc *obfReadWriteCloser) Read(p []byte) (int, error) { - return rwc.r.Read(p) -} - -func (rwc *obfReadWriteCloser) Write(p []byte) (int, error) { - n, err := rwc.w.Write(p) - rwc.bufrw.Flush() - return n, err -} - -func (rwc *obfReadWriteCloser) Close() error { - return rwc.Conn.Close() -} - -func NewOutboundObfReadWriteCloser(key []byte, conn net.Conn) *obfReadWriteCloser { - // use bufio to avoid fixed ws packet length - bufrw := common.NewBufioReadWriter(conn) - iv := [aes.BlockSize]byte{} - common.Must2(io.ReadFull(rand.Reader, iv[:])) - bufrw.Write(iv[:]) - log.Debug("obfs sent iv", iv) - - block, err := aes.NewCipher(key) - common.Must(err) - - return &obfReadWriteCloser{ - r: cipher.StreamReader{ - S: cipher.NewCTR(block, iv[:]), - R: bufrw, - }, - w: cipher.StreamWriter{ - S: cipher.NewCTR(block, iv[:]), - W: bufrw, - }, - Conn: conn, - bufrw: bufrw, - } -} - -func NewInboundObfReadWriteCloser(key []byte, conn net.Conn) (*obfReadWriteCloser, error) { - bufrw := common.NewBufioReadWriter(conn) - iv := [aes.BlockSize]byte{} - _, err := bufrw.Read(iv[:]) - if err != nil { - return nil, err - } - log.Debug("obfs recv iv", iv) - - block, err := aes.NewCipher(key) - common.Must(err) - - return &obfReadWriteCloser{ - r: cipher.StreamReader{ - S: cipher.NewCTR(block, iv[:]), - R: bufrw, - }, - w: cipher.StreamWriter{ - S: cipher.NewCTR(block, iv[:]), - W: bufrw, - }, - Conn: conn, - bufrw: bufrw, - }, nil -} - -//Fake response writer -//Websocket ServeHTTP method uses its Hijack method to get the Readwriter -type wsHttpResponseWriter struct { - http.Hijacker - http.ResponseWriter - - ReadWriter *bufio.ReadWriter - Conn net.Conn -} - -func (w *wsHttpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return w.Conn, w.ReadWriter, nil -} - -// TODO wrap this with a struct -var tlsSessionCache = tls.NewLRUClientSessionCache(-1) - -func NewOutboundWebosocket(conn net.Conn, config *conf.GlobalConfig) (io.ReadWriteCloser, error) { - url := "wss://" + config.Websocket.HostName + config.Websocket.Path - origin := "https://" + config.Websocket.HostName - wsConfig, err := websocket.NewConfig(url, origin) - if err != nil { - return nil, err - } - wsConn, err := websocket.NewClient(wsConfig, conn) - if err != nil { - return nil, err - } - var transport net.Conn = wsConn - if config.Websocket.ObfuscationPassword != "" { - log.Debug("ws obfs enabled") - transport = NewOutboundObfReadWriteCloser(config.Websocket.ObfuscationKey, wsConn) - } - if !config.Websocket.DoubleTLS { - return transport, nil - } - log.Debug("ws double tls enabled") - tlsConfig := &tls.Config{ - CipherSuites: config.Websocket.TLS.CipherSuites, - RootCAs: config.Websocket.TLS.CertPool, - ServerName: config.Websocket.TLS.SNI, - SessionTicketsDisabled: !config.Websocket.TLS.SessionTicket, - InsecureSkipVerify: !config.Websocket.TLS.Verify, - ClientSessionCache: tlsSessionCache, - } - tlsConn := tls.Client(transport, tlsConfig) - if err := tlsConn.Handshake(); err != nil { - return nil, err - } - if config.LogLevel == 0 { - state := tlsConn.ConnectionState() - chain := state.VerifiedChains - log.Trace("Websocket double TLS handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume) - for i := range chain { - for j := range chain[i] { - log.Trace("Subject:", chain[i][j].Subject, "Issuer:", chain[i][j].Issuer) - } - } - } - return tlsConn, nil -} - -func dialToWebosocketServer(config *conf.GlobalConfig, url, origin string) (*websocket.Conn, error) { - wsConfig, err := websocket.NewConfig(url, origin) - if err != nil { - return nil, err - } - conn, err := net.Dial("tcp", config.RemoteAddress.String()) - if err != nil { - return nil, err - } - newWsConn, err := websocket.NewClient(wsConfig, conn) - if err != nil { - return nil, err - } - return newWsConn, nil -} - -func getWebsocketScapegoat(config *conf.GlobalConfig, url, origin, info string, conn net.Conn) (*shadow.Scapegoat, error) { - shadowConn, err := dialToWebosocketServer(config, url, origin) - if err != nil { - return nil, err - } - return &shadow.Scapegoat{ - Conn: conn, - ShadowConn: shadowConn, - Info: info, - }, nil -} - -func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.GlobalConfig, shadowMan *shadow.ShadowManager) (io.ReadWriteCloser, error) { - rewindConn := common.NewRewindConn(conn) - rewindConn.R.SetBufferSize(512) - defer rewindConn.R.StopBuffering() - - bufrw := bufio.NewReadWriter(bufio.NewReader(rewindConn), bufio.NewWriter(rewindConn)) - httpRequest, err := http.ReadRequest(bufrw.Reader) - if err != nil { - log.Debug(common.NewError("Not a http request:").Base(err)) - return nil, nil - } - - //this is a http request - if httpRequest.URL.Path != config.Websocket.Path || //check url path - strings.ToLower(httpRequest.Header.Get("Upgrade")) != "websocket" { //check upgrade field - //not a valid websocket conn - rewindConn.R.Rewind() - err := common.NewError("Invalid websocket request from " + conn.RemoteAddr().String()) - shadowMan.SubmitScapegoat(&shadow.Scapegoat{ - Conn: rewindConn, - ShadowAddress: config.RemoteAddress, - Info: err.Error(), - }) - return nil, err - } - - //this is a websocket upgrade request - //no need to record the recv content for now - rewindConn.R.SetBufferSize(0) - url := "wss://" + config.Websocket.HostName + config.Websocket.Path - origin := "https://" + config.Websocket.HostName - wsConfig, err := websocket.NewConfig(url, origin) - - handshaked := make(chan struct{}) - - var wsConn *websocket.Conn - wsServer := websocket.Server{ - Config: *wsConfig, - Handler: func(conn *websocket.Conn) { - wsConn = conn //store the websocket after handshaking - log.Debug("websocket obtained") - handshaked <- struct{}{} - //this function will NOT return unless the connection is ended - //or the websocket will be closed by ServeHTTP method - <-ctx.Done() - log.Debug("websocket closed") - }, - Handshake: func(wsConfig *websocket.Config, httpRequest *http.Request) error { - log.Debug("websocket url", httpRequest.URL, "origin", httpRequest.Header.Get("Origin")) - return nil - }, - } - - responseWriter := &wsHttpResponseWriter{ - Conn: conn, - ReadWriter: bufrw, - } - go wsServer.ServeHTTP(responseWriter, httpRequest) - - select { - case <-handshaked: - case <-time.After(protocol.TCPTimeout): - } - - if wsConn == nil { - //conn has been closed at this point - return nil, common.NewError("failed to perform websocket handshake") - } - - //use ws to transfer - var transport net.Conn - rewindConn = common.NewRewindConn(wsConn) - transport = rewindConn - - //start buffering the websocket payload - rewindConn.R.SetBufferSize(512) - defer rewindConn.R.StopBuffering() - - if config.Websocket.ObfuscationPassword != "" { - log.Debug("ws obfs") - - //deadline for sending the iv and hash - protocol.SetRandomizedTimeout(rewindConn) - transport, err = NewInboundObfReadWriteCloser(config.Websocket.ObfuscationKey, transport) - protocol.CancelTimeout(rewindConn) - - if err != nil { - rewindConn.R.Rewind() - //redirect this to our own ws server - err = common.NewError("Remote websocket " + conn.RemoteAddr().String() + "didn't send any valid iv").Base(err) - goat, err := getWebsocketScapegoat( - config, - url, - origin, - err.Error(), - rewindConn, - ) - if err != nil { - log.Error(common.NewError("Failed to obtain websocket scapegoat").Base(err)) - wsConn.WriteClose(500) - } else { - shadowMan.SubmitScapegoat(goat) - } - return nil, err - } - } - if !config.Websocket.DoubleTLS { - rewindConn.R.SetBufferSize(0) - return transport, nil - } - tlsConfig := &tls.Config{ - Certificates: config.Websocket.TLS.KeyPair, - CipherSuites: config.Websocket.TLS.CipherSuites, - PreferServerCipherSuites: config.Websocket.TLS.PreferServerCipher, - SessionTicketsDisabled: !config.Websocket.TLS.SessionTicket, - } - tlsConn := tls.Server(transport, tlsConfig) - protocol.SetRandomizedTimeout(tlsConn) - if tlsErr := tlsConn.Handshake(); tlsErr != nil { - rewindConn.R.Rewind() - rewindConn.R.StopBuffering() - //proxy this to our own ws server - tlsErr = common.NewError("Invalid double TLS handshake from " + conn.RemoteAddr().String()).Base(tlsErr) - goat, err := getWebsocketScapegoat( - config, - url, - origin, - tlsErr.Error(), - rewindConn, - ) - if err != nil { - log.Error(common.NewError("Failed to obtain websocket scapegoat").Base(err)) - wsConn.WriteClose(500) - } else { - shadowMan.SubmitScapegoat(goat) - } - return nil, tlsErr - } - protocol.CancelTimeout(tlsConn) - rewindConn.R.SetBufferSize(0) - return tlsConn, nil -} diff --git a/proxy/client/app.go b/proxy/client/app.go deleted file mode 100644 index b4e4efb..0000000 --- a/proxy/client/app.go +++ /dev/null @@ -1,55 +0,0 @@ -package client - -import ( - "context" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/simplesocks" - "github.com/p4gefau1t/trojan-go/protocol/trojan" - "github.com/p4gefau1t/trojan-go/stat" -) - -type AppManager struct { - auth stat.Authenticator - config *conf.GlobalConfig - transport TransportManager - ctx context.Context -} - -func (m *AppManager) OpenAppConn(req *protocol.Request) (protocol.ConnSession, error) { - var outboundConn protocol.ConnSession - //transport layer - transport, err := m.transport.DialToServer() - if err != nil { - return nil, common.NewError("Failed to init transport layer").Base(err) - } - //application layer - if m.config.Mux.Enabled { - outboundConn, err = simplesocks.NewOutboundConnSession(req, transport) - } else { - outboundConn, err = trojan.NewOutboundConnSession(req, transport, m.config, m.auth) - } - if err != nil { - return nil, common.NewError("Fail to open conn session").Base(err) - } - log.Info("Tunneling to", req) - return outboundConn, nil -} - -func NewAppManager(ctx context.Context, config *conf.GlobalConfig, auth stat.Authenticator) *AppManager { - c := &AppManager{ - ctx: ctx, - config: config, - auth: auth, - } - if config.Mux.Enabled { - log.Info("Mux enabled") - c.transport = NewMuxPoolManager(ctx, config, auth) - } else { - c.transport = NewTLSManager(config) - } - return c -} diff --git a/proxy/client/client.go b/proxy/client/client.go index ab36050..92c3284 100644 --- a/proxy/client/client.go +++ b/proxy/client/client.go @@ -2,381 +2,45 @@ package client import ( "context" - "io" - "net" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/direct" - "github.com/p4gefau1t/trojan-go/protocol/http" - "github.com/p4gefau1t/trojan-go/protocol/socks" - "github.com/p4gefau1t/trojan-go/protocol/trojan" + "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/proxy" - "github.com/p4gefau1t/trojan-go/router" - "github.com/p4gefau1t/trojan-go/stat" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/mux" + "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" + "github.com/p4gefau1t/trojan-go/tunnel/socks" + "github.com/p4gefau1t/trojan-go/tunnel/transport" + "github.com/p4gefau1t/trojan-go/tunnel/trojan" + "github.com/p4gefau1t/trojan-go/tunnel/websocket" ) -type TransportManager interface { - DialToServer() (io.ReadWriteCloser, error) -} +const Name = "CLIENT" -type packetInfo struct { - request *protocol.Request - packet []byte -} - -type Client struct { - config *conf.GlobalConfig - ctx context.Context - cancel context.CancelFunc - associated *common.Notifier - router router.Router - tcpListener net.Listener - udpListener net.PacketConn - auth stat.Authenticator - appMan *AppManager -} - -func (c *Client) handleSocksConn(conn io.ReadWriteCloser) { - rwc := common.NewRewindReadWriteCloser(conn) - defer rwc.Close() - - inboundConn, req, err := socks.NewInboundConnSession(rwc) - if err != nil { - log.Error(common.NewError("Failed to handle socks requests").Base(err)) - return +// GenerateClientTree generate general outbound protocol stack +func GenerateClientTree(isMux bool, isWebsocket bool) []string { + clientStack := []string{transport.Name} + if isWebsocket { + clientStack = append(clientStack, websocket.Name) } - defer inboundConn.Close() - - if req.Command == protocol.Associate { - // setting up the bind address to respond - // listenUDP() will handle the incoming udp packets - localIP, err := c.config.LocalAddress.ResolveIP() - if err != nil { - log.Error(common.NewError("Invalid local address").Base(err)) - return - } - // bind port and IP - req.IP = localIP - req.Port = c.config.LocalAddress.Port - if localIP.To4() != nil { - req.AddressType = common.IPv4 - } else { - req.AddressType = common.IPv6 - } - - // notify listenUDP to get ready for relaying udp packets - c.associated.Signal() - log.Debug("UDP associated to", req) - if err := inboundConn.(protocol.NeedRespond).Respond(); err != nil { - log.Error("Failed to repsond") - return - } - - var buf [1]byte - _, err = rwc.Read(buf[:]) - log.Debug(common.NewError("UDP conn ends").Base(err)) - return + clientStack = append(clientStack, trojan.Name) + if isMux { + clientStack = append(clientStack, []string{mux.Name, simplesocks.Name}...) } - - if err := inboundConn.(protocol.NeedRespond).Respond(); err != nil { - log.Error(common.NewError("Failed to respond").Base(err)) - return - } - - policy, err := c.router.RouteRequest(req) - if err != nil { - log.Error(err) - return - } - if policy == router.Bypass { - outboundConn, err := direct.NewOutboundConnSession(c.ctx, req, c.config) - if err != nil { - log.Error(err) - return - } - log.Info("[Bypass]", req) - defer outboundConn.Close() - proxy.RelayConn(c.ctx, inboundConn, outboundConn, c.config.BufferSize) - } else if policy == router.Block { - log.Info("[Block]", req) - } else { - outboundConn, err := c.appMan.OpenAppConn(req) - if err != nil { - log.Error(err) - return - } - defer outboundConn.Close() - proxy.RelayConn(c.ctx, inboundConn, outboundConn, c.config.BufferSize) - } -} - -func (c *Client) handleHTTPConn(conn io.ReadWriteCloser) { - rwc := common.NewRewindReadWriteCloser(conn) - defer rwc.Close() - inboundConn, req, inboundPacket, err := http.NewHTTPInbound(rwc) - if err != nil { - log.Error(common.NewError("Failed to handle HTTP requests").Base(err)) - return - } - - if inboundConn != nil { // CONNECT requests - defer inboundConn.Close() - - if err := inboundConn.(protocol.NeedRespond).Respond(); err != nil { - log.Error(common.NewError("Failed to respond").Base(err)) - return - } - - policy, err := c.router.RouteRequest(req) - if err != nil { - log.Error(err) - return - } - if policy == router.Bypass { - outboundConn, err := direct.NewOutboundConnSession(c.ctx, req, c.config) - if err != nil { - log.Error(err) - return - } - log.Info("[Bypass]", req) - proxy.RelayConn(c.ctx, inboundConn, outboundConn, c.config.BufferSize) - return - } else if policy == router.Block { - log.Info("[Block]", req) - return - } - - outboundConn, err := c.appMan.OpenAppConn(req) - if err != nil { - log.Error(common.NewError("Fail to start conn session").Base(err)) - return - } - defer outboundConn.Close() - proxy.RelayConn(c.ctx, inboundConn, outboundConn, c.config.BufferSize) - } else { // GET/POST requests - defer inboundPacket.Close() - packetChan := make(chan *packetInfo, 512) - errChan := make(chan error, 1) - - readHTTPPackets := func() { - for { - req, packet, err := inboundPacket.ReadPacket() - if err != nil { - log.Error(common.NewError("Failed to parse packet").Base(err)) - return - } - if req.String() == c.config.LocalAddress.String() { //loop - err := common.NewError("HTTP loop detected") - errChan <- err - log.Error(err) - return - } - if err != nil { - log.Error(err) - errChan <- err - return - } - packetChan <- &packetInfo{ - request: req, - packet: packet, - } - } - } - - writeHTTPPackets := func() { - for { - select { - case <-errChan: - return - case packet := <-packetChan: - outboundConn, err := c.appMan.OpenAppConn(packet.request) - if err != nil { - log.Error(err) - continue - } - _, err = outboundConn.Write(packet.packet) - if err != nil { - log.Error(err) - continue - } - go func(outboundConn protocol.ConnSession) { - buf := [4096]byte{} - defer outboundConn.Close() - for { - n, err := outboundConn.Read(buf[:]) - if err != nil { - if err == io.ErrShortBuffer { - log.Debug("Short buffer") - } else { - log.Error(err) - return - } - } - if _, err = inboundPacket.WritePacket(nil, buf[:n]); err != nil { - log.Debug(err) - return - } - } - }(outboundConn) - case <-c.ctx.Done(): - return - } - } - } - - go readHTTPPackets() - writeHTTPPackets() - } -} - -func (c *Client) listenUDP(errChan chan error) { - listener, err := net.ListenPacket("udp", c.config.LocalAddress.String()) - if err != nil { - errChan <- common.NewError("Failed to listen udp").Base(err) - return - } - c.udpListener = listener - inboundPacket, err := socks.NewInboundPacketSession(c.ctx, listener.(*net.UDPConn)) - common.Must(err) - for { - select { - case <-c.associated.Wait(): - log.Debug("associated signal") - req := &protocol.Request{ - Address: &common.Address{ - DomainName: "UDP_CONN", - AddressType: common.DomainName, - }, - Command: protocol.Associate, - } - outboundConn, err := c.appMan.OpenAppConn(req) - if err != nil { - log.Error(common.NewError("Failed to init udp tunnel").Base(err)) - return - } - outboundPacket, err := trojan.NewPacketSession(outboundConn) - common.Must(err) - directOutboundPacket, err := direct.NewOutboundPacketSession(c.ctx) - common.Must(err) - table := map[router.Policy]protocol.PacketReadWriter{ - router.Proxy: outboundPacket, - router.Bypass: directOutboundPacket, - } - proxy.RelayPacketWithRouter(c.ctx, inboundPacket, table, c.router) - outboundPacket.Close() - directOutboundPacket.Close() - case <-c.ctx.Done(): - return - } - } -} - -func (c *Client) listenTCP(errChan chan error) { - listener, err := net.Listen("tcp", c.config.LocalAddress.String()) - if err != nil { - errChan <- common.NewError("Failed to listen local address").Base(err) - return - } - c.tcpListener = listener - defer listener.Close() - for { - conn, err := listener.Accept() - if err != nil { - errChan <- common.NewError("Error occured when accepting conn").Base(err) - return - } - rwc := common.NewRewindReadWriteCloser(conn) - rwc.SetBufferSize(128) - first, err := rwc.ReadByte() - if err != nil { - log.Error(common.NewError("Failed to obtain proxy type").Base(err)) - rwc.Close() - continue - } - rwc.Rewind() - rwc.StopBuffering() - if first == 0x05 { - go c.handleSocksConn(rwc) - } else { - go c.handleHTTPConn(rwc) - } - } -} - -func (c *Client) Run() error { - log.Info("Trojan-Go client is listening on", c.config.LocalAddress.String()) - errChan := make(chan error, 4) - if c.config.TransportPlugin.Enabled && c.config.TransportPlugin.Cmd != nil { - go func() { - log.Info("Initiating plugin...") - select { - case errChan <- c.config.TransportPlugin.Cmd.Run(): - case <-c.ctx.Done(): - c.config.TransportPlugin.Cmd.Process.Kill() - log.Info("Plugin killed") - } - }() - } - go c.listenUDP(errChan) - go c.listenTCP(errChan) - if c.config.API.Enabled { - go func() { - errChan <- proxy.RunAPIService(conf.Client, c.ctx, c.config, c.auth) - }() - } - select { - case err := <-errChan: - return err - case <-c.ctx.Done(): - return nil - } -} - -func (c *Client) Close() error { - log.Info("Shutting down client..") - c.cancel() - if c.udpListener != nil { - c.udpListener.Close() - } - if c.tcpListener != nil { - c.tcpListener.Close() - } - return nil -} - -func (c *Client) Build(config *conf.GlobalConfig) (common.Runnable, error) { - ctx, cancel := context.WithCancel(context.Background()) - auth, err := stat.NewAuth(ctx, "memory", config) - if err != nil { - cancel() - return nil, err - } - - var rtr router.Router = &router.EmptyRouter{} - if config.Router.Enabled { - log.Info("Router enabled") - rtr, err = router.NewRouter(&config.Router) - if err != nil { - log.Fatal(common.NewError("invalid router list").Base(err)) - } - } - appMan := NewAppManager(ctx, config, auth) - - newClient := &Client{ - ctx: ctx, - cancel: cancel, - config: config, - router: rtr, - associated: common.NewNotifier(), - auth: auth, - appMan: appMan, - } - return newClient, nil + return clientStack } func init() { - proxy.RegisterProxy(conf.Client, &Client{}) + proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { + cfg := config.FromContext(ctx, Name).(*Config) + serverStack := []string{socks.Name} + clientStack := GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled) + c, err := proxy.CreateClientStack(ctx, clientStack) + if err != nil { + return nil, err + } + s, err := proxy.CreateServerStack(ctx, serverStack) + if err != nil { + return nil, err + } + return proxy.NewProxy(ctx, []tunnel.Server{s}, c), nil + }) } diff --git a/proxy/client/config.go b/proxy/client/config.go new file mode 100644 index 0000000..ae9636f --- /dev/null +++ b/proxy/client/config.go @@ -0,0 +1,22 @@ +package client + +import "github.com/p4gefau1t/trojan-go/config" + +type MuxConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` +} + +type WebsocketConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` +} + +type Config struct { + Mux MuxConfig `json:"mux" yaml:"mux"` + Websocket WebsocketConfig `json:"websocket" yaml:"websocket"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return new(Config) + }) +} diff --git a/proxy/client/forward.go b/proxy/client/forward.go deleted file mode 100644 index 85927be..0000000 --- a/proxy/client/forward.go +++ /dev/null @@ -1,212 +0,0 @@ -package client - -import ( - "context" - "net" - "sync" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/trojan" - "github.com/p4gefau1t/trojan-go/proxy" - "github.com/p4gefau1t/trojan-go/stat" -) - -type dispatchInfo struct { - addr net.Addr - payload []byte -} - -type Forward struct { - config *conf.GlobalConfig - ctx context.Context - cancel context.CancelFunc - clientPackets chan *dispatchInfo - outboundPacketTableLock sync.Mutex - outboundPacketTable map[string]protocol.PacketSession - udpListener net.PacketConn - tcpListener net.Listener - auth stat.Authenticator - appMan *AppManager -} - -func (f *Forward) dispatchServerPacket(addr net.Addr) { - for { - f.outboundPacketTableLock.Lock() - //use src addr as the key - outboundPacket, found := f.outboundPacketTable[addr.String()] - f.outboundPacketTableLock.Unlock() - if !found { - log.Error("Address key not found, expired?", addr.String()) - return - } - payloadChan := make(chan []byte, 64) - go func() { - _, payload, err := outboundPacket.ReadPacket() - if err != nil { //expired - return - } - payloadChan <- payload - }() - select { - case payload := <-payloadChan: - _, err := f.udpListener.WriteTo(payload, addr) - if err != nil { //closed - return - } - case <-time.After(protocol.UDPTimeout): - outboundPacket.Close() - f.outboundPacketTableLock.Lock() - delete(f.outboundPacketTable, addr.String()) - f.outboundPacketTableLock.Unlock() - log.Debug("UDP timeout, exiting..") - return - case <-f.ctx.Done(): - log.Debug("Forward closed, exiting..") - return - } - } -} - -func (f *Forward) dispatchClientPacket() { - fixedReq := &protocol.Request{ - Address: f.config.TargetAddress, - } - associateReq := &protocol.Request{ - Address: &common.Address{ - DomainName: "UDP_CONN", - AddressType: common.DomainName, - }, - Command: protocol.Associate, - } - for { - select { - case packet := <-f.clientPackets: - f.outboundPacketTableLock.Lock() - outboundPacket, found := f.outboundPacketTable[packet.addr.String()] - if !found { - outboundConn, err := f.appMan.OpenAppConn(associateReq) - if err != nil { - log.Error(err) - continue - } - outboundPacket, err = trojan.NewPacketSession(outboundConn) - common.Must(err) - f.outboundPacketTable[packet.addr.String()] = outboundPacket - go f.dispatchServerPacket(packet.addr) - } - f.outboundPacketTableLock.Unlock() - outboundPacket.WritePacket(fixedReq, packet.payload) - case <-f.ctx.Done(): - return - } - } -} - -func (f *Forward) listenUDP(errChan chan error) { - listener, err := net.ListenPacket("udp", f.config.LocalAddress.String()) - if err != nil { - errChan <- common.NewError("Failed to listen udp") - return - } - f.udpListener = listener - go f.dispatchClientPacket() - for { - buf := make([]byte, protocol.MaxUDPPacketSize) - n, addr, err := listener.ReadFrom(buf) - log.Info("Packet from", addr, "tunneling to", f.config.TargetAddress) - if err != nil { - errChan <- err - return - } - f.clientPackets <- &dispatchInfo{ - addr: addr, - payload: buf[:n], - } - } -} - -func (f *Forward) listenTCP(errChan chan error) { - listener, err := net.Listen("tcp", f.config.LocalAddress.String()) - if err != nil { - errChan <- common.NewError("Failed to listen local address").Base(err) - return - } - f.tcpListener = listener - defer listener.Close() - req := &protocol.Request{ - Address: f.config.TargetAddress, - Command: protocol.Connect, - } - for { - inboundConn, err := listener.Accept() - if err != nil { - errChan <- common.NewError("Error occured when accepting conn").Base(err) - } - handle := func(inboundConn net.Conn) { - outboundConn, err := f.appMan.OpenAppConn(req) - if err != nil { - log.Error(common.NewError("Failed to start outbound session").Base(err)) - return - } - defer outboundConn.Close() - proxy.RelayConn(f.ctx, inboundConn, outboundConn, f.config.BufferSize) - } - go handle(inboundConn) - } -} - -func (f *Forward) Run() error { - log.Info("Trojan-Go forward is listening on", f.config.LocalAddress) - errChan := make(chan error, 2) - go f.listenUDP(errChan) - go f.listenTCP(errChan) - select { - case <-f.ctx.Done(): - return nil - case err := <-errChan: - return err - } -} - -func (f *Forward) Close() error { - log.Info("Shutting down forward..") - f.cancel() - if f.udpListener != nil { - f.udpListener.Close() - } - if f.tcpListener != nil { - f.tcpListener.Close() - } - return nil -} - -func (f *Forward) Build(config *conf.GlobalConfig) (common.Runnable, error) { - ctx, cancel := context.WithCancel(context.Background()) - - authDriver := "memory" - auth, err := stat.NewAuth(ctx, authDriver, config) - if err != nil { - cancel() - return nil, err - } - appMan := NewAppManager(ctx, config, auth) - - newForward := &Forward{ - ctx: ctx, - cancel: cancel, - config: config, - auth: auth, - appMan: appMan, - clientPackets: make(chan *dispatchInfo, 1024), - outboundPacketTable: make(map[string]protocol.PacketSession), - } - return newForward, nil -} - -func init() { - proxy.RegisterProxy(conf.Forward, &Forward{}) -} diff --git a/proxy/client/mux.go b/proxy/client/mux.go deleted file mode 100644 index ce6a43c..0000000 --- a/proxy/client/mux.go +++ /dev/null @@ -1,256 +0,0 @@ -package client - -import ( - "context" - "fmt" - "io" - "math/rand" - "sync" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/trojan" - "github.com/p4gefau1t/trojan-go/stat" - "github.com/xtaci/smux" -) - -// HACK stick the smux 8 bytes header to the payload -type smuxStickyReadWriteCloser struct { - io.ReadWriteCloser - synQueue chan []byte - finQueue chan []byte -} - -func (rwc *smuxStickyReadWriteCloser) stickToPayload(p []byte) []byte { - buf := make([]byte, 0, len(p)+16) - for { - select { - case header := <-rwc.synQueue: - buf = append(buf, header...) - default: - goto stick1 - } - } -stick1: - buf = append(buf, p...) - for { - select { - case header := <-rwc.finQueue: - buf = append(buf, header...) - default: - goto stick2 - } - } -stick2: - return buf -} - -func (rwc *smuxStickyReadWriteCloser) Close() error { - const maxPaddingLength = 512 - padding := [maxPaddingLength + 8]byte{'A', 'B', 'C', 'D', 'E', 'F'} // for debugging - buf := rwc.stickToPayload(nil) - rwc.Write(append(buf, padding[:rand.Intn(maxPaddingLength)]...)) - return rwc.ReadWriteCloser.Close() -} - -func (rwc *smuxStickyReadWriteCloser) Write(p []byte) (int, error) { - if len(p) == 8 { - if p[0] == 1 || p[0] == 2 { //smux 8 bytes header - switch p[1] { - // THE CONTENT OF THE BUFFER MIGHT CHANGE - // NEVER STORE THE POINTER TO HEADER, COPY THE HEADER INSTEAD - case 0: - // cmdSYN - header := make([]byte, 8) - copy(header, p) - rwc.synQueue <- header - return 8, nil - case 1: - // cmdFIN - header := make([]byte, 8) - copy(header, p) - rwc.finQueue <- header - return 8, nil - } - } else { - log.Debug("Unknown 8 bytes") - } - } - _, err := rwc.ReadWriteCloser.Write(rwc.stickToPayload(p)) - return len(p), err -} - -func newSmuxStickyReadWriteCloser(rwc io.ReadWriteCloser) *smuxStickyReadWriteCloser { - return &smuxStickyReadWriteCloser{ - ReadWriteCloser: rwc, - synQueue: make(chan []byte, 128), - finQueue: make(chan []byte, 128), - } -} - -type muxID uint32 - -func generateMuxID() muxID { - return muxID(rand.Uint32()) -} - -type muxClientInfo struct { - id muxID - client *smux.Session - lastActiveTime time.Time -} - -type MuxManager struct { - TransportManager - - sync.Mutex - muxPool map[muxID]*muxClientInfo - config *conf.GlobalConfig - auth stat.Authenticator - ctx context.Context - transport *TLSManager -} - -func (m *MuxManager) newMuxClient() (*muxClientInfo, error) { - id := generateMuxID() - if _, found := m.muxPool[id]; found { - return nil, common.NewError("Duplicated id") - } - req := &protocol.Request{ - Command: protocol.Mux, - Address: &common.Address{ - DomainName: "MUX_CONN", - AddressType: common.DomainName, - }, - } - rwc, err := m.transport.DialToServer() - if err != nil { - return nil, common.NewError("Failed to dail to remote server").Base(err) - } - trojanConn, err := trojan.NewOutboundConnSession(req, rwc, m.config, m.auth) - if err != nil { - rwc.Close() - log.Error(common.NewError("Failed to dial tls tunnel").Base(err)) - return nil, err - } - - smuxRWC := newSmuxStickyReadWriteCloser(trojanConn) - - smuxConfig := smux.DefaultConfig() - smuxConfig.KeepAliveDisabled = true - client, err := smux.Client(smuxRWC, smuxConfig) - common.Must(err) - log.Info(fmt.Sprintf("Mux TLS tunnel established with mux client %x", id)) - return &muxClientInfo{ - client: client, - id: id, - lastActiveTime: time.Now(), - }, nil -} - -func (m *MuxManager) pickMuxClient() (*muxClientInfo, error) { - m.Lock() - defer m.Unlock() - - for _, info := range m.muxPool { - if info.client.IsClosed() { - delete(m.muxPool, info.id) - log.Info(fmt.Sprintf("Mux client %x is closed", info.id)) - continue - } - if info.client.NumStreams() < m.config.Mux.Concurrency || m.config.Mux.Concurrency <= 0 { - info.lastActiveTime = time.Now() - return info, nil - } - } - - select { - case <-m.ctx.Done(): - return nil, common.NewError("Mux manager closed") - default: - } - - // not found - info, err := m.newMuxClient() - if err != nil { - return nil, err - } - m.muxPool[info.id] = info - return info, nil -} - -func (m *MuxManager) DialToServer() (io.ReadWriteCloser, error) { - info, err := m.pickMuxClient() - if err != nil { - return nil, err - } - stream, err := info.client.OpenStream() - if err != nil { - m.Lock() - defer m.Unlock() - delete(m.muxPool, info.id) - info.client.Close() - log.Warn(common.NewError(fmt.Sprintf("Somthing wrong with mux client %x, closing", info.id)).Base(err)) - return nil, err - } - log.Info(fmt.Sprintf("New mux stream established with mux client %x", info.id)) - info.lastActiveTime = time.Now() - return stream, nil -} - -func (m *MuxManager) checkAndCloseIdleMuxClient() { - var muxIdleDuration, checkDuration time.Duration - if m.config.Mux.IdleTimeout <= 0 { - muxIdleDuration = 0 - checkDuration = time.Second * 10 - log.Warn("Invalid mux idle timeout") - } else { - muxIdleDuration = time.Duration(m.config.Mux.IdleTimeout) * time.Second - checkDuration = muxIdleDuration / 4 - } - for { - select { - case <-time.After(checkDuration): - m.Lock() - for id, info := range m.muxPool { - if info.client.IsClosed() { - delete(m.muxPool, id) - log.Info("Mux client", id, "is dead") - } else if info.client.NumStreams() == 0 && time.Now().Sub(info.lastActiveTime) > muxIdleDuration { - info.client.Close() - delete(m.muxPool, id) - log.Info("Mux client", id, "is closed due to inactive") - } - } - log.Debug("Current mux pool clients: ", len(m.muxPool)) - for i, c := range m.muxPool { - log.Debug(fmt.Sprintf(" Client %x: %d/%d", i, c.client.NumStreams(), m.config.Mux.Concurrency)) - } - m.Unlock() - case <-m.ctx.Done(): - log.Debug("Shutting down mux manager..") - m.Lock() - for id, info := range m.muxPool { - info.client.Close() - log.Debug("Mux client", id, "closed") - } - m.Unlock() - return - } - } -} - -func NewMuxPoolManager(ctx context.Context, config *conf.GlobalConfig, auth stat.Authenticator) *MuxManager { - m := &MuxManager{ - ctx: ctx, - config: config, - muxPool: make(map[muxID]*muxClientInfo), - transport: NewTLSManager(config), - auth: auth, - } - go m.checkAndCloseIdleMuxClient() - return m -} diff --git a/proxy/client/nat.go b/proxy/client/nat.go deleted file mode 100644 index 5291c45..0000000 --- a/proxy/client/nat.go +++ /dev/null @@ -1,168 +0,0 @@ -// +build linux - -package client - -import ( - "context" - "net" - "time" - - gotproxy "github.com/LiamHaworth/go-tproxy" - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/tproxy" - "github.com/p4gefau1t/trojan-go/protocol/trojan" - "github.com/p4gefau1t/trojan-go/proxy" - "github.com/p4gefau1t/trojan-go/stat" -) - -type NAT struct { - config *conf.GlobalConfig - ctx context.Context - cancel context.CancelFunc - tcpListener net.Listener - udpListener net.PacketConn - auth stat.Authenticator - appMan *AppManager -} - -func (n *NAT) handleConn(conn net.Conn) { - inboundConn, req, err := tproxy.NewInboundConnSession(conn) - if err != nil { - log.Error(common.NewError("Failed to start inbound session").Base(err)) - return - } - defer inboundConn.Close() - outboundConn, err := n.appMan.OpenAppConn(req) - if err != nil { - log.Error(err) - return - } - defer outboundConn.Close() - log.Info("[TProxy] conn from", conn.RemoteAddr(), "tunneling to", req) - proxy.RelayConn(n.ctx, inboundConn, outboundConn, n.config.BufferSize) -} - -func (n *NAT) listenUDP(errChan chan error) { - ip, err := n.config.LocalAddress.ResolveIP() - if err != nil { - errChan <- err - } - - // listen with IP_TRANSPARENT option - listener, err := gotproxy.ListenUDP("udp", &net.UDPAddr{ - IP: ip, - Port: n.config.LocalAddress.Port, - }) - - inboundPacket, err := tproxy.NewInboundPacketSession(n.ctx, listener) - if err != nil { - errChan <- err - return - } - defer inboundPacket.Close() - - req := &protocol.Request{ - Address: &common.Address{ - DomainName: "UDP_CONN", - AddressType: common.DomainName, - }, - Command: protocol.Associate, - } - - for { - outboundConn, err := n.appMan.OpenAppConn(req) - if err != nil { - log.Error(err) - time.Sleep(time.Second) - continue - } - outboundPacket, err := trojan.NewPacketSession(outboundConn) - common.Must(err) - proxy.RelayPacket(n.ctx, inboundPacket, outboundPacket) - outboundPacket.Close() - } -} - -func (n *NAT) listenTCP(errChan chan error) { - ip, err := n.config.LocalAddress.ResolveIP() - if err != nil { - errChan <- err - } - - // listen with IP_TRANSPARENT option - listener, err := gotproxy.ListenTCP("tcp", &net.TCPAddr{ - IP: ip, - Port: n.config.LocalAddress.Port, - }) - if err != nil { - errChan <- err - return - } - n.tcpListener = listener - defer listener.Close() - - for { - conn, err := n.tcpListener.Accept() - if err != nil { - select { - case <-n.ctx.Done(): - return - default: - } - errChan <- err - break - } - go n.handleConn(conn) - } -} - -func (n *NAT) Run() error { - log.Info("Trojan-Go NAT is listening on", n.config.LocalAddress) - errChan := make(chan error, 2) - go n.listenUDP(errChan) - go n.listenTCP(errChan) - select { - case err := <-errChan: - return err - case <-n.ctx.Done(): - return nil - } -} - -func (n *NAT) Close() error { - log.Info("Shutting down NAT...") - n.cancel() - if n.tcpListener != nil { - n.tcpListener.Close() - } - if n.udpListener != nil { - n.udpListener.Close() - } - return nil -} - -func (n *NAT) Build(config *conf.GlobalConfig) (common.Runnable, error) { - ctx, cancel := context.WithCancel(context.Background()) - auth, err := stat.NewAuth(ctx, "memory", config) - if err != nil { - cancel() - return nil, err - } - appMan := NewAppManager(ctx, config, auth) - - newNAT := &NAT{ - ctx: ctx, - cancel: cancel, - config: config, - auth: auth, - appMan: appMan, - } - return newNAT, nil -} - -func init() { - proxy.RegisterProxy(conf.NAT, &NAT{}) -} diff --git a/proxy/client/tls.go b/proxy/client/tls.go deleted file mode 100644 index 98172ed..0000000 --- a/proxy/client/tls.go +++ /dev/null @@ -1,458 +0,0 @@ -package client - -import ( - "crypto/tls" - "io" - "math/rand" - "net" - "sync" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/trojan" - "github.com/p4gefau1t/trojan-go/sockopt" - utls "github.com/refraction-networking/utls" - "golang.org/x/net/proxy" -) - -type TLSManager struct { - TransportManager - - fingerprints []string - workingFingerprint string - fingerprintsLock sync.Mutex - config *conf.GlobalConfig - sessionCache tls.ClientSessionCache -} - -func (m *TLSManager) genClientSpec(name string) (*utls.ClientHelloSpec, error) { - var spec *utls.ClientHelloSpec - switch name { - case "chrome": - spec = &utls.ClientHelloSpec{ - TLSVersMin: utls.VersionTLS10, - TLSVersMax: utls.VersionTLS13, - CipherSuites: []uint16{ - utls.GREASE_PLACEHOLDER, - utls.TLS_AES_128_GCM_SHA256, - utls.TLS_AES_256_GCM_SHA384, - utls.TLS_CHACHA20_POLY1305_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, - }, - CompressionMethods: []byte{ - 0x00, // compressionNone - }, - Extensions: []utls.TLSExtension{ - &utls.UtlsGREASEExtension{}, - &utls.SNIExtension{}, - &utls.UtlsExtendedMasterSecretExtension{}, - &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, - &utls.SupportedCurvesExtension{[]utls.CurveID{ - utls.CurveID(utls.GREASE_PLACEHOLDER), - utls.X25519, - utls.CurveP256, - utls.CurveP384, - }}, - &utls.SupportedPointsExtension{SupportedPoints: []byte{ - 0x00, // pointFormatUncompressed - }}, - &utls.SessionTicketExtension{}, - &utls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, - &utls.StatusRequestExtension{}, - &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ - utls.ECDSAWithP256AndSHA256, - utls.PSSWithSHA256, - utls.PKCS1WithSHA256, - utls.ECDSAWithP384AndSHA384, - utls.PSSWithSHA384, - utls.PKCS1WithSHA384, - utls.PSSWithSHA512, - utls.PKCS1WithSHA512, - utls.PKCS1WithSHA1, - }}, - &utls.SCTExtension{}, - &utls.KeyShareExtension{[]utls.KeyShare{ - {Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}}, - {Group: utls.X25519}, - }}, - &utls.PSKKeyExchangeModesExtension{[]uint8{ - utls.PskModeDHE, - }}, - &utls.SupportedVersionsExtension{[]uint16{ - utls.GREASE_PLACEHOLDER, - utls.VersionTLS13, - utls.VersionTLS12, - utls.VersionTLS11, - utls.VersionTLS10, - }}, - &utls.FakeCertCompressionAlgsExtension{[]utls.CertCompressionAlgo{ - utls.CertCompressionBrotli, - }}, - &utls.UtlsGREASEExtension{}, - &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, - }, - } - case "firefox": - spec = &utls.ClientHelloSpec{ - TLSVersMin: utls.VersionTLS10, - TLSVersMax: utls.VersionTLS13, - CipherSuites: []uint16{ - utls.TLS_AES_128_GCM_SHA256, - utls.TLS_CHACHA20_POLY1305_SHA256, - utls.TLS_AES_256_GCM_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - utls.FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - utls.FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, - }, - CompressionMethods: []byte{ - 0, //compressionNone, - }, - Extensions: []utls.TLSExtension{ - &utls.SNIExtension{}, - &utls.UtlsExtendedMasterSecretExtension{}, - &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, - &utls.SupportedCurvesExtension{[]utls.CurveID{ - utls.X25519, - utls.CurveP256, - utls.CurveP384, - utls.CurveP521, - utls.CurveID(utls.FakeFFDHE2048), - utls.CurveID(utls.FakeFFDHE3072), - }}, - &utls.SupportedPointsExtension{SupportedPoints: []byte{ - 0, //pointFormatUncompressed, - }}, - &utls.SessionTicketExtension{}, - &utls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, - &utls.StatusRequestExtension{}, - &utls.KeyShareExtension{[]utls.KeyShare{ - {Group: utls.X25519}, - {Group: utls.CurveP256}, - }}, - &utls.SupportedVersionsExtension{[]uint16{ - utls.VersionTLS13, - utls.VersionTLS12, - utls.VersionTLS11, - utls.VersionTLS10}}, - &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ - utls.ECDSAWithP256AndSHA256, - utls.ECDSAWithP384AndSHA384, - utls.ECDSAWithP521AndSHA512, - utls.PSSWithSHA256, - utls.PSSWithSHA384, - utls.PSSWithSHA512, - utls.PKCS1WithSHA256, - utls.PKCS1WithSHA384, - utls.PKCS1WithSHA512, - utls.ECDSAWithSHA1, - utls.PKCS1WithSHA1, - }}, - &utls.PSKKeyExchangeModesExtension{[]uint8{utls.PskModeDHE}}, - &utls.FakeRecordSizeLimitExtension{0x4001}, - &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, - }, - } - case "ios": - spec = &utls.ClientHelloSpec{ - TLSVersMin: utls.VersionTLS10, - TLSVersMax: utls.VersionTLS13, - CipherSuites: []uint16{ - utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - utls.DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - utls.DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - utls.TLS_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_RSA_WITH_AES_128_GCM_SHA256, - utls.DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256, - utls.TLS_RSA_WITH_AES_128_CBC_SHA256, - utls.TLS_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_AES_128_CBC_SHA, - 0xc008, - utls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - utls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, - }, - CompressionMethods: []byte{ - 0, //compressionNone, - }, - Extensions: []utls.TLSExtension{ - &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, - &utls.SNIExtension{}, - &utls.UtlsExtendedMasterSecretExtension{}, - &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ - utls.ECDSAWithP256AndSHA256, - utls.PSSWithSHA256, - utls.PKCS1WithSHA256, - utls.ECDSAWithP384AndSHA384, - utls.ECDSAWithSHA1, - utls.PSSWithSHA384, - utls.PSSWithSHA384, - utls.PKCS1WithSHA384, - utls.PSSWithSHA512, - utls.PKCS1WithSHA512, - utls.PKCS1WithSHA1, - }}, - &utls.StatusRequestExtension{}, - &utls.NPNExtension{}, - &utls.SCTExtension{}, - &utls.ALPNExtension{AlpnProtocols: []string{"h2", "h2-16", "h2-15", "h2-14", "spdy/3.1", "spdy/3", "http/1.1"}}, - &utls.SupportedPointsExtension{SupportedPoints: []byte{ - 0, //pointFormatUncompressed, - }}, - &utls.SupportedCurvesExtension{[]utls.CurveID{ - utls.X25519, - utls.CurveP256, - utls.CurveP384, - utls.CurveP521, - }}, - }, - } - } - if spec == nil { - return nil, common.NewError("Invalid fingerprint:" + name) - } - if m.config.Websocket.Enabled { - for i := range spec.Extensions { - if alpn, ok := spec.Extensions[i].(*utls.ALPNExtension); ok { - alpn.AlpnProtocols = []string{"http/1.1"} - spec.Extensions[i] = alpn - log.Debug("Force http/1.1") - } - } - } - return spec, nil -} - -func (m *TLSManager) printConnInfo(conn net.Conn) { - if m.config.LogLevel != 0 { - return - } - switch conn.(type) { - case *tls.Conn: - tlsConn := conn.(*tls.Conn) - state := tlsConn.ConnectionState() - chain := state.VerifiedChains - log.Trace("TLS handshaked", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol) - for i := range chain { - for j := range chain[i] { - log.Trace("Subject:", chain[i][j].Subject, "Issuer:", chain[i][j].Issuer) - } - } - case *utls.UConn: - tlsConn := conn.(*utls.UConn) - state := tlsConn.ConnectionState() - chain := state.VerifiedChains - log.Trace("uTLS handshaked", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol) - for i := range chain { - for j := range chain[i] { - log.Trace("Subject:", chain[i][j].Subject, "Issuer:", chain[i][j].Issuer) - } - } - default: - panic(conn) - } -} - -func (m *TLSManager) dialTCP() (net.Conn, error) { - network := "tcp" - if m.config.TCP.PreferIPV4 { - network = "tcp4" - } - if m.config.ForwardProxy.Enabled { - var auth *proxy.Auth - if m.config.ForwardProxy.Username != "" { - auth = &proxy.Auth{ - User: m.config.ForwardProxy.Username, - Password: m.config.ForwardProxy.Password, - } - } - dialer, err := proxy.SOCKS5(network, m.config.ForwardProxy.ProxyAddress.String(), auth, nil) - if err != nil { - return nil, err - } - return dialer.Dial(network, m.config.RemoteAddress.String()) - } - conn, err := net.DialTimeout(network, m.config.RemoteAddress.String(), protocol.GetRandomTimeoutDuration()) - if err != nil { - return nil, common.NewError("Failed to dial to remote server").Base(err) - } - if err := sockopt.ApplyTCPConnOption(conn.(*net.TCPConn), &m.config.TCP); err != nil { - log.Warn(common.NewError("Failed to apply tcp options").Base(err)) - } - return conn, nil -} - -func (m *TLSManager) dialTLSWithFakeFingerprint() (*utls.UConn, error) { - m.fingerprintsLock.Lock() - workingFingerprint := m.workingFingerprint - m.fingerprintsLock.Unlock() - - utlsConfig := &utls.Config{ - RootCAs: m.config.TLS.CertPool, - ServerName: m.config.TLS.SNI, - InsecureSkipVerify: !m.config.TLS.Verify, - KeyLogWriter: m.config.TLS.KeyLogger, - } - if workingFingerprint != "" { - spec, err := m.genClientSpec(workingFingerprint) - if err != nil { - return nil, err - } - tcpConn, err := m.dialTCP() - if err != nil { - return nil, err // on tcp Dial failure return with error right away - } - tlsConn := utls.UClient(tcpConn, utlsConfig, utls.HelloCustom) - if err := tlsConn.ApplyPreset(spec); err != nil { - m.fingerprintsLock.Lock() - workingFingerprint = "" - m.fingerprintsLock.Unlock() - log.Error(common.NewError("Failed to apply working fingerprint").Base(err)) - } else { - protocol.SetRandomizedTimeout(tlsConn) - err = tlsConn.Handshake() - protocol.CancelTimeout(tlsConn) - if err != nil { - log.Error("Working hello id is no longer working, err:", err) - } else { - return tlsConn, nil - } - } - } - - for _, name := range m.fingerprints { - spec, err := m.genClientSpec(name) - if err != nil { - return nil, err - } - - tcpConn, err := m.dialTCP() - if err != nil { - return nil, err // on tcp Dial failure return with error right away - } - - tlsConn := utls.UClient(tcpConn, utlsConfig, utls.HelloCustom) - - if err := tlsConn.ApplyPreset(spec); err != nil { - log.Error(common.NewError("Failed to apply fingerprint:" + name).Base(err)) - continue - } - - protocol.SetRandomizedTimeout(tlsConn) - err = tlsConn.Handshake() - protocol.CancelTimeout(tlsConn) - if err != nil { - log.Info("Handshaking with fingerprint:", name, "failed:", err) - continue // on tls Dial error keep trying - } - - log.Info("Avaliable hello id found:", name) - m.fingerprintsLock.Lock() - m.workingFingerprint = name - m.fingerprintsLock.Unlock() - return tlsConn, err - } - return nil, common.NewError("All client hello IDs tried but failed") -} - -func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) { - if m.config.TransportPlugin.Enabled { - // plain text - return m.dialTCP() - } - var transport net.Conn - if m.config.TLS.Fingerprint != "" { - // use utls fingerprints - tlsConn, err := m.dialTLSWithFakeFingerprint() - if err != nil { - return nil, err - } - m.printConnInfo(tlsConn) - transport = tlsConn - } else { - // default golang tls library - tcpConn, err := m.dialTCP() - if err != nil { - return nil, err - } - tlsConfig := &tls.Config{ - CipherSuites: m.config.TLS.CipherSuites, - RootCAs: m.config.TLS.CertPool, - ServerName: m.config.TLS.SNI, - InsecureSkipVerify: !m.config.TLS.Verify, - SessionTicketsDisabled: !m.config.TLS.SessionTicket, - CurvePreferences: m.config.TLS.CurvePreferences, - NextProtos: m.config.TLS.ALPN, - ClientSessionCache: m.sessionCache, - KeyLogWriter: m.config.TLS.KeyLogger, - } - tlsConn := tls.Client(tcpConn, tlsConfig) - err = tlsConn.Handshake() - if err != nil { - return nil, err - } - transport = tlsConn - m.printConnInfo(tlsConn) - } - if m.config.Websocket.Enabled { - ws, err := trojan.NewOutboundWebosocket(transport, m.config) - if err != nil { - transport.Close() - return nil, common.NewError("Failed to start websocket connection").Base(err) - } - return ws, nil - } - return transport, nil -} - -func NewTLSManager(config *conf.GlobalConfig) *TLSManager { - m := &TLSManager{ - config: config, - } - - if config.TLS.Fingerprint != "" { - m.fingerprints = []string{config.TLS.Fingerprint} - } - if config.TLS.Fingerprint == "auto" { - m.fingerprints = []string{"chrome", "firefox", "ios"} - rand.Shuffle(len(m.fingerprints), func(i, j int) { - m.fingerprints[i], m.fingerprints[j] = m.fingerprints[j], m.fingerprints[i] - }) - } - return m -} diff --git a/proxy/config.go b/proxy/config.go new file mode 100644 index 0000000..bbd37ac --- /dev/null +++ b/proxy/config.go @@ -0,0 +1,15 @@ +package proxy + +import "github.com/p4gefau1t/trojan-go/config" + +type Config struct { + RunType string `json:"run_type" yaml:"run-type"` + LogLevel int `json:"log_level" yaml:"log-level"` + LogFile string `json:"log_file" yaml:"log-file"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return new(Config) + }) +} diff --git a/proxy/forward/forward.go b/proxy/forward/forward.go new file mode 100644 index 0000000..409d205 --- /dev/null +++ b/proxy/forward/forward.go @@ -0,0 +1,35 @@ +package forward + +import ( + "context" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/proxy" + "github.com/p4gefau1t/trojan-go/proxy/client" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/dokodemo" +) + +const Name = "FORWARD" + +func init() { + proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { + cfg := config.FromContext(ctx, Name).(*client.Config) + serverStack := []string{dokodemo.Name} + clientStack := client.GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled) + c, err := proxy.CreateClientStack(ctx, clientStack) + if err != nil { + return nil, err + } + s, err := proxy.CreateServerStack(ctx, serverStack) + if err != nil { + return nil, err + } + return proxy.NewProxy(ctx, []tunnel.Server{s}, c), nil + }) +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return new(client.Config) + }) +} diff --git a/proxy/nat/nat.go b/proxy/nat/nat.go new file mode 100644 index 0000000..6c049ab --- /dev/null +++ b/proxy/nat/nat.go @@ -0,0 +1,35 @@ +package nat + +import ( + "context" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/proxy" + "github.com/p4gefau1t/trojan-go/proxy/client" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/tproxy" +) + +const Name = "NAT" + +func init() { + proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { + cfg := config.FromContext(ctx, Name).(*client.Config) + serverStack := []string{tproxy.Name} + clientStack := client.GenerateClientTree(cfg.Mux.Enabled, cfg.Websocket.Enabled) + c, err := proxy.CreateClientStack(ctx, clientStack) + if err != nil { + return nil, err + } + s, err := proxy.CreateServerStack(ctx, serverStack) + if err != nil { + return nil, err + } + return proxy.NewProxy(ctx, []tunnel.Server{s}, c), nil + }) +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return new(client.Config) + }) +} diff --git a/proxy/option.go b/proxy/option.go index 4efd290..77af976 100644 --- a/proxy/option.go +++ b/proxy/option.go @@ -2,66 +2,45 @@ package proxy import ( "flag" - "io/ioutil" - "os" - "os/signal" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/option" + "io/ioutil" + "strings" ) -type proxyOption struct { +type Option struct { path *string } -func (*proxyOption) Name() string { - return "proxy" +func (o *Option) Name() string { + return Name } -func (*proxyOption) Priority() int { +func (o *Option) Handle() error { + data, err := ioutil.ReadFile(*o.path) + if err != nil { + log.Fatal(err) + } + if strings.HasSuffix(*o.path, ".json") { + if err := RunProxy(data, true); err != nil { + log.Fatal(err) + } + } else if strings.HasSuffix(*o.path, ".yaml") { + if err := RunProxy(data, false); err != nil { + log.Fatal(err) + } + } else { + log.Fatal("unknown file suffix", *o.path) + } + return nil +} + +func (o *Option) Priority() int { return 0 } -func (c *proxyOption) Handle() error { - log.Info("Trojan-Go", common.Version) - log.Info("Loading config file from", *c.path) - - //exit code 23 stands for initializing error, and systemd will not trying to restart it - data, err := ioutil.ReadFile(*c.path) - if err != nil { - log.Error(common.NewError("Failed to read config file").Base(err)) - os.Exit(23) - } - config, err := conf.ParseJSON(data) - if err != nil { - log.Error(common.NewError("Failed to parse config file").Base(err)) - os.Exit(23) - } - proxy, err := NewProxy(config) - if err != nil { - log.Error(common.NewError("Failed to launch proxy").Base(err)) - os.Exit(23) - } - errChan := make(chan error) - go func() { - errChan <- proxy.Run() - }() - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, os.Interrupt) - select { - case <-sigs: - proxy.Close() - return nil - case err := <-errChan: - log.Fatal(err) - return err - } -} - func init() { - common.RegisterOptionHandler(&proxyOption{ - path: flag.String("config", common.GetProgramDir()+"/config.json", "Config filename"), + option.RegisterOptionHandler(&Option{ + path: flag.String("config", "config.json", "Trojan-Go config filename (.yaml/.json)"), }) } diff --git a/proxy/proxy.go b/proxy/proxy.go index 36256b4..2931b41 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -4,167 +4,181 @@ import ( "context" "io" "net" + "strings" "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" + "github.com/p4gefau1t/trojan-go/config" "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/router" - "github.com/p4gefau1t/trojan-go/stat" + "github.com/p4gefau1t/trojan-go/tunnel" ) -type Buildable interface { - Build(config *conf.GlobalConfig) (common.Runnable, error) +const Name = "PROXY" + +const ( + MaxPacketSize = 1024 * 8 +) + +// Proxy relay connections and packets +type Proxy struct { + sources []tunnel.Server + sink tunnel.Client + errChan chan error + ctx context.Context + cancel context.CancelFunc } -func RelayConn(ctx context.Context, a, b io.ReadWriter, bufferSize int) { - if a == nil || b == nil { - log.Debug("Empty RW") - return +func (p *Proxy) Run() error { + p.relayConnLoop() + p.relayPacketLoop() + return <-p.errChan +} + +func (p *Proxy) Close() error { + p.cancel() + for _, source := range p.sources { + source.Close() } - errChan := make(chan error, 2) - copyConn := func(dst io.Writer, src io.Reader) { - buf := make([]byte, bufferSize) - _, err := io.CopyBuffer(dst, src, buf) - errChan <- err + return p.sink.Close() +} + +func (p *Proxy) relayConnLoop() { + for _, source := range p.sources { + go func(source tunnel.Server) { + for { + inbound, err := source.AcceptConn(nil) + if err != nil { + select { + case <-p.ctx.Done(): + log.Debug("exiting") + return + default: + } + log.Error(common.NewError("failed to accept connection").Base(err)) + continue + } + go func(inbound tunnel.Conn) { + defer inbound.Close() + outbound, err := p.sink.DialConn(inbound.Metadata().Address, nil) + if err != nil { + log.Error(err) + return + } + defer outbound.Close() + log.Debug("relaying connection") + errChan := make(chan error, 2) + copyConn := func(a, b net.Conn) { + _, err := io.Copy(a, b) + errChan <- err + return + } + go copyConn(inbound, outbound) + go copyConn(outbound, inbound) + err = <-errChan + if err != nil { + log.Error(err) + } + log.Debug("connection relay ends") + }(inbound) + } + }(source) } - go copyConn(a, b) - go copyConn(b, a) - select { - case err := <-errChan: +} + +func (p *Proxy) relayPacketLoop() { + for _, source := range p.sources { + go func(source tunnel.Server) { + for { + inbound, err := source.AcceptPacket(nil) + if err != nil { + select { + case <-p.ctx.Done(): + log.Debug("exiting") + return + default: + } + log.Error(common.NewError("failed to accept packet").Base(err)) + continue + } + go func(inbound tunnel.PacketConn) { + defer inbound.Close() + outbound, err := p.sink.DialPacket(nil) + if err != nil { + log.Error(err) + return + } + defer outbound.Close() + log.Debug("relaying packets") + errChan := make(chan error, 2) + copyPacket := func(a, b tunnel.PacketConn) { + buf := make([]byte, MaxPacketSize) + n, metadata, err := a.ReadWithMetadata(buf) + if err != nil { + errChan <- err + return + } + n, err = b.WriteWithMetadata(buf[:n], metadata) + if err != nil { + errChan <- err + return + } + } + go copyPacket(inbound, outbound) + go copyPacket(outbound, inbound) + err = <-errChan + if err != nil { + log.Error(err) + } + log.Debug("packet relay ends") + }(inbound) + } + }(source) + } +} + +func NewProxy(ctx context.Context, sources []tunnel.Server, sink tunnel.Client) *Proxy { + ctx, cancel := context.WithCancel(ctx) + return &Proxy{ + sources: sources, + sink: sink, + errChan: make(chan error, 32), + ctx: ctx, + cancel: cancel, + } +} + +type Creator func(ctx context.Context) (*Proxy, error) + +var creators = make(map[string]Creator) + +func RegisterProxyCreator(name string, creator Creator) { + creators[name] = creator +} + +func RunProxy(data []byte, isJSON bool) error { + ctx := context.Background() + var err error + if isJSON { + ctx, err = config.WithJSONConfig(context.Background(), data) if err != nil { - if err == io.EOF { - return - } - log.Debug(common.NewError("Conn relaying ends").Base(err)) + return err } - case <-ctx.Done(): - return - } -} - -func RelayPacket(ctx context.Context, a, b protocol.PacketReadWriter) { - if a == nil || b == nil { - log.Debug("Empty RW") - return - } - errChan := make(chan error, 2) - copyPacket := func(dst protocol.PacketWriter, src protocol.PacketReader) { - for { - req, packet, err := src.ReadPacket() - if err != nil { - errChan <- err - return - } - _, err = dst.WritePacket(req, packet) - if err != nil { - errChan <- err - return - } + } else { + ctx, err = config.WithYAMLConfig(context.Background(), data) + if err != nil { + return err } } - go copyPacket(a, b) - go copyPacket(b, a) - select { - case err := <-errChan: - if err, ok := err.(net.Error); ok && err.Timeout() { - return - } - if err == io.EOF { - return - } - log.Error(common.NewError("Packet relaying ends").Base(err)) - case <-ctx.Done(): - return - } -} - -func RelayPacketWithRouter(ctx context.Context, from protocol.PacketReadWriter, table map[router.Policy]protocol.PacketReadWriter, router router.Router) { - errChan := make(chan error, 1+len(table)) - copyPacket := func(dst protocol.PacketWriter, src protocol.PacketReader) { - for { - req, packet, err := src.ReadPacket() - if err != nil { - errChan <- err - return - } - _, err = dst.WritePacket(req, packet) - if err != nil { - errChan <- err - return - } - } - } - copyToDst := func() { - for { - req, packet, err := from.ReadPacket() - if err != nil { - errChan <- err - return - } - policy, err := router.RouteRequest(req) - if err != nil { - errChan <- err - return - } - to, found := table[policy] - if !found { - log.Debug("Policy not found, skiped:", policy) - continue - } - log.Debug("UDP packet ", req, "routing policy:", policy) - _, err = to.WritePacket(req, packet) - if err != nil { - errChan <- err - return - } - } - } - - for _, to := range table { - go copyPacket(from, to) - } - go copyToDst() - select { - case err := <-errChan: - if err, ok := err.(net.Error); ok && err.Timeout() { - return - } - if err == io.EOF { - return - } - log.Error(common.NewError("Packet relaying with router ends").Base(err)) - case <-ctx.Done(): - return - } -} - -var proxys = make(map[conf.RunType]Buildable) - -func NewProxy(config *conf.GlobalConfig) (common.Runnable, error) { - runType := config.RunType - if buildable, found := proxys[runType]; found { - return buildable.Build(config) - } - return nil, common.NewError("Invalid run_type " + string(runType)) -} - -func RegisterProxy(t conf.RunType, b Buildable) { - proxys[t] = b -} - -type APIRunner func(context.Context, *conf.GlobalConfig, stat.Authenticator) error - -var apis = make(map[conf.RunType]APIRunner) - -func RegisterAPI(t conf.RunType, r APIRunner) { - apis[t] = r -} - -func RunAPIService(t conf.RunType, ctx context.Context, config *conf.GlobalConfig, auth stat.Authenticator) error { - r, ok := apis[t] + cfg := config.FromContext(ctx, Name).(*Config) + create, ok := creators[strings.ToUpper(cfg.RunType)] if !ok { - return common.NewError("API module for type " + string(t) + " not found") + return common.NewError("unknown type " + cfg.RunType) } - return r(ctx, config, auth) + proxy, err := create(ctx) + if err != nil { + return common.NewError("failed to create proxy instance").Base(err) + } + if err := proxy.Run(); err != nil { + log.Fatal(err) + } + return nil } diff --git a/proxy/relay/relay.go b/proxy/relay/relay.go deleted file mode 100644 index 7d12932..0000000 --- a/proxy/relay/relay.go +++ /dev/null @@ -1,69 +0,0 @@ -package relay - -import ( - "context" - "net" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/proxy" -) - -type Relay struct { - config *conf.GlobalConfig - ctx context.Context - cancel context.CancelFunc - listener net.Listener -} - -func (f *Relay) handleConn(conn net.Conn) { - defer conn.Close() - newConn, err := net.Dial("tcp", f.config.RemoteAddress.String()) - if err != nil { - log.Error("Failed to connect to remote endpoint:", err) - return - } - defer newConn.Close() - proxy.RelayConn(f.ctx, newConn, conn, f.config.BufferSize) -} - -func (f *Relay) Run() error { - log.Info("Trojan-Go relay is listening on", f.config.LocalAddress) - listener, err := net.Listen("tcp", f.config.LocalAddress.String()) - f.listener = listener - if err != nil { - return common.NewError("Failed to listen local address").Base(err) - } - defer listener.Close() - for { - conn, err := listener.Accept() - if err != nil { - select { - case <-f.ctx.Done(): - return nil - default: - } - log.Error(err) - return err - } - go f.handleConn(conn) - } -} - -func (f *Relay) Close() error { - log.Info("Shutting down relay..") - f.cancel() - f.listener.Close() - return nil -} - -func (f *Relay) Build(config *conf.GlobalConfig) (common.Runnable, error) { - f.ctx, f.cancel = context.WithCancel(context.Background()) - f.config = config - return f, nil -} - -func init() { - proxy.RegisterProxy(conf.Relay, &Relay{}) -} diff --git a/proxy/server/server.go b/proxy/server/server.go index 2eba811..b33e858 100644 --- a/proxy/server/server.go +++ b/proxy/server/server.go @@ -2,297 +2,67 @@ package server import ( "context" - "crypto/tls" - "fmt" - "net" - "strings" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/protocol/direct" - "github.com/p4gefau1t/trojan-go/protocol/simplesocks" - "github.com/p4gefau1t/trojan-go/protocol/trojan" "github.com/p4gefau1t/trojan-go/proxy" - "github.com/p4gefau1t/trojan-go/router" - "github.com/p4gefau1t/trojan-go/shadow" - "github.com/p4gefau1t/trojan-go/sockopt" - "github.com/p4gefau1t/trojan-go/stat" - "github.com/xtaci/smux" + "github.com/p4gefau1t/trojan-go/tunnel/mux" + "github.com/p4gefau1t/trojan-go/tunnel/raw" + "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" + "github.com/p4gefau1t/trojan-go/tunnel/transport" + "github.com/p4gefau1t/trojan-go/tunnel/trojan" + "github.com/p4gefau1t/trojan-go/tunnel/websocket" ) -type Server struct { - listener net.Listener - auth stat.Authenticator - config *conf.GlobalConfig - shadow *shadow.ShadowManager - router router.Router - ctx context.Context - cancel context.CancelFunc -} - -func (s *Server) handleMuxConn(stream *smux.Stream) { - inboundConn, req, err := simplesocks.NewInboundConnSession(stream) - if err != nil { - stream.Close() - log.Error(common.NewError("Failed to init inbound session").Base(err)) - return - } - defer stream.Close() - - if policy, err := s.router.RouteRequest(req); err != nil || policy == router.Block { - log.Info("[Block] conn to", req.String()) - return - } - - switch req.Command { - case protocol.Connect: - outboundConn, err := direct.NewOutboundConnSession(s.ctx, req, s.config) - if err != nil { - log.Error(err) - return - } - log.Info("Mux conn tunneling to", req.String()) - defer outboundConn.Close() - proxy.RelayConn(s.ctx, inboundConn, outboundConn, s.config.BufferSize) - case protocol.Associate: - outboundPacket, err := direct.NewOutboundPacketSession(s.ctx) - common.Must(err) - inboundPacket, err := trojan.NewPacketSession(inboundConn) - defer inboundPacket.Close() - proxy.RelayPacket(s.ctx, inboundPacket, outboundPacket) - default: - log.Error(fmt.Sprintf("Invalid command %d", req.Command)) - return - } -} - -func (s *Server) handleConn(conn net.Conn) { - protocol.SetRandomizedTimeout(conn) - inboundConn, req, err := trojan.NewInboundConnSession(s.ctx, conn, s.config, s.auth, s.shadow) - if err != nil { - //once the auth is failed, the conn will be took over by shadow manager. DO NOT close it. - log.Error(common.NewError("Failed to start inbound session, remote:" + conn.RemoteAddr().String()).Base(err)) - return - } - protocol.CancelTimeout(conn) - defer conn.Close() - - if req.Command == protocol.Mux { - smuxConfig := smux.DefaultConfig() - smuxConfig.KeepAliveDisabled = true - muxServer, err := smux.Server(inboundConn, smuxConfig) - common.Must(err) - defer muxServer.Close() - for { - stream, err := muxServer.AcceptStream() - if err != nil { - log.Error(common.NewError("Failed to accpet mux conn from " + conn.RemoteAddr().String()).Base(err)) - return - } - go s.handleMuxConn(stream) - } - } - - if policy, err := s.router.RouteRequest(req); err != nil || policy == router.Block { - log.Info("[Block] conn to", req.String()) - return - } - - if req.Command == protocol.Associate { - inboundPacket, err := trojan.NewPacketSession(inboundConn) - common.Must(err) - defer inboundPacket.Close() - - outboundPacket, err := direct.NewOutboundPacketSession(s.ctx) - if err != nil { - log.Error(err) - return - } - defer outboundPacket.Close() - log.Info("UDP tunnel established") - proxy.RelayPacket(s.ctx, inboundPacket, outboundPacket) - log.Debug("UDP tunnel closed") - return - } - - defer inboundConn.Close() - outboundConn, err := direct.NewOutboundConnSession(s.ctx, req, s.config) - if err != nil { - log.Error(err) - return - } - defer outboundConn.Close() - - log.Info("Conn from", conn.RemoteAddr(), "tunneling to", req.String()) - proxy.RelayConn(s.ctx, inboundConn, outboundConn, s.config.BufferSize) -} - -func (s *Server) ListenTCP(errChan chan error) { - log.Info("Trojan-Go server is listening on", s.config.LocalAddress) - - var listener net.Listener - listener, err := net.Listen("tcp", s.config.LocalAddress.String()) - if err != nil { - errChan <- err - return - } - s.listener = listener - defer listener.Close() - - err = sockopt.ApplyTCPListenerOption(listener.(*net.TCPListener), &s.config.TCP) - if err != nil { - errChan <- common.NewError(fmt.Sprintf("Failed to apply tcp option: %v", &s.config.TCP)).Base(err) - return - } - - for { - conn, err := listener.Accept() - if err != nil { - select { - case <-s.ctx.Done(): - return - default: - errChan <- err - return - } - } - log.Info("Conn accepted from", conn.RemoteAddr()) - go func(conn net.Conn) { - if s.config.TransportPlugin.Enabled { - s.handleConn(conn) - return - } - //using randomized timeout - protocol.SetRandomizedTimeout(conn) - - rewindConn := common.NewRewindConn(conn) - rewindConn.R.SetBufferSize(2048) - - sniVerified := true - tlsConfig := &tls.Config{ - Certificates: s.config.TLS.KeyPair, - CipherSuites: s.config.TLS.CipherSuites, - PreferServerCipherSuites: s.config.TLS.PreferServerCipher, - SessionTicketsDisabled: !s.config.TLS.SessionTicket, - NextProtos: s.config.TLS.ALPN, - KeyLogWriter: s.config.TLS.KeyLogger, - GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { - if s.config.TLS.VerifyHostName && hello.ServerName != s.config.TLS.SNI { - sniVerified = false - return nil, common.NewError("Invalid SNI: " + hello.ServerName) - } - return &s.config.TLS.KeyPair[0], nil - }, - } - tlsConn := tls.Server(rewindConn, tlsConfig) - err = tlsConn.Handshake() - rewindConn.R.StopBuffering() - protocol.CancelTimeout(conn) - - if err != nil { - if !sniVerified { - // close tls conn immediately if the sni is invalid - tlsConn.Close() - return - } else if strings.Contains(err.Error(), "first record does not look like a TLS handshake") { - rewindConn.R.Rewind() - err = common.NewError("Failed to perform TLS handshake with " + conn.RemoteAddr().String()).Base(err) - log.Warn(err) - if s.config.TLS.FallbackAddress != nil { - s.shadow.SubmitScapegoat(&shadow.Scapegoat{ - Conn: rewindConn, - ShadowAddress: s.config.TLS.FallbackAddress, - Info: err.Error(), - }) - } else if s.config.TLS.HTTPResponse != nil { - rewindConn.Write(s.config.TLS.HTTPResponse) - rewindConn.Close() - } else { - rewindConn.Close() - } - } else { - log.Error(err) - tlsConn.Close() - } - return - } - - if s.config.LogLevel == 0 { - state := tlsConn.ConnectionState() - log.Trace("TLS handshaked", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol) - } - s.handleConn(tlsConn) - }(conn) - } -} - -func (s *Server) Run() error { - errChan := make(chan error, 3) - if s.config.API.Enabled { - log.Info("API enabled") - go func() { - errChan <- proxy.RunAPIService(conf.Server, s.ctx, s.config, s.auth) - }() - } - if s.config.TransportPlugin.Enabled && s.config.TransportPlugin.Cmd != nil { - go func() { - log.Info("Initiating plugin...") - select { - case errChan <- s.config.TransportPlugin.Cmd.Run(): - case <-s.ctx.Done(): - s.config.TransportPlugin.Cmd.Process.Kill() - log.Info("Plugin killed") - } - }() - } - go s.ListenTCP(errChan) - select { - case <-s.ctx.Done(): - return nil - case err := <-errChan: - return err - } -} - -func (s *Server) Close() error { - log.Info("Shutting down server..") - s.cancel() - s.listener.Close() - return nil -} - -func (*Server) Build(config *conf.GlobalConfig) (common.Runnable, error) { - ctx, cancel := context.WithCancel(context.Background()) - var err error - authDriver := "memory" - if config.MySQL.Enabled { - authDriver = "mysql" - } else if config.Redis.Enabled { - authDriver = "redis" - } - auth, err := stat.NewAuth(ctx, authDriver, config) - if err != nil { - cancel() - return nil, err - } - router, err := router.NewRouter(&config.Router) - if err != nil { - cancel() - return nil, err - } - s := &Server{ - config: config, - ctx: ctx, - cancel: cancel, - shadow: shadow.NewShadowManager(ctx, config), - router: router, - auth: auth, - } - return s, nil -} +const Name = "SERVER" func init() { - proxy.RegisterProxy(conf.Server, &Server{}) + proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { + clientStack := []string{raw.Name} + serverTree := &proxy.Node{ + Name: transport.Name, + Next: []*proxy.Node{ + { + Name: trojan.Name, + IsEndpoint: true, + Next: []*proxy.Node{ + { + Name: mux.Name, + Next: []*proxy.Node{ + { + Name: simplesocks.Name, + }, + }, + }, + }, + }, + { + Name: websocket.Name, + Next: []*proxy.Node{ + { + Name: trojan.Name, + IsEndpoint: true, + Next: []*proxy.Node{ + { + Name: mux.Name, + Next: []*proxy.Node{ + { + Name: simplesocks.Name, + }, + }, + }, + }, + }, + }, + }, + }, + } + c, err := proxy.CreateClientStack(ctx, clientStack) + if err != nil { + return nil, err + } + s, err := proxy.CreateServersStacksTree(ctx, serverTree) + if err != nil { + return nil, err + } + return proxy.NewProxy(ctx, s, c), nil + }) + } diff --git a/proxy/stack.go b/proxy/stack.go new file mode 100644 index 0000000..69fccf0 --- /dev/null +++ b/proxy/stack.go @@ -0,0 +1,73 @@ +package proxy + +import ( + "context" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type Node struct { + Name string + Next []*Node + IsEndpoint bool + tunnel.Server +} + +func buildServerStacksTree(ctx context.Context, current *Node, parent *Node) ([]tunnel.Server, error) { + t, err := tunnel.GetTunnel(current.Name) + if err != nil { + return nil, err + } + current.Server, err = t.NewServer(ctx, parent) + if err != nil { + return nil, err + } + leaves := make([]tunnel.Server, 0) + for _, child := range current.Next { + subTreeLeaves, err := buildServerStacksTree(ctx, child, current) + if err != nil { + return nil, err + } + leaves = append(leaves, subTreeLeaves...) + } + // current node is a leave node + if len(leaves) == 0 || current.IsEndpoint { + leaves = append(leaves, current) + } + return leaves, nil +} + +func CreateServersStacksTree(ctx context.Context, root *Node) ([]tunnel.Server, error) { + return buildServerStacksTree(ctx, root, nil) +} + +// CreateClientStack create client tunnel stacks from lists +func CreateClientStack(ctx context.Context, clientStack []string) (tunnel.Client, error) { + var client tunnel.Client + for _, name := range clientStack { + t, err := tunnel.GetTunnel(name) + if err != nil { + return nil, err + } + client, err = t.NewClient(ctx, client) + if err != nil { + return nil, err + } + } + return client, nil +} + +// CreateServerStack create server tunnel stack from list +func CreateServerStack(ctx context.Context, serverStack []string) (tunnel.Server, error) { + var server tunnel.Server + for _, name := range serverStack { + t, err := tunnel.GetTunnel(name) + if err != nil { + return nil, err + } + server, err = t.NewServer(ctx, server) + if err != nil { + return nil, err + } + } + return server, nil +} diff --git a/redirector/redirector.go b/redirector/redirector.go new file mode 100644 index 0000000..6962312 --- /dev/null +++ b/redirector/redirector.go @@ -0,0 +1,77 @@ +package redirector + +import ( + "context" + "io" + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" +) + +const Name = "REDIRECTOR" + +type Dial func(net.Addr) (net.Conn, error) + +func defaultDial(addr net.Addr) (net.Conn, error) { + return net.Dial("tcp", addr.String()) +} + +type Redirection struct { + Dial + RedirectTo net.Addr + InboundConn net.Conn +} + +type Redirector struct { + ctx context.Context + redirectionChan chan *Redirection +} + +func (r *Redirector) Redirect(redirection *Redirection) { + r.redirectionChan <- redirection + log.Debug("redirect request") +} + +func (r *Redirector) worker() { + for { + select { + case redirection := <-r.redirectionChan: + handle := func(redirection *Redirection) { + defer redirection.InboundConn.Close() + if redirection.Dial == nil { + redirection.Dial = defaultDial + } + log.Warn("redirecting connection from", redirection.InboundConn.RemoteAddr(), "to", redirection.RedirectTo.String()) + outboundConn, err := redirection.Dial(redirection.RedirectTo) + if err != nil { + log.Error(common.NewError("failed to redirect to target address").Base(err)) + return + } + defer outboundConn.Close() + errChan := make(chan error, 2) + copyConn := func(a, b net.Conn) { + _, err := io.Copy(a, b) + errChan <- err + } + go copyConn(outboundConn, redirection.InboundConn) + go copyConn(redirection.InboundConn, outboundConn) + err = <-errChan + log.Info("redirection done:", err) + } + go handle(redirection) + case <-r.ctx.Done(): + log.Debug("shutting down redirector") + return + } + } +} + +func NewRedirector(ctx context.Context) *Redirector { + r := &Redirector{ + ctx: ctx, + redirectionChan: make(chan *Redirection, 64), + } + go r.worker() + return r +} diff --git a/router/mixed/geo.go b/router/mixed/geo.go deleted file mode 100644 index 9e691ed..0000000 --- a/router/mixed/geo.go +++ /dev/null @@ -1,189 +0,0 @@ -package mixed - -import ( - "net" - "regexp" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/router" - v2router "v2ray.com/core/app/router" -) - -type GeoRouter struct { - domains []*v2router.Domain - cidrs []*v2router.CIDR - matchPolicy router.Policy - nonMatchPolicy router.Policy - strategy router.Strategy -} - -func (r *GeoRouter) matchDomain(fulldomain string) bool { - for _, d := range r.domains { - switch d.GetType() { - case v2router.Domain_Domain, v2router.Domain_Full: - domain := d.GetValue() - if strings.HasSuffix(fulldomain, domain) { - idx := strings.Index(fulldomain, domain) - if idx == 0 || fulldomain[idx-1] == '.' { - log.Trace("Domain:", fulldomain, "hit domain rule:", domain) - return true - } - } - case v2router.Domain_Plain: - //keyword - if strings.Contains(fulldomain, d.GetValue()) { - log.Trace("Domain:", fulldomain, "hit keyword rule:", d.GetValue()) - return true - } - case v2router.Domain_Regex: - matched, err := regexp.Match(d.GetValue(), []byte(fulldomain)) - if err != nil { - log.Error("Invalid regex", d.GetValue()) - return false - } - if matched { - log.Trace("Domain:", fulldomain, "hit regex rule:", d.GetValue()) - return true - } - default: - log.Debug("Unknown rule type:" + d.GetType().String()) - } - } - return false -} - -func (r *GeoRouter) matchIP(ip net.IP) bool { - isIPv6 := true - len := net.IPv6len - if ip.To4() != nil { - len = net.IPv4len - isIPv6 = false - } - for _, c := range r.cidrs { - n := int(c.GetPrefix()) - mask := net.CIDRMask(n, 8*len) - cidrIP := net.IP(c.GetIp()) - if cidrIP.To4() != nil { //IPv4 CIDR - if isIPv6 { - continue - } - } else { //IPv6 CIDR - if !isIPv6 { - continue - } - } - subnet := &net.IPNet{IP: cidrIP.Mask(mask), Mask: mask} - if subnet.Contains(ip) { - return true - } - } - return false -} - -func (r *GeoRouter) routeRequestByIP(domain string) (router.Policy, error) { - addr, err := net.ResolveIPAddr("ip", domain) - if err != nil { - return router.Unknown, err - } - atype := common.IPv6 - if addr.IP.To4() != nil { - atype = common.IPv4 - } - return r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - IP: addr.IP, - AddressType: atype, - }, - }) -} - -func (r *GeoRouter) RouteRequest(req *protocol.Request) (router.Policy, error) { - switch req.AddressType { - case common.DomainName: - if r.domains == nil { - return r.nonMatchPolicy, nil - } - domain := string(req.DomainName) - if r.strategy == router.IPOnDemand { - return r.routeRequestByIP(domain) - } - if r.matchDomain(domain) { - return r.matchPolicy, nil - } - if r.strategy == router.IPIfNonMatch { - return r.routeRequestByIP(domain) - } - return r.nonMatchPolicy, nil - case common.IPv4, common.IPv6: - if r.cidrs == nil { - return r.nonMatchPolicy, nil - } - if r.matchIP(req.IP) { - return r.matchPolicy, nil - } - return r.nonMatchPolicy, nil - default: - return router.Unknown, common.NewError("invalid address type") - } -} - -func (r *GeoRouter) LoadGeoData(geoipData []byte, ipCode []string, geositeData []byte, siteCode []string) error { - geoip := new(v2router.GeoIPList) - if err := proto.Unmarshal(geoipData, geoip); err != nil { - return err - } - for _, c := range ipCode { - c = strings.ToUpper(c) - found := false - for _, e := range geoip.GetEntry() { - code := e.GetCountryCode() - if c == code { - r.cidrs = append(r.cidrs, e.GetCidr()...) - found = true - break - } - } - if found { - log.Info("GeoIP tag", c, "loaded") - } else { - log.Warn("GeoIP tag", c, "not found") - } - } - - geosite := new(v2router.GeoSiteList) - if err := proto.Unmarshal(geositeData, geosite); err != nil { - return err - } - for _, c := range siteCode { - c = strings.ToUpper(c) - found := false - for _, s := range geosite.GetEntry() { - code := s.GetCountryCode() - if c == code { - domainList := s.GetDomain() - r.domains = append(r.domains, domainList...) - found = true - break - } - } - if found { - log.Info("GeoSite tag", c, "loaded") - } else { - log.Warn("GeoSite tag", c, "not found") - } - } - return nil -} - -func NewGeoRouter(matchPolicy router.Policy, nonMatchPolicy router.Policy, strategy router.Strategy) (*GeoRouter, error) { - r := GeoRouter{ - matchPolicy: matchPolicy, - nonMatchPolicy: nonMatchPolicy, - strategy: strategy, - } - return &r, nil -} diff --git a/router/mixed/geo_test.go b/router/mixed/geo_test.go deleted file mode 100644 index 8ed01b6..0000000 --- a/router/mixed/geo_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package mixed - -import ( - "io/ioutil" - "net" - "testing" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/router" -) - -func TestGeoRouter(t *testing.T) { - r, err := NewGeoRouter(router.Bypass, router.Proxy, router.IPIfNonMatch) - common.Must(err) - geoipData, err := ioutil.ReadFile("geoip.dat") - common.Must(err) - geositeData, err := ioutil.ReadFile("geosite.dat") - common.Must(err) - common.Must(r.LoadGeoData(geoipData, []string{"CN"}, geositeData, []string{"CN"})) - - p, err := r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - DomainName: "mail.google.com", - AddressType: common.DomainName, - }, - }) - common.Must(err) - if p != router.Proxy { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - DomainName: "tupian.baidu.com", - AddressType: common.DomainName, - }, - }) - common.Must(err) - if p != router.Bypass { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - IP: net.ParseIP("8.8.8.8"), - AddressType: common.IPv4, - }, - }) - common.Must(err) - if p != router.Proxy { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - IP: net.ParseIP("114.114.114.114"), - AddressType: common.IPv4, - }, - }) - common.Must(err) - if p != router.Bypass { - t.Fatal("wrong result") - } -} diff --git a/router/mixed/list.go b/router/mixed/list.go deleted file mode 100644 index 524b384..0000000 --- a/router/mixed/list.go +++ /dev/null @@ -1,128 +0,0 @@ -package mixed - -import ( - "bytes" - "net" - "strings" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/router" -) - -type ListRouter struct { - router.Router - domainList []string - ipList []*net.IPNet - matchPolicy router.Policy - nonMatchPolicy router.Policy - strategy router.Strategy -} - -func (r *ListRouter) isSubdomain(fulldomain, domain string) bool { - if strings.HasSuffix(fulldomain, domain) { - idx := strings.Index(fulldomain, domain) - if idx == 0 || fulldomain[idx-1] == '.' { - return true - } - } - return false -} - -func (r *ListRouter) RouteRequest(req *protocol.Request) (router.Policy, error) { - switch req.AddressType { - case common.DomainName: - domain := string(req.DomainName) - if ip := net.ParseIP(domain); ip != nil { - for _, net := range r.ipList { - if net.Contains(ip) { - return r.matchPolicy, nil - } - } - return r.nonMatchPolicy, nil - } - if r.strategy == router.IPOnDemand { - addr, err := net.ResolveIPAddr("ip", domain) - if err != nil { - return router.Unknown, err - } - atype := common.IPv6 - if addr.IP.To4() != nil { - atype = common.IPv4 - } - return r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - IP: addr.IP, - AddressType: atype, - }, - }) - } - for _, d := range r.domainList { - if r.isSubdomain(domain, d) { - return r.matchPolicy, nil - } - } - if r.strategy == router.IPIfNonMatch { - addr, err := net.ResolveIPAddr("ip", domain) - if err != nil { - return router.Unknown, err - } - atype := common.IPv6 - if addr.IP.To4() != nil { - atype = common.IPv4 - } - return r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - IP: addr.IP, - AddressType: atype, - }, - }) - } - return r.nonMatchPolicy, nil - case common.IPv4, common.IPv6: - ip := req.IP - for _, ipNet := range r.ipList { - if ipNet.Contains(ip) { - return r.matchPolicy, nil - } - } - return r.nonMatchPolicy, nil - default: - return router.Unknown, common.NewError("invalid address type") - } -} - -func (r *ListRouter) LoadList(data []byte) error { - buf := bytes.NewBuffer(data) - for { - line, err := buf.ReadBytes('\n') - if err != nil { - break - } - if line[0] == '\n' || line[0] == '\r' { - continue - } - record := string(line) - record = strings.Replace(string(record), "\r\n", "", -1) - record = strings.Replace(string(record), "\n", "", -1) - _, ipNet, err := net.ParseCIDR(record) - if err != nil { - r.domainList = append(r.domainList, record) - continue - } - r.ipList = append(r.ipList, ipNet) - } - return nil -} - -func NewListRouter(matchPolicy router.Policy, nonMatchPolicy router.Policy, strategy router.Strategy, list []byte) (*ListRouter, error) { - r := ListRouter{ - matchPolicy: matchPolicy, - nonMatchPolicy: nonMatchPolicy, - strategy: strategy, - } - if err := r.LoadList(list); err != nil { - return nil, err - } - return &r, nil -} diff --git a/router/mixed/mixed.go b/router/mixed/mixed.go deleted file mode 100644 index 0e49f42..0000000 --- a/router/mixed/mixed.go +++ /dev/null @@ -1,121 +0,0 @@ -package mixed - -import ( - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/router" -) - -type MixedRouter struct { - proxyList *ListRouter - bypassList *ListRouter - blockList *ListRouter - proxyGeo *GeoRouter - bypassGeo *GeoRouter - blockGeo *GeoRouter - defaultPolicy router.Policy -} - -func (r *MixedRouter) match(rr router.Router, req *protocol.Request) bool { - policy, err := rr.RouteRequest(req) - if err != nil { - log.Warn(common.NewError("Match error").Base(err)) - return false - } - if policy == router.Match { - return true - } - return false -} - -func (r *MixedRouter) RouteRequest(req *protocol.Request) (router.Policy, error) { - if r.match(r.blockGeo, req) { - return router.Block, nil - } - if r.match(r.blockList, req) { - return router.Block, nil - } - - if r.match(r.bypassGeo, req) { - return router.Bypass, nil - } - if r.match(r.bypassList, req) { - return router.Bypass, nil - } - - if r.match(r.proxyGeo, req) { - return router.Proxy, nil - } - if r.match(r.proxyList, req) { - return router.Proxy, nil - } - - return r.defaultPolicy, nil -} - -func NewMixedRouter(config *conf.RouterConfig) (router.Router, error) { - var defaultPolicy router.Policy - - switch config.DefaultPolicy { - case "proxy": - defaultPolicy = router.Proxy - case "bypass": - defaultPolicy = router.Bypass - case "block": - defaultPolicy = router.Block - default: - return nil, common.NewError("Invalid router policy " + config.DefaultPolicy) - } - - var strategy router.Strategy - switch config.DomainStrategy { - case "as_is": - strategy = router.AsIs - case "ip_if_nonmatch": - strategy = router.IPIfNonMatch - case "ip_on_demand": - strategy = router.IPOnDemand - default: - return nil, common.NewError("Invalid domain strategy " + config.DomainStrategy) - } - - block := config.BlockList - bypass := config.BypassList - proxy := config.ProxyList - - r := &MixedRouter{ - defaultPolicy: defaultPolicy, - } - - var err error - if r.blockList, err = NewListRouter(router.Match, router.NonMatch, strategy, block); err != nil { - return nil, err - } - if r.bypassList, err = NewListRouter(router.Match, router.NonMatch, strategy, bypass); err != nil { - return nil, err - } - if r.proxyList, err = NewListRouter(router.Match, router.NonMatch, strategy, proxy); err != nil { - return nil, err - } - - r.blockGeo, _ = NewGeoRouter(router.Match, router.NonMatch, strategy) - r.bypassGeo, _ = NewGeoRouter(router.Match, router.NonMatch, strategy) - r.proxyGeo, _ = NewGeoRouter(router.Match, router.NonMatch, strategy) - - if err := r.blockGeo.LoadGeoData(config.GeoIP, config.BlockIPCode, config.GeoSite, config.BlockSiteCode); err != nil { - log.Warn(err) - } - if err := r.bypassGeo.LoadGeoData(config.GeoIP, config.BypassIPCode, config.GeoSite, config.BypassSiteCode); err != nil { - log.Warn(err) - } - if err := r.proxyGeo.LoadGeoData(config.GeoIP, config.ProxyIPCode, config.GeoSite, config.ProxySiteCode); err != nil { - log.Warn(err) - } - return r, nil -} - -func init() { - router.NewRouter = NewMixedRouter -} diff --git a/router/mixed/mixed_test.go b/router/mixed/mixed_test.go deleted file mode 100644 index f2ee2c6..0000000 --- a/router/mixed/mixed_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package mixed - -import ( - "io/ioutil" - "log" - "net" - "testing" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/protocol" - "github.com/p4gefau1t/trojan-go/router" -) - -func TestSimpleMixedRouter(t *testing.T) { - bypass := []byte("0.0.0.0/8\n10.0.0.0/8\n192.0.0.0/24\nbaidu.com\nqq.com\n") - - r, err := NewMixedRouter( - &conf.RouterConfig{ - BypassList: bypass, - DefaultPolicy: "proxy", - }, - ) - common.Must(err) - p, err := r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.IPv4, - IP: net.ParseIP("10.1.1.1"), - }, - }) - common.Must(err) - if p != router.Bypass { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.IPv4, - IP: net.ParseIP("1.1.1.1"), - }, - }) - common.Must(err) - if p != router.Proxy { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.DomainName, - DomainName: "www.baidu.com", - }, - }) - common.Must(err) - if p != router.Bypass { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.DomainName, - DomainName: "im.qq.com", - }, - }) - common.Must(err) - if p != router.Bypass { - t.Fatal("wrong result") - } - - p, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.DomainName, - DomainName: "www.google.com", - }, - }) - common.Must(err) - if p != router.Proxy { - t.Fatal("wrong result") - } -} - -func TestMixedRouter(t *testing.T) { - bypass := "" - buf, err := ioutil.ReadFile("../data/cn-domain.txt") - common.Must(err) - bypass += string(buf) - buf, err = ioutil.ReadFile("../data/cn-ip.txt") - common.Must(err) - bypass += string(buf) - - r, err := NewMixedRouter( - &conf.RouterConfig{ - BypassList: []byte(bypass), - DefaultPolicy: "proxy", - }, - ) - - policy, err := r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.DomainName, - DomainName: "baidu.com", - }, - }) - if policy != router.Bypass { - log.Fatal("wrong result") - } - - policy, err = r.RouteRequest(&protocol.Request{ - Address: &common.Address{ - AddressType: common.DomainName, - DomainName: "api.github.com", - }, - }) - if policy != router.Proxy { - log.Fatal("wrong result") - } -} diff --git a/router/router.go b/router/router.go deleted file mode 100644 index b393622..0000000 --- a/router/router.go +++ /dev/null @@ -1,41 +0,0 @@ -package router - -import ( - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/protocol" -) - -type Policy int -type Strategy int - -const ( - Proxy Policy = iota - Bypass - Block - Unknown - - Match - NonMatch -) - -const ( - AsIs Strategy = iota - IPIfNonMatch - IPOnDemand -) - -type EmptyRouter struct{} - -func (r *EmptyRouter) RouteRequest(req *protocol.Request) (Policy, error) { - return Proxy, nil -} - -func NewEmptyRouter(*conf.RouterConfig) (Router, error) { - return &EmptyRouter{}, nil -} - -type Router interface { - RouteRequest(*protocol.Request) (Policy, error) -} - -var NewRouter func(config *conf.RouterConfig) (Router, error) = NewEmptyRouter diff --git a/shadow/shadow.go b/shadow/shadow.go deleted file mode 100644 index f8a3aa7..0000000 --- a/shadow/shadow.go +++ /dev/null @@ -1,89 +0,0 @@ -package shadow - -import ( - "context" - "fmt" - "io" - "math/rand" - "net" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/proxy" -) - -type Scapegoat struct { - Conn io.ReadWriteCloser - ShadowConn io.ReadWriteCloser - ShadowAddress *common.Address - Info string -} - -type ShadowManager struct { - config *conf.GlobalConfig - ctx context.Context - scapegoatChan chan *Scapegoat -} - -func (m *ShadowManager) SubmitScapegoat(goat *Scapegoat) { - m.scapegoatChan <- goat - log.Debug("scapegoat submited") -} - -func (m *ShadowManager) handleScapegoat() { - for { - select { - case goat := <-m.scapegoatChan: - if goat.Conn == nil { - log.Error("Invalid inbound conn", goat.Conn) - return - } - if goat.Info != "" { - log.Info("Scapegoat: ", goat.Info) - } - //cancel the deadline - if conn, ok := goat.Conn.(net.Conn); ok { - conn.SetDeadline(time.Time{}) - } - - //sleep for a while to resist time-based detection - time.Sleep(time.Millisecond * time.Duration(rand.Intn(50))) - - if goat.ShadowConn == nil { - if goat.ShadowAddress == nil { - panic("incorrect shadow server") - } - var err error - goat.ShadowConn, err = net.Dial("tcp", goat.ShadowAddress.String()) - if err != nil { - log.Error(common.NewError("Failed to dial to shadow server").Base(err)) - continue - } - } - go func(goat *Scapegoat) { - if goat.Conn == nil || goat.ShadowConn == nil { - panic(fmt.Sprintf("Empty conn: %v %v", goat.Conn, goat.ShadowConn)) - } - proxy.RelayConn(m.ctx, goat.Conn, goat.ShadowConn, m.config.BufferSize) - goat.Conn.Close() - goat.ShadowConn.Close() - log.Info("Scapegoat relaying done: ", goat.Info) - }(goat) - case <-m.ctx.Done(): - log.Debug("Shadow manager exiting..") - return - } - } -} - -func NewShadowManager(ctx context.Context, config *conf.GlobalConfig) *ShadowManager { - m := &ShadowManager{ - config: config, - ctx: ctx, - scapegoatChan: make(chan *Scapegoat, 1024), - } - go m.handleScapegoat() - return m -} diff --git a/sockopt/darwin.go b/sockopt/darwin.go deleted file mode 100644 index 85cc4c2..0000000 --- a/sockopt/darwin.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build darwin - -package sockopt - -import ( - "syscall" - - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" -) - -const ( - // TCP_FASTOPEN is the socket option on darwin for TCP fast open. - TCP_FASTOPEN = 0x105 - // TCP_FASTOPEN_SERVER is the value to enable TCP fast open on darwin for server connections. - TCP_FASTOPEN_SERVER = 0x01 - // TCP_FASTOPEN_CLIENT is the value to enable TCP fast open on darwin for client connections. - TCP_FASTOPEN_CLIENT = 0x02 -) - -func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { - if config.FastOpen { - if isInbound { - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, TCP_FASTOPEN_SERVER); err != nil { - return err - } - } else { - //if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, TCP_FASTOPEN_CLIENT); err != nil { - //return err - //} - } - log.Debug("tcp fast open enabled") - } - return nil -} diff --git a/sockopt/linux.go b/sockopt/linux.go deleted file mode 100644 index ef1bc02..0000000 --- a/sockopt/linux.go +++ /dev/null @@ -1,37 +0,0 @@ -// +build linux - -package sockopt - -import ( - "syscall" - - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "golang.org/x/sys/unix" -) - -func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { - if config.ReusePort && isInbound { - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil { - return err - } - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { - return err - } - log.Debug("port reusing enabled") - } - - if config.FastOpen { - if isInbound { - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN, config.FastOpenQLen); err != nil { - return err - } - } else { - //if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN_CONNECT, 1); err != nil { - //return err - //} - } - log.Debug("tcp fast open enabled") - } - return nil -} diff --git a/sockopt/other.go b/sockopt/other.go deleted file mode 100644 index 4b3b40e..0000000 --- a/sockopt/other.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build !linux -// +build !windows -// +build !darwin - -package sockopt - -import ( - "runtime" - - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" -) - -func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { - log.Warn("TCP options is ignored in this os:", runtime.GOOS) - return nil -} diff --git a/sockopt/sockopt.go b/sockopt/sockopt.go deleted file mode 100644 index 654b95c..0000000 --- a/sockopt/sockopt.go +++ /dev/null @@ -1,35 +0,0 @@ -package sockopt - -import ( - "net" - - "github.com/p4gefau1t/trojan-go/conf" -) - -func ApplyTCPListenerOption(l *net.TCPListener, config *conf.TCPConfig) error { - rawConn, err := l.SyscallConn() - if err != nil { - return err - } - rawConn.Control(func(fd uintptr) { - err = ApplySocketOption(fd, config, true) - }) - return err -} - -func ApplyTCPConnOption(conn *net.TCPConn, config *conf.TCPConfig) error { - if err := conn.SetKeepAlive(config.KeepAlive); err != nil { - return err - } - if err := conn.SetNoDelay(config.NoDelay); err != nil { - return err - } - rawConn, err := conn.SyscallConn() - if err != nil { - return err - } - rawConn.Control(func(fd uintptr) { - err = ApplySocketOption(fd, config, false) - }) - return err -} diff --git a/sockopt/windows.go b/sockopt/windows.go deleted file mode 100644 index ba13928..0000000 --- a/sockopt/windows.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build windows - -package sockopt - -import ( - "syscall" - - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" -) - -const ( - TCP_FASTOPEN = 15 -) - -func ApplySocketOption(fd uintptr, config *conf.TCPConfig, isInbound bool) error { - if config.FastOpen { - if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, 1); err != nil { - return err - } - log.Debug("tcp fast open enabled") - } - return nil -} diff --git a/stat/memory/memory_test.go b/stat/memory/memory_test.go deleted file mode 100644 index 5b169d7..0000000 --- a/stat/memory/memory_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package memory - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" -) - -func TestMemoryAuth(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - Hash: map[string]string{ - "hash": "password", - }, - } - auth, err := NewMemoryAuth(ctx, config) - common.Must(err) - valid, user := auth.AuthUser("hash") - if !valid { - t.Fail() - } - user.AddTraffic(1234, 5678) - sent, recv := user.GetTraffic() - if sent != 1234 || recv != 5678 { - t.Fail() - } - go func() { - for i := 0; i < 100; i++ { - user.AddTraffic(500, 200) - time.Sleep(time.Millisecond * 100) - } - }() - - for i := 0; i < 15; i++ { - fmt.Println(user.GetSpeed()) - time.Sleep(time.Millisecond * 1000) - } - cancel() -} - -func TestLimitSpeed(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - Hash: map[string]string{ - "hash": "password", - }, - } - auth, err := NewMemoryAuth(ctx, config) - common.Must(err) - valid, user := auth.AuthUser("hash") - if !valid { - t.Fail() - } - user.SetSpeedLimit(5000, 6000) - go func() { - for { - user.AddTraffic(50, 0) - } - }() - go func() { - for { - user.AddTraffic(0, 100) - } - }() - for i := 0; i < 15; i++ { - fmt.Println(user.GetSpeed()) - time.Sleep(time.Millisecond * 1000) - } - cancel() -} - -func TestIPLimit(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - config := &conf.GlobalConfig{ - Hash: map[string]string{ - "hash": "password", - }, - } - auth, err := NewMemoryAuth(ctx, config) - common.Must(err) - valid, user := auth.AuthUser("hash") - if !valid { - t.Fail() - } - user.SetIPLimit(2) - ok := user.AddIP("ip1") - if !ok { - t.Fail() - } - ok = user.AddIP("ip2") - if !ok { - t.Fail() - } - ok = user.AddIP("ip3") - if ok { - t.Fail() - } - cancel() -} diff --git a/stat/mysql/mysql_test.go b/stat/mysql/mysql_test.go deleted file mode 100644 index eaf9269..0000000 --- a/stat/mysql/mysql_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package mysql - -import ( - "context" - "database/sql" - "testing" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/stat/memory" - _ "github.com/proullon/ramsql/driver" -) - -func TestDBAuth(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - db, err := sql.Open("ramsql", "TestLoadUserAddresses") - common.Must(err) - common.Must2(db.Exec(` - CREATE TABLE users ( - password CHAR(56) NOT NULL, - quota BIGINT NOT NULL DEFAULT 0, - download BIGINT NOT NULL DEFAULT 0, - upload BIGINT NOT NULL DEFAULT 0 - ); - `)) - common.Must2(db.Exec(`INSERT INTO users (password, quota, download, upload) VALUES ("hashhash", 20000, 0, 0);`)) - memoryAuth, err := memory.NewMemoryAuth(ctx, &conf.GlobalConfig{}) - auth := &MySQLAuthenticator{ - db: db, - ctx: ctx, - updateDuration: time.Second, - MemoryAuthenticator: memoryAuth.(*memory.MemoryAuthenticator), - } - go auth.updater() - time.Sleep(time.Second * 5) - valid, _ := auth.AuthUser("hashhash") - if !valid { - t.Fail() - } - time.Sleep(time.Second * 5) - valid, _ = auth.AuthUser("hashhash") - common.Must2(db.Exec(`DELETE FROM users WHERE password="hashhash"`)) - time.Sleep(time.Second * 5) - valid, _ = auth.AuthUser("hashhash") - if valid { - t.Fail() - } - cancel() -} diff --git a/stat/redis/redis.go b/stat/redis/redis.go deleted file mode 100644 index e2f7bf5..0000000 --- a/stat/redis/redis.go +++ /dev/null @@ -1,104 +0,0 @@ -package redis - -import ( - "context" - "strconv" - - "github.com/mediocregopher/radix/v3" - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/stat" -) - -type RedisUser struct { - hash string - db *radix.Pool - ctx context.Context -} - -func (m *RedisUser) Close() error { return nil } - -func (m *RedisUser) AddTraffic(sent, recv int) { - key := m.hash - evalScript := radix.NewEvalScript(1, ` - if redis.call('exists', KEYS[1]) == 1 - then - redis.call('hincrby', KEYS[1], 'upload', ARGV[1]) - redis.call('hincrby', KEYS[1], 'download', ARGV[2]) - end - `) - - if err := m.db.Do(evalScript.Cmd(nil, key, strconv.Itoa(recv), strconv.Itoa(sent))); err != nil { - log.Error(common.NewError("Failed to update data to user").Base(err)) - } -} - -// TODO implement these methods - -func (m *RedisUser) SetSpeedLimit(send, recv int) {} - -func (m *RedisUser) GetSpeedLimit() (send, recv int) { return 0, 0 } - -func (m *RedisUser) Hash() string { return m.hash } - -func (m *RedisUser) GetTraffic() (uint64, uint64) { return 0, 0 } - -func (m *RedisUser) ResetTraffic() {} - -func (m *RedisUser) GetAndResetTraffic() (uint64, uint64) { return 0, 0 } - -func (m *RedisUser) GetSpeed() (uint64, uint64) { return 0, 0 } - -func (m *RedisUser) AddIP(string) bool { return true } - -func (m *RedisUser) DelIP(string) bool { return true } - -func (u *RedisUser) GetIP() int { return 0 } - -func (m *RedisUser) SetIPLimit(int) {} - -func (m *RedisUser) GetIPLimit() int { return 0 } - -type RedisAuthenticator struct { - stat.Authenticator - db *radix.Pool - ctx context.Context -} - -func (a *RedisAuthenticator) AuthUser(hash string) (bool, stat.User) { - var exist bool - if err := a.db.Do(radix.Cmd(&exist, "EXISTS", hash)); err != nil { - log.Error(common.NewError("Failed to check user in DB").Base(err)) - } - if exist { - return true, &RedisUser{hash: hash, db: a.db, ctx: a.ctx} - } - return false, nil -} - -// TODO implement these methods - -func (a *RedisAuthenticator) AddUser(hash string) error { return nil } - -func (a *RedisAuthenticator) DelUser(hash string) error { return nil } - -func (a *RedisAuthenticator) ListUsers() []stat.User { return []stat.User{} } - -func NewRedisAuth(ctx context.Context, config *conf.GlobalConfig) (stat.Authenticator, error) { - addr := config.Redis.ServerHost + ":" + strconv.Itoa(config.Redis.ServerPort) - conn := func(network, addr string) (radix.Conn, error) { - return radix.Dial(network, addr, - radix.DialAuthPass(config.Redis.Password), - ) - } - db, err := radix.NewPool("tcp", addr, 10, radix.PoolConnFunc(conn)) - if err != nil { - return nil, common.NewError("Failed to connect to database server").Base(err) - } - return &RedisAuthenticator{db: db, ctx: ctx}, nil -} - -func init() { - stat.RegisterAuthCreator("redis", NewRedisAuth) -} diff --git a/statistic/memory/config.go b/statistic/memory/config.go new file mode 100644 index 0000000..1154b99 --- /dev/null +++ b/statistic/memory/config.go @@ -0,0 +1,15 @@ +package memory + +import ( + "github.com/p4gefau1t/trojan-go/config" +) + +type Config struct { + Passwords []string `json:"password" yaml:"password"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{} + }) +} diff --git a/stat/memory/memory.go b/statistic/memory/memory.go similarity index 68% rename from stat/memory/memory.go rename to statistic/memory/memory.go index 73a8dc6..7d24ca5 100644 --- a/stat/memory/memory.go +++ b/statistic/memory/memory.go @@ -6,13 +6,16 @@ import ( "sync/atomic" "time" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - "github.com/p4gefau1t/trojan-go/stat" + "github.com/p4gefau1t/trojan-go/statistic" "golang.org/x/time/rate" ) -type MemoryUser struct { +const Name = "MEMORY" + +type User struct { sent uint64 recv uint64 lastSent uint64 @@ -30,13 +33,13 @@ type MemoryUser struct { cancel context.CancelFunc } -func (u *MemoryUser) Close() error { +func (u *User) Close() error { u.ResetTraffic() u.cancel() return nil } -func (u *MemoryUser) AddIP(ip string) bool { +func (u *User) AddIP(ip string) bool { if u.maxIPNum <= 0 { return true } @@ -53,7 +56,7 @@ func (u *MemoryUser) AddIP(ip string) bool { return true } -func (u *MemoryUser) DelIP(ip string) bool { +func (u *User) DelIP(ip string) bool { if u.maxIPNum <= 0 { return true } @@ -67,21 +70,21 @@ func (u *MemoryUser) DelIP(ip string) bool { return true } -func (u *MemoryUser) GetIP() int { +func (u *User) GetIP() int { u.ipTableLock.Lock() defer u.ipTableLock.Unlock() return len(u.ipTable) } -func (u *MemoryUser) SetIPLimit(n int) { +func (u *User) SetIPLimit(n int) { u.maxIPNum = n } -func (u *MemoryUser) GetIPLimit() int { +func (u *User) GetIPLimit() int { return u.maxIPNum } -func (u *MemoryUser) AddTraffic(sent, recv int) { +func (u *User) AddTraffic(sent, recv int) { if u.sendLimiter != nil && sent != 0 { u.sendLimiter.WaitN(u.ctx, sent) } else if u.recvLimiter != nil && recv != 0 { @@ -91,7 +94,7 @@ func (u *MemoryUser) AddTraffic(sent, recv int) { atomic.AddUint64(&u.recv, uint64(recv)) } -func (u *MemoryUser) SetSpeedLimit(send, recv int) { +func (u *User) SetSpeedLimit(send, recv int) { if send <= 0 { u.sendLimiter = nil } else { @@ -104,7 +107,7 @@ func (u *MemoryUser) SetSpeedLimit(send, recv int) { } } -func (u *MemoryUser) GetSpeedLimit() (send, recv int) { +func (u *User) GetSpeedLimit() (send, recv int) { sendLimit := 0 recvLimit := 0 if u.sendLimiter != nil { @@ -116,22 +119,22 @@ func (u *MemoryUser) GetSpeedLimit() (send, recv int) { return sendLimit, recvLimit } -func (u *MemoryUser) Hash() string { +func (u *User) Hash() string { return u.hash } -func (u *MemoryUser) GetTraffic() (uint64, uint64) { +func (u *User) GetTraffic() (uint64, uint64) { return atomic.LoadUint64(&u.sent), atomic.LoadUint64(&u.recv) } -func (u *MemoryUser) ResetTraffic() { +func (u *User) ResetTraffic() { atomic.StoreUint64(&u.sent, 0) atomic.StoreUint64(&u.recv, 0) atomic.StoreUint64(&u.lastSent, 0) atomic.StoreUint64(&u.lastRecv, 0) } -func (u *MemoryUser) GetAndResetTraffic() (uint64, uint64) { +func (u *User) GetAndResetTraffic() (uint64, uint64) { sent := atomic.SwapUint64(&u.sent, 0) recv := atomic.SwapUint64(&u.recv, 0) atomic.StoreUint64(&u.lastSent, 0) @@ -139,7 +142,7 @@ func (u *MemoryUser) GetAndResetTraffic() (uint64, uint64) { return sent, recv } -func (u *MemoryUser) speedUpdater() { +func (u *User) speedUpdater() { for { select { case <-u.ctx.Done(): @@ -156,21 +159,20 @@ func (u *MemoryUser) speedUpdater() { } } -func (m *MemoryUser) GetSpeed() (uint64, uint64) { - m.speedLock.Lock() - defer m.speedLock.Unlock() - return m.sendSpeed, m.recvSpeed +func (u *User) GetSpeed() (uint64, uint64) { + u.speedLock.Lock() + defer u.speedLock.Unlock() + return u.sendSpeed, u.recvSpeed } -type MemoryAuthenticator struct { - stat.Authenticator +type Authenticator struct { sync.RWMutex - users map[string]*MemoryUser + users map[string]*User ctx context.Context } -func (a *MemoryAuthenticator) AuthUser(hash string) (bool, stat.User) { +func (a *Authenticator) AuthUser(hash string) (bool, statistic.User) { a.RLock() defer a.RUnlock() if user, found := a.users[hash]; found { @@ -179,14 +181,14 @@ func (a *MemoryAuthenticator) AuthUser(hash string) (bool, stat.User) { return false, nil } -func (a *MemoryAuthenticator) AddUser(hash string) error { +func (a *Authenticator) AddUser(hash string) error { a.Lock() defer a.Unlock() if _, found := a.users[hash]; found { return common.NewError("Hash " + hash + " is already exist") } ctx, cancel := context.WithCancel(a.ctx) - meter := &MemoryUser{ + meter := &User{ hash: hash, ctx: ctx, cancel: cancel, @@ -197,7 +199,7 @@ func (a *MemoryAuthenticator) AddUser(hash string) error { return nil } -func (a *MemoryAuthenticator) DelUser(hash string) error { +func (a *Authenticator) DelUser(hash string) error { a.Lock() defer a.Unlock() meter, found := a.users[hash] @@ -209,27 +211,33 @@ func (a *MemoryAuthenticator) DelUser(hash string) error { return nil } -func (a *MemoryAuthenticator) ListUsers() []stat.User { +func (a *Authenticator) ListUsers() []statistic.User { a.RLock() defer a.RUnlock() - result := make([]stat.User, 0, len(a.users)) + result := make([]statistic.User, 0, len(a.users)) for _, u := range a.users { result = append(result, u) } return result } -func NewMemoryAuth(ctx context.Context, config *conf.GlobalConfig) (stat.Authenticator, error) { - u := &MemoryAuthenticator{ +func (a *Authenticator) Close() error { + return nil +} + +func NewAuthenticator(ctx context.Context) (statistic.Authenticator, error) { + cfg := config.FromContext(ctx, Name).(*Config) + u := &Authenticator{ ctx: ctx, - users: make(map[string]*MemoryUser), + users: make(map[string]*User), } - for hash := range config.Hash { + for _, password := range cfg.Passwords { + hash := common.SHA224String(password) u.AddUser(hash) } return u, nil } func init() { - stat.RegisterAuthCreator("memory", NewMemoryAuth) + statistic.RegisterAuthenticatorCreator(Name, NewAuthenticator) } diff --git a/statistic/mysql/config.go b/statistic/mysql/config.go new file mode 100644 index 0000000..3025108 --- /dev/null +++ b/statistic/mysql/config.go @@ -0,0 +1,30 @@ +package mysql + +import ( + "github.com/p4gefau1t/trojan-go/config" +) + +type MySQLConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` + ServerHost string `json:"server_addr" yaml:"server-addr"` + ServerPort int `json:"server_port" yaml:"server-port"` + Database string `json:"database" yaml:"database"` + Username string `json:"username" yaml:"username"` + Password string `json:"password" yaml:"password"` + CheckRate int `json:"check_rate" yaml:"check-rate"` +} + +type Config struct { + MySQL MySQLConfig `json:"mysql" yaml:"mysql"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{ + MySQL: MySQLConfig{ + ServerPort: 3306, + CheckRate: 30, + }, + } + }) +} diff --git a/stat/mysql/mysql.go b/statistic/mysql/mysql.go similarity index 72% rename from stat/mysql/mysql.go rename to statistic/mysql/mysql.go index ecbd15a..10c1fcd 100644 --- a/stat/mysql/mysql.go +++ b/statistic/mysql/mysql.go @@ -7,24 +7,27 @@ import ( "strings" "time" + "github.com/p4gefau1t/trojan-go/config" + // MySQL Driver _ "github.com/go-sql-driver/mysql" "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/log" - "github.com/p4gefau1t/trojan-go/stat" - "github.com/p4gefau1t/trojan-go/stat/memory" + "github.com/p4gefau1t/trojan-go/statistic" + "github.com/p4gefau1t/trojan-go/statistic/memory" ) -type MySQLAuthenticator struct { - *memory.MemoryAuthenticator +const Name = "MYSQL" + +type Authenticator struct { + *memory.Authenticator db *sql.DB updateDuration time.Duration ctx context.Context } -func (a *MySQLAuthenticator) updater() { +func (a *Authenticator) updater() { for { for _, user := range a.ListUsers() { //swap upload and download for users @@ -80,32 +83,33 @@ func connectDatabase(driverName, username, password, ip string, port int, dbName return sql.Open(driverName, path) } -func NewMySQLAuthenticator(ctx context.Context, config *conf.GlobalConfig) (stat.Authenticator, error) { +func NewAuthenticator(ctx context.Context) (statistic.Authenticator, error) { + cfg := config.FromContext(ctx, Name).(*Config) db, err := connectDatabase( "mysql", - config.MySQL.Username, - config.MySQL.Password, - config.MySQL.ServerHost, - config.MySQL.ServerPort, - config.MySQL.Database, + cfg.MySQL.Username, + cfg.MySQL.Password, + cfg.MySQL.ServerHost, + cfg.MySQL.ServerPort, + cfg.MySQL.Database, ) if err != nil { return nil, common.NewError("Failed to connect to database server").Base(err) } - memoryAuth, err := memory.NewMemoryAuth(ctx, config) + memoryAuth, err := memory.NewAuthenticator(ctx) if err != nil { return nil, err } - a := &MySQLAuthenticator{ - db: db, - ctx: ctx, - updateDuration: time.Duration(config.MySQL.CheckRate) * time.Second, - MemoryAuthenticator: memoryAuth.(*memory.MemoryAuthenticator), + a := &Authenticator{ + db: db, + ctx: ctx, + updateDuration: time.Duration(cfg.MySQL.CheckRate) * time.Second, + Authenticator: memoryAuth.(*memory.Authenticator), } go a.updater() return a, nil } func init() { - stat.RegisterAuthCreator("mysql", NewMySQLAuthenticator) + statistic.RegisterAuthenticatorCreator(Name, NewAuthenticator) } diff --git a/stat/stat.go b/statistic/statistics.go similarity index 65% rename from stat/stat.go rename to statistic/statistics.go index 44ef20b..632063f 100644 --- a/stat/stat.go +++ b/statistic/statistics.go @@ -1,11 +1,16 @@ -package stat +package statistic import ( "context" "io" + "strings" "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" +) + +const ( + TrafficMeterKey = "TRAFFIC_METER" + AuthenticatorKey = "AUTHENTICATOR" ) type TrafficMeter interface { @@ -41,18 +46,18 @@ type Authenticator interface { ListUsers() []User } -type AuthCreator func(ctx context.Context, config *conf.GlobalConfig) (Authenticator, error) +type Creator func(ctx context.Context) (Authenticator, error) -var authCreators = map[string]AuthCreator{} +var authCreators = map[string]Creator{} -func RegisterAuthCreator(name string, creator AuthCreator) { +func RegisterAuthenticatorCreator(name string, creator Creator) { authCreators[name] = creator } -func NewAuth(ctx context.Context, name string, config *conf.GlobalConfig) (Authenticator, error) { - creator, found := authCreators[name] +func NewAuthenticator(ctx context.Context, name string) (Authenticator, error) { + creator, found := authCreators[strings.ToUpper(name)] if !found { return nil, common.NewError("Auth driver name " + name + " not found") } - return creator(ctx, config) + return creator(ctx) } diff --git a/test/api_test.go b/test/api_test.go deleted file mode 100644 index c7e4fee..0000000 --- a/test/api_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/p4gefau1t/trojan-go/api/service" - "github.com/p4gefau1t/trojan-go/common" - "golang.org/x/net/proxy" - "google.golang.org/grpc" -) - -func TestServerAPI(t *testing.T) { - serverConfig := addAPIConfig(getBasicServerConfig()) - clientConfig := getBasicClientConfig() - clientConfig.Hash = getHash("apitest") - clientConfig.Passwords = getPasswords("apitest") - - ctx, cancel := context.WithCancel(context.Background()) - - go RunBlackHoleTCPServer(ctx) - go RunServer(ctx, serverConfig) - go RunClient(ctx, clientConfig) - - time.Sleep(time.Second * 2) - grpcConn, err := grpc.Dial("127.0.0.1:10000", grpc.WithInsecure()) - common.Must(err) - server := service.NewTrojanServerServiceClient(grpcConn) - - listUserStream, err := server.ListUsers(ctx, &service.ListUsersRequest{}) - common.Must(err) - defer listUserStream.CloseSend() - for { - resp, err := listUserStream.Recv() - if err != nil { - break - } - fmt.Println(resp.User.Hash) - fmt.Println(*resp.Status.SpeedCurrent) - fmt.Println(*resp.Status.SpeedLimit) - } - listUserStream.CloseSend() - setUserStream, err := server.SetUsers(ctx) - setUserStream.Send(&service.SetUsersRequest{ - User: &service.User{ - Hash: common.SHA224String("apitest"), - }, - SpeedLimit: &service.Speed{ - UploadSpeed: 1024 * 1024 * 2, - }, - Operation: service.SetUsersRequest_Add, - }) - resp3, err := setUserStream.Recv() - if err != nil || !resp3.Success { - t.Fail() - } - setUserStream.CloseSend() - - go func() { - dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil) - common.Must(err) - conn, err := dialer.Dial("tcp", "127.0.0.1:5000") - common.Must(err) - mbytes := 16 - payload := GeneratePayload(1024 * 1024 * mbytes) - t1 := time.Now() - conn.Write(payload) - t2 := time.Now() - speed := float64(mbytes) / t2.Sub(t1).Seconds() - t.Log("single-thread link speed:", speed, "MiB/s") - conn.Close() - }() - - time.Sleep(time.Second * 5) - listUserStream, err = server.ListUsers(ctx, &service.ListUsersRequest{}) - common.Must(err) - defer listUserStream.CloseSend() - for { - resp, err := listUserStream.Recv() - if err != nil { - break - } - fmt.Println(resp.User.Hash) - fmt.Println(resp.Status.SpeedCurrent.UploadSpeed) - fmt.Println(resp.Status.SpeedLimit.UploadSpeed) - } - listUserStream.CloseSend() - cancel() -} diff --git a/test/proxy_test.go b/test/proxy_test.go deleted file mode 100644 index 20c8ce2..0000000 --- a/test/proxy_test.go +++ /dev/null @@ -1,622 +0,0 @@ -package test - -import ( - "bytes" - "context" - "crypto/md5" - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "net" - "os" - "os/exec" - "strconv" - "sync" - "testing" - "time" - - "net/http" - _ "net/http/pprof" - - _ "github.com/p4gefau1t/trojan-go/api/service" - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" - _ "github.com/p4gefau1t/trojan-go/log/golog" - "github.com/p4gefau1t/trojan-go/proxy/client" - "github.com/p4gefau1t/trojan-go/proxy/server" - - //_ "github.com/p4gefau1t/trojan-go/router/mixed" - _ "github.com/p4gefau1t/trojan-go/stat/memory" - _ "github.com/p4gefau1t/trojan-go/stat/mysql" - "golang.org/x/net/proxy" - "golang.org/x/net/websocket" -) - -var cert string = ` ------BEGIN CERTIFICATE----- -MIIDZTCCAk0CFFphZh018B5iAD9F5fV4y0AlD0LxMA0GCSqGSIb3DQEBCwUAMG8x -CzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARNYXJzMRMwEQYDVQQHDAppVHJhbnN3YXJw -MRMwEQYDVQQKDAppVHJhbnN3YXJwMRMwEQYDVQQLDAppVHJhbnN3YXJwMRIwEAYD -VQQDDAlsb2NhbGhvc3QwHhcNMjAwMzMxMTAwMDUxWhcNMzAwMzI5MTAwMDUxWjBv -MQswCQYDVQQGEwJVUzENMAsGA1UECAwETWFyczETMBEGA1UEBwwKaVRyYW5zd2Fy -cDETMBEGA1UECgwKaVRyYW5zd2FycDETMBEGA1UECwwKaVRyYW5zd2FycDESMBAG -A1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -ml44fThYMkCcT627o7ibEs7mq2WOhImjDwYijYJ1684BatrCsHJNcw8PJGTuP+tg -GdngmALjA3l+RipjaE/UK4FJrAjruphA/hOCjZfWqk8KBR4qk0OltxCMWJlp/XCM -9ny1ogFdWUlBbqThs4NWSOUESgxf/Be2njeiOrngGR31qxSiLCLBvafIhKqq/4av -Rlx0Ht770uvF97MlAj1ASAvzTZICHAfUZxEdWl0J4MBbG7SNcnMBbyAF+s60eFTa -4RGMfRGnUa2Fzz/gfjhvfSIGeLQ3JRG6sl6jkc5xe0PZzhq3UNpK0gtQ48yy9CSP -neZnrynoKks7XC2bizsr3QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAHS/xuG5+F -yGU3N6V4kv+HbKqHaXNOq4zKVsCc1k7vg4MFFpKUJKxtJYooCI8n2ypp5XRUTIGQ -bmEbVcIPqm9Rf/4vHtF0falNCwieAbXDkiEHoykRmmU1UE/ccPA7X8NO9aVLJAJO -N2Li8MH0Ixgs02pQH56eyGKoRBWPR5C3ETQ9Leqvazg6Dn1iJWvmfF0mOte5228s -mZJOntF9t8MZOJdIWGdrUHn6euRfhd0btkmL/NUDzeCTwJcuPORLxkBbCP5mTC6G -GnLS5Z4oRYgCgvT2pLtcM0r48hYjwgjXFQ4zalkW6YI9LPpqwwMhhOzINlXjBaDi -Haz8uKI4EciU ------END CERTIFICATE----- -` - -var key string = ` ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAml44fThYMkCcT627o7ibEs7mq2WOhImjDwYijYJ1684BatrC -sHJNcw8PJGTuP+tgGdngmALjA3l+RipjaE/UK4FJrAjruphA/hOCjZfWqk8KBR4q -k0OltxCMWJlp/XCM9ny1ogFdWUlBbqThs4NWSOUESgxf/Be2njeiOrngGR31qxSi -LCLBvafIhKqq/4avRlx0Ht770uvF97MlAj1ASAvzTZICHAfUZxEdWl0J4MBbG7SN -cnMBbyAF+s60eFTa4RGMfRGnUa2Fzz/gfjhvfSIGeLQ3JRG6sl6jkc5xe0PZzhq3 -UNpK0gtQ48yy9CSPneZnrynoKks7XC2bizsr3QIDAQABAoIBAFpYUo9W7qdakSFA -+NS1Mm0rkm01nteLBlfAq3BOrl030DSNm+xQuWthoOcX+yiFxVTb40qURfC+plzC -ajOepPphTJDXF7+5ZDBPktTzzLsYTzD3mstdiBtAICOqhhHCUX3hNxx91/htm1H6 -Re4eK921y3DbFUIhTswCm3vrVXDc4yTXtURGllVzo40K/1Of39CpufKFdpJ81HV+ -h/VW++h3o+sFV4KqcqIjClxBfDxoJpBaRlOCunTiHqZNvqO+EPqPR5zdn34werjU -xQEvPzmz+ClwnaEXQxYWgIcYQii9VNsHogDxEw4R31S7lVrUt0f0atDmGJip1lPb -E7IomAECgYEAzKQ3PzBV46nUNfVO9SODpf14Z+xYfLKouPC+Qnepwp0V0JS6zY1+ -Wzskyb80drjnoQraWSEvGsX+tEWeLcnjN7JuMu/U8DPKRcQ+Q2dsVo/q4sfBOgvl -VhPNMZLfa7NIkRUx2KXku++Ep0Xtak0dskrfQrZnvhymRPyWuIMM6IECgYEAwRwL -Gt/ZZdUueE/hwT3c1hNn6igeDLOwK2t6frib+Ofw5oCAQxtTROvP1ljlnWUPkeIS -uzTusmqucalcK3lCHIsyHLwApOI/B31M971pxMVBRZ0wIbBaoarCGND7gi8JUPFR -VErGcAB5YnpRlmfLPEgw2o7DpjsDc2KmdE9oNV0CgYEAmfNEWLYtNztxGTK1treD -96ELLutf2lexlIgQKgLJ5E22tpbdPXwfvdRtpZTBjDsojj+S6hCL1lFzfv0MtZe2 -5xTF0G4avKXJmti6moy4tRpJ81ehZuDCJBJ7gLrkd6qFghf2yuxqenQDUK/Lnvfq -ylGHSjHdM+lrsGRxotd8I4ECgYBoo4GA9nseqv2bQ+3YgGUBu1I7l7FwwI1decfO -ksoxfb0Tqd3WfyAH4J+mTlVdjD17lzz/JBeTpisQe+ztwa8JOIPW/ih7L/1nWYYz -V/fQH/LWfe5u0tjJcXXrbJJcYJBzw8+GFV6hoiAkNJOxJF0ENToDtAhgMuoTxAje -TYjyIQKBgQCmHkLLq0Bj3FpIOVrwo2gNvQteNPa7jkkGp4lljO8JQUHhCHDGWKEH -MUJ0EFsxS/EaQa+rW6jHhs3GyBA2TxmC783stAOOEX+hO/zpcbzdCWgp6eZ0aGMW -WS94/5WE/lwHJi8ZPSjH1AURCzXhUi4fGvBrNBtry95e+jcEvP5c0g== ------END RSA PRIVATE KEY----- -` - -func getKeyPair() []tls.Certificate { - cert, err := tls.X509KeyPair([]byte(cert), []byte(key)) - common.Must(err) - return []tls.Certificate{cert} -} - -func getTLSConfig() conf.TLSConfig { - KeyPair := getKeyPair() - pool := x509.NewCertPool() - if ok := pool.AppendCertsFromPEM([]byte(cert)); !ok { - panic("invalid cert") - } - c := conf.TLSConfig{ - SNI: "localhost", - CertPool: pool, - KeyPair: KeyPair, - Verify: true, - VerifyHostName: true, - ReuseSession: true, - SessionTicket: true, - FallbackAddress: common.NewAddress("127.0.0.1", 10080, "tcp"), - ALPN: []string{ - "http/1.1", - "h2", - }, - Fingerprint: "firefox", - } - return c -} - -func getHash(password string) map[string]string { - hash := common.SHA224String(password) - m := make(map[string]string) - m[hash] = password - return m -} - -func getPasswords(password string) []string { - return []string{password} -} - -func getBasicServerConfig() *conf.GlobalConfig { - config := &conf.GlobalConfig{ - LocalHost: "0.0.0.0", - LocalPort: 4445, - RemoteHost: "127.0.0.1", - RemotePort: 10080, - LocalAddress: common.NewAddress("0.0.0.0", 4445, "tcp"), - RemoteAddress: common.NewAddress("127.0.0.1", 10080, "tcp"), - TLS: getTLSConfig(), - Hash: getHash("trojanpassword"), - Passwords: getPasswords("trojanpassword"), - BufferSize: 512 * 1024, - } - return config -} - -func getBasicClientConfig() *conf.GlobalConfig { - config := &conf.GlobalConfig{ - LocalHost: "0.0.0.0", - LocalPort: 4444, - RemoteHost: "127.0.0.1", - RemotePort: 4445, - LocalAddress: common.NewAddress("0.0.0.0", 4444, "tcp"), - RemoteAddress: common.NewAddress("127.0.0.1", 4445, "tcp"), - TLS: getTLSConfig(), - Hash: getHash("trojanpassword"), - Passwords: getPasswords("trojanpassword"), - BufferSize: 512 * 1024, - } - file, err := os.OpenFile("keylog.txt", os.O_CREATE|os.O_WRONLY, 0600) - common.Must(err) - config.TLS.KeyLogger = file - return config -} - -func addWsConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.Websocket = conf.WebsocketConfig{ - Enabled: true, - HostName: "127.0.0.1", - Path: "/websocket", - ObfuscationPassword: "123456789", - DoubleTLS: true, - TLS: getTLSConfig(), - } - hash := md5.New() - hash.Write([]byte(config.Websocket.ObfuscationPassword)) - config.Websocket.ObfuscationKey = hash.Sum(nil) - return config -} - -func addMuxConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.Mux = conf.MuxConfig{ - Enabled: true, - Concurrency: 8, - IdleTimeout: 30, - } - return config -} - -func addRouterConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.Router = conf.RouterConfig{ - Enabled: true, - BypassList: []byte("127.0.0.1\nlocalhost"), - DefaultPolicy: "proxy", - } - return config -} - -func addTCPOption(config *conf.GlobalConfig) *conf.GlobalConfig { - config.TCP = conf.TCPConfig{ - KeepAlive: true, - FastOpen: true, - NoDelay: true, - FastOpenQLen: 5, - } - return config -} - -func addMySQLConfig(t *testing.T, config *conf.GlobalConfig) *conf.GlobalConfig { - database := os.Getenv("mysql_database") - username := os.Getenv("mysql_username") - password := os.Getenv("mysql_password") - if database == "" || username == "" || password == "" { - t.Skip("skipping mysql test") - database = "trojan" - username = "root" - password = "password" - } - config.MySQL = conf.MySQLConfig{ - Enabled: true, - ServerHost: "127.0.0.1", - ServerPort: 3306, - Database: database, - Username: username, - Password: password, - CheckRate: 1, - } - return config -} - -func addAPIConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.API = conf.APIConfig{ - Enabled: true, - APIAddress: common.NewAddress("127.0.0.1", 10000, "tcp"), - } - return config -} - -func addDNSConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.DNS = []string{ - "dot://223.5.5.5", - "8.8.8.8", - } - return config -} - -func addServerPluginConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.TransportPlugin.Enabled = true - config.TransportPlugin.Command = "v2ray-plugin" - config.TransportPlugin.Arg = []string{"-server"} - - trojanHost := "127.0.0.1" - trojanPort := common.PickPort("tcp", trojanHost) - config.TransportPlugin.Env = append( - config.TransportPlugin.Env, - "SS_REMOTE_HOST="+config.LocalHost, - "SS_REMOTE_PORT="+strconv.FormatInt(int64(config.LocalPort), 10), - "SS_LOCAL_HOST="+trojanHost, - "SS_LOCAL_PORT="+strconv.FormatInt(int64(trojanPort), 10), - ) - - config.LocalHost = trojanHost - config.LocalPort = trojanPort - config.LocalAddress = common.NewAddress(config.LocalHost, config.LocalPort, "tcp") - - cmd := exec.Command(config.TransportPlugin.Command, config.TransportPlugin.Arg...) - cmd.Env = append(cmd.Env, config.TransportPlugin.Env...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - config.TransportPlugin.Cmd = cmd - return config -} - -func addClientPluginConfig(config *conf.GlobalConfig) *conf.GlobalConfig { - config.TransportPlugin.Enabled = true - config.TransportPlugin.Command = "v2ray-plugin" - pluginHost := "127.0.0.1" - pluginPort := common.PickPort("tcp", pluginHost) - config.TransportPlugin.Env = append( - config.TransportPlugin.Env, - "SS_LOCAL_HOST="+pluginHost, - "SS_LOCAL_PORT="+strconv.FormatInt(int64(pluginPort), 10), - "SS_REMOTE_HOST="+config.RemoteHost, - "SS_REMOTE_PORT="+strconv.FormatInt(int64(config.RemotePort), 10), - ) - - config.RemoteHost = pluginHost - config.RemotePort = pluginPort - config.RemoteAddress = common.NewAddress(config.RemoteHost, config.RemotePort, "tcp") - - cmd := exec.Command(config.TransportPlugin.Command, config.TransportPlugin.Arg...) - cmd.Env = append(cmd.Env, config.TransportPlugin.Env...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - config.TransportPlugin.Cmd = cmd - return config -} - -func RunClient(ctx context.Context, config *conf.GlobalConfig) { - c := client.Client{} - r, err := c.Build(config) - common.Must(err) - go r.Run() - <-ctx.Done() - r.Close() -} - -func RunForward(ctx context.Context, config *conf.GlobalConfig) { - c := client.Forward{} - r, err := c.Build(config) - common.Must(err) - go r.Run() - <-ctx.Done() - r.Close() -} - -func RunServer(ctx context.Context, config *conf.GlobalConfig) { - s := server.Server{} - r, err := s.Build(config) - common.Must(err) - go r.Run() - <-ctx.Done() - r.Close() -} - -func CheckClientServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) { - ctx, cancel := context.WithCancel(context.Background()) - go RunEchoTCPServer(ctx) - go RunServer(ctx, serverConfig) - go RunClient(ctx, clientConfig) - - time.Sleep(time.Second) - - payloadSize := 1024 - sendBuf := GeneratePayload(payloadSize) - recvBuf := make([]byte, payloadSize) - - dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil) - common.Must(err) - conn, err := dialer.Dial("tcp", "127.0.0.1:5000") - common.Must(err) - common.Must2(conn.Write(sendBuf)) - common.Must2(conn.Read(recvBuf)) - if !bytes.Equal(sendBuf, recvBuf) { - t.Fatal("not equal") - } - conn.Close() - cancel() - time.Sleep(time.Second) -} - -func CheckForwardServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) { - time.Sleep(time.Second) - ctx, cancel := context.WithCancel(context.Background()) - clientConfig.TargetAddress = common.NewAddress("localhost", 5000, "tcp") - go RunEchoTCPServer(ctx) - go RunEchoUDPServer(ctx) - go RunServer(ctx, serverConfig) - go RunForward(ctx, clientConfig) - - time.Sleep(time.Second) - - payloadSize := 1024 - sendBuf := GeneratePayload(payloadSize) - recvBuf := make([]byte, payloadSize) - - conn, err := net.Dial("tcp", "127.0.0.1:4444") - common.Must(err) - common.Must2(conn.Write(sendBuf)) - common.Must2(conn.Read(recvBuf)) - if !bytes.Equal(sendBuf, recvBuf) { - t.Fatal("not equal") - } - conn.Close() - - conn, err = net.Dial("udp", "127.0.0.1:4444") - common.Must(err) - common.Must2(conn.Write(sendBuf)) - common.Must2(conn.Read(recvBuf)) - if !bytes.Equal(sendBuf, recvBuf) { - t.Fatal("not equal") - } - conn.Close() - cancel() -} - -func SingleThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) { - time.Sleep(time.Second) - ctx, cancel := context.WithCancel(context.Background()) - go RunBlackHoleTCPServer(ctx) - go RunServer(ctx, serverConfig) - go RunClient(ctx, clientConfig) - - time.Sleep(time.Second) - dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil) - common.Must(err) - conn, err := dialer.Dial("tcp", "127.0.0.1:5000") - common.Must(err) - mbytes := 2048 - payload := GeneratePayload(1024 * 1024 * mbytes) - t1 := time.Now() - common.Must2(conn.Write(payload)) - t2 := time.Now() - speed := float64(mbytes) / t2.Sub(t1).Seconds() - b.Log("single-thread link speed:", speed, "MiB/s") - conn.Close() - cancel() -} - -func MultiThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) { - time.Sleep(time.Second) - ctx, cancel := context.WithCancel(context.Background()) - go RunBlackHoleTCPServer(ctx) - go RunServer(ctx, serverConfig) - go RunClient(ctx, clientConfig) - - time.Sleep(time.Second) - dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil) - common.Must(err) - mbytes := 2048 - threads := 16 - payload := GeneratePayload(1024 * 1024 * mbytes / threads) - - wg := sync.WaitGroup{} - wg.Add(threads) - t1 := time.Now() - for i := 0; i < threads; i++ { - go func() { - conn, err := dialer.Dial("tcp", "127.0.0.1:5000") - common.Must(err) - common.Must2(conn.Write(payload)) - wg.Done() - conn.Close() - }() - } - wg.Wait() - t2 := time.Now() - speed := float64(mbytes) / t2.Sub(t1).Seconds() - - b.Log("multi-thread link speed:", speed, "MiB/s") - cancel() -} - -func TestNormal(t *testing.T) { - clientConfig := getBasicClientConfig() - serverConfig := getBasicServerConfig() - CheckClientServer(t, clientConfig, serverConfig) - CheckForwardServer(t, clientConfig, serverConfig) -} - -func TestMux(t *testing.T) { - clientConfig := addMuxConfig(getBasicClientConfig()) - serverConfig := getBasicServerConfig() - CheckClientServer(t, clientConfig, serverConfig) - CheckForwardServer(t, clientConfig, serverConfig) -} - -func TestWebsocket(t *testing.T) { - clientConfig := addWsConfig(getBasicClientConfig()) - serverConfig := addWsConfig(getBasicServerConfig()) - CheckClientServer(t, clientConfig, serverConfig) - CheckForwardServer(t, clientConfig, serverConfig) -} - -func TestWebsocketMux(t *testing.T) { - clientConfig := addMuxConfig(addWsConfig(getBasicClientConfig())) - serverConfig := addWsConfig(getBasicServerConfig()) - CheckClientServer(t, clientConfig, serverConfig) - CheckForwardServer(t, clientConfig, serverConfig) -} - -func BenchmarkNormal(b *testing.B) { - clientConfig := getBasicClientConfig() - serverConfig := getBasicServerConfig() - SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig) - MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig) -} - -func BenchmarkMux(b *testing.B) { - clientConfig := addMuxConfig(getBasicClientConfig()) - serverConfig := getBasicServerConfig() - SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig) - MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig) -} - -func BenchmarkWebsocket(b *testing.B) { - clientConfig := addWsConfig(getBasicClientConfig()) - serverConfig := addWsConfig(getBasicServerConfig()) - SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig) - MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig) -} - -func BenchmarkMuxWebsocket(b *testing.B) { - clientConfig := addMuxConfig(addWsConfig(getBasicClientConfig())) - serverConfig := addWsConfig(getBasicServerConfig()) - SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig) - MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig) -} - -func TestWebsocketShadow(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - go RunHelloHTTPServer(ctx) - serverConfig := addWsConfig(getBasicServerConfig()) - go RunServer(ctx, serverConfig) - time.Sleep(time.Second) - - //test http - httpClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } - resp, err := httpClient.Get("https://127.0.0.1:4445") - common.Must(err) - body, err := ioutil.ReadAll(resp.Body) - common.Must(err) - if string(body) != "HelloWorld" { - t.Fatal("http shadow") - } - - //test websocket - conn, err := tls.Dial("tcp", "127.0.0.1:4445", &tls.Config{InsecureSkipVerify: true}) - common.Must(err) - wsConfig, err := websocket.NewConfig("wss://127.0.0.1:65535/websocket", "https://127.0.0.1:65535") - common.Must(err) - wsClient, err := websocket.NewClient(wsConfig, conn) - common.Must(err) - buf := [100]byte{} - common.Must2(wsClient.Write([]byte("I'm GFW1231231231231212391273871283719823791237912398721933123"))) - n, err := wsClient.Read(buf[:]) - common.Must(err) - if string(buf[:n]) != "HelloWorld" { - t.Fatal("ws shadow") - } - conn.Close() - cancel() -} - -func TestShadow(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - go RunHelloHTTPServer(ctx) - serverConfig := getBasicServerConfig() - go RunServer(ctx, serverConfig) - time.Sleep(time.Second) - - //test http - httpClient := &http.Client{ - //some config - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } - resp, err := httpClient.Get("https://127.0.0.1:4445") - common.Must(err) - body, err := ioutil.ReadAll(resp.Body) - common.Must(err) - if string(body) != "HelloWorld" { - t.Fatal("http shadow") - } - - //fallback - resp, err = http.Get("http://127.0.0.1:4445") - common.Must(err) - body, err = ioutil.ReadAll(resp.Body) - common.Must(err) - if string(body) != "HelloWorld" { - t.Fatal("http shadow") - } - cancel() -} - -func TestAutoClientID(t *testing.T) { - serverConfig := getBasicServerConfig() - clientConfig := getBasicClientConfig() - clientConfig.TLS.Fingerprint = "auto" - CheckClientServer(t, clientConfig, serverConfig) -} - -func TestTCPOptions(t *testing.T) { - serverConfig := addTCPOption(getBasicServerConfig()) - clientConfig := addTCPOption(getBasicClientConfig()) - CheckClientServer(t, clientConfig, serverConfig) -} - -func TestMySQL(t *testing.T) { - serverConfig := addMySQLConfig(t, getBasicServerConfig()) - clientConfig := getBasicClientConfig() - clientConfig.Passwords = getPasswords("mysqlpassword") - clientConfig.Hash = getHash("mysqlpassword") - CheckClientServer(t, clientConfig, serverConfig) -} - -func TestDNS(t *testing.T) { - serverConfig := addDNSConfig(getBasicServerConfig()) - clientConfig := getBasicClientConfig() - ctx, cancel := context.WithCancel(context.Background()) - go RunServer(ctx, serverConfig) - go RunClient(ctx, clientConfig) - time.Sleep(time.Second) - dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil) - common.Must(err) - conn, err := dialer.Dial("tcp", "www.baidu.com:80") - common.Must(err) - httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) - common.Must(err) - httpReq.Write(conn) - buf := [1024]byte{} - common.Must2(conn.Read(buf[:])) - fmt.Println(string(buf[:])) - conn.Close() - cancel() -} - -func TestPlugin(t *testing.T) { - serverConfig := addServerPluginConfig(getBasicServerConfig()) - clientConfig := addClientPluginConfig(getBasicClientConfig()) - CheckClientServer(t, clientConfig, serverConfig) -} diff --git a/test/real_test.go b/test/real_test.go deleted file mode 100644 index 69f4f33..0000000 --- a/test/real_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package test - -import ( - "context" - "io/ioutil" - "os" - "testing" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/conf" -) - -func TestRealProxy(t *testing.T) { - if os.Getenv("real_test") == "" { - t.Skip("skipping real proxy test") - } - clientConfig := addMuxConfig(getBasicClientConfig()) - serverConfig := getBasicServerConfig() - go RunClient(context.Background(), clientConfig) - go RunHelloHTTPServer(context.Background()) - RunServer(context.Background(), serverConfig) -} - -func TestRealClient(t *testing.T) { - if os.Getenv("real_test") == "" { - t.Skip("skipping real proxy test") - } - b, err := ioutil.ReadFile("client.json") - common.Must(err) - config, err := conf.ParseJSON(b) - common.Must(err) - RunClient(context.Background(), config) -} - -func TestRealServer(t *testing.T) { - if os.Getenv("real_test") == "" { - t.Skip("skipping real proxy test") - } - b, err := ioutil.ReadFile("server.json") - common.Must(err) - config, err := conf.ParseJSON(b) - common.Must(err) - RunServer(context.Background(), config) -} diff --git a/test/senario/proxy_test.go b/test/senario/proxy_test.go new file mode 100644 index 0000000..c31e80c --- /dev/null +++ b/test/senario/proxy_test.go @@ -0,0 +1,116 @@ +package senario_test + +import ( + "io/ioutil" + "testing" + + "github.com/p4gefau1t/trojan-go/common" + _ "github.com/p4gefau1t/trojan-go/log/golog" + "github.com/p4gefau1t/trojan-go/proxy" + _ "github.com/p4gefau1t/trojan-go/proxy/client" + _ "github.com/p4gefau1t/trojan-go/proxy/server" + _ "github.com/p4gefau1t/trojan-go/statistic/memory" +) + +var cert string = ` +-----BEGIN CERTIFICATE----- +MIIDZTCCAk0CFFphZh018B5iAD9F5fV4y0AlD0LxMA0GCSqGSIb3DQEBCwUAMG8x +CzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARNYXJzMRMwEQYDVQQHDAppVHJhbnN3YXJw +MRMwEQYDVQQKDAppVHJhbnN3YXJwMRMwEQYDVQQLDAppVHJhbnN3YXJwMRIwEAYD +VQQDDAlsb2NhbGhvc3QwHhcNMjAwMzMxMTAwMDUxWhcNMzAwMzI5MTAwMDUxWjBv +MQswCQYDVQQGEwJVUzENMAsGA1UECAwETWFyczETMBEGA1UEBwwKaVRyYW5zd2Fy +cDETMBEGA1UECgwKaVRyYW5zd2FycDETMBEGA1UECwwKaVRyYW5zd2FycDESMBAG +A1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ml44fThYMkCcT627o7ibEs7mq2WOhImjDwYijYJ1684BatrCsHJNcw8PJGTuP+tg +GdngmALjA3l+RipjaE/UK4FJrAjruphA/hOCjZfWqk8KBR4qk0OltxCMWJlp/XCM +9ny1ogFdWUlBbqThs4NWSOUESgxf/Be2njeiOrngGR31qxSiLCLBvafIhKqq/4av +Rlx0Ht770uvF97MlAj1ASAvzTZICHAfUZxEdWl0J4MBbG7SNcnMBbyAF+s60eFTa +4RGMfRGnUa2Fzz/gfjhvfSIGeLQ3JRG6sl6jkc5xe0PZzhq3UNpK0gtQ48yy9CSP +neZnrynoKks7XC2bizsr3QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAHS/xuG5+F +yGU3N6V4kv+HbKqHaXNOq4zKVsCc1k7vg4MFFpKUJKxtJYooCI8n2ypp5XRUTIGQ +bmEbVcIPqm9Rf/4vHtF0falNCwieAbXDkiEHoykRmmU1UE/ccPA7X8NO9aVLJAJO +N2Li8MH0Ixgs02pQH56eyGKoRBWPR5C3ETQ9Leqvazg6Dn1iJWvmfF0mOte5228s +mZJOntF9t8MZOJdIWGdrUHn6euRfhd0btkmL/NUDzeCTwJcuPORLxkBbCP5mTC6G +GnLS5Z4oRYgCgvT2pLtcM0r48hYjwgjXFQ4zalkW6YI9LPpqwwMhhOzINlXjBaDi +Haz8uKI4EciU +-----END CERTIFICATE----- +` + +var key string = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAml44fThYMkCcT627o7ibEs7mq2WOhImjDwYijYJ1684BatrC +sHJNcw8PJGTuP+tgGdngmALjA3l+RipjaE/UK4FJrAjruphA/hOCjZfWqk8KBR4q +k0OltxCMWJlp/XCM9ny1ogFdWUlBbqThs4NWSOUESgxf/Be2njeiOrngGR31qxSi +LCLBvafIhKqq/4avRlx0Ht770uvF97MlAj1ASAvzTZICHAfUZxEdWl0J4MBbG7SN +cnMBbyAF+s60eFTa4RGMfRGnUa2Fzz/gfjhvfSIGeLQ3JRG6sl6jkc5xe0PZzhq3 +UNpK0gtQ48yy9CSPneZnrynoKks7XC2bizsr3QIDAQABAoIBAFpYUo9W7qdakSFA ++NS1Mm0rkm01nteLBlfAq3BOrl030DSNm+xQuWthoOcX+yiFxVTb40qURfC+plzC +ajOepPphTJDXF7+5ZDBPktTzzLsYTzD3mstdiBtAICOqhhHCUX3hNxx91/htm1H6 +Re4eK921y3DbFUIhTswCm3vrVXDc4yTXtURGllVzo40K/1Of39CpufKFdpJ81HV+ +h/VW++h3o+sFV4KqcqIjClxBfDxoJpBaRlOCunTiHqZNvqO+EPqPR5zdn34werjU +xQEvPzmz+ClwnaEXQxYWgIcYQii9VNsHogDxEw4R31S7lVrUt0f0atDmGJip1lPb +E7IomAECgYEAzKQ3PzBV46nUNfVO9SODpf14Z+xYfLKouPC+Qnepwp0V0JS6zY1+ +Wzskyb80drjnoQraWSEvGsX+tEWeLcnjN7JuMu/U8DPKRcQ+Q2dsVo/q4sfBOgvl +VhPNMZLfa7NIkRUx2KXku++Ep0Xtak0dskrfQrZnvhymRPyWuIMM6IECgYEAwRwL +Gt/ZZdUueE/hwT3c1hNn6igeDLOwK2t6frib+Ofw5oCAQxtTROvP1ljlnWUPkeIS +uzTusmqucalcK3lCHIsyHLwApOI/B31M971pxMVBRZ0wIbBaoarCGND7gi8JUPFR +VErGcAB5YnpRlmfLPEgw2o7DpjsDc2KmdE9oNV0CgYEAmfNEWLYtNztxGTK1treD +96ELLutf2lexlIgQKgLJ5E22tpbdPXwfvdRtpZTBjDsojj+S6hCL1lFzfv0MtZe2 +5xTF0G4avKXJmti6moy4tRpJ81ehZuDCJBJ7gLrkd6qFghf2yuxqenQDUK/Lnvfq +ylGHSjHdM+lrsGRxotd8I4ECgYBoo4GA9nseqv2bQ+3YgGUBu1I7l7FwwI1decfO +ksoxfb0Tqd3WfyAH4J+mTlVdjD17lzz/JBeTpisQe+ztwa8JOIPW/ih7L/1nWYYz +V/fQH/LWfe5u0tjJcXXrbJJcYJBzw8+GFV6hoiAkNJOxJF0ENToDtAhgMuoTxAje +TYjyIQKBgQCmHkLLq0Bj3FpIOVrwo2gNvQteNPa7jkkGp4lljO8JQUHhCHDGWKEH +MUJ0EFsxS/EaQa+rW6jHhs3GyBA2TxmC783stAOOEX+hO/zpcbzdCWgp6eZ0aGMW +WS94/5WE/lwHJi8ZPSjH1AURCzXhUi4fGvBrNBtry95e+jcEvP5c0g== +-----END RSA PRIVATE KEY----- +` + +func init() { + ioutil.WriteFile("server.crt", []byte(cert), 0777) + ioutil.WriteFile("server.key", []byte(key), 0777) +} + +func TestProxy(t *testing.T) { + clientData := ` +run-type: client +local-addr: 127.0.0.1 +local-port: 4444 +local-addr: 127.0.0.1 +remote-port: 4443 +password: + - password +ssl: + verify: false +websocket: + enabled: true + path: /ws + hostname: 127.0.0.1 +mux: + enabled: true +` + go func() { + err := proxy.RunProxy([]byte(clientData), false) + common.Must(err) + }() + + serverData := ` +run-type: server +local-addr: 127.0.0.1 +local-port: 4443 +remote-addr: 127.0.0.1 +remote-port: 80 +password: + - password +ssl: + verify: false + key: server.key + cert: server.crt +websocket: + enabled: true + path: /ws + hostname: 127.0.0.1 +` + err := proxy.RunProxy([]byte(serverData), false) + common.Must(err) +} diff --git a/test/target.go b/test/target.go deleted file mode 100644 index 9be3865..0000000 --- a/test/target.go +++ /dev/null @@ -1,147 +0,0 @@ -package test - -import ( - "context" - "crypto/rand" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "time" - - "github.com/p4gefau1t/trojan-go/common" - "github.com/p4gefau1t/trojan-go/log" - "golang.org/x/net/websocket" -) - -func RunEchoUDPServer(ctx context.Context) { - conn, err := net.ListenUDP("udp", &net.UDPAddr{ - IP: net.ParseIP("0.0.0.0"), - Port: 5000, - }) - common.Must(err) - defer conn.Close() - go func() { - for { - buf := make([]byte, 2048) - n, addr, err := conn.ReadFromUDP(buf[:]) - if err != nil { - return - } - log.Info("Echo from", addr) - conn.WriteToUDP(buf[0:n], addr) - } - }() - <-ctx.Done() -} - -func RunMultipleUDPEchoServer(ctx context.Context) { - for i := 0; i < 10; i++ { - go func(port int) { - conn, err := net.ListenUDP("udp", &net.UDPAddr{ - IP: net.ParseIP("0.0.0.0"), - Port: port, - }) - common.Must(err) - fmt.Println("udp echo:", conn.LocalAddr()) - defer conn.Close() - go func() { - for { - buf := make([]byte, 2048) - n, addr, err := conn.ReadFromUDP(buf[:]) - if err != nil { - return - } - log.Info("Echo from", addr) - conn.WriteToUDP(buf[0:n], addr) - } - }() - <-ctx.Done() - }(6000 + i) - } - <-ctx.Done() -} - -func RunEchoTCPServer(ctx context.Context) { - listener, err := net.Listen("tcp", "0.0.0.0:5000") - common.Must(err) - defer listener.Close() - go func() { - for { - conn, err := listener.Accept() - if err != nil { - return - } - go func(conn net.Conn) { - for { - conn.SetDeadline(time.Now().Add(time.Second)) - buf := make([]byte, 2048) - n, err := conn.Read(buf) - if err != nil { - return - } - _, err = conn.Write(buf[0:n]) - if err != nil { - return - } - } - }(conn) - } - }() - <-ctx.Done() -} - -func RunBlackHoleTCPServer(ctx context.Context) { - listener, err := net.Listen("tcp", "0.0.0.0:5000") - common.Must(err) - go func() { - for { - conn, err := listener.Accept() - if err != nil { - return - } - go func(conn net.Conn) { - io.Copy(ioutil.Discard, conn) - conn.Close() - }(conn) - } - }() - <-ctx.Done() - listener.Close() -} - -func RunHelloHTTPServer(ctx context.Context) { - httpHello := func(w http.ResponseWriter, req *http.Request) { - w.Write([]byte("HelloWorld")) - } - - wsConfig, err := websocket.NewConfig("wss://127.0.0.1/websocket", "https://127.0.0.1") - common.Must(err) - wsServer := websocket.Server{ - Config: *wsConfig, - Handler: func(conn *websocket.Conn) { - conn.Write([]byte("HelloWorld")) - }, - Handshake: func(wsConfig *websocket.Config, httpRequest *http.Request) error { - log.Debug("websocket url", httpRequest.URL, "origin", httpRequest.Header.Get("Origin")) - return nil - }, - } - mux := &http.ServeMux{} - mux.HandleFunc("/", httpHello) - mux.HandleFunc("/websocket", wsServer.ServeHTTP) - server := http.Server{ - Addr: "127.0.0.1:10080", - Handler: mux, - } - go server.ListenAndServe() - <-ctx.Done() - server.Close() -} - -func GeneratePayload(length int) []byte { - buf := make([]byte, length) - io.ReadFull(rand.Reader, buf) - return buf -} diff --git a/test/util/util.go b/test/util/util.go new file mode 100644 index 0000000..fac6fa7 --- /dev/null +++ b/test/util/util.go @@ -0,0 +1,102 @@ +package util + +import ( + "bytes" + "crypto/rand" + "fmt" + "net" + "sync" + + "github.com/p4gefau1t/trojan-go/common" +) + +// CheckConn checks if two netConn were connected and work properly +func CheckConn(a net.Conn, b net.Conn) bool { + payload1 := [1024]byte{} + payload2 := [1024]byte{} + rand.Reader.Read(payload1[:]) + rand.Reader.Read(payload2[:]) + + result1 := [1024]byte{} + result2 := [1024]byte{} + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + a.Write(payload1[:]) + a.Read(result2[:]) + wg.Done() + }() + go func() { + b.Read(result1[:]) + b.Write(payload2[:]) + wg.Done() + }() + wg.Wait() + if !bytes.Equal(payload1[:], result1[:]) || !bytes.Equal(payload2[:], result2[:]) { + return false + } + return true +} + +// CheckPacketOverConn checks if two PacketConn streaming over a connection work properly +func CheckPacketOverConn(a, b net.PacketConn) bool { + port := common.PickPort("tcp", "127.0.0.1") + addr := &net.UDPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: port, + } + payload1 := [1024]byte{} + payload2 := [1024]byte{} + rand.Reader.Read(payload1[:]) + rand.Reader.Read(payload2[:]) + + result1 := [1024]byte{} + result2 := [1024]byte{} + + common.Must2(a.WriteTo(payload1[:], addr)) + _, addr1, err := b.ReadFrom(result1[:]) + common.Must(err) + if addr1.String() != addr.String() { + return false + } + + common.Must2(a.WriteTo(payload2[:], addr)) + _, addr2, err := b.ReadFrom(result2[:]) + common.Must(err) + if addr2.String() != addr.String() { + return false + } + if !bytes.Equal(payload1[:], result1[:]) || !bytes.Equal(payload2[:], result2[:]) { + return false + } + return true +} + +func CheckPacket(a, b net.PacketConn) bool { + payload1 := [1024]byte{} + payload2 := [1024]byte{} + rand.Reader.Read(payload1[:]) + rand.Reader.Read(payload2[:]) + + result1 := [1024]byte{} + result2 := [1024]byte{} + + _, err := a.WriteTo(payload1[:], b.LocalAddr()) + common.Must(err) + _, _, err = b.ReadFrom(result1[:]) + common.Must(err) + + _, err = b.WriteTo(payload2[:], a.LocalAddr()) + common.Must(err) + _, _, err = a.ReadFrom(result2[:]) + common.Must(err) + if !bytes.Equal(payload1[:], result1[:]) || !bytes.Equal(payload2[:], result2[:]) { + return false + } + return true +} + +func FindAvaliableAddr() string { + port := common.PickPort("tcp", "127.0.0.1") + return fmt.Sprintf("127.0.0.1:%d", port) +} diff --git a/tunnel/dokodemo/config.go b/tunnel/dokodemo/config.go new file mode 100644 index 0000000..1ba764c --- /dev/null +++ b/tunnel/dokodemo/config.go @@ -0,0 +1,19 @@ +package dokodemo + +import "github.com/p4gefau1t/trojan-go/config" + +type Config struct { + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` + TargetHost string `json:"target_addr" yaml:"target-addr"` + TargetPort int `json:"target_port" yaml:"target-port"` + UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{ + UDPTimeout: 30, + } + }) +} diff --git a/tunnel/dokodemo/conn.go b/tunnel/dokodemo/conn.go new file mode 100644 index 0000000..c01605f --- /dev/null +++ b/tunnel/dokodemo/conn.go @@ -0,0 +1,70 @@ +package dokodemo + +import ( + "context" + "github.com/p4gefau1t/trojan-go/tunnel" + "io" + "net" +) + +const MaxPacketSize = 1024 * 8 + +type Conn struct { + net.Conn + src *tunnel.Address + targetMetadata *tunnel.Metadata +} + +func (c *Conn) Metadata() *tunnel.Metadata { + return c.targetMetadata +} + +// PacketConn receive packet info from the packet dispatcher +// TODO implement net.PacketConn +type PacketConn struct { + net.PacketConn + M *tunnel.Metadata //fixed + Input chan []byte + Output chan []byte + Source net.Addr + context.Context + context.CancelFunc +} + +func (c *PacketConn) Close() error { + c.CancelFunc() + return nil +} + +func (c *PacketConn) ReadFrom(p []byte) (int, net.Addr, error) { + return c.ReadWithMetadata(p) +} + +func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { + address, err := tunnel.NewAddressFromAddr("udp", addr.String()) + if err != nil { + return 0, err + } + return c.WriteWithMetadata(p, &tunnel.Metadata{ + Address: address, + }) +} + +func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { + select { + case payload := <-c.Input: + n := copy(p, payload) + return n, c.M, nil + case <-c.Done(): + return 0, nil, io.EOF + } +} + +func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { + select { + case c.Output <- p: + case <-c.Done(): + return 0, io.EOF + } + return len(p), nil +} diff --git a/tunnel/dokodemo/dokodemo_test.go b/tunnel/dokodemo/dokodemo_test.go new file mode 100644 index 0000000..cf88808 --- /dev/null +++ b/tunnel/dokodemo/dokodemo_test.go @@ -0,0 +1,81 @@ +package dokodemo + +import ( + "context" + "fmt" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/test/util" + "net" + "sync" + "testing" +) + +func TestDokodemo(t *testing.T) { + cfg := &Config{ + LocalHost: "127.0.0.1", + LocalPort: common.PickPort("tcp", "127.0.0.1"), + TargetHost: "127.0.0.1", + TargetPort: common.PickPort("tcp", "127.0.0.1"), + UDPTimeout: 30, + } + ctx := config.WithConfig(context.Background(), Name, cfg) + s, err := NewServer(ctx, nil) + common.Must(err) + conn1, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", cfg.LocalPort)) + common.Must(err) + conn2, err := s.AcceptConn(nil) + common.Must(err) + if !util.CheckConn(conn1, conn2) { + t.Fail() + } + conn1.Close() + conn2.Close() + + wg := sync.WaitGroup{} + wg.Add(1) + + packet1, err := net.ListenPacket("udp", "") + common.Must(err) + common.Must2(packet1.(*net.UDPConn).WriteToUDP([]byte("hello1"), &net.UDPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: cfg.LocalPort, + })) + packet2, err := s.AcceptPacket(nil) + buf := [100]byte{} + n, m, err := packet2.ReadWithMetadata(buf[:]) + if m.Address.Port != cfg.TargetPort { + t.Fail() + } + if string(buf[:n]) != "hello1" { + t.Fail() + } + fmt.Println(n, m, string(buf[:n])) + + if !util.CheckPacket(packet1, packet2) { + t.Fail() + } + + packet3, err := net.ListenPacket("udp", "") + common.Must(err) + common.Must2(packet3.(*net.UDPConn).WriteToUDP([]byte("hello2"), &net.UDPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: cfg.LocalPort, + })) + packet4, err := s.AcceptPacket(nil) + n, m, err = packet4.ReadWithMetadata(buf[:]) + if m.Address.Port != cfg.TargetPort { + t.Fail() + } + if string(buf[:n]) != "hello2" { + t.Fail() + } + fmt.Println(n, m, string(buf[:n])) + + if !util.CheckPacket(packet3, packet4) { + t.Fail() + } + if !util.CheckPacket(packet1, packet2) { + t.Fail() + } +} diff --git a/tunnel/dokodemo/server.go b/tunnel/dokodemo/server.go new file mode 100644 index 0000000..2e6472d --- /dev/null +++ b/tunnel/dokodemo/server.go @@ -0,0 +1,144 @@ +package dokodemo + +import ( + "context" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" + "io" + "net" + "sync" + "time" +) + +type Server struct { + tunnel.Server + tcpListener net.Listener + udpListener net.PacketConn + packetChan chan tunnel.PacketConn + timeout time.Duration + targetAddr *tunnel.Address + mappingLock sync.Mutex + mapping map[string]*PacketConn + ctx context.Context + cancel context.CancelFunc +} + +func (s *Server) dispatchLoop() { + fixedMetadata := &tunnel.Metadata{ + Address: s.targetAddr, + } + for { + buf := make([]byte, MaxPacketSize) + n, addr, err := s.udpListener.ReadFrom(buf) + if err != nil { + s.cancel() + log.Debug(common.NewError("dokodemo udp read error, closing").Base(err)) + return + } + log.Debug("udp packet from", addr) + s.mappingLock.Lock() + if conn, found := s.mapping[addr.String()]; found { + conn.Input <- buf[:n] + s.mappingLock.Unlock() + continue + } + ctx, cancel := context.WithCancel(s.ctx) + conn := &PacketConn{ + Input: make(chan []byte, 16), + Output: make(chan []byte, 16), + M: fixedMetadata, + Source: addr, + PacketConn: s.udpListener, + Context: ctx, + CancelFunc: cancel, + } + s.mapping[addr.String()] = conn + s.mappingLock.Unlock() + + conn.Input <- buf[:n] + s.packetChan <- conn + + go func(conn *PacketConn) { + for { + select { + case payload := <-conn.Output: + // "Multiple goroutines may invoke methods on a Conn simultaneously." + _, err := s.udpListener.WriteTo(payload, conn.Source) + if err != nil { + log.Error(common.NewError("dokodemo udp write error").Base(err)) + return + } + case <-s.ctx.Done(): + return + case <-time.After(s.timeout): + s.mappingLock.Lock() + delete(s.mapping, conn.Source.String()) + s.mappingLock.Unlock() + conn.Close() + log.Debug("closing timeout packetConn") + return + } + } + }(conn) + } +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := s.tcpListener.Accept() + if err != nil { + return nil, err + } + return &Conn{ + Conn: conn, + targetMetadata: &tunnel.Metadata{ + Address: s.targetAddr, + }, + }, nil +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + select { + case conn := <-s.packetChan: + return conn, nil + case <-s.ctx.Done(): + return nil, io.EOF + } +} + +func (s *Server) Close() error { + s.cancel() + s.tcpListener.Close() + s.udpListener.Close() + return nil +} + +func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) { + cfg := config.FromContext(ctx, Name).(*Config) + targetAddr := tunnel.NewAddressFromHostPort("tcp", cfg.TargetHost, cfg.TargetPort) + listenAddr := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort) + + tcpListener, err := net.Listen("tcp", listenAddr.String()) + if err != nil { + return nil, common.NewError("failed to listen tcp").Base(err) + } + udpListener, err := net.ListenPacket("udp", listenAddr.String()) + if err != nil { + return nil, common.NewError("failed to listen udp").Base(err) + } + + ctx, cancel := context.WithCancel(ctx) + server := &Server{ + tcpListener: tcpListener, + udpListener: udpListener, + targetAddr: targetAddr, + mapping: make(map[string]*PacketConn), + packetChan: make(chan tunnel.PacketConn, 32), + timeout: time.Second * time.Duration(cfg.UDPTimeout), + ctx: ctx, + cancel: cancel, + } + go server.dispatchLoop() + return server, nil +} diff --git a/tunnel/dokodemo/tunnel.go b/tunnel/dokodemo/tunnel.go new file mode 100644 index 0000000..256fc18 --- /dev/null +++ b/tunnel/dokodemo/tunnel.go @@ -0,0 +1,27 @@ +package dokodemo + +import ( + "context" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "DOKODEMO" + +type Tunnel struct{ tunnel.Tunnel } + +func (*Tunnel) Name() string { + return Name +} + +func (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, underlay) +} + +func (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) { + return nil, common.NewError("not supported") +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/metadata.go b/tunnel/metadata.go new file mode 100644 index 0000000..b8c621a --- /dev/null +++ b/tunnel/metadata.go @@ -0,0 +1,213 @@ +package tunnel + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "strconv" + + "github.com/p4gefau1t/trojan-go/common" +) + +type Command byte + +type Metadata struct { + Command + *Address +} + +func (r *Metadata) ReadFrom(rr io.Reader) error { + byteBuf := [1]byte{} + _, err := io.ReadFull(rr, byteBuf[:]) + if err != nil { + return err + } + r.Command = Command(byteBuf[0]) + r.Address = new(Address) + err = r.Address.ReadFrom(rr) + if err != nil { + return common.NewError("failed to marshal address").Base(err) + } + return nil +} + +func (r *Metadata) WriteTo(w io.Writer) error { + buf := bytes.NewBuffer(make([]byte, 0, 64)) + buf.WriteByte(byte(r.Command)) + if err := r.Address.WriteTo(buf); err != nil { + return err + } + //use tcp by default + r.Address.NetworkType = "tcp" + _, err := w.Write(buf.Bytes()) + return err +} + +func (r *Metadata) Network() string { + return r.Address.Network() +} + +func (r *Metadata) String() string { + return r.Address.String() +} + +type AddressType byte + +const ( + IPv4 AddressType = 1 + DomainName AddressType = 3 + IPv6 AddressType = 4 +) + +type Address struct { + DomainName string + Port int + NetworkType string + net.IP + AddressType +} + +func (a *Address) String() string { + switch a.AddressType { + case IPv4: + return fmt.Sprintf("%s:%d", a.IP.String(), a.Port) + case IPv6: + return fmt.Sprintf("[%s]:%d", a.IP.String(), a.Port) + case DomainName: + return fmt.Sprintf("%s:%d", a.DomainName, a.Port) + default: + return "INVALID_ADDRESS_TYPE" + } +} + +func (a *Address) Network() string { + return a.NetworkType +} + +func (a *Address) ResolveIP() (net.IP, error) { + if a.AddressType == IPv4 || a.AddressType == IPv6 { + return a.IP, nil + } + if a.IP != nil { + return a.IP, nil + } + addr, err := net.ResolveIPAddr("ip", a.DomainName) + if err != nil { + return nil, err + } + a.IP = addr.IP + return addr.IP, nil +} + +func NewAddressFromAddr(network string, addr string) (*Address, error) { + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseInt(portStr, 10, 32) + common.Must(err) + return NewAddressFromHostPort(network, host, int(port)), nil +} + +func NewAddressFromHostPort(network string, host string, port int) *Address { + if ip := net.ParseIP(host); ip != nil { + if ip.To4() != nil { + return &Address{ + IP: ip, + Port: int(port), + AddressType: IPv4, + NetworkType: network, + } + } + return &Address{ + IP: ip, + Port: int(port), + AddressType: IPv6, + NetworkType: network, + } + } + return &Address{ + DomainName: host, + Port: int(port), + AddressType: DomainName, + NetworkType: network, + } +} + +func (a *Address) ReadFrom(r io.Reader) error { + byteBuf := [1]byte{} + _, err := io.ReadFull(r, byteBuf[:]) + if err != nil { + return common.NewError("unable to read ATYPE").Base(err) + } + a.AddressType = AddressType(byteBuf[0]) + switch a.AddressType { + case IPv4: + var buf [6]byte + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return common.NewError("failed to read IPv4").Base(err) + } + a.IP = buf[0:4] + a.Port = int(binary.BigEndian.Uint16(buf[4:6])) + case IPv6: + var buf [18]byte + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return common.NewError("failed to read IPv6").Base(err) + } + a.IP = buf[0:16] + a.Port = int(binary.BigEndian.Uint16(buf[16:18])) + case DomainName: + _, err := io.ReadFull(r, byteBuf[:]) + length := byteBuf[0] + if err != nil { + return common.NewError("failed to read domain name length") + } + buf := make([]byte, length+2) + _, err = io.ReadFull(r, buf) + if err != nil { + return common.NewError("failed to read domain name") + } + //the fucking browser uses IP as a domain name sometimes + host := buf[0:length] + if ip := net.ParseIP(string(host)); ip != nil { + a.IP = ip + if ip.To4() != nil { + a.AddressType = IPv4 + } else { + a.AddressType = IPv6 + } + } else { + a.DomainName = string(host) + } + a.Port = int(binary.BigEndian.Uint16(buf[length : length+2])) + default: + return common.NewError("invalid ATYPE " + strconv.FormatInt(int64(a.AddressType), 10)) + } + return nil +} + +func (a *Address) WriteTo(w io.Writer) error { + _, err := w.Write([]byte{byte(a.AddressType)}) + switch a.AddressType { + case DomainName: + w.Write([]byte{byte(len(a.DomainName))}) + _, err = w.Write([]byte(a.DomainName)) + case IPv4: + _, err = w.Write(a.IP.To4()) + case IPv6: + _, err = w.Write(a.IP.To16()) + default: + return common.NewError("Invalid ATYPE " + strconv.FormatInt(int64(a.AddressType), 10)) + } + if err != nil { + return err + } + port := [2]byte{} + binary.BigEndian.PutUint16(port[:], uint16(a.Port)) + _, err = w.Write(port[:]) + return err +} diff --git a/tunnel/mux/client.go b/tunnel/mux/client.go new file mode 100644 index 0000000..89da719 --- /dev/null +++ b/tunnel/mux/client.go @@ -0,0 +1,176 @@ +package mux + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/xtaci/smux" +) + +type muxID uint32 + +func generateMuxID() muxID { + return muxID(rand.Uint32()) +} + +type smuxClientInfo struct { + id muxID + client *smux.Session + lastActiveTime time.Time + underlayConn tunnel.Conn +} + +//Client is a smux client +type Client struct { + clientPoolLock sync.Mutex + clientPool map[muxID]*smuxClientInfo + underlay tunnel.Client + concurrency int + timeout time.Duration + ctx context.Context + cancel context.CancelFunc +} + +func (c *Client) Close() error { + c.cancel() + c.clientPoolLock.Lock() + defer c.clientPoolLock.Unlock() + for id, info := range c.clientPool { + info.client.Close() + log.Debug("mux client", id, "closed") + } + return nil +} + +func (c *Client) cleanWorker() { + var checkDuration time.Duration + if c.timeout <= 0 { + checkDuration = time.Second * 10 + log.Warn("invalid mux timeout") + } else { + checkDuration = c.timeout / 4 + } + for { + select { + case <-time.After(checkDuration): + c.clientPoolLock.Lock() + for id, info := range c.clientPool { + if info.client.IsClosed() { + delete(c.clientPool, id) + log.Info("mux client", id, "is dead") + } else if info.client.NumStreams() == 0 && time.Now().Sub(info.lastActiveTime) > c.timeout { + info.client.Close() + info.underlayConn.Close() + delete(c.clientPool, id) + log.Info("mux client", id, "is closed due to inactivity") + } + } + for id, info := range c.clientPool { + log.Debug(fmt.Sprintf(" %x: %d/%d", id, info.client.NumStreams(), c.concurrency)) + } + log.Debug("current mux clients: ", len(c.clientPool)) + c.clientPoolLock.Unlock() + case <-c.ctx.Done(): + log.Debug("shutting down mux cleaner..") + c.clientPoolLock.Lock() + for id, info := range c.clientPool { + info.client.Close() + log.Debug("mux client", id, "closed") + } + c.clientPoolLock.Unlock() + return + } + } +} + +func (c *Client) newMuxClient() (*smuxClientInfo, error) { + // The mutex should be locked when this function is called + id := generateMuxID() + if _, found := c.clientPool[id]; found { + return nil, common.NewError("Duplicated id") + } + + fakeAddr := &tunnel.Address{ + DomainName: "MUX_CONN", + AddressType: tunnel.DomainName, + } + conn, err := c.underlay.DialConn(fakeAddr, &Tunnel{}) + if err != nil { + return nil, err + } + conn = newStickyConn(conn) + + smuxConfig := smux.DefaultConfig() + smuxConfig.KeepAliveDisabled = true + client, err := smux.Client(conn, smuxConfig) + info := &smuxClientInfo{ + client: client, + underlayConn: conn, + id: id, + lastActiveTime: time.Now(), + } + c.clientPool[id] = info + return info, nil +} + +func (c *Client) DialConn(addr *tunnel.Address, _ tunnel.Tunnel) (tunnel.Conn, error) { + c.clientPoolLock.Lock() + defer c.clientPoolLock.Unlock() + + createNewConn := func(info *smuxClientInfo) (tunnel.Conn, error) { + info.lastActiveTime = time.Now() + rwc, err := info.client.Open() + info.lastActiveTime = time.Now() + if err != nil { + return nil, err + } + return &Conn{ + rwc: rwc, + Conn: info.underlayConn, + }, nil + } + + for _, info := range c.clientPool { + if info.client.IsClosed() { + delete(c.clientPool, info.id) + log.Info(fmt.Sprintf("Mux client %x is closed", info.id)) + continue + } + if info.client.NumStreams() < c.concurrency || c.concurrency <= 0 { + return createNewConn(info) + } + } + + info, err := c.newMuxClient() + if err != nil { + return nil, common.NewError("no avaliable mux client found") + } + return createNewConn(info) +} + +func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + panic("not supported") +} + +func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { + clientConfig := config.FromContext(ctx, Name).(*Config) + ctx, cancel := context.WithCancel(ctx) + client := &Client{ + underlay: underlay, + concurrency: clientConfig.Mux.Concurrency, + timeout: time.Duration(clientConfig.Mux.Timeout) * time.Second, + ctx: ctx, + cancel: cancel, + clientPool: make(map[muxID]*smuxClientInfo), + } + go client.cleanWorker() + log.Debug("mux client created") + return client, nil +} diff --git a/tunnel/mux/config.go b/tunnel/mux/config.go new file mode 100644 index 0000000..79a534b --- /dev/null +++ b/tunnel/mux/config.go @@ -0,0 +1,26 @@ +package mux + +import "github.com/p4gefau1t/trojan-go/config" + +type MuxConfig struct { + Enabled bool `json,yaml:"enabled"` + Timeout int `json,yaml:"timeout"` + Concurrency int `json,yaml:"concurrency"` +} + +type Config struct { + Mux MuxConfig `json,yaml:"mux"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{ + Mux: MuxConfig{ + Enabled: false, + Timeout: 30, + Concurrency: 8, + }, + } + }) + +} diff --git a/tunnel/mux/conn.go b/tunnel/mux/conn.go new file mode 100644 index 0000000..fd02596 --- /dev/null +++ b/tunnel/mux/conn.go @@ -0,0 +1,99 @@ +package mux + +import ( + "io" + "math/rand" + + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type stickyConn struct { + tunnel.Conn + synQueue chan []byte + finQueue chan []byte +} + +func (c *stickyConn) stickToPayload(p []byte) []byte { + buf := make([]byte, 0, len(p)+16) + for { + select { + case header := <-c.synQueue: + buf = append(buf, header...) + default: + goto stick1 + } + } +stick1: + buf = append(buf, p...) + for { + select { + case header := <-c.finQueue: + buf = append(buf, header...) + default: + goto stick2 + } + } +stick2: + return buf +} + +func (c *stickyConn) Close() error { + const maxPaddingLength = 512 + padding := [maxPaddingLength + 8]byte{'A', 'B', 'C', 'D', 'E', 'F'} // for debugging + buf := c.stickToPayload(nil) + c.Write(append(buf, padding[:rand.Intn(maxPaddingLength)]...)) + return c.Conn.Close() +} + +func (c *stickyConn) Write(p []byte) (int, error) { + if len(p) == 8 { + if p[0] == 1 || p[0] == 2 { //smux 8 bytes header + switch p[1] { + // THE CONTENT OF THE BUFFER MIGHT CHANGE + // NEVER STORE THE POINTER TO HEADER, COPY THE HEADER INSTEAD + case 0: + // cmdSYN + header := make([]byte, 8) + copy(header, p) + c.synQueue <- header + return 8, nil + case 1: + // cmdFIN + header := make([]byte, 8) + copy(header, p) + c.finQueue <- header + return 8, nil + } + } else { + log.Debug("Unknown 8 bytes header") + } + } + _, err := c.Conn.Write(c.stickToPayload(p)) + return len(p), err +} + +func newStickyConn(conn tunnel.Conn) *stickyConn { + return &stickyConn{ + Conn: conn, + synQueue: make(chan []byte, 128), + finQueue: make(chan []byte, 128), + } +} + +type Conn struct { + rwc io.ReadWriteCloser + tunnel.Conn +} + +func (c *Conn) Read(p []byte) (int, error) { + return c.rwc.Read(p) +} + +func (c *Conn) Write(p []byte) (int, error) { + return c.rwc.Write(p) +} + +func (c *Conn) Close() error { + return c.rwc.Close() +} diff --git a/tunnel/mux/mux_test.go b/tunnel/mux/mux_test.go new file mode 100644 index 0000000..c873b20 --- /dev/null +++ b/tunnel/mux/mux_test.go @@ -0,0 +1,52 @@ +package mux + +import ( + "context" + "fmt" + "testing" + + "github.com/p4gefau1t/trojan-go/test/util" + "github.com/p4gefau1t/trojan-go/tunnel" + + "github.com/p4gefau1t/trojan-go/tunnel/raw" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "gopkg.in/yaml.v2" +) + +func TestMux(t *testing.T) { + cfg := newDefaultConfig().(*Config) + data, err := yaml.Marshal(cfg) + common.Must(err) + fmt.Println(string(data)) + + ctx, err := config.WithYAMLConfig(context.Background(), data) + common.Must(err) + + port := common.PickPort("tcp", "127.0.0.1") + addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", port) + + tcpClient := &raw.FixedClient{ + FixedAddr: addr, + } + tcpServer, err := raw.NewServer(addr) + common.Must(err) + + muxTunnel := Tunnel{} + muxClient, _ := muxTunnel.NewClient(ctx, tcpClient) + muxServer, _ := muxTunnel.NewServer(ctx, tcpServer) + + first := []byte("12345678") + conn1, err := muxClient.DialConn(addr, nil) + common.Must2(conn1.Write([]byte(first))) + common.Must(err) + buf := [8]byte{} + conn2, err := muxServer.AcceptConn(nil) + common.Must(err) + common.Must2(conn2.Read(buf[:])) + fmt.Println(string(buf[:])) + if !util.CheckConn(conn1, conn2) { + t.Fail() + } +} diff --git a/tunnel/mux/server.go b/tunnel/mux/server.go new file mode 100644 index 0000000..b3ae6a0 --- /dev/null +++ b/tunnel/mux/server.go @@ -0,0 +1,90 @@ +package mux + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/xtaci/smux" +) + +// Server is a smux server +type Server struct { + underlay tunnel.Server + connChan chan tunnel.Conn + errChan chan error + ctx context.Context + cancel context.CancelFunc +} + +func (s *Server) acceptConnWorker() { + for { + conn, err := s.underlay.AcceptConn(&Tunnel{}) + if err != nil { + log.Debug(err) + select { + case <-s.ctx.Done(): + return + default: + } + continue + } + smuxConfig := smux.DefaultConfig() + smuxConfig.KeepAliveDisabled = true + smuxSession, err := smux.Server(conn, smuxConfig) + if err != nil { + s.errChan <- err + continue + } + // TODO context + go func(session *smux.Session, conn tunnel.Conn) { + defer session.Close() + defer conn.Close() + for { + stream, err := session.AcceptStream() + if err != nil { + s.errChan <- err + return + } + s.connChan <- &Conn{ + rwc: stream, + Conn: conn, + } + } + }(smuxSession, conn) + } +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + select { + case conn := <-s.connChan: + return conn, nil + case err := <-s.errChan: + return nil, err + case <-s.ctx.Done(): + return nil, common.NewError("mux client closed") + } +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + panic("not supported") +} + +func (s *Server) Close() error { + s.cancel() + return s.underlay.Close() +} + +func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { + ctx, cancel := context.WithCancel(ctx) + server := &Server{ + underlay: underlay, + ctx: ctx, + cancel: cancel, + connChan: make(chan tunnel.Conn, 32), + } + go server.acceptConnWorker() + log.Debug("mux server created") + return server, nil +} diff --git a/tunnel/mux/tunnel.go b/tunnel/mux/tunnel.go new file mode 100644 index 0000000..203d260 --- /dev/null +++ b/tunnel/mux/tunnel.go @@ -0,0 +1,27 @@ +package mux + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "MUX" + +type Tunnel struct{} + +func (*Tunnel) Name() string { + return Name +} + +func (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { + return NewClient(ctx, client) +} + +func (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, server) +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/raw/client.go b/tunnel/raw/client.go new file mode 100644 index 0000000..9512eca --- /dev/null +++ b/tunnel/raw/client.go @@ -0,0 +1,48 @@ +package raw + +import ( + "context" + "net" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type Client struct{} + +func (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) { + tcpConn, err := net.Dial("tcp", addr.String()) + if err != nil { + return nil, err + } + return &Conn{ + TCPConn: tcpConn.(*net.TCPConn), + }, nil +} + +func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + udpConn, err := net.ListenPacket("udp", "") + if err != nil { + return nil, err + } + return &PacketConn{ + UDPConn: udpConn.(*net.UDPConn), + }, nil +} + +func (c *Client) Close() error { + return nil +} + +func NewFreeClient(context.Context, tunnel.Client) (*Client, error) { + return &Client{}, nil +} + +// FixedClient will always dial to the FixedAddr +type FixedClient struct { + FixedAddr *tunnel.Address + Client +} + +func (c *FixedClient) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) { + return c.Client.DialConn(c.FixedAddr, t) +} diff --git a/tunnel/raw/config.go b/tunnel/raw/config.go new file mode 100644 index 0000000..45d721a --- /dev/null +++ b/tunnel/raw/config.go @@ -0,0 +1,21 @@ +package raw + +type Config struct { + LocalHost string `json:"local_addr"` + LocalPort int `json:"local_port"` + TargetHost string `json:"target_addr"` + TargetPort int `json:"target_port"` + RemoteHost string `json:"remote_addr"` + RemotePort int `json:"remote_port"` + DNS []string `json:"dns"` + TCP TCPConfig `json:"tcp"` +} + +type TCPConfig struct { + PreferIPV4 bool `json:"prefer_ipv4"` + KeepAlive bool `json:"keep_alive"` + FastOpen bool `json:"fast_open"` + FastOpenQLen int `json:"fast_open_qlen"` + ReusePort bool `json:"reuse_port"` + NoDelay bool `json:"no_delay"` +} diff --git a/tunnel/raw/conn.go b/tunnel/raw/conn.go new file mode 100644 index 0000000..7f5b257 --- /dev/null +++ b/tunnel/raw/conn.go @@ -0,0 +1,52 @@ +package raw + +import ( + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type Conn struct { + *net.TCPConn +} + +func (c *Conn) Metadata() *tunnel.Metadata { + return nil +} + +type PacketConn struct { + *net.UDPConn +} + +func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { + return c.WriteTo(p, m.Address) +} + +func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { + n, addr, err := c.ReadFrom(p) + if err != nil { + return 0, nil, err + } + address, err := tunnel.NewAddressFromAddr("udp", addr.String()) + common.Must(err) + metadata := &tunnel.Metadata{ + Address: address, + } + return n, metadata, nil +} + +func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { + if udpAddr, ok := addr.(*net.UDPAddr); ok { + return c.WriteToUDP(p, udpAddr) + } + ip, err := addr.(*tunnel.Address).ResolveIP() + if err != nil { + return 0, err + } + udpAddr := &net.UDPAddr{ + IP: ip, + Port: addr.(*tunnel.Address).Port, + } + return c.WriteToUDP(p, udpAddr) +} diff --git a/tunnel/raw/raw_test.go b/tunnel/raw/raw_test.go new file mode 100644 index 0000000..a41b928 --- /dev/null +++ b/tunnel/raw/raw_test.go @@ -0,0 +1,50 @@ +package raw + +import ( + "testing" + + "github.com/p4gefau1t/trojan-go/test/util" + "github.com/p4gefau1t/trojan-go/tunnel" + + "github.com/p4gefau1t/trojan-go/common" +) + +func TestConn(t *testing.T) { + port := common.PickPort("tcp", "127.0.0.1") + addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", port) + server, err := NewServer(addr) + common.Must(err) + client := &Client{} + + conn1, err := client.DialConn(addr, nil) + common.Must(err) + conn2, err := server.AcceptConn(nil) + common.Must(err) + + if !util.CheckConn(conn1, conn2) { + t.Fail() + } +} + +func TestPacket(t *testing.T) { + port := common.PickPort("tcp", "127.0.0.1") + addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", port) + server, err := NewServer(addr) + common.Must(err) + client := &Client{} + + conn1, err := client.DialPacket(nil) + common.Must(err) + conn2, err := server.AcceptPacket(nil) + common.Must(err) + + buf := [100]byte{} + + str1 := "1234567890zxvbouehrofisjad;fowqiooewuroqpe" + common.Must2(conn1.WriteTo([]byte(str1), addr)) + n, _, err := conn2.ReadFrom(buf[:]) + common.Must(err) + if string(buf[:n]) != str1 { + t.Fail() + } +} diff --git a/tunnel/raw/server.go b/tunnel/raw/server.go new file mode 100644 index 0000000..d032889 --- /dev/null +++ b/tunnel/raw/server.go @@ -0,0 +1,47 @@ +package raw + +import ( + "net" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type Server struct { + tcpListener net.Listener + addr *tunnel.Address +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := s.tcpListener.Accept() + if err != nil { + return nil, err + } + return &Conn{ + TCPConn: conn.(*net.TCPConn), + }, nil +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + packetConn, err := net.ListenPacket("udp", s.addr.String()) + if err != nil { + return nil, err + } + return &PacketConn{ + UDPConn: packetConn.(*net.UDPConn), + }, nil +} + +func (s *Server) Close() error { + return s.tcpListener.Close() +} + +func NewServer(addr *tunnel.Address) (*Server, error) { + l, err := net.Listen("tcp", addr.String()) + if err != nil { + return nil, err + } + return &Server{ + addr: addr, + tcpListener: l, + }, nil +} diff --git a/tunnel/raw/tunnel.go b/tunnel/raw/tunnel.go new file mode 100644 index 0000000..83cbecb --- /dev/null +++ b/tunnel/raw/tunnel.go @@ -0,0 +1,41 @@ +package raw + +import ( + "context" + "net" + + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "RAW" + +type Tunnel struct{} + +func (*Tunnel) Name() string { + return Name +} + +// NewClient creates a raw client +func (*Tunnel) NewClient(ctx context.Context, _ tunnel.Client) (tunnel.Client, error) { + return &Client{}, nil +} + +// NewServer creates a raw server, which is used by "Forward" +func (*Tunnel) NewServer(ctx context.Context, _ tunnel.Server) (tunnel.Server, error) { + serverConfig := config.FromContext(ctx, Name).(*Config) + addr := tunnel.NewAddressFromHostPort("tcp", serverConfig.LocalHost, serverConfig.LocalPort) + + l, err := net.Listen("tcp", addr.String()) + if err != nil { + return nil, err + } + return &Server{ + addr: addr, + tcpListener: l, + }, nil +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/simplesocks/client.go b/tunnel/simplesocks/client.go new file mode 100644 index 0000000..3f23a56 --- /dev/null +++ b/tunnel/simplesocks/client.go @@ -0,0 +1,65 @@ +package simplesocks + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/trojan" +) + +const ( + Connect tunnel.Command = 1 + Associate tunnel.Command = 3 +) + +type Client struct { + underlay tunnel.Client +} + +func (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := c.underlay.DialConn(nil, &Tunnel{}) + if err != nil { + return nil, common.NewError("simplesocks failed to dial using underlying tunnel").Base(err) + } + return &Conn{ + Conn: conn, + isOutbound: true, + metadata: &tunnel.Metadata{ + Command: Connect, + Address: addr, + }, + }, nil +} + +func (c *Client) DialPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) { + conn, err := c.underlay.DialConn(nil, &Tunnel{}) + if err != nil { + return nil, common.NewError("simplesocks failed to dial using underlying tunnel").Base(err) + } + metadata := &tunnel.Metadata{ + Command: Associate, + Address: &tunnel.Address{ + DomainName: "UDP_CONN", + AddressType: tunnel.DomainName, + }, + } + if err := metadata.WriteTo(conn); err != nil { + return nil, common.NewError("simplesocks failed to write udp associate").Base(err) + } + return &PacketConn{ + PacketConn: trojan.PacketConn{ + Conn: conn, + }, + }, nil +} + +func (c *Client) Close() error { + return c.underlay.Close() +} + +func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { + return &Client{ + underlay: underlay, + }, nil +} diff --git a/tunnel/simplesocks/conn.go b/tunnel/simplesocks/conn.go new file mode 100644 index 0000000..1da917e --- /dev/null +++ b/tunnel/simplesocks/conn.go @@ -0,0 +1,42 @@ +package simplesocks + +import ( + "bytes" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/trojan" +) + +// Conn is a simplesocks connection +type Conn struct { + tunnel.Conn + metadata *tunnel.Metadata + isOutbound bool + headerWritten bool +} + +func (c *Conn) Metadata() *tunnel.Metadata { + return c.metadata +} + +func (c *Conn) Write(payload []byte) (int, error) { + if c.isOutbound && !c.headerWritten { + buf := bytes.NewBuffer(make([]byte, 0, 4096)) + c.metadata.WriteTo(buf) + buf.Write(payload) + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, common.NewError("failed to write simplesocks header").Base(err) + } + c.headerWritten = true + return len(payload), nil + } + return c.Conn.Write(payload) +} + +// PacketConn is a simplesocks packet connection +// The header syntax is the same as trojan's +type PacketConn struct { + trojan.PacketConn +} diff --git a/tunnel/simplesocks/server.go b/tunnel/simplesocks/server.go new file mode 100644 index 0000000..74fe3a7 --- /dev/null +++ b/tunnel/simplesocks/server.go @@ -0,0 +1,93 @@ +package simplesocks + +import ( + "context" + "fmt" + "github.com/p4gefau1t/trojan-go/log" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/trojan" +) + +// Server is a simplesocks server +type Server struct { + underlay tunnel.Server + connChan chan tunnel.Conn + packetChan chan tunnel.PacketConn + errChan chan error + ctx context.Context +} + +func (s *Server) Close() error { + return s.underlay.Close() +} + +func (s *Server) acceptLoop() { + for { + conn, err := s.underlay.AcceptConn(&Tunnel{}) + if err != nil { + log.Error(common.NewError("simplesocks failed to accept connection from underlying tunnel").Base(err)) + select { + case <-s.ctx.Done(): + return + default: + } + continue + } + metadata := new(tunnel.Metadata) + if err := metadata.ReadFrom(conn); err != nil { + s.errChan <- common.NewError("simplesocks server faield to read header").Base(err) + conn.Close() + continue + } + switch metadata.Command { + case Connect: + s.connChan <- &Conn{ + metadata: metadata, + Conn: conn, + } + case Associate: + s.packetChan <- &PacketConn{ + PacketConn: trojan.PacketConn{ + Conn: conn, + }, + } + default: + s.errChan <- common.NewError(fmt.Sprintf("simplesocks unknown command %d", metadata.Command)) + conn.Close() + } + } +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + select { + case conn := <-s.connChan: + return conn, nil + case err := <-s.errChan: + return nil, err + case <-s.ctx.Done(): + return nil, common.NewError("simplesocks server closed") + } +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + select { + case packetConn := <-s.packetChan: + return packetConn, nil + case <-s.ctx.Done(): + return nil, common.NewError("simplesocks server closed") + } +} + +func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { + server := &Server{ + underlay: underlay, + ctx: ctx, + connChan: make(chan tunnel.Conn, 32), + packetChan: make(chan tunnel.PacketConn, 32), + errChan: make(chan error, 32), + } + go server.acceptLoop() + return server, nil +} diff --git a/tunnel/simplesocks/simplesocks_test.go b/tunnel/simplesocks/simplesocks_test.go new file mode 100644 index 0000000..ee5dafd --- /dev/null +++ b/tunnel/simplesocks/simplesocks_test.go @@ -0,0 +1,60 @@ +package simplesocks + +import ( + "context" + "fmt" + "testing" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/test/util" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/raw" +) + +func TestSimpleSocks(t *testing.T) { + addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", common.PickPort("tcp", "127.0.0.1")) + rawClient := &raw.FixedClient{ + FixedAddr: addr, + } + rawServer, err := raw.NewServer(addr) + common.Must(err) + c, err := NewClient(context.Background(), rawClient) + common.Must(err) + s, err := NewServer(context.Background(), rawServer) + common.Must(err) + + conn1, err := c.DialConn(&tunnel.Address{ + DomainName: "www.baidu.com", + AddressType: tunnel.DomainName, + Port: 443, + }, nil) + common.Must(err) + conn1.Write([]byte("12345678")) + conn2, err := s.AcceptConn(nil) + common.Must(err) + buf := [8]byte{} + common.Must2(conn2.Read(buf[:])) + if string(buf[:]) != "12345678" { + t.Fail() + } + if !util.CheckConn(conn1, conn2) { + t.Fail() + } + + packet1, err := c.DialPacket(nil) + packet1.WriteWithMetadata([]byte("12345678"), &tunnel.Metadata{ + Address: &tunnel.Address{ + DomainName: "test.com", + AddressType: tunnel.DomainName, + Port: 443, + }, + }) + packet2, err := s.AcceptPacket(nil) + _, m, err := packet2.ReadWithMetadata(buf[:]) + common.Must(err) + fmt.Println(m) + + if !util.CheckPacketOverConn(packet1, packet2) { + t.Fail() + } +} diff --git a/tunnel/simplesocks/tunnel.go b/tunnel/simplesocks/tunnel.go new file mode 100644 index 0000000..50859f3 --- /dev/null +++ b/tunnel/simplesocks/tunnel.go @@ -0,0 +1,27 @@ +package simplesocks + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "SIMPLESOCKS" + +type Tunnel struct{} + +func (*Tunnel) Name() string { + return Name +} + +func (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, underlay) +} + +func (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) { + return NewClient(ctx, underlay) +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/socks/client.go b/tunnel/socks/client.go new file mode 100644 index 0000000..902dbba --- /dev/null +++ b/tunnel/socks/client.go @@ -0,0 +1,9 @@ +package socks + +import "github.com/p4gefau1t/trojan-go/tunnel" + +// TODO implement socks5 client + +type Client struct { + underlay tunnel.Client +} diff --git a/tunnel/socks/config.go b/tunnel/socks/config.go new file mode 100644 index 0000000..ef489c9 --- /dev/null +++ b/tunnel/socks/config.go @@ -0,0 +1,14 @@ +package socks + +import "github.com/p4gefau1t/trojan-go/config" + +type Config struct { + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return new(Config) + }) +} diff --git a/tunnel/socks/conn.go b/tunnel/socks/conn.go new file mode 100644 index 0000000..839b6df --- /dev/null +++ b/tunnel/socks/conn.go @@ -0,0 +1,82 @@ +package socks + +import ( + "bytes" + "net" + "time" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type Conn struct { + net.Conn + metadata *tunnel.Metadata +} + +func (c *Conn) Metadata() *tunnel.Metadata { + return c.metadata +} + +type PacketConn struct { + net.PacketConn + srcAddr net.Addr + timeout time.Duration + shutdownChan chan struct{} +} + +func (c *PacketConn) Close() error { + c.shutdownChan <- struct{}{} + return c.PacketConn.Close() +} + +func (c *PacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize)) + buf.Write([]byte{0, 0, 0}) //RSV, FRAG + common.Must(metadata.Address.WriteTo(buf)) + buf.Write(payload) + var clientAddr net.Addr + if c.srcAddr != nil { + clientAddr = c.srcAddr + } else { + return 0, common.NewError("client address not found") + } + _, err := c.PacketConn.WriteTo(buf.Bytes(), clientAddr) + if err != nil { + return 0, err + } + log.Debug("sent udp packet to " + clientAddr.String() + " with metadata " + metadata.String()) + return len(payload), nil +} + +func (c *PacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, error) { + buf := make([]byte, MaxPacketSize) + n, from, err := c.PacketConn.ReadFrom(buf) + if err != nil { + return 0, nil, err + } + log.Debug("recv udp packet from " + from.String()) + addr := new(tunnel.Address) + c.srcAddr = from + r := bytes.NewBuffer(buf[3:n]) + if err := addr.ReadFrom(r); err != nil { + return 0, nil, common.NewError("socks5 failed to parse addr in the packet").Base(err) + } + length, err := r.Read(payload) + if err != nil { + return 0, nil, err + } + return length, &tunnel.Metadata{ + Address: addr, + }, nil +} + +func NewPacketConn(packet net.PacketConn) *PacketConn { + conn := &PacketConn{ + PacketConn: packet, + timeout: time.Second * 10, + shutdownChan: make(chan struct{}), + } + return conn +} diff --git a/tunnel/socks/server.go b/tunnel/socks/server.go new file mode 100644 index 0000000..088c608 --- /dev/null +++ b/tunnel/socks/server.go @@ -0,0 +1,184 @@ +package socks + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const ( + Connect tunnel.Command = 1 + Associate tunnel.Command = 3 +) + +const ( + MaxPacketSize = 1024 * 8 +) + +// Server is a socks4/5 server +type Server struct { + connChan chan tunnel.Conn + packetChan chan tunnel.PacketConn + errChan chan error + listenAddr net.Addr + tcpListener net.Listener + ctx context.Context +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + select { + case conn := <-s.connChan: + return conn, nil + case err := <-s.errChan: + return nil, err + case <-s.ctx.Done(): + return nil, common.NewError("socks server closed") + } +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + select { + case conn := <-s.packetChan: + return conn, nil + case err := <-s.errChan: + return nil, err + case <-s.ctx.Done(): + return nil, common.NewError("socks server closed") + } +} + +func (s *Server) Close() error { + return s.tcpListener.Close() +} + +func (s *Server) handshake(conn net.Conn) (*Conn, error) { + version := [1]byte{} + if _, err := conn.Read(version[:]); err != nil { + return nil, common.NewError("failed to read socks version").Base(err) + } + if version[0] != 5 { + return nil, common.NewError(fmt.Sprintf("invalid socks version %d", version[0])) + } + nmethods := [1]byte{} + if _, err := conn.Read(nmethods[:]); err != nil { + return nil, common.NewError("failed to read NMETHODS") + } + if _, err := io.CopyN(ioutil.Discard, conn, int64(nmethods[0])); err != nil { + return nil, common.NewError("socks5 failed to read methods").Base(err) + } + if _, err := conn.Write([]byte{0x5, 0x0}); err != nil { + return nil, common.NewError("failed to respond auth").Base(err) + } + + buf := [3]byte{} + if _, err := conn.Read(buf[:]); err != nil { + return nil, common.NewError("failed to read command") + } + + addr := new(tunnel.Address) + if err := addr.ReadFrom(conn); err != nil { + return nil, err + } + + return &Conn{ + metadata: &tunnel.Metadata{ + Command: tunnel.Command(buf[1]), + Address: addr, + }, + Conn: conn, + }, nil +} + +func (s *Server) connect(conn net.Conn) error { + _, err := conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + return err +} + +func (s *Server) associate(conn net.Conn, addr *tunnel.Address) error { + buf := bytes.NewBuffer([]byte{0x05, 0x00, 0x00}) + common.Must(addr.WriteTo(buf)) + _, err := conn.Write(buf.Bytes()) + return err +} + +func (s *Server) acceptLoop() { + l, err := net.Listen("tcp", s.listenAddr.String()) + if err != nil { + s.errChan <- common.NewError("socks5 failed to listen").Base(err) + return + } + s.tcpListener = l + + for { + conn, err := l.Accept() + if err != nil { + s.errChan <- common.NewError("socks5 accept err").Base(err) + return + } + go func(conn net.Conn) { + newConn, err := s.handshake(conn) + if err != nil { + s.errChan <- common.NewError("socks5 failed to handshake with client").Base(err) + return + } + log.Info("socks5 connection from", conn.RemoteAddr(), "metadata", newConn.metadata.String()) + switch newConn.metadata.Command { + case Connect: + if err := s.connect(newConn); err != nil { + s.errChan <- common.NewError("socks5 failed to respond CONNECT").Base(err) + newConn.Close() + return + } + s.connChan <- newConn + return + case Associate: + defer newConn.Close() + host, _, err := net.SplitHostPort(s.listenAddr.String()) + common.Must(err) + port := common.PickPort("udp", host) + associateAddr := tunnel.NewAddressFromHostPort("udp", host, port) + l, err := net.ListenPacket("udp", associateAddr.String()) + if err != nil { + s.errChan <- common.NewError("socks5 failed to bind udp").Base(err) + return + } + s.packetChan <- NewPacketConn(l) + log.Info("socks5 udp session") + if err := s.associate(newConn, associateAddr); err != nil { + s.errChan <- common.NewError("socks5 failed to respond to ASSOCIATE").Base(err) + return + } + buf := [1]byte{} + newConn.Read(buf[:]) + log.Debug("socks5 udp session ends") + default: + s.errChan <- common.NewError(fmt.Sprintf("unknown socks5 command %d", newConn.metadata.Command)) + newConn.Close() + } + }(conn) + } +} + +// NewServer create a socks server +func NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { + cfg := config.FromContext(ctx, Name).(*Config) + listenAddr := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort) + server := &Server{ + listenAddr: listenAddr, + ctx: ctx, + connChan: make(chan tunnel.Conn, 32), + packetChan: make(chan tunnel.PacketConn, 32), + errChan: make(chan error, 32), + } + go server.acceptLoop() + log.Debug("socks server created") + return server, nil +} diff --git a/tunnel/socks/socks_test.go b/tunnel/socks/socks_test.go new file mode 100644 index 0000000..d252273 --- /dev/null +++ b/tunnel/socks/socks_test.go @@ -0,0 +1,49 @@ +package socks + +import ( + "context" + "fmt" + "net" + "sync" + "testing" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/test/util" + "github.com/p4gefau1t/trojan-go/tunnel" + "golang.org/x/net/proxy" +) + +func TestSocks(t *testing.T) { + port := common.PickPort("tcp", "127.0.0.1") + ctx := config.WithConfig(context.Background(), Name, &Config{ + LocalHost: "127.0.0.1", + LocalPort: port, + }) + addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", port) + s, err := NewServer(ctx, nil) + common.Must(err) + socksClient, err := proxy.SOCKS5("tcp", addr.String(), nil, proxy.Direct) + common.Must(err) + var conn1, conn2 net.Conn + wg := sync.WaitGroup{} + wg.Add(2) + + go func() { + conn2, err = s.AcceptConn(nil) + common.Must(err) + wg.Done() + }() + + go func() { + conn1, err = socksClient.Dial("tcp", "www.baidu.com:80") + common.Must(err) + wg.Done() + }() + + wg.Wait() + if !util.CheckConn(conn1, conn2) { + t.Fail() + } + fmt.Println(conn2.(tunnel.Conn).Metadata()) +} diff --git a/tunnel/socks/tunnel.go b/tunnel/socks/tunnel.go new file mode 100644 index 0000000..9486bf2 --- /dev/null +++ b/tunnel/socks/tunnel.go @@ -0,0 +1,25 @@ +package socks + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "SOCKS" + +type Tunnel struct{} + +func (*Tunnel) Name() string { + return Name +} +func (*Tunnel) NewClient(context.Context, tunnel.Client) (tunnel.Client, error) { + panic("not supported") +} +func (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, server) +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/tproxy/config.go b/tunnel/tproxy/config.go new file mode 100644 index 0000000..756561d --- /dev/null +++ b/tunnel/tproxy/config.go @@ -0,0 +1,17 @@ +package tproxy + +import "github.com/p4gefau1t/trojan-go/config" + +type Config struct { + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` + UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{ + UDPTimeout: 30, + } + }) +} diff --git a/tunnel/tproxy/conn.go b/tunnel/tproxy/conn.go new file mode 100644 index 0000000..5db292b --- /dev/null +++ b/tunnel/tproxy/conn.go @@ -0,0 +1,20 @@ +package tproxy + +import ( + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/dokodemo" + "net" +) + +type Conn struct { + net.Conn + metadata *tunnel.Metadata +} + +func (c *Conn) Metadata() *tunnel.Metadata { + return c.metadata +} + +type PacketConn struct { + dokodemo.PacketConn +} diff --git a/tunnel/tproxy/server.go b/tunnel/tproxy/server.go new file mode 100644 index 0000000..e642991 --- /dev/null +++ b/tunnel/tproxy/server.go @@ -0,0 +1,160 @@ +package tproxy + +import ( + "context" + "github.com/LiamHaworth/go-tproxy" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/dokodemo" + "io" + "net" + "sync" + "time" +) + +const MaxPacketSize = 1024 * 8 + +type Server struct { + tcpListener net.Listener + udpListener *net.UDPConn + packetChan chan tunnel.PacketConn + timeout time.Duration + mappingLock sync.Mutex + mapping map[string]*PacketConn + ctx context.Context + cancel context.CancelFunc +} + +func (s *Server) Close() error { + s.cancel() + s.tcpListener.Close() + return s.udpListener.Close() +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := s.tcpListener.Accept() + if err != nil { + return nil, common.NewError("tproxy failed to accept connection").Base(err) + } + addr, err := getOriginalTCPDest(conn.(*net.TCPConn)) + if err != nil { + return nil, common.NewError("tproxy failed to obtain original address of tcp socket").Base(err) + } + address, err := tunnel.NewAddressFromAddr("tcp", addr.String()) + common.Must(err) + return &Conn{ + metadata: &tunnel.Metadata{ + Address: address, + }, + Conn: conn, + }, nil +} + +func (s *Server) packetDispatchLoop() { + for { + buf := make([]byte, MaxPacketSize) + n, src, dst, err := tproxy.ReadFromUDP(s.udpListener, buf) + if err != nil { + s.cancel() + log.Error("tproxy failed to read from udp") + return + } + log.Debug("udp packet from", src, "to", dst) + s.mappingLock.Lock() + if conn, found := s.mapping[src.String()]; found { + conn.Input <- buf[:n] + s.mappingLock.Unlock() + continue + } + log.Info("tproxy udp session from", src, "to", dst) + ctx, cancel := context.WithCancel(s.ctx) + conn := &PacketConn{ + dokodemo.PacketConn{ + Input: make(chan []byte, 16), + Output: make(chan []byte, 16), + Source: src, + PacketConn: s.udpListener, + Context: ctx, + CancelFunc: cancel, + M: &tunnel.Metadata{ + Address: &tunnel.Address{}, + }, + }, + } + s.mapping[src.String()] = conn + s.mappingLock.Unlock() + + conn.Input <- buf[:n] + s.packetChan <- conn + + go func(conn *PacketConn) { + for { + select { + case payload := <-conn.Output: + _, err := s.udpListener.WriteTo(payload, conn.Source) + if err != nil { + log.Error(common.NewError("tproxy udp write error").Base(err)) + return + } + case <-s.ctx.Done(): + return + case <-time.After(s.timeout): + s.mappingLock.Lock() + delete(s.mapping, conn.Source.String()) + s.mappingLock.Unlock() + conn.Close() + log.Debug("timeout packet session closed", conn.Source.String()) + return + } + } + }(conn) + } +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + select { + case conn := <-s.packetChan: + return conn, nil + case <-s.ctx.Done(): + return nil, io.EOF + } +} + +func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) { + cfg := config.FromContext(ctx, Name).(*Config) + ctx, cancel := context.WithCancel(ctx) + listenAddr := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort) + ip, err := listenAddr.ResolveIP() + if err != nil { + return nil, common.NewError("invalid tproxy local address").Base(err) + } + tcpListener, err := tproxy.ListenTCP("tcp", &net.TCPAddr{ + IP: ip, + Port: cfg.LocalPort, + }) + if err != nil { + return nil, common.NewError("tproxy failed to listen tcp").Base(err) + } + + udpListener, err := tproxy.ListenUDP("udp", &net.UDPAddr{ + IP: ip, + Port: cfg.LocalPort, + }) + if err != nil { + return nil, common.NewError("tproxy failed to listen tcp").Base(err) + } + + server := &Server{ + tcpListener: tcpListener, + udpListener: udpListener, + ctx: ctx, + cancel: cancel, + timeout: time.Duration(cfg.UDPTimeout) * time.Second, + mapping: make(map[string]*PacketConn), + packetChan: make(chan tunnel.PacketConn), + } + go server.packetDispatchLoop() + return server, nil +} diff --git a/protocol/tproxy/tproxy.go b/tunnel/tproxy/tproxy.go similarity index 98% rename from protocol/tproxy/tproxy.go rename to tunnel/tproxy/tproxy.go index 0b552ac..01e67a5 100644 --- a/protocol/tproxy/tproxy.go +++ b/tunnel/tproxy/tproxy.go @@ -1,13 +1,11 @@ // +build linux darwin - package tproxy import ( "net" "os" + "syscall" "unsafe" - - syscall "golang.org/x/sys/unix" ) const ( diff --git a/tunnel/tproxy/tunnel.go b/tunnel/tproxy/tunnel.go new file mode 100644 index 0000000..9e7e59d --- /dev/null +++ b/tunnel/tproxy/tunnel.go @@ -0,0 +1,22 @@ +package tproxy + +import ( + "context" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "TPROXY" + +type Tunnel struct{} + +func (t *Tunnel) Name() string { + return Name +} + +func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { + panic("not supported") +} + +func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, server) +} diff --git a/tunnel/transport/client.go b/tunnel/transport/client.go new file mode 100644 index 0000000..a0a9889 --- /dev/null +++ b/tunnel/transport/client.go @@ -0,0 +1,106 @@ +package transport + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "io" + "io/ioutil" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +// Client implements tunnel.Client +type Client struct { + serverAddress *tunnel.Address + verify bool + sni string + ca *x509.CertPool + cipher []uint16 + sessionTicket bool + curve []tls.CurveID + keyLogger io.WriteCloser +} + +func (c *Client) Close() error { + return c.keyLogger.Close() +} + +func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + return nil, common.NewError("Not supported") +} + +// DialConn implements tunnel.Client. It will ignore the params and directly dial to remote server +func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) { + tlsConn, err := tls.Dial("tcp", c.serverAddress.String(), &tls.Config{ + InsecureSkipVerify: !c.verify, + ServerName: c.sni, + RootCAs: c.ca, + KeyLogWriter: c.keyLogger, + }) + if err != nil { + return nil, err + } + return &Conn{ + Conn: tlsConn, + }, nil +} + +// NewClient creates a transport layer client +func NewClient(ctx context.Context, c tunnel.Client) (*Client, error) { + clientConfig := config.FromContext(ctx, Name).(*Config) + serverAddress := tunnel.NewAddressFromHostPort("tcp", clientConfig.RemoteHost, clientConfig.RemotePort) + client := &Client{ + verify: clientConfig.TLS.Verify, + sni: clientConfig.TLS.SNI, + serverAddress: serverAddress, + } + if clientConfig.TLS.CertPath != "" { + caCertByte, err := ioutil.ReadFile(clientConfig.TLS.CertPath) + if err != nil { + return nil, common.NewError("failed to load cert file").Base(err) + } + client.ca = x509.NewCertPool() + ok := client.ca.AppendCertsFromPEM(caCertByte) + if !ok { + log.Warn("invalid cert list") + } + log.Info("using custom cert") + + // print cert info + pemCerts := caCertByte + for len(pemCerts) > 0 { + var block *pem.Block + block, pemCerts = pem.Decode(pemCerts) + if block == nil { + break + } + if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { + continue + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + continue + } + log.Trace("issuer:", cert.Issuer, "subject:", cert.Subject) + } + } else if len(clientConfig.TLS.CertBytes) != 0 { + client.ca = x509.NewCertPool() + ok := client.ca.AppendCertsFromPEM(clientConfig.TLS.CertBytes) + if !ok { + log.Warn("invalid cert list") + } + log.Info("using custom cert (data)") + } + + if clientConfig.TLS.CertPath == "" && len(clientConfig.TLS.CertBytes) == 0 { + log.Info("cert is unspecified, using default ca list") + } + + log.Debug("transport client created") + return client, nil +} diff --git a/tunnel/transport/config.go b/tunnel/transport/config.go new file mode 100644 index 0000000..d779461 --- /dev/null +++ b/tunnel/transport/config.go @@ -0,0 +1,59 @@ +package transport + +import ( + "github.com/p4gefau1t/trojan-go/config" +) + +type Config struct { + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` + RemoteHost string `json:"remote_addr" yaml:"remote-addr"` + RemotePort int `json:"remote_port" yaml:"remote-port"` + TLS TLSConfig `json:"ssl" yaml:"ssl"` + TransportPlugin TransportPluginConfig `json:"transport_plugin" yaml:"transport-plugin"` +} + +type TLSConfig struct { + Verify bool `json:"verify" yaml:"verify"` + VerifyHostName bool `json:"verify_hostname" yaml:"verify-hostname"` + CertPath string `json:"cert" yaml:"cert"` + KeyPath string `json:"key" yaml:"key"` + KeyPassword string `json:"key_password" yaml:"key-password"` + Cipher string `json:"cipher" yaml:"cipher"` + PreferServerCipher bool `json:"prefer_server_cipher" yaml:"prefer-server-cipher"` + SNI string `json:"sni" yaml:"sni"` + HTTPResponseFileName string `json:"plain_http_response" yaml:"plain-http-response"` + FallbackHost string `json:"fallback_addr" yaml:"fallback-addr"` + FallbackPort int `json:"fallback_port" yaml:"fallback-port"` + ReuseSession bool `json:"reuse_session" yaml:"reuse-session"` + ALPN []string `json:"alpn" yaml:"alpn"` + Curves string `json:"curves" yaml:"curves"` + Fingerprint string `json:"fingerprint" yaml:"fingerprint"` + KeyLogPath string `json:"key_log" yaml:"key-log"` + KeyBytes []byte `json:"key_bytes" yaml:"key-bytes"` + CertBytes []byte `json:"cert_bytes" yaml:"cert-bytes"` +} + +type TransportPluginConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` + Type string `json:"type" yaml:"type"` + Command string `json:"command" yaml:"command"` + PluginOption string `json:"plugin_option" yaml:"plugin-option"` + Arg []string `json:"arg" yaml:"arg"` + Env []string `json:"env" yaml:"env"` +} + +func newDefaultConfig() interface{} { + return &Config{ + TLS: TLSConfig{ + Verify: true, + VerifyHostName: true, + Fingerprint: "firefox", + ALPN: []string{"http/1.1"}, + }, + } +} + +func init() { + config.RegisterConfigCreator(Name, newDefaultConfig) +} diff --git a/tunnel/transport/conn.go b/tunnel/transport/conn.go new file mode 100644 index 0000000..02dbc1c --- /dev/null +++ b/tunnel/transport/conn.go @@ -0,0 +1,17 @@ +package transport + +import ( + "net" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +// Conn is the TLS connections +type Conn struct { + net.Conn +} + +// Metadata implements tunnel.Conn. We don't need and metadata here +func (c *Conn) Metadata() *tunnel.Metadata { + return nil +} diff --git a/tunnel/transport/server.go b/tunnel/transport/server.go new file mode 100644 index 0000000..ba87284 --- /dev/null +++ b/tunnel/transport/server.go @@ -0,0 +1,248 @@ +package transport + +import ( + "bufio" + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "github.com/p4gefau1t/trojan-go/tunnel/websocket" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "strings" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/redirector" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +// Server is a server of trasport layer +type Server struct { + tcpListener net.Listener + listenAddress *tunnel.Address + fallbackAddress *tunnel.Address + verifySNI bool + sni string + alpn []string + PreferServerCipher bool + certPool *x509.CertPool + keyPair []tls.Certificate + httpResp []byte + cipherSuite []uint16 + sessionTicket bool + curve []tls.CurveID + keyLogger io.WriteCloser + redir *redirector.Redirector + connChan chan tunnel.Conn + wsChan chan tunnel.Conn + errChan chan error + ctx context.Context + cancel context.CancelFunc +} + +func (s *Server) Close() error { + s.cancel() + s.keyLogger.Close() + return s.tcpListener.Close() +} + +func (s *Server) acceptLoop() { + for { + tcpConn, err := s.tcpListener.Accept() + if err != nil { + s.cancel() + s.errChan <- common.NewError("transport accept error") + return + } + go func(tcpConn net.Conn) { + sniVerified := true + tlsConfig := &tls.Config{ + Certificates: s.keyPair, + CipherSuites: s.cipherSuite, + PreferServerCipherSuites: s.PreferServerCipher, + SessionTicketsDisabled: !s.sessionTicket, + NextProtos: s.alpn, + KeyLogWriter: s.keyLogger, + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + if s.verifySNI && hello.ServerName != s.sni { + sniVerified = false + return nil, common.NewError("sni mismatched: " + hello.ServerName + ", expected: " + s.sni) + } + return &s.keyPair[0], nil + }, + } + + // ------------------------ WAR ZONE ---------------------------- + + rewindConn := common.NewRewindConn(tcpConn) + rewindConn.SetBufferSize(2048) + + tlsConn := tls.Server(rewindConn, tlsConfig) + tlsConn.Handshake() + err = tlsConn.Handshake() + rewindConn.StopBuffering() + + if err != nil { + if !sniVerified { + // close tls conn immediately if the sni is invalid + tlsConn.Close() + s.errChan <- common.NewError("tls client hello with wrong sni").Base(err) + } else if strings.Contains(err.Error(), "first record does not look like a TLS handshake") { + // not a valid tls client hello + rewindConn.Rewind() + err = common.NewError("failed to perform tls handshake with " + tlsConn.RemoteAddr().String() + ", redirecting").Base(err) + s.errChan <- err + if s.fallbackAddress != nil { + s.redir.Redirect(&redirector.Redirection{ + InboundConn: rewindConn, + RedirectTo: s.fallbackAddress, + }) + } else if s.httpResp != nil { + rewindConn.Write(s.httpResp) + rewindConn.Close() + } else { + rewindConn.Close() + } + } else { + // other cases, simply close it + tlsConn.Close() + s.errChan <- common.NewError("tls handshake failed").Base(err) + } + return + } + + state := tlsConn.ConnectionState() + log.Trace("tls handshake", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol) + + // we use a real http header parser to mimic a real http server + tlsRewindConn := common.NewRewindConn(tlsConn) + tlsRewindConn.SetBufferSize(512) + defer tlsRewindConn.StopBuffering() + r := bufio.NewReader(tlsRewindConn) + httpReq, err := http.ReadRequest(r) + tlsRewindConn.Rewind() + if err != nil { + // this is not a http request, pass it to trojan protocol layer for further inspection + s.connChan <- &Conn{ + Conn: tlsRewindConn, + } + } else { + // this is a http request, pass it to websocket protocol layer + log.Debug("http req: ", httpReq) + s.wsChan <- &Conn{ + Conn: tlsRewindConn, + } + } + }(tcpConn) + } +} + +func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { + if _, ok := overlay.(*websocket.Tunnel); ok { + // websocket overlay + select { + case conn := <-s.wsChan: + return conn, nil + case <-s.ctx.Done(): + return nil, io.EOF + case err := <-s.errChan: + return nil, err + } + } + // trojan overlay + select { + case conn := <-s.connChan: + return conn, nil + case <-s.ctx.Done(): + return nil, io.EOF + case err := <-s.errChan: + return nil, err + } +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + return nil, common.NewError("not supported") +} + +// NewServer creates a transport layer server +func NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) { + cfg := config.FromContext(ctx, Name).(*Config) + + listenAddress := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort) + fallbackAddress := tunnel.NewAddressFromHostPort("tcp", cfg.TLS.FallbackHost, cfg.TLS.FallbackPort) + + tcpListener, err := net.Listen("tcp", listenAddress.String()) + if err != nil { + return nil, err + } + ctx, cancel := context.WithCancel(ctx) + server := &Server{ + fallbackAddress: fallbackAddress, + redir: redirector.NewRedirector(ctx), + ctx: ctx, + cancel: cancel, + tcpListener: tcpListener, + connChan: make(chan tunnel.Conn, 32), + wsChan: make(chan tunnel.Conn, 32), + sni: cfg.TLS.SNI, + alpn: cfg.TLS.ALPN, + } + + if cfg.TLS.KeyLogPath != "" { + log.Warn("tls key logging activated. USE OF KEY LOGGING COMPROMISES SECURITY. IT SHOULD ONLY BE USED FOR DEBUGGING.") + file, err := os.OpenFile(cfg.TLS.KeyLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return nil, common.NewError("failed to open key log file").Base(err) + } + server.keyLogger = file + } + if cfg.TLS.KeyPassword != "" { + keyFile, err := ioutil.ReadFile(cfg.TLS.KeyPath) + if err != nil { + return nil, common.NewError("failed to load key file").Base(err) + } + keyBlock, _ := pem.Decode(keyFile) + if keyBlock == nil { + return nil, common.NewError("failed to decode key file").Base(err) + } + decryptedKey, err := x509.DecryptPEMBlock(keyBlock, []byte(cfg.TLS.KeyPassword)) + if err == nil { + return nil, common.NewError("failed to decrypt key").Base(err) + } + + certFile, err := ioutil.ReadFile(cfg.TLS.CertPath) + certBlock, _ := pem.Decode(certFile) + if certBlock == nil { + return nil, common.NewError("failed to decode cert file").Base(err) + } + + keyPair, err := tls.X509KeyPair(certBlock.Bytes, decryptedKey) + if err != nil { + return nil, err + } + + server.keyPair = []tls.Certificate{keyPair} + } else { + if len(cfg.TLS.CertBytes) != 0 { + keyPair, err := tls.X509KeyPair(cfg.TLS.CertBytes, cfg.TLS.KeyBytes) + if err != nil { + return nil, err + } + server.keyPair = []tls.Certificate{keyPair} + } else { + keyPair, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath) + if err != nil { + return nil, common.NewError("failed to load key pair").Base(err) + } + server.keyPair = []tls.Certificate{keyPair} + } + } + go server.acceptLoop() + log.Debug("transport server created") + return server, nil +} diff --git a/tunnel/transport/transport_test.go b/tunnel/transport/transport_test.go new file mode 100644 index 0000000..13a303e --- /dev/null +++ b/tunnel/transport/transport_test.go @@ -0,0 +1,114 @@ +package transport + +import ( + "context" + "github.com/p4gefau1t/trojan-go/config" + "net" + "sync" + "testing" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/test/util" +) + +var cert string = ` +-----BEGIN CERTIFICATE----- +MIIDZTCCAk0CFFphZh018B5iAD9F5fV4y0AlD0LxMA0GCSqGSIb3DQEBCwUAMG8x +CzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARNYXJzMRMwEQYDVQQHDAppVHJhbnN3YXJw +MRMwEQYDVQQKDAppVHJhbnN3YXJwMRMwEQYDVQQLDAppVHJhbnN3YXJwMRIwEAYD +VQQDDAlsb2NhbGhvc3QwHhcNMjAwMzMxMTAwMDUxWhcNMzAwMzI5MTAwMDUxWjBv +MQswCQYDVQQGEwJVUzENMAsGA1UECAwETWFyczETMBEGA1UEBwwKaVRyYW5zd2Fy +cDETMBEGA1UECgwKaVRyYW5zd2FycDETMBEGA1UECwwKaVRyYW5zd2FycDESMBAG +A1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ml44fThYMkCcT627o7ibEs7mq2WOhImjDwYijYJ1684BatrCsHJNcw8PJGTuP+tg +GdngmALjA3l+RipjaE/UK4FJrAjruphA/hOCjZfWqk8KBR4qk0OltxCMWJlp/XCM +9ny1ogFdWUlBbqThs4NWSOUESgxf/Be2njeiOrngGR31qxSiLCLBvafIhKqq/4av +Rlx0Ht770uvF97MlAj1ASAvzTZICHAfUZxEdWl0J4MBbG7SNcnMBbyAF+s60eFTa +4RGMfRGnUa2Fzz/gfjhvfSIGeLQ3JRG6sl6jkc5xe0PZzhq3UNpK0gtQ48yy9CSP +neZnrynoKks7XC2bizsr3QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAHS/xuG5+F +yGU3N6V4kv+HbKqHaXNOq4zKVsCc1k7vg4MFFpKUJKxtJYooCI8n2ypp5XRUTIGQ +bmEbVcIPqm9Rf/4vHtF0falNCwieAbXDkiEHoykRmmU1UE/ccPA7X8NO9aVLJAJO +N2Li8MH0Ixgs02pQH56eyGKoRBWPR5C3ETQ9Leqvazg6Dn1iJWvmfF0mOte5228s +mZJOntF9t8MZOJdIWGdrUHn6euRfhd0btkmL/NUDzeCTwJcuPORLxkBbCP5mTC6G +GnLS5Z4oRYgCgvT2pLtcM0r48hYjwgjXFQ4zalkW6YI9LPpqwwMhhOzINlXjBaDi +Haz8uKI4EciU +-----END CERTIFICATE----- +` + +var key string = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAml44fThYMkCcT627o7ibEs7mq2WOhImjDwYijYJ1684BatrC +sHJNcw8PJGTuP+tgGdngmALjA3l+RipjaE/UK4FJrAjruphA/hOCjZfWqk8KBR4q +k0OltxCMWJlp/XCM9ny1ogFdWUlBbqThs4NWSOUESgxf/Be2njeiOrngGR31qxSi +LCLBvafIhKqq/4avRlx0Ht770uvF97MlAj1ASAvzTZICHAfUZxEdWl0J4MBbG7SN +cnMBbyAF+s60eFTa4RGMfRGnUa2Fzz/gfjhvfSIGeLQ3JRG6sl6jkc5xe0PZzhq3 +UNpK0gtQ48yy9CSPneZnrynoKks7XC2bizsr3QIDAQABAoIBAFpYUo9W7qdakSFA ++NS1Mm0rkm01nteLBlfAq3BOrl030DSNm+xQuWthoOcX+yiFxVTb40qURfC+plzC +ajOepPphTJDXF7+5ZDBPktTzzLsYTzD3mstdiBtAICOqhhHCUX3hNxx91/htm1H6 +Re4eK921y3DbFUIhTswCm3vrVXDc4yTXtURGllVzo40K/1Of39CpufKFdpJ81HV+ +h/VW++h3o+sFV4KqcqIjClxBfDxoJpBaRlOCunTiHqZNvqO+EPqPR5zdn34werjU +xQEvPzmz+ClwnaEXQxYWgIcYQii9VNsHogDxEw4R31S7lVrUt0f0atDmGJip1lPb +E7IomAECgYEAzKQ3PzBV46nUNfVO9SODpf14Z+xYfLKouPC+Qnepwp0V0JS6zY1+ +Wzskyb80drjnoQraWSEvGsX+tEWeLcnjN7JuMu/U8DPKRcQ+Q2dsVo/q4sfBOgvl +VhPNMZLfa7NIkRUx2KXku++Ep0Xtak0dskrfQrZnvhymRPyWuIMM6IECgYEAwRwL +Gt/ZZdUueE/hwT3c1hNn6igeDLOwK2t6frib+Ofw5oCAQxtTROvP1ljlnWUPkeIS +uzTusmqucalcK3lCHIsyHLwApOI/B31M971pxMVBRZ0wIbBaoarCGND7gi8JUPFR +VErGcAB5YnpRlmfLPEgw2o7DpjsDc2KmdE9oNV0CgYEAmfNEWLYtNztxGTK1treD +96ELLutf2lexlIgQKgLJ5E22tpbdPXwfvdRtpZTBjDsojj+S6hCL1lFzfv0MtZe2 +5xTF0G4avKXJmti6moy4tRpJ81ehZuDCJBJ7gLrkd6qFghf2yuxqenQDUK/Lnvfq +ylGHSjHdM+lrsGRxotd8I4ECgYBoo4GA9nseqv2bQ+3YgGUBu1I7l7FwwI1decfO +ksoxfb0Tqd3WfyAH4J+mTlVdjD17lzz/JBeTpisQe+ztwa8JOIPW/ih7L/1nWYYz +V/fQH/LWfe5u0tjJcXXrbJJcYJBzw8+GFV6hoiAkNJOxJF0ENToDtAhgMuoTxAje +TYjyIQKBgQCmHkLLq0Bj3FpIOVrwo2gNvQteNPa7jkkGp4lljO8JQUHhCHDGWKEH +MUJ0EFsxS/EaQa+rW6jHhs3GyBA2TxmC783stAOOEX+hO/zpcbzdCWgp6eZ0aGMW +WS94/5WE/lwHJi8ZPSjH1AURCzXhUi4fGvBrNBtry95e+jcEvP5c0g== +-----END RSA PRIVATE KEY----- +` + +func TestTLS(t *testing.T) { + serverCfg := &Config{ + LocalHost: "127.0.0.1", + LocalPort: common.PickPort("tcp", "127.0.0.1"), + RemoteHost: "127.0.0.1", + RemotePort: common.PickPort("tcp", "127.0.0.1"), + TLS: TLSConfig{ + KeyBytes: []byte(key), + CertBytes: []byte(cert), + }, + } + clientCfg := &Config{ + LocalHost: "127.0.0.1", + LocalPort: common.PickPort("tcp", "127.0.0.1"), + RemoteHost: "127.0.0.1", + RemotePort: serverCfg.LocalPort, + TLS: TLSConfig{ + Verify: false, + }, + } + sctx := config.WithConfig(context.Background(), Name, serverCfg) + cctx := config.WithConfig(context.Background(), Name, clientCfg) + + s, err := NewServer(sctx, nil) + common.Must(err) + c, err := NewClient(cctx, nil) + common.Must(err) + + wg := sync.WaitGroup{} + wg.Add(1) + var conn1, conn2 net.Conn + go func() { + conn2, err = s.AcceptConn(nil) + common.Must(err) + wg.Done() + }() + conn1, err = c.DialConn(nil, nil) + common.Must(err) + + common.Must2(conn1.Write([]byte("12345678\r\n"))) + wg.Wait() + buf := [10]byte{} + conn2.Read(buf[:]) + if !util.CheckConn(conn1, conn2) { + t.Fail() + } +} diff --git a/tunnel/transport/tunnel.go b/tunnel/transport/tunnel.go new file mode 100644 index 0000000..019cc78 --- /dev/null +++ b/tunnel/transport/tunnel.go @@ -0,0 +1,27 @@ +package transport + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "TRANSPORT" + +type Tunnel struct{} + +func (*Tunnel) Name() string { + return Name +} + +func (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { + return NewClient(ctx, client) +} + +func (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, server) +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/trojan/client.go b/tunnel/trojan/client.go new file mode 100644 index 0000000..5af7267 --- /dev/null +++ b/tunnel/trojan/client.go @@ -0,0 +1,154 @@ +package trojan + +import ( + "bytes" + "context" + "github.com/p4gefau1t/trojan-go/tunnel/mux" + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/statistic" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const ( + MaxPacketSize = 1024 * 8 +) + +const ( + Connect tunnel.Command = 1 + Associate tunnel.Command = 3 + Mux tunnel.Command = 0x7f +) + +type OutboundConn struct { + metadata *tunnel.Metadata + sent uint64 + recv uint64 + auth statistic.Authenticator + user statistic.User + headerWritten bool + net.Conn +} + +func (c *OutboundConn) Metadata() *tunnel.Metadata { + return c.metadata +} + +func (c *OutboundConn) WriteHeader() error { + if !c.headerWritten { + users := c.auth.ListUsers() + if len(users) == 0 { + return common.NewError("no password found") + } + user := users[0] + hash := user.Hash() + c.user = user + buf := bytes.NewBuffer(make([]byte, 0, 128)) + crlf := []byte{0x0d, 0x0a} + buf.Write([]byte(hash)) + buf.Write(crlf) + c.metadata.WriteTo(buf) + buf.Write(crlf) + _, err := c.Conn.Write(buf.Bytes()) + c.headerWritten = true + return err + /* + // stick the payload after the trojan request header + _, err := c.Conn.Write(append(buf.Bytes(), p...)) + c.meter.AddTraffic(len(p)+len(buf.Bytes()), 0) + c.sent += uint64(len(p) + len(buf.Bytes())) + c.headerWritten = true + log.Debug("trojan header and payload flushed") + return len(p), err + */ + } + return common.NewError("header is already written") +} + +func (c *OutboundConn) Write(p []byte) (int, error) { + if !c.headerWritten { + c.WriteHeader() + } + n, err := c.Conn.Write(p) + c.user.AddTraffic(n, 0) + c.sent += uint64(n) + return n, err +} + +func (c *OutboundConn) Read(p []byte) (int, error) { + n, err := c.Conn.Read(p) + c.user.AddTraffic(0, n) + c.recv += uint64(n) + return n, err +} + +func (c *OutboundConn) Close() error { + log.Info("connection to", c.metadata, "closed", "sent:", common.HumanFriendlyTraffic(c.sent), "recv:", common.HumanFriendlyTraffic(c.recv)) + return c.Conn.Close() +} + +type Client struct { + underlay tunnel.Client + ctx context.Context + auth statistic.Authenticator +} + +func (c *Client) Close() error { + return c.underlay.Close() +} + +func (c *Client) DialConn(addr *tunnel.Address, overlay tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := c.underlay.DialConn(addr, &Tunnel{}) + if err != nil { + return nil, err + } + newConn := &OutboundConn{ + Conn: conn, + auth: c.auth, + metadata: &tunnel.Metadata{ + Command: Connect, + Address: addr, + }, + } + if _, ok := overlay.(*mux.Tunnel); ok { + newConn.metadata.Command = Mux + } + newConn.WriteHeader() + return newConn, nil +} + +func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + fakeAddr := &tunnel.Address{ + DomainName: "UDP_CONN", + AddressType: tunnel.DomainName, + } + conn, err := c.underlay.DialConn(fakeAddr, &Tunnel{}) + if err != nil { + return nil, err + } + return &PacketConn{ + Conn: &OutboundConn{ + Conn: conn, + auth: c.auth, + metadata: &tunnel.Metadata{ + Command: Associate, + Address: fakeAddr, + }, + }, + }, nil +} + +func NewClient(ctx context.Context, client tunnel.Client) (*Client, error) { + auth, err := statistic.NewAuthenticator(ctx, "memory") + if err != nil { + return nil, err + } + log.Debug("trojan client created") + return &Client{ + underlay: client, + ctx: ctx, + auth: auth, + }, nil +} diff --git a/tunnel/trojan/config.go b/tunnel/trojan/config.go new file mode 100644 index 0000000..a2aa521 --- /dev/null +++ b/tunnel/trojan/config.go @@ -0,0 +1,16 @@ +package trojan + +import "github.com/p4gefau1t/trojan-go/config" + +type Config struct { + LocalHost string `json:"local_addr" yaml:"local-addr"` + LocalPort int `json:"local_port" yaml:"local-port"` + RemoteHost string `json:"remote_addr" yaml:"remote-addr"` + RemotePort int `json:"remote_port" yaml:"remote-port"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return &Config{} + }) +} diff --git a/tunnel/trojan/packet.go b/tunnel/trojan/packet.go new file mode 100644 index 0000000..39c1dab --- /dev/null +++ b/tunnel/trojan/packet.go @@ -0,0 +1,83 @@ +package trojan + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +type PacketConn struct { + tunnel.Conn +} + +func (c *PacketConn) ReadFrom(payload []byte) (int, net.Addr, error) { + return c.ReadWithMetadata(payload) +} + +func (c *PacketConn) WriteTo(payload []byte, addr net.Addr) (int, error) { + address, err := tunnel.NewAddressFromAddr("udp", addr.String()) + if err != nil { + return 0, err + } + m := &tunnel.Metadata{ + Address: address, + } + return c.WriteWithMetadata(payload, m) +} + +func (c *PacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata) (int, error) { + packet := make([]byte, 0, MaxPacketSize) + w := bytes.NewBuffer(packet) + metadata.Address.WriteTo(w) + + length := len(payload) + lengthBuf := [2]byte{} + crlf := [2]byte{0x0d, 0x0a} + + binary.BigEndian.PutUint16(lengthBuf[:], uint16(length)) + w.Write(lengthBuf[:]) + w.Write(crlf[:]) + w.Write(payload) + + _, err := c.Conn.Write(w.Bytes()) + return len(payload), err +} + +func (c *PacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, error) { + addr := &tunnel.Address{ + NetworkType: "udp", + } + if err := addr.ReadFrom(c.Conn); err != nil { + return 0, nil, common.NewError("failed to parse udp packet addr").Base(err) + } + lengthBuf := [2]byte{} + _, err := io.ReadFull(c.Conn, lengthBuf[:]) + if err != nil { + return 0, nil, common.NewError("failed to read length") + } + length := int(binary.BigEndian.Uint16(lengthBuf[:])) + + crlf := [2]byte{} + io.ReadFull(c.Conn, crlf[:]) + if err != nil { + return 0, nil, common.NewError("failed to read crlf") + } + + if len(payload) < int(length) || length > MaxPacketSize { + io.CopyN(ioutil.Discard, c.Conn, int64(length)) + return 0, nil, common.NewError("incoming packet size is too large") + } + _, err = io.ReadFull(c.Conn, payload[:length]) + if err != nil { + return 0, nil, common.NewError("failed to read payload") + } + + return length, &tunnel.Metadata{ + Address: addr, + }, err +} diff --git a/tunnel/trojan/server.go b/tunnel/trojan/server.go new file mode 100644 index 0000000..5c39517 --- /dev/null +++ b/tunnel/trojan/server.go @@ -0,0 +1,212 @@ +package trojan + +import ( + "context" + "fmt" + "io" + "net" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/redirector" + "github.com/p4gefau1t/trojan-go/statistic" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/mux" +) + +// InboundConn is a trojan inbound connection +type InboundConn struct { + net.Conn + sent uint64 + recv uint64 + auth statistic.Authenticator + user statistic.User + hash string + metadata *tunnel.Metadata +} + +func (c *InboundConn) Metadata() *tunnel.Metadata { + return c.metadata +} + +func (c *InboundConn) Write(p []byte) (int, error) { + n, err := c.Conn.Write(p) + c.sent += uint64(n) + c.user.AddTraffic(n, 0) + return n, err +} + +func (c *InboundConn) Read(p []byte) (int, error) { + n, err := c.Conn.Read(p) + c.recv += uint64(n) + c.user.AddTraffic(0, n) + return n, err +} + +func (c *InboundConn) Close() error { + log.Info("user", c.hash, "from", c.Conn.RemoteAddr(), "tunneling to", c.metadata.Address, "closed", "sent:", common.HumanFriendlyTraffic(c.sent), "recv:", common.HumanFriendlyTraffic(c.recv)) + c.user.DelIP(c.Conn.RemoteAddr().String()) + return c.Conn.Close() +} + +func (c *InboundConn) Auth() error { + userHash := [56]byte{} + n, err := c.Conn.Read(userHash[:]) + if err != nil || n != 56 { + return common.NewError("failed to read hash").Base(err) + } + + valid, user := c.auth.AuthUser(string(userHash[:])) + if !valid { + return common.NewError("invalid hash:" + string(userHash[:])) + } + c.hash = string(userHash[:]) + c.user = user + + ok := user.AddIP(c.Conn.RemoteAddr().String()) + if !ok { + return common.NewError("ip limit reached") + } + + crlf := [2]byte{} + _, err = io.ReadFull(c.Conn, crlf[:]) + if err != nil { + return err + } + + c.metadata = &tunnel.Metadata{} + if err := c.metadata.ReadFrom(c.Conn); err != nil { + return err + } + + _, err = io.ReadFull(c.Conn, crlf[:]) + if err != nil { + return err + } + return nil +} + +// Server is a trojan tunnel server +type Server struct { + auth statistic.Authenticator + redir redirector.Redirector + redirAddr *tunnel.Address + underlay tunnel.Server + connChan chan tunnel.Conn + muxChan chan tunnel.Conn + packetChan chan tunnel.PacketConn + errChan chan error + ctx context.Context +} + +func (s *Server) Close() error { + return s.underlay.Close() +} + +func (s *Server) acceptLoop() { + for { + conn, err := s.underlay.AcceptConn(&Tunnel{}) + if err != nil { // Closing + log.Debug(err) + select { + case <-s.ctx.Done(): + return + default: + } + continue + } + go func(conn tunnel.Conn) { + rewindConn := common.NewRewindConn(conn) + rewindConn.SetBufferSize(128) + defer rewindConn.StopBuffering() + + inboundConn := &InboundConn{ + Conn: rewindConn, + auth: s.auth, + } + + if err := inboundConn.Auth(); err != nil { + rewindConn.Rewind() + rewindConn.StopBuffering() + log.Warn(common.NewError("connection with invalid trojan header from " + rewindConn.RemoteAddr().String()).Base(err)) + s.redir.Redirect(&redirector.Redirection{ + RedirectTo: s.redirAddr, + InboundConn: rewindConn, + }) + return + } + + rewindConn.StopBuffering() + switch inboundConn.metadata.Command { + case Connect: + s.connChan <- inboundConn + log.Debug("normal trojan connection") + case Associate: + s.packetChan <- &PacketConn{ + Conn: inboundConn, + } + log.Debug("trojan udp connection") + case Mux: + s.muxChan <- inboundConn + log.Debug("mux connection") + default: + s.errChan <- common.NewError(fmt.Sprintf("unknown trojan command %d", inboundConn.metadata.Command)) + } + }(conn) + } +} + +func (s *Server) AcceptConn(nextTunnel tunnel.Tunnel) (tunnel.Conn, error) { + switch nextTunnel.(type) { + case *mux.Tunnel: + select { + case t := <-s.muxChan: + return t, nil + case err := <-s.errChan: + return nil, err + case <-s.ctx.Done(): + return nil, common.NewError("trojan client closed") + } + default: + select { + case t := <-s.connChan: + return t, nil + case err := <-s.errChan: + return nil, err + case <-s.ctx.Done(): + return nil, common.NewError("trojan client closed") + } + } +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + select { + case t := <-s.packetChan: + return t, nil + case <-s.ctx.Done(): + return nil, common.NewError("trojan client closed") + } +} + +func NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { + auth, err := statistic.NewAuthenticator(ctx, "memory") + if err != nil { + return nil, common.NewError("failed to create server authenticator").Base(err) + } + cfg := config.FromContext(ctx, Name).(*Config) + redirAddr := tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort) + s := &Server{ + underlay: underlay, + auth: auth, + ctx: ctx, + redirAddr: redirAddr, + connChan: make(chan tunnel.Conn, 32), + muxChan: make(chan tunnel.Conn, 32), + packetChan: make(chan tunnel.PacketConn, 32), + errChan: make(chan error, 32), + } + go s.acceptLoop() + log.Debug("trojan server created") + return s, nil +} diff --git a/tunnel/trojan/trojan_test.go b/tunnel/trojan/trojan_test.go new file mode 100644 index 0000000..944d342 --- /dev/null +++ b/tunnel/trojan/trojan_test.go @@ -0,0 +1,103 @@ +package trojan + +import ( + "context" + "fmt" + "testing" + + "github.com/p4gefau1t/trojan-go/test/util" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/raw" + + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/statistic" +) + +type MockUser struct { + statistic.User +} + +func (*MockUser) AddIP(string) bool { + return true +} +func (*MockUser) DelIP(string) bool { + return true +} + +func (*MockUser) Hash() string { + return common.SHA224String("user") +} + +func (*MockUser) AddTraffic(sent, recv int) {} + +type MockAuth struct { + statistic.Authenticator +} + +func (*MockAuth) AuthUser(hash string) (bool, statistic.User) { + if hash == common.SHA224String("user") { + return true, &MockUser{} + } + return false, nil +} + +func (*MockAuth) ListUsers() []statistic.User { + return []statistic.User{&MockUser{}} +} + +func TestTrojan(t *testing.T) { + addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", common.PickPort("tcp", "127.0.0.1")) + tcpClient := &raw.FixedClient{ + FixedAddr: addr, + } + tcpServer, err := raw.NewServer(addr) + ctx := context.Background() + s := &Server{ + underlay: tcpServer, + auth: &MockAuth{}, + ctx: ctx, + redirAddr: nil, + connChan: make(chan tunnel.Conn, 32), + muxChan: make(chan tunnel.Conn, 32), + packetChan: make(chan tunnel.PacketConn, 32), + } + go s.acceptLoop() + c := &Client{ + underlay: tcpClient, + ctx: ctx, + auth: &MockAuth{}, + } + + conn1, err := c.DialConn(&tunnel.Address{ + DomainName: "example.com", + AddressType: tunnel.DomainName, + }, nil) + common.Must2(conn1.Write([]byte("12345678"))) + conn2, err := s.AcceptConn(nil) + buf := [8]byte{} + conn2.Read(buf[:]) + if !util.CheckConn(conn1, conn2) { + t.Fail() + } + + packet1, err := c.DialPacket(nil) + common.Must(err) + packet1.WriteWithMetadata([]byte("12345678"), &tunnel.Metadata{ + Address: &tunnel.Address{ + DomainName: "example.com", + AddressType: tunnel.DomainName, + Port: 80, + }, + }) + packet2, err := s.AcceptPacket(nil) + common.Must(err) + + _, m, err := packet2.ReadWithMetadata(buf[:]) + common.Must(err) + + fmt.Println(m) + + if !util.CheckPacketOverConn(packet1, packet2) { + t.Fail() + } +} diff --git a/tunnel/trojan/tunnel.go b/tunnel/trojan/tunnel.go new file mode 100644 index 0000000..08959b7 --- /dev/null +++ b/tunnel/trojan/tunnel.go @@ -0,0 +1,27 @@ +package trojan + +import ( + "context" + + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "TROJAN" + +type Tunnel struct{} + +func (c *Tunnel) Name() string { + return Name +} + +func (c *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { + return NewClient(ctx, client) +} + +func (c *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, server) +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go new file mode 100644 index 0000000..6f5484e --- /dev/null +++ b/tunnel/tunnel.go @@ -0,0 +1,89 @@ +package tunnel + +import ( + "context" + "io" + "net" + + "github.com/p4gefau1t/trojan-go/common" +) + +// Conn is the TCP connection in the tunnel +type Conn interface { + net.Conn + Metadata() *Metadata +} + +// PacketConn is the UDP packet stream in the tunnel +type PacketConn interface { + net.PacketConn + WriteWithMetadata([]byte, *Metadata) (int, error) + ReadWithMetadata([]byte) (int, *Metadata, error) +} + +// ConnDialer creates TCP connections from the tunnel +type ConnDialer interface { + DialConn(*Address, Tunnel) (Conn, error) +} + +// PacketDialer creates UDP packet stream from the tunnel +type PacketDialer interface { + DialPacket(Tunnel) (PacketConn, error) +} + +// ConnListener accept TCP connections +type ConnListener interface { + AcceptConn(Tunnel) (Conn, error) +} + +// PacketListener accept UDP packet stream +// We don't have any tunnel based on packet streams, so AcceptPacket will always recieve a real PacketConn +type PacketListener interface { + AcceptPacket(Tunnel) (PacketConn, error) +} + +// Dialer can dial to original server with a tunnel +type Dialer interface { + ConnDialer + PacketDialer +} + +// Listener can accept TCP and UDP streams from a tunnel +type Listener interface { + ConnListener + PacketListener +} + +// Client is the tunnel client based on stream connections +type Client interface { + Dialer + io.Closer +} + +// Server is the tunnel server based on stream connections +type Server interface { + Listener + io.Closer +} + +// Tunnel describes a tunnel, allowing creating a tunnel from another tunnel +// We assume that the lower tunnels know exatly how upper tunnels work, and lower tunnels is transparent for the upper tunnels +type Tunnel interface { + Name() string + NewClient(context.Context, Client) (Client, error) + NewServer(context.Context, Server) (Server, error) +} + +var tunnels = make(map[string]Tunnel) + +// RegisterTunnel register a tunnel by tunnel name +func RegisterTunnel(name string, tunnel Tunnel) { + tunnels[name] = tunnel +} + +func GetTunnel(name string) (Tunnel, error) { + if t, ok := tunnels[name]; ok { + return t, nil + } + return nil, common.NewError("unknown tunnel name " + string(name)) +} diff --git a/tunnel/websocket/client.go b/tunnel/websocket/client.go new file mode 100644 index 0000000..0f35a53 --- /dev/null +++ b/tunnel/websocket/client.go @@ -0,0 +1,58 @@ +package websocket + +import ( + "context" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/tunnel" + "golang.org/x/net/websocket" + "strings" +) + +type Client struct { + underlay tunnel.Client + hostname string + path string +} + +func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := c.underlay.DialConn(nil, &Tunnel{}) + if err != nil { + return nil, common.NewError("websocket cannot dial with underlying client").Base(err) + } + url := "wss://" + c.hostname + c.path + origin := "https://" + c.hostname + wsConfig, err := websocket.NewConfig(url, origin) + if err != nil { + return nil, common.NewError("invalid websocket config").Base(err) + } + wsConn, err := websocket.NewClient(wsConfig, conn) + if err != nil { + return nil, common.NewError("websocket failed to handshake with server").Base(err) + } + return &OutboundConn{ + Conn: wsConn, + }, nil +} + +func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + return nil, common.NewError("not supported by websocket") +} + +func (c *Client) Close() error { + return c.underlay.Close() +} + +func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { + cfg := config.FromContext(ctx, Name).(*Config) + if !strings.HasPrefix(cfg.Websocket.Path, "/") { + return nil, common.NewError("websocket path must start with \"/\"") + } + log.Debug("websocket client created") + return &Client{ + hostname: cfg.Websocket.Hostname, + path: cfg.Websocket.Path, + underlay: underlay, + }, nil +} diff --git a/tunnel/websocket/config.go b/tunnel/websocket/config.go new file mode 100644 index 0000000..5d64ce6 --- /dev/null +++ b/tunnel/websocket/config.go @@ -0,0 +1,21 @@ +package websocket + +import "github.com/p4gefau1t/trojan-go/config" + +type WebsocketConfig struct { + Enabled bool `json,yaml:"enabled""` + Hostname string `json,yaml:"hostname"` + Path string `json,yaml:"path"` +} + +type Config struct { + RemoteHost string `json:"remote_addr" yaml:"remote-addr"` + RemotePort int `json:"remote_port" yaml:"remote-port"` + Websocket WebsocketConfig `json,yaml:"websocket"` +} + +func init() { + config.RegisterConfigCreator(Name, func() interface{} { + return new(Config) + }) +} diff --git a/tunnel/websocket/conn.go b/tunnel/websocket/conn.go new file mode 100644 index 0000000..7bf2461 --- /dev/null +++ b/tunnel/websocket/conn.go @@ -0,0 +1,26 @@ +package websocket + +import ( + "context" + "github.com/p4gefau1t/trojan-go/tunnel" + "golang.org/x/net/websocket" +) + +type OutboundConn struct { + *websocket.Conn +} + +func (c *OutboundConn) Metadata() *tunnel.Metadata { + return nil +} + +type InboundConn struct { + OutboundConn + ctx context.Context + cancel context.CancelFunc +} + +func (c *InboundConn) Close() error { + c.cancel() + return c.Conn.Close() +} diff --git a/tunnel/websocket/server.go b/tunnel/websocket/server.go new file mode 100644 index 0000000..5cd28b1 --- /dev/null +++ b/tunnel/websocket/server.go @@ -0,0 +1,159 @@ +package websocket + +import ( + "bufio" + "context" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/log" + "github.com/p4gefau1t/trojan-go/redirector" + "github.com/p4gefau1t/trojan-go/tunnel" + "golang.org/x/net/websocket" + "math/rand" + "net" + "net/http" + "strings" + "time" +) + +// Fake response writer +// Websocket ServeHTTP method uses Hijack method to get the ReadWriter +type fakeHttpResponseWriter struct { + http.Hijacker + http.ResponseWriter + + ReadWriter *bufio.ReadWriter + Conn net.Conn +} + +func (w *fakeHttpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return w.Conn, w.ReadWriter, nil +} + +type Server struct { + underlay tunnel.Server + hostname string + path string + enabled bool + redirAddr net.Addr + redir *redirector.Redirector + ctx context.Context + cancel context.CancelFunc + timeout time.Duration +} + +func (s *Server) Close() error { + s.cancel() + return s.underlay.Close() +} + +func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { + conn, err := s.underlay.AcceptConn(&Tunnel{}) + if err != nil { + return nil, common.NewError("websocket failed to accept connection from underlying server") + } + if !s.enabled { + s.redir.Redirect(&redirector.Redirection{ + InboundConn: conn, + RedirectTo: s.redirAddr, + }) + return nil, common.NewError("websocket is disabled. redirecting http request from " + conn.RemoteAddr().String()) + } + rewindConn := common.NewRewindConn(conn) + rewindConn.SetBufferSize(512) + defer rewindConn.StopBuffering() + rw := bufio.NewReadWriter(bufio.NewReader(rewindConn), bufio.NewWriter(rewindConn)) + req, err := http.ReadRequest(rw.Reader) + + if err != nil { + rewindConn.Rewind() + s.redir.Redirect(&redirector.Redirection{ + InboundConn: rewindConn, + RedirectTo: s.redirAddr, + }) + return nil, common.NewError("not a valid http request: " + conn.RemoteAddr().String()).Base(err) + } + if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || req.URL.Path != s.path { + rewindConn.Rewind() + s.redir.Redirect(&redirector.Redirection{ + InboundConn: rewindConn, + RedirectTo: s.redirAddr, + }) + return nil, common.NewError("not a valid websocket handshake request: " + conn.RemoteAddr().String()).Base(err) + } + + handshake := make(chan struct{}) + + url := "wss://" + s.hostname + s.path + origin := "https://" + s.hostname + wsConfig, err := websocket.NewConfig(url, origin) + var wsConn *websocket.Conn + ctx, cancel := context.WithCancel(s.ctx) + + wsServer := websocket.Server{ + Config: *wsConfig, + Handler: func(conn *websocket.Conn) { + wsConn = conn //store the websocket after handshaking + log.Debug("websocket obtained") + handshake <- struct{}{} + // this function SHOULD NOT return unless the connection is ended + // or the websocket will be closed by ServeHTTP method + <-ctx.Done() + log.Debug("websocket closed") + }, + Handshake: func(wsConfig *websocket.Config, httpRequest *http.Request) error { + log.Debug("websocket url", httpRequest.URL, "origin", httpRequest.Header.Get("Origin")) + return nil + }, + } + + respWriter := &fakeHttpResponseWriter{ + Conn: conn, + ReadWriter: rw, + } + go wsServer.ServeHTTP(respWriter, req) + + select { + case <-handshake: + case <-time.After(s.timeout): + } + + if wsConn == nil { + cancel() + return nil, common.NewError("websocket failed to handshake") + } + + return &InboundConn{ + OutboundConn: OutboundConn{ + Conn: wsConn, + }, + ctx: ctx, + cancel: cancel, + }, nil +} + +func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { + return nil, common.NewError("not supported") +} + +func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { + cfg := config.FromContext(ctx, Name).(*Config) + if cfg.Websocket.Enabled { + if !strings.HasPrefix(cfg.Websocket.Path, "/") { + return nil, common.NewError("websocket path must start with \"/\"") + } + } + ctx, cancel := context.WithCancel(ctx) + log.Debug("websocket server created") + return &Server{ + enabled: cfg.Websocket.Enabled, + hostname: cfg.Websocket.Hostname, + path: cfg.Websocket.Path, + ctx: ctx, + cancel: cancel, + underlay: underlay, + timeout: time.Second * time.Duration(rand.Intn(10)+5), + redir: redirector.NewRedirector(ctx), + redirAddr: tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort), + }, nil +} diff --git a/tunnel/websocket/tunnel.go b/tunnel/websocket/tunnel.go new file mode 100644 index 0000000..b5e3a49 --- /dev/null +++ b/tunnel/websocket/tunnel.go @@ -0,0 +1,27 @@ +package websocket + +import ( + "context" + "github.com/p4gefau1t/trojan-go/tunnel" +) + +const Name = "WEBSOCKET" + +type Tunnel struct { +} + +func (*Tunnel) Name() string { + return Name +} + +func (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { + return NewServer(ctx, underlay) +} + +func (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) { + return NewClient(ctx, underlay) +} + +func init() { + tunnel.RegisterTunnel(Name, &Tunnel{}) +} diff --git a/tunnel/websocket/websocket_test.go b/tunnel/websocket/websocket_test.go new file mode 100644 index 0000000..c1b8f08 --- /dev/null +++ b/tunnel/websocket/websocket_test.go @@ -0,0 +1,45 @@ +package websocket + +import ( + "context" + "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/config" + "github.com/p4gefau1t/trojan-go/test/util" + "github.com/p4gefau1t/trojan-go/tunnel" + "github.com/p4gefau1t/trojan-go/tunnel/raw" + "sync" + "testing" +) + +func TestWebsocket(t *testing.T) { + cfg := &Config{ + Websocket: WebsocketConfig{ + Hostname: "localhost", + Path: "/ws", + }, + } + + ctx := config.WithConfig(context.Background(), Name, cfg) + + tcpClient := &raw.FixedClient{ + FixedAddr: tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", common.PickPort("tcp", "127.0.0.1")), + } + tcpServer, err := raw.NewServer(tcpClient.FixedAddr) + common.Must(err) + c, err := NewClient(ctx, tcpClient) + s, err := NewServer(ctx, tcpServer) + var conn2 tunnel.Conn + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + conn2, err = s.AcceptConn(nil) + common.Must(err) + wg.Done() + }() + conn1, err := c.DialConn(nil, nil) + common.Must(err) + wg.Wait() + if !util.CheckConn(conn1, conn2) { + t.Fail() + } +} diff --git a/version/version.go b/version/version.go index 15fd782..d60d489 100644 --- a/version/version.go +++ b/version/version.go @@ -3,6 +3,7 @@ package version import ( "flag" "fmt" + "github.com/p4gefau1t/trojan-go/option" "runtime" "github.com/p4gefau1t/trojan-go/common" @@ -33,7 +34,7 @@ func (c *versionOption) Handle() error { } func init() { - common.RegisterOptionHandler(&versionOption{ + option.RegisterOptionHandler(&versionOption{ flag: flag.Bool("version", false, "Display version and help info"), }) }