From 1a435b79e6f359db2c403595c47457010624e565 Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Mon, 17 May 2021 20:33:00 +0800 Subject: [PATCH] Feature: add protocol test --- test/README.md | 49 +++ test/clash_test.go | 635 ++++++++++++++++++++++++++++++++ test/config/example.org-key.pem | 28 ++ test/config/example.org.pem | 25 ++ test/config/snell-http.conf | 4 + test/config/snell-tls.conf | 4 + test/config/snell.conf | 3 + test/config/trojan-grpc.json | 40 ++ test/config/trojan.json | 40 ++ test/config/vmess-aead.json | 28 ++ test/config/vmess-grpc.json | 40 ++ test/config/vmess-http.json | 55 +++ test/config/vmess-http2.json | 43 +++ test/config/vmess-tls.json | 37 ++ test/config/vmess-ws-tls.json | 34 ++ test/config/vmess-ws.json | 26 ++ test/config/vmess.json | 28 ++ test/dns_test.go | 103 ++++++ test/docker_test.go | 45 +++ test/go.mod | 26 ++ test/go.sum | 202 ++++++++++ test/snell_test.go | 121 ++++++ test/ss_test.go | 172 +++++++++ test/trojan_test.go | 94 +++++ test/util_test.go | 70 ++++ test/vmess_test.go | 347 +++++++++++++++++ 26 files changed, 2299 insertions(+) create mode 100644 test/README.md create mode 100644 test/clash_test.go create mode 100644 test/config/example.org-key.pem create mode 100644 test/config/example.org.pem create mode 100644 test/config/snell-http.conf create mode 100644 test/config/snell-tls.conf create mode 100644 test/config/snell.conf create mode 100644 test/config/trojan-grpc.json create mode 100644 test/config/trojan.json create mode 100644 test/config/vmess-aead.json create mode 100644 test/config/vmess-grpc.json create mode 100644 test/config/vmess-http.json create mode 100644 test/config/vmess-http2.json create mode 100644 test/config/vmess-tls.json create mode 100644 test/config/vmess-ws-tls.json create mode 100644 test/config/vmess-ws.json create mode 100644 test/config/vmess.json create mode 100644 test/dns_test.go create mode 100644 test/docker_test.go create mode 100644 test/go.mod create mode 100644 test/go.sum create mode 100644 test/snell_test.go create mode 100644 test/ss_test.go create mode 100644 test/trojan_test.go create mode 100644 test/util_test.go create mode 100644 test/vmess_test.go diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..dec9999c --- /dev/null +++ b/test/README.md @@ -0,0 +1,49 @@ +## Clash testing suit + +### Protocol testing suit + +* TCP pingpong test +* UDP pingpong test +* TCP large data test +* UDP large data test + +### Protocols + +- [x] Shadowsocks + - [x] Normal + - [x] ObfsHTTP + - [x] ObfsTLS + - [x] ObfsV2rayPlugin +- [x] Vmess + - [x] Normal + - [x] AEAD + - [x] HTTP + - [x] HTTP2 + - [x] TLS + - [x] Websocket + - [x] Websocket TLS + - [x] gRPC +- [x] Trojan + - [x] Normal + - [x] gRPC +- [x] Snell + - [x] Normal + - [x] ObfsHTTP + - [x] ObfsTLS + +### Features + +- [ ] DNS + - [x] DNS Server + - [x] FakeIP + - [x] Host + +### Command + +Prerequisite + +* docker (support Linux and macOS) + +``` +$ go test -p 1 -v +``` diff --git a/test/clash_test.go b/test/clash_test.go new file mode 100644 index 00000000..e835b0fe --- /dev/null +++ b/test/clash_test.go @@ -0,0 +1,635 @@ +package main + +import ( + "context" + "crypto/md5" + "crypto/rand" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "runtime" + "sync" + "testing" + "time" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/hub/executor" + "github.com/Dreamacro/clash/transport/socks5" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" +) + +const ( + ImageShadowsocks = "mritd/shadowsocks:latest" + ImageVmess = "v2fly/v2fly-core:latest" + ImageTrojan = "trojangfw/trojan:latest" + ImageSnell = "icpz/snell-server:latest" + ImageXray = "teddysun/xray:latest" +) + +var ( + waitTime = time.Second + localIP = net.ParseIP("127.0.0.1") + + defaultExposedPorts = nat.PortSet{ + "10002/tcp": struct{}{}, + "10002/udp": struct{}{}, + } + defaultPortBindings = nat.PortMap{ + "10002/tcp": []nat.PortBinding{ + {HostPort: "10002", HostIP: "0.0.0.0"}, + }, + "10002/udp": []nat.PortBinding{ + {HostPort: "10002", HostIP: "0.0.0.0"}, + }, + } +) + +func init() { + if runtime.GOOS == "darwin" { + isDarwin = true + } + + currentDir, err := os.Getwd() + if err != nil { + panic(err) + } + homeDir := filepath.Join(currentDir, "config") + C.SetHomeDir(homeDir) + + if isDarwin { + localIP, err = defaultRouteIP() + if err != nil { + panic(err) + } + } + + c, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + panic(err) + } + defer c.Close() + + list, err := c.ImageList(context.Background(), types.ImageListOptions{All: true}) + if err != nil { + panic(err) + } + + imageExist := func(image string) bool { + for _, item := range list { + for _, tag := range item.RepoTags { + if image == tag { + return true + } + } + } + return false + } + + images := []string{ + ImageShadowsocks, + ImageVmess, + ImageTrojan, + ImageSnell, + ImageXray, + } + + for _, image := range images { + if imageExist(image) { + continue + } + + imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{}) + if err != nil { + panic(err) + } + + io.Copy(io.Discard, imageStream) + } +} + +var clean = ` +port: 0 +socks-port: 0 +mixed-port: 0 +redir-port: 0 +tproxy-port: 0 +dns: + enable: false +` + +func cleanup() { + parseAndApply(clean) +} + +func parseAndApply(cfgStr string) error { + cfg, err := executor.ParseWithBytes([]byte(cfgStr)) + if err != nil { + return err + } + + executor.ApplyConfig(cfg, true) + return nil +} + +func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) { + pingCh := make(chan []byte) + pongCh := make(chan []byte) + test := func(t *testing.T) error { + defer close(pingCh) + defer close(pongCh) + pingOpen := false + pongOpen := false + var recv []byte + + for { + if pingOpen && pongOpen { + break + } + + select { + case recv, pingOpen = <-pingCh: + assert.True(t, pingOpen) + assert.Equal(t, []byte("ping"), recv) + case recv, pongOpen = <-pongCh: + assert.True(t, pongOpen) + assert.Equal(t, []byte("pong"), recv) + case <-time.After(10 * time.Second): + return errors.New("timeout") + } + } + return nil + } + + return pingCh, pongCh, test +} + +func newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error) { + pingCh := make(chan hashPair) + pongCh := make(chan hashPair) + test := func(t *testing.T) error { + defer close(pingCh) + defer close(pongCh) + pingOpen := false + pongOpen := false + var serverPair hashPair + var clientPair hashPair + + for { + if pingOpen && pongOpen { + break + } + + select { + case serverPair, pingOpen = <-pingCh: + assert.True(t, pingOpen) + case clientPair, pongOpen = <-pongCh: + assert.True(t, pongOpen) + case <-time.After(10 * time.Second): + return errors.New("timeout") + } + } + + assert.Equal(t, serverPair.recvHash, clientPair.sendHash) + assert.Equal(t, serverPair.sendHash, clientPair.recvHash) + + return nil + } + + return pingCh, pongCh, test +} + +func testPingPongWithSocksPort(t *testing.T, port int) { + pingCh, pongCh, test := newPingPongPair() + go func() { + l, err := net.Listen("tcp", ":10001") + if err != nil { + assert.FailNow(t, err.Error()) + } + + c, err := l.Accept() + if err != nil { + assert.FailNow(t, err.Error()) + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + assert.FailNow(t, err.Error()) + } + + pingCh <- buf + if _, err := c.Write([]byte("pong")); err != nil { + assert.FailNow(t, err.Error()) + } + l.Close() + }() + + go func() { + c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + assert.FailNow(t, err.Error()) + } + + if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil { + assert.FailNow(t, err.Error()) + } + + if _, err := c.Write([]byte("ping")); err != nil { + assert.FailNow(t, err.Error()) + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + assert.FailNow(t, err.Error()) + } + + pongCh <- buf + c.Close() + }() + + test(t) +} + +func testPingPongWithConn(t *testing.T, c net.Conn) error { + l, err := net.Listen("tcp", ":10001") + if err != nil { + return err + } + defer l.Close() + + pingCh, pongCh, test := newPingPongPair() + go func() { + c, err := l.Accept() + if err != nil { + return + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + return + } + + pingCh <- buf + if _, err := c.Write([]byte("pong")); err != nil { + return + } + }() + + go func() { + if _, err := c.Write([]byte("ping")); err != nil { + return + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + return + } + + pongCh <- buf + }() + + return test(t) +} + +func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error { + l, err := net.ListenPacket("udp", ":10001") + if err != nil { + return err + } + defer l.Close() + + rAddr := &net.UDPAddr{IP: localIP, Port: 10001} + + pingCh, pongCh, test := newPingPongPair() + go func() { + buf := make([]byte, 1024) + n, rAddr, err := l.ReadFrom(buf) + if err != nil { + return + } + + pingCh <- buf[:n] + if _, err := l.WriteTo([]byte("pong"), rAddr); err != nil { + return + } + }() + + go func() { + if _, err := pc.WriteTo([]byte("ping"), rAddr); err != nil { + return + } + + buf := make([]byte, 1024) + n, _, err := pc.ReadFrom(buf) + if err != nil { + return + } + + pongCh <- buf[:n] + }() + + return test(t) +} + +type hashPair struct { + sendHash map[int][]byte + recvHash map[int][]byte +} + +func testLargeDataWithConn(t *testing.T, c net.Conn) error { + l, err := net.Listen("tcp", ":10001") + if err != nil { + return err + } + defer l.Close() + + times := 100 + chunkSize := int64(64 * 1024) + + pingCh, pongCh, test := newLargeDataPair() + writeRandData := func(conn net.Conn) (map[int][]byte, error) { + buf := make([]byte, chunkSize) + hashMap := map[int][]byte{} + for i := 0; i < times; i++ { + if _, err := rand.Read(buf[1:]); err != nil { + return nil, err + } + buf[0] = byte(i) + + hash := md5.Sum(buf) + hashMap[i] = hash[:] + + if _, err := conn.Write(buf); err != nil { + return nil, err + } + } + + return hashMap, nil + } + + go func() { + c, err := l.Accept() + if err != nil { + return + } + + hashMap := map[int][]byte{} + buf := make([]byte, chunkSize) + + for i := 0; i < times; i++ { + _, err := io.ReadFull(c, buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf) + hashMap[int(buf[0])] = hash[:] + } + + sendHash, err := writeRandData(c) + if err != nil { + t.Log(err.Error()) + return + } + + pingCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + go func() { + sendHash, err := writeRandData(c) + if err != nil { + t.Log(err.Error()) + return + } + + hashMap := map[int][]byte{} + buf := make([]byte, chunkSize) + + for i := 0; i < times; i++ { + _, err := io.ReadFull(c, buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf) + hashMap[int(buf[0])] = hash[:] + } + + pongCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + return test(t) +} + +func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error { + l, err := net.ListenPacket("udp", ":10001") + if err != nil { + return err + } + defer l.Close() + + rAddr := &net.UDPAddr{IP: localIP, Port: 10001} + + times := 50 + chunkSize := int64(1024) + + pingCh, pongCh, test := newLargeDataPair() + writeRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) { + hashMap := map[int][]byte{} + mux := sync.Mutex{} + for i := 0; i < times; i++ { + go func(idx int) { + buf := make([]byte, chunkSize) + if _, err := rand.Read(buf[1:]); err != nil { + t.Log(err.Error()) + return + } + buf[0] = byte(idx) + + hash := md5.Sum(buf) + mux.Lock() + hashMap[idx] = hash[:] + mux.Unlock() + + if _, err := pc.WriteTo(buf, addr); err != nil { + t.Log(err.Error()) + return + } + }(i) + } + + return hashMap, nil + } + + go func() { + var rAddr net.Addr + hashMap := map[int][]byte{} + buf := make([]byte, 64*1024) + + for i := 0; i < times; i++ { + _, rAddr, err = l.ReadFrom(buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf[:chunkSize]) + hashMap[int(buf[0])] = hash[:] + } + + sendHash, err := writeRandData(l, rAddr) + if err != nil { + t.Log(err.Error()) + return + } + + pingCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + go func() { + sendHash, err := writeRandData(pc, rAddr) + if err != nil { + t.Log(err.Error()) + return + } + + hashMap := map[int][]byte{} + buf := make([]byte, 64*1024) + + for i := 0; i < times; i++ { + _, _, err := pc.ReadFrom(buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf[:chunkSize]) + hashMap[int(buf[0])] = hash[:] + } + + pongCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + return test(t) +} + +func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error { + err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300)) + assert.NoError(t, err) + + errCh := make(chan error, 1) + go func() { + buf := make([]byte, 1024) + _, _, err := pc.ReadFrom(buf) + errCh <- err + }() + + select { + case <-errCh: + return nil + case <-time.After(time.Second * 10): + return errors.New("timeout") + } +} + +func testSuit(t *testing.T, proxy C.ProxyAdapter) { + conn, err := proxy.DialContext(context.Background(), &C.Metadata{ + Host: localIP.String(), + DstPort: "10001", + AddrType: socks5.AtypDomainName, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer conn.Close() + assert.NoError(t, testPingPongWithConn(t, conn)) + + conn, err = proxy.DialContext(context.Background(), &C.Metadata{ + Host: localIP.String(), + DstPort: "10001", + AddrType: socks5.AtypDomainName, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer conn.Close() + assert.NoError(t, testLargeDataWithConn(t, conn)) + + if !proxy.SupportUDP() { + return + } + + pc, err := proxy.DialUDP(&C.Metadata{ + NetWork: C.UDP, + DstIP: localIP, + DstPort: "10001", + AddrType: socks5.AtypIPv4, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer pc.Close() + + assert.NoError(t, testPingPongWithPacketConn(t, pc)) + + pc, err = proxy.DialUDP(&C.Metadata{ + NetWork: C.UDP, + DstIP: localIP, + DstPort: "10001", + AddrType: socks5.AtypIPv4, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer pc.Close() + + assert.NoError(t, testLargeDataWithPacketConn(t, pc)) + + pc, err = proxy.DialUDP(&C.Metadata{ + NetWork: C.UDP, + DstIP: localIP, + DstPort: "10001", + AddrType: socks5.AtypIPv4, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer pc.Close() + + assert.NoError(t, testPacketConnTimeout(t, pc)) +} + +func TestClash_Basic(t *testing.T) { + basic := ` +mixed-port: 10000 +log-level: silent +` + + if err := parseAndApply(basic); err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanup() + + time.Sleep(waitTime) + testPingPongWithSocksPort(t, 10000) +} diff --git a/test/config/example.org-key.pem b/test/config/example.org-key.pem new file mode 100644 index 00000000..dbe9a3db --- /dev/null +++ b/test/config/example.org-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5 +5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo +PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE +sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R +i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5 +LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge +gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+ +y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO +jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z +ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv +H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG +o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ +CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49 +aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33 +bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7 +Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh +ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO +pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT +4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi +GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA +vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB +fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z +zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X +DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28 +9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP +XCar+uxMBXI1zbXqd9QdEwy4Ig== +-----END PRIVATE KEY----- diff --git a/test/config/example.org.pem b/test/config/example.org.pem new file mode 100644 index 00000000..9b99259a --- /dev/null +++ b/test/config/example.org.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB +hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh +bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl +cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz +MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl +bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy +by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8 +3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI +YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b +IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo +UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2 +I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln +F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP +TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa +Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1 +Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz +Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG +WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6 +AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6 +gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS +tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w= +-----END CERTIFICATE----- diff --git a/test/config/snell-http.conf b/test/config/snell-http.conf new file mode 100644 index 00000000..f5130dc1 --- /dev/null +++ b/test/config/snell-http.conf @@ -0,0 +1,4 @@ +[snell-server] +listen = 0.0.0.0:10002 +psk = password +obfs = http diff --git a/test/config/snell-tls.conf b/test/config/snell-tls.conf new file mode 100644 index 00000000..bf8b5139 --- /dev/null +++ b/test/config/snell-tls.conf @@ -0,0 +1,4 @@ +[snell-server] +listen = 0.0.0.0:10002 +psk = password +obfs = tls diff --git a/test/config/snell.conf b/test/config/snell.conf new file mode 100644 index 00000000..40266391 --- /dev/null +++ b/test/config/snell.conf @@ -0,0 +1,3 @@ +[snell-server] +listen = 0.0.0.0:10002 +psk = password diff --git a/test/config/trojan-grpc.json b/test/config/trojan-grpc.json new file mode 100644 index 00000000..eb0dcc99 --- /dev/null +++ b/test/config/trojan-grpc.json @@ -0,0 +1,40 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "trojan", + "settings": { + "clients": [ + { + "password": "example", + "email": "grpc@example.com" + } + ] + }, + "streamSettings": { + "network": "grpc", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "grpcSettings": { + "serviceName": "example" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/trojan.json b/test/config/trojan.json new file mode 100644 index 00000000..18e33a98 --- /dev/null +++ b/test/config/trojan.json @@ -0,0 +1,40 @@ +{ + "run_type": "server", + "local_addr": "0.0.0.0", + "local_port": 10002, + "password": [ + "password" + ], + "log_level": 1, + "ssl": { + "cert": "/path/to/certificate.crt", + "key": "/path/to/private.key", + "key_password": "", + "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384", + "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", + "prefer_server_cipher": true, + "alpn": [ + "http/1.1" + ], + "alpn_port_override": { + "h2": 81 + }, + "reuse_session": true, + "session_ticket": false, + "session_timeout": 600, + "plain_http_response": "", + "curves": "", + "dhparam": "" + }, + "tcp": { + "prefer_ipv4": false, + "no_delay": true, + "keep_alive": true, + "reuse_port": false, + "fast_open": false, + "fast_open_qlen": 20 + }, + "mysql": { + "enabled": false + } +} \ No newline at end of file diff --git a/test/config/vmess-aead.json b/test/config/vmess-aead.json new file mode 100644 index 00000000..27bc4757 --- /dev/null +++ b/test/config/vmess-aead.json @@ -0,0 +1,28 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 0 + } + ] + }, + "streamSettings": { + "network": "tcp" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-grpc.json b/test/config/vmess-grpc.json new file mode 100644 index 00000000..1b3a8e9a --- /dev/null +++ b/test/config/vmess-grpc.json @@ -0,0 +1,40 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "grpc", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "grpcSettings": { + "serviceName": "example" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-http.json b/test/config/vmess-http.json new file mode 100644 index 00000000..aa6c6c8e --- /dev/null +++ b/test/config/vmess-http.json @@ -0,0 +1,55 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "tcp", + "tcpSettings": { + "header": { + "type": "http", + "response": { + "version": "1.1", + "status": "200", + "reason": "OK", + "headers": { + "Content-Type": [ + "application/octet-stream", + "video/mpeg", + "application/x-msdownload", + "text/html", + "application/x-shockwave-flash" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Connection": [ + "keep-alive" + ], + "Pragma": "no-cache" + } + } + } + }, + "security": "none" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-http2.json b/test/config/vmess-http2.json new file mode 100644 index 00000000..48873731 --- /dev/null +++ b/test/config/vmess-http2.json @@ -0,0 +1,43 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "http", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "httpSettings": { + "host": [ + "example.org" + ], + "path": "/test" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-tls.json b/test/config/vmess-tls.json new file mode 100644 index 00000000..ae2fa489 --- /dev/null +++ b/test/config/vmess-tls.json @@ -0,0 +1,37 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-ws-tls.json b/test/config/vmess-ws-tls.json new file mode 100644 index 00000000..dda1e0c9 --- /dev/null +++ b/test/config/vmess-ws-tls.json @@ -0,0 +1,34 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} \ No newline at end of file diff --git a/test/config/vmess-ws.json b/test/config/vmess-ws.json new file mode 100644 index 00000000..94ace4e3 --- /dev/null +++ b/test/config/vmess-ws.json @@ -0,0 +1,26 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "none" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} \ No newline at end of file diff --git a/test/config/vmess.json b/test/config/vmess.json new file mode 100644 index 00000000..bcf53cd8 --- /dev/null +++ b/test/config/vmess.json @@ -0,0 +1,28 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "tcp" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/dns_test.go b/test/dns_test.go new file mode 100644 index 00000000..dd07bda8 --- /dev/null +++ b/test/dns_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func exchange(address, domain string, tp uint16) ([]dns.RR, error) { + client := dns.Client{} + query := &dns.Msg{} + query.SetQuestion(dns.Fqdn(domain), tp) + + r, _, err := client.Exchange(query, address) + if err != nil { + return nil, err + } + return r.Answer, nil +} + +func TestClash_DNS(t *testing.T) { + basic := ` +log-level: silent +dns: + enable: true + listen: 0.0.0.0:8553 + nameserver: + - 119.29.29.29 +` + + if err := parseAndApply(basic); err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanup() + + time.Sleep(waitTime) + + rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA) + assert.NoError(t, err) + if !assert.NotEmpty(t, rr) { + assert.FailNow(t, "record empty") + } + + record := rr[0].(*dns.A) + assert.Equal(t, record.A.String(), "1.1.1.1") + + rr, err = exchange("127.0.0.1:8553", "2606-4700-4700--1111.sslip.io", dns.TypeAAAA) + assert.NoError(t, err) + assert.Empty(t, rr) +} + +func TestClash_DNSHostAndFakeIP(t *testing.T) { + basic := ` +log-level: silent +hosts: + foo.clash.dev: 1.1.1.1 +dns: + enable: true + listen: 0.0.0.0:8553 + ipv6: true + enhanced-mode: fake-ip + fake-ip-range: 198.18.0.1/16 + fake-ip-filter: + - .sslip.io + nameserver: + - 119.29.29.29 +` + + if err := parseAndApply(basic); err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanup() + + time.Sleep(waitTime) + + type domainPair struct { + domain string + ip string + } + + list := []domainPair{ + {"foo.org", "198.18.0.2"}, + {"bar.org", "198.18.0.3"}, + {"foo.org", "198.18.0.2"}, + {"foo.clash.dev", "1.1.1.1"}, + } + + for _, pair := range list { + rr, err := exchange("127.0.0.1:8553", pair.domain, dns.TypeA) + assert.NoError(t, err) + assert.NotEmpty(t, rr) + + record := rr[0].(*dns.A) + assert.Equal(t, record.A.String(), pair.ip) + } + + rr, err := exchange("127.0.0.1:8553", "2606-4700-4700--1111.sslip.io", dns.TypeAAAA) + assert.NoError(t, err) + assert.NotEmpty(t, rr) + assert.Equal(t, rr[0].(*dns.AAAA).AAAA.String(), "2606:4700:4700::1111") +} diff --git a/test/docker_test.go b/test/docker_test.go new file mode 100644 index 00000000..ce4c2de4 --- /dev/null +++ b/test/docker_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" +) + +var isDarwin = false + +func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) { + c, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return "", err + } + defer c.Close() + + if !isDarwin { + hostCfg.NetworkMode = "host" + } + + container, err := c.ContainerCreate(context.Background(), cfg, hostCfg, nil, nil, name) + if err != nil { + return "", err + } + + if err = c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil { + return "", err + } + + return container.ID, nil +} + +func cleanContainer(id string) error { + c, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + defer c.Close() + + removeOpts := types.ContainerRemoveOptions{Force: true} + return c.ContainerRemove(context.Background(), id, removeOpts) +} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 00000000..1a5cb676 --- /dev/null +++ b/test/go.mod @@ -0,0 +1,26 @@ +module clash-test + +go 1.16 + +require ( + github.com/Dreamacro/clash v1.6.1-0.20210516120541-06fdd3abe0ab + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/containerd/containerd v1.4.4 // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker v20.10.6+incompatible + github.com/docker/go-connections v0.4.0 + github.com/docker/go-units v0.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.5.5 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/miekg/dns v1.1.42 + github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/stretchr/testify v1.7.0 + golang.org/x/net v0.0.0-20210510120150-4163338589ed + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect + google.golang.org/grpc v1.36.0 // indirect + gotest.tools/v3 v3.0.3 // indirect +) diff --git a/test/go.sum b/test/go.sum new file mode 100644 index 00000000..2a641fb7 --- /dev/null +++ b/test/go.sum @@ -0,0 +1,202 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Dreamacro/clash v1.6.1-0.20210516120541-06fdd3abe0ab h1:hfjxpY+73Xo6k+JHMkLlaOR3Tw5hG/YZxmDFyV9Y6sY= +github.com/Dreamacro/clash v1.6.1-0.20210516120541-06fdd3abe0ab/go.mod h1:Pz9GwWR244jrMSInQ0KUL02jMCe5xIJ+pkgzuNIy2Iw= +github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q= +github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= +github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.6+incompatible h1:oXI3Vas8TI8Eu/EjH4srKHJBVqraSzJybhxY7Om9faQ= +github.com/docker/docker v20.10.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +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.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= +github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw= +github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s= +github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk= +github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210508051633-16afe75a6701/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo= +golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.36.0 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +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.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/test/snell_test.go b/test/snell_test.go new file mode 100644 index 00000000..0d9e059a --- /dev/null +++ b/test/snell_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/Dreamacro/clash/adapters/outbound" + C "github.com/Dreamacro/clash/constant" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_SnellObfsHTTP(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell-http") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + ObfsOpts: map[string]interface{}{ + "mode": "http", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_SnellObfsTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-tls.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell-tls") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + ObfsOpts: map[string]interface{}{ + "mode": "tls", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_Snell(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} diff --git a/test/ss_test.go b/test/ss_test.go new file mode 100644 index 00000000..6876d3fb --- /dev/null +++ b/test/ss_test.go @@ -0,0 +1,172 @@ +package main + +import ( + "testing" + "time" + + "github.com/Dreamacro/clash/adapters/outbound" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_Shadowsocks(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{"SS_MODULE=ss-server", "SS_CONFIG=-s 0.0.0.0 -u -v -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL"}, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_ShadowsocksObfsHTTP(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{ + "SS_MODULE=ss-server", + "SS_CONFIG=-s 0.0.0.0 -u -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL --plugin obfs-server --plugin-opts obfs=http", + }, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss-obfs-http") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + Plugin: "obfs", + PluginOpts: map[string]interface{}{ + "mode": "http", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_ShadowsocksObfsTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{ + "SS_MODULE=ss-server", + "SS_CONFIG=-s 0.0.0.0 -u -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL --plugin obfs-server --plugin-opts obfs=tls", + }, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss-obfs-tls") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + Plugin: "obfs", + PluginOpts: map[string]interface{}{ + "mode": "tls", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_ShadowsocksV2RayPlugin(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{ + "SS_MODULE=ss-server", + "SS_CONFIG=-s 0.0.0.0 -u -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL --plugin v2ray-plugin --plugin-opts=server", + }, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + Plugin: "v2ray-plugin", + PluginOpts: map[string]interface{}{ + "mode": "websocket", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} diff --git a/test/trojan_test.go b/test/trojan_test.go new file mode 100644 index 00000000..c20f1279 --- /dev/null +++ b/test/trojan_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/Dreamacro/clash/adapters/outbound" + C "github.com/Dreamacro/clash/constant" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_Trojan(t *testing.T) { + cfg := &container.Config{ + Image: ImageTrojan, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/config/config.json", C.Path.Resolve("trojan.json")), + fmt.Sprintf("%s:/path/to/certificate.crt", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/path/to/private.key", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "password", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_TrojanGrpc(t *testing.T) { + cfg := &container.Config{ + Image: ImageXray, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("trojan-grpc.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan-grpc") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "example", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{ + GrpcServiceName: "example", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} diff --git a/test/util_test.go b/test/util_test.go new file mode 100644 index 00000000..ab9b8b2b --- /dev/null +++ b/test/util_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "errors" + "fmt" + "net" + "syscall" + + "golang.org/x/net/route" +) + +func defaultRouteIP() (net.IP, error) { + idx, err := defaultRouteInterfaceIndex() + if err != nil { + return nil, err + } + iface, err := net.InterfaceByIndex(idx) + if err != nil { + return nil, err + } + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + for _, addr := range addrs { + ip := addr.(*net.IPNet).IP + if ip.To4() != nil { + return ip, nil + } + } + + return nil, errors.New("no ipv4 addr") +} + +func defaultRouteInterfaceIndex() (int, error) { + rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) + if err != nil { + return 0, fmt.Errorf("route.FetchRIB: %w", err) + } + msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) + if err != nil { + return 0, fmt.Errorf("route.ParseRIB: %w", err) + } + for _, message := range msgs { + routeMessage := message.(*route.RouteMessage) + if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 { + continue + } + + addresses := routeMessage.Addrs + + destination, ok := addresses[0].(*route.Inet4Addr) + if !ok { + continue + } + + if destination.IP != [4]byte{0, 0, 0, 0} { + continue + } + + switch addresses[1].(type) { + case *route.Inet4Addr: + return routeMessage.Index, nil + default: + continue + } + } + + return 0, fmt.Errorf("ambiguous gateway interfaces found") +} diff --git a/test/vmess_test.go b/test/vmess_test.go new file mode 100644 index 00000000..cf539bf8 --- /dev/null +++ b/test/vmess_test.go @@ -0,0 +1,347 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/Dreamacro/clash/adapters/outbound" + C "github.com/Dreamacro/clash/constant" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_Vmess(t *testing.T) { + configPath := C.Path.Resolve("vmess.json") + + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)}, + } + + id, err := startContainer(cfg, hostCfg, "vmess") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessAEAD(t *testing.T) { + configPath := C.Path.Resolve("vmess-aead.json") + + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)}, + } + + id, err := startContainer(cfg, hostCfg, "vmess-aead") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 0, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-tls.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-tls") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + TLS: true, + SkipCertVerify: true, + ServerName: "example.org", + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessHTTP2(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-http2.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-http2") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "h2", + TLS: true, + SkipCertVerify: true, + ServerName: "example.org", + UDP: true, + HTTP2Opts: outbound.HTTP2Options{ + Host: []string{"example.org"}, + Path: "/test", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessHTTP(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-http.json")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-http") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "http", + UDP: true, + HTTPOpts: outbound.HTTPOptions{ + Method: "GET", + Path: []string{"/"}, + Headers: map[string][]string{ + "Host": {"www.amazon.com"}, + "User-Agent": { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 Edg/84.0.522.49", + }, + "Accept-Encoding": { + "gzip, deflate", + }, + "Connection": { + "keep-alive", + }, + "Pragma": {"no-cache"}, + }, + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessWebsocket(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-ws.json")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-ws") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "ws", + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessWebsocketTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-ws-tls.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-ws") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "ws", + TLS: true, + SkipCertVerify: true, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessGrpc(t *testing.T) { + cfg := &container.Config{ + Image: ImageXray, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("vmess-grpc.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-grpc") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "grpc", + TLS: true, + SkipCertVerify: true, + UDP: true, + ServerName: "example.org", + GrpcOpts: outbound.GrpcOptions{ + GrpcServiceName: "example", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +}