refactoring

This commit is contained in:
Page Fault
2020-06-09 19:07:05 +00:00
parent 82a342a038
commit 3c869bfb77
145 changed files with 4813 additions and 10405 deletions
-181
View File
@@ -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(),
})
}
-7
View File
@@ -1,7 +0,0 @@
package control
import "testing"
func TestControl(t *testing.T) {
}
File diff suppressed because it is too large Load Diff
-88
View File
@@ -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){}
}
-93
View File
@@ -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)
}
-46
View File
@@ -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
View File
@@ -1 +0,0 @@
protoc ./api.proto --go_out=plugins=grpc:.
-206
View File
@@ -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)
}
-126
View File
@@ -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()
}
-8
View File
@@ -1,8 +0,0 @@
// +build api full
package build
import (
_ "github.com/p4gefau1t/trojan-go/api/control"
_ "github.com/p4gefau1t/trojan-go/api/service"
)
-9
View File
@@ -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
)
-7
View File
@@ -1,7 +0,0 @@
// +build cert full
package build
import (
_ "github.com/p4gefau1t/trojan-go/cert"
)
-8
View File
@@ -1,8 +0,0 @@
// +build client full
package build
import (
_ "github.com/p4gefau1t/trojan-go/proxy/client"
_ "github.com/p4gefau1t/trojan-go/stat/memory"
)
-7
View File
@@ -1,7 +0,0 @@
// +build auth_mysql full
package build
import (
_ "github.com/p4gefau1t/trojan-go/stat/mysql"
)
-9
View File
@@ -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"
)
-7
View File
@@ -1,7 +0,0 @@
// +build auth_redis full
package build
import (
_ "github.com/p4gefau1t/trojan-go/stat/redis"
)
-7
View File
@@ -1,7 +0,0 @@
// +build relay full
package build
import (
_ "github.com/p4gefau1t/trojan-go/proxy/relay"
)
-7
View File
@@ -1,7 +0,0 @@
// +build router full
package build
import (
_ "github.com/p4gefau1t/trojan-go/router/mixed"
)
-8
View File
@@ -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
View File
@@ -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
}
-21
View File
@@ -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
View File
@@ -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")
}
-49
View File
@@ -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"),
})
}
+6
View File
@@ -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
View File
@@ -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,
},
}
}
-36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
-96
View File
@@ -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)
}
+82
View File
@@ -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")
}
+47
View File
@@ -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()
}
}
-49
View File
@@ -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
View File
@@ -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"),
})
}
+3 -18
View File
@@ -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 -532
View File
@@ -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=
+9 -4
View File
@@ -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")
}
+4 -2
View File
@@ -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
-210
View File
@@ -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
}
-135
View File
@@ -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()
}
-142
View File
@@ -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
}
-134
View File
@@ -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
}
-74
View File
@@ -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
}
-233
View File
@@ -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
}
-151
View File
@@ -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
}
-148
View File
@@ -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
}
-74
View File
@@ -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
}
-70
View File
@@ -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
}
-328
View File
@@ -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
}
-55
View File
@@ -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
View File
@@ -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
})
}
+22
View File
@@ -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)
})
}
-212
View File
@@ -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{})
}
-256
View File
@@ -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
}
-168
View File
@@ -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{})
}
-458
View File
@@ -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
}
+15
View File
@@ -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)
})
}
+35
View File
@@ -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)
})
}
+35
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
-69
View File
@@ -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
View File
@@ -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
})
}
+73
View File
@@ -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
}
+77
View File
@@ -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
}
-189
View File
@@ -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
}
-65
View File
@@ -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")
}
}
-128
View File
@@ -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
}
-121
View File
@@ -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
}
-116
View File
@@ -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")
}
}
-41
View File
@@ -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
-89
View File
@@ -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
}
-35
View File
@@ -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
}
-37
View File
@@ -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
}
-17
View File
@@ -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
}
-35
View File
@@ -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
}
-24
View File
@@ -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
}
-103
View File
@@ -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()
}
-50
View File
@@ -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()
}
-104
View File
@@ -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)
}
+15
View File
@@ -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)
}
+30
View File
@@ -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)
}
+13 -8
View File
@@ -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)
}
-91
View File
@@ -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()
}
-622
View File
@@ -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)
}
-44
View File
@@ -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)
}
+116
View File
@@ -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
View File
@@ -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
}
+102
View File
@@ -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)
}
+19
View File
@@ -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,
}
})
}
+70
View File
@@ -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
}
+81
View File
@@ -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()
}
}
+144
View File
@@ -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
}
+27
View File
@@ -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{})
}
+213
View File
@@ -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
}
+176
View File
@@ -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
}
+26
View File
@@ -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,
},
}
})
}
+99
View File
@@ -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