package scenario import ( "bytes" "fmt" "net" "net/http" _ "net/http/pprof" "os" "sync" "testing" "time" netproxy "golang.org/x/net/proxy" _ "github.com/p4gefau1t/trojan-go/api" _ "github.com/p4gefau1t/trojan-go/api/service" "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/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/test/util" ) // test key and cert var cert = ` -----BEGIN CERTIFICATE----- MIIC5TCCAc2gAwIBAgIJAJqNVe6g/10vMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV BAMMCWxvY2FsaG9zdDAeFw0yMTA5MTQwNjE1MTFaFw0yNjA5MTMwNjE1MTFaMBQx EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAK7bupJ8tmHM3shQ/7N730jzpRsXdNiBxq/Jxx8j+vB3AcxuP5bjXQZqS6YR 5W5vrfLlegtq1E/mmaI3Ht0RfIlzev04Dua9PWmIQJD801nEPknbfgCLXDh+pYr2 sfg8mUh3LjGtrxyH+nmbTjWg7iWSKohmZ8nUDcX94Llo5FxibMAz8OsAwOmUueCH jP3XswZYHEy+OOP3K0ZEiJy0f5T6ZXk9OWYuPN4VQKJx1qrc9KzZtSPHwqVdkGUi ase9tOPA4aMutzt0btgW7h7UrvG6C1c/Rr1BxdiYq1EQ+yypnAlyToVQSNbo67zz wGQk4GeruIkOgJOLdooN/HjhbHMCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B AQsFAAOCAQEASsBzHHYiWDDiBVWUEwVZAduTrslTLNOxG0QHBKsHWIlz/3QlhQil ywb3OhfMTUR1dMGY5Iq5432QiCHO4IMCOv7tDIkgb4Bc3v/3CRlBlnurtAmUfNJ6 pTRSlK4AjWpGHAEEd/8aCaOE86hMP8WDht8MkJTRrQqpJ1HeDISoKt9nepHOIsj+ I2zLZZtw0pg7FuR4MzWuqOt071iRS46Pupryb3ZEGIWNz5iLrDQod5Iz2ZGSRGqE rB8idX0mlj5AHRRanVR3PAes+eApsW9JvYG/ImuCOs+ZsukY614zQZdR+SyFm85G 4NICyeQsmiypNHHgw+xZmGqZg65bXNGoyg== -----END CERTIFICATE----- ` var key = ` -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu27qSfLZhzN7I UP+ze99I86UbF3TYgcavyccfI/rwdwHMbj+W410GakumEeVub63y5XoLatRP5pmi Nx7dEXyJc3r9OA7mvT1piECQ/NNZxD5J234Ai1w4fqWK9rH4PJlIdy4xra8ch/p5 m041oO4lkiqIZmfJ1A3F/eC5aORcYmzAM/DrAMDplLngh4z917MGWBxMvjjj9ytG RIictH+U+mV5PTlmLjzeFUCicdaq3PSs2bUjx8KlXZBlImrHvbTjwOGjLrc7dG7Y Fu4e1K7xugtXP0a9QcXYmKtREPssqZwJck6FUEjW6Ou888BkJOBnq7iJDoCTi3aK Dfx44WxzAgMBAAECggEBAKYhib/H0ZhWB4yWuHqUxG4RXtrAjHlvw5Acy5zgmHiC +Sh7ztrTJf0EXN9pvWwRm1ldgXj7hMBtPaaLbD1pccM9/qo66p17Sq/LjlyyeTOe affOHIbz4Sij2zCOdkR9fr0EztTQScF3yBhl4Aa/4cO8fcCeWxm86WEldq9x4xWJ s5WMR4CnrOJhDINLNPQPKX92KyxEQ/RfuBWovx3M0nl3fcUWfESY134t5g/UBFId In19tZ+pGIpCkxP0U1AZWrlZRA8Q/3sO2orUpoAOdCrGk/DcCTMh0c1pMzbYZ1/i cYXn38MpUo8QeG4FElUhAv6kzeBIl2tRBMVzIigo+AECgYEA3No1rHdFu6Ox9vC8 E93PTZevYVcL5J5yx6x7khCaOLKKuRXpjOX/h3Ll+hlN2DVAg5Jli/JVGCco4GeK kbFLSyxG1+E63JbgsVpaEOgvFT3bHHSPSRJDnIU+WkcNQ2u4Ky5ahZzbNdV+4fj2 NO2iMgkm7hoJANrm3IqqW8epenMCgYEAyq+qdNj5DiDzBcDvLwY+4/QmMOOgDqeh /TzhbDRyr+m4xNT7LLS4s/3wcbkQC33zhMUI3YvOHnYq5Ze/iL/TSloj0QCp1I7L J7sZeM1XimMBQIpCfOC7lf4tU76Fz0DTHAL+CmX1DgmRJdYO09843VsKkscC968R 4cwL5oGxxgECgYAM4TTsH/CTJtLEIfn19qOWVNhHhvoMlSkAeBCkzg8Qa2knrh12 uBsU3SCIW11s1H40rh758GICDJaXr7InGP3ZHnXrNRlnr+zeqvRBtCi6xma23B1X F5eV0zd1sFsXqXqOGh/xVtp54z+JEinZoForLNl2XVJVGG8KQZP50kUR/QKBgH4O 8zzpFT0sUPlrHVdp0wODfZ06dPmoWJ9flfPuSsYN3tTMgcs0Owv3C+wu5UPAegxB X1oq8W8Qn21cC8vJQmgj19LNTtLcXI3BV/5B+Aghu02gr+lq/EA1bYuAG0jjUGlD kyx0bQzl9lhJ4b70PjGtxc2z6KyTPdPpTB143FABAoGAQDoIUdc77/IWcjzcaXeJ 8abak5rAZA7cu2g2NVfs+Km+njsB0pbTwMnV1zGoFABdaHLdqbthLWtX7WOb1PDD MQ+kbiLw5uj8IY2HEqJhDGGEdXBqxbW7kyuIAN9Mw+mwKzkikNcFQdxgchWH1d1o lVkr92iEX+IhIeYb4DN1vQw= -----END PRIVATE KEY----- ` func init() { os.WriteFile("server.crt", []byte(cert), 0o777) os.WriteFile("server.key", []byte(key), 0o777) } func CheckClientServer(clientData, serverData string, socksPort int) (ok bool) { server, err := proxy.NewProxyFromConfigData([]byte(serverData), false) common.Must(err) go server.Run() client, err := proxy.NewProxyFromConfigData([]byte(clientData), false) common.Must(err) go client.Run() time.Sleep(time.Second * 2) dialer, err := netproxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", socksPort), nil, netproxy.Direct) common.Must(err) ok = true const num = 100 wg := sync.WaitGroup{} wg.Add(num) for i := 0; i < num; i++ { go func() { const payloadSize = 1024 payload := util.GeneratePayload(payloadSize) buf := [payloadSize]byte{} conn, err := dialer.Dial("tcp", util.EchoAddr) common.Must(err) common.Must2(conn.Write(payload)) common.Must2(conn.Read(buf[:])) if !bytes.Equal(payload, buf[:]) { ok = false } conn.Close() wg.Done() }() } wg.Wait() client.Close() server.Close() return } func TestClientServerWebsocketSubTree(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` run-type: client local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d password: - password ssl: verify: false fingerprint: firefox sni: localhost websocket: enabled: true path: /ws host: somedomainname.com shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 mux: enabled: true `, socksPort, serverPort) serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %s disable-http-check: true password: - password ssl: verify-hostname: false key: server.key cert: server.crt sni: localhost shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 websocket: enabled: true path: /ws host: 127.0.0.1 `, serverPort, util.HTTPPort) if !CheckClientServer(clientData, serverData, socksPort) { t.Fail() } } func TestClientServerTrojanSubTree(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` run-type: client local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d password: - password ssl: verify: false fingerprint: firefox sni: localhost shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 mux: enabled: true `, socksPort, serverPort) serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %s disable-http-check: true password: - password ssl: verify-hostname: false key: server.key cert: server.crt sni: localhost shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 `, serverPort, util.HTTPPort) if !CheckClientServer(clientData, serverData, socksPort) { t.Fail() } } func TestWebsocketDetection(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` run-type: client local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d password: - password ssl: verify: false fingerprint: firefox sni: localhost shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 mux: enabled: true `, socksPort, serverPort) serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %s disable-http-check: true password: - password ssl: verify-hostname: false key: server.key cert: server.crt sni: localhost shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 websocket: enabled: true path: /ws hostname: 127.0.0.1 `, serverPort, util.HTTPPort) if !CheckClientServer(clientData, serverData, socksPort) { t.Fail() } } func TestPluginWebsocket(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` run-type: client local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d password: - password transport-plugin: enabled: true type: plaintext shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 mux: enabled: true websocket: enabled: true path: /ws hostname: 127.0.0.1 `, socksPort, serverPort) serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %s disable-http-check: true password: - password transport-plugin: enabled: true type: plaintext shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 websocket: enabled: true path: /ws hostname: 127.0.0.1 `, serverPort, util.HTTPPort) if !CheckClientServer(clientData, serverData, socksPort) { t.Fail() } } func TestForward(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") clientPort := common.PickPort("tcp", "127.0.0.1") _, targetPort, _ := net.SplitHostPort(util.EchoAddr) clientData := fmt.Sprintf(` run-type: forward local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d target-addr: 127.0.0.1 target-port: %s password: - password ssl: verify: false fingerprint: firefox sni: localhost websocket: enabled: true path: /ws hostname: 127.0.0.1 shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 mux: enabled: true `, clientPort, serverPort, targetPort) go func() { proxy, err := proxy.NewProxyFromConfigData([]byte(clientData), false) common.Must(err) common.Must(proxy.Run()) }() serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %s disable-http-check: true password: - password ssl: verify-hostname: false key: server.key cert: server.crt sni: "localhost" websocket: enabled: true path: /ws hostname: 127.0.0.1 shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 `, serverPort, util.HTTPPort) go func() { proxy, err := proxy.NewProxyFromConfigData([]byte(serverData), false) common.Must(err) common.Must(proxy.Run()) }() time.Sleep(time.Second * 2) payload := util.GeneratePayload(1024) buf := [1024]byte{} conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", clientPort)) common.Must(err) common.Must2(conn.Write(payload)) common.Must2(conn.Read(buf[:])) if !bytes.Equal(payload, buf[:]) { t.Fail() } packet, err := net.ListenPacket("udp", "") common.Must(err) common.Must2(packet.WriteTo(payload, &net.UDPAddr{ IP: net.ParseIP("127.0.0.1"), Port: clientPort, })) _, _, err = packet.ReadFrom(buf[:]) common.Must(err) if !bytes.Equal(payload, buf[:]) { t.Fail() } } func TestLeak(t *testing.T) { serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` run-type: client local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d log-level: 0 password: - password ssl: verify: false fingerprint: firefox sni: localhost shadowsocks: enabled: true method: AEAD_CHACHA20_POLY1305 password: 12345678 mux: enabled: true api: enabled: true api-port: 0 `, socksPort, serverPort) client, err := proxy.NewProxyFromConfigData([]byte(clientData), false) common.Must(err) go client.Run() time.Sleep(time.Second * 3) client.Close() time.Sleep(time.Second * 3) // http.ListenAndServe("localhost:6060", nil) } func SingleThreadBenchmark(clientData, serverData string, socksPort int) { server, err := proxy.NewProxyFromConfigData([]byte(clientData), false) common.Must(err) go server.Run() client, err := proxy.NewProxyFromConfigData([]byte(serverData), false) common.Must(err) go client.Run() time.Sleep(time.Second * 2) dialer, err := netproxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", socksPort), nil, netproxy.Direct) common.Must(err) const num = 100 wg := sync.WaitGroup{} wg.Add(num) const payloadSize = 1024 * 1024 * 1024 payload := util.GeneratePayload(payloadSize) for i := 0; i < 100; i++ { conn, err := dialer.Dial("tcp", util.BlackHoleAddr) common.Must(err) t1 := time.Now() common.Must2(conn.Write(payload)) t2 := time.Now() speed := float64(payloadSize) / (float64(t2.Sub(t1).Nanoseconds()) / float64(time.Second)) fmt.Printf("speed: %f Gbps\n", speed/1024/1024/1024) conn.Close() } client.Close() server.Close() } func BenchmarkClientServer(b *testing.B) { go func() { fmt.Println(http.ListenAndServe("localhost:6060", nil)) }() serverPort := common.PickPort("tcp", "127.0.0.1") socksPort := common.PickPort("tcp", "127.0.0.1") clientData := fmt.Sprintf(` run-type: client local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %d log-level: 0 password: - password ssl: verify: false fingerprint: firefox sni: localhost `, socksPort, serverPort) serverData := fmt.Sprintf(` run-type: server local-addr: 127.0.0.1 local-port: %d remote-addr: 127.0.0.1 remote-port: %s log-level: 0 disable-http-check: true password: - password ssl: verify-hostname: false key: server.key cert: server.crt sni: localhost `, serverPort, util.HTTPPort) SingleThreadBenchmark(clientData, serverData, socksPort) }