mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2024-04-21 12:21:34 +00:00
refactoring
This commit is contained in:
@@ -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(),
|
||||
})
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package control
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestControl(t *testing.T) {
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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){}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
protoc ./api.proto --go_out=plugins=grpc:.
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// +build api full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/api/control"
|
||||
_ "github.com/p4gefau1t/trojan-go/api/service"
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build cert full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/cert"
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
// +build client full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/proxy/client"
|
||||
_ "github.com/p4gefau1t/trojan-go/stat/memory"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build auth_mysql full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/stat/mysql"
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build auth_redis full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/stat/redis"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build relay full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/proxy/relay"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build router full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/router/mixed"
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
// +build server full
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
_ "github.com/p4gefau1t/trojan-go/proxy/server"
|
||||
_ "github.com/p4gefau1t/trojan-go/stat/memory"
|
||||
)
|
||||
-207
@@ -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
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
-133
@@ -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")
|
||||
}
|
||||
@@ -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"),
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-44
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+22
-159
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
-197
@@ -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
|
||||
}
|
||||
-540
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
})
|
||||
}
|
||||
-142
@@ -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"),
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+32
-368
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
+28
-49
@@ -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)"),
|
||||
})
|
||||
}
|
||||
|
||||
+162
-148
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
+58
-288
@@ -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
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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{}
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
-147
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user