mihomo/test/clash_test.go

684 lines
13 KiB
Go
Raw Normal View History

2021-05-17 20:33:00 +08:00
package main
import (
"context"
"crypto/md5"
"crypto/rand"
"errors"
"fmt"
"io"
"net"
2022-06-07 10:45:32 +08:00
"net/netip"
2021-05-17 20:33:00 +08:00
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"time"
2022-06-07 10:45:32 +08:00
"github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/transport/socks5"
2021-05-17 20:33:00 +08:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
2022-05-21 17:37:06 +08:00
"github.com/stretchr/testify/require"
2021-05-17 20:33:00 +08:00
)
const (
ImageShadowsocks = "mritd/shadowsocks:latest"
ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest"
ImageVmess = "v2fly/v2fly-core:latest"
2022-06-07 10:45:32 +08:00
ImageVless = "teddysun/xray:latest"
ImageTrojan = "trojangfw/trojan:latest"
2021-10-16 20:19:59 +08:00
ImageTrojanGo = "p4gefau1t/trojan-go:latest"
2022-01-10 20:24:20 +08:00
ImageSnell = "ghcr.io/icpz/snell-server:latest"
ImageXray = "teddysun/xray:latest"
2022-06-07 13:38:45 +08:00
ImageHysteria = "tobyxdd/hysteria:latest"
2021-05-17 20:33:00 +08:00
)
var (
waitTime = time.Second
2022-06-07 10:45:32 +08:00
localIP = netip.MustParseAddr("127.0.0.1")
2021-05-17 20:33:00 +08:00
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"},
},
}
2022-05-21 17:37:06 +08:00
isDarwin = runtime.GOOS == "darwin"
2021-05-17 20:33:00 +08:00
)
func init() {
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)
}
}
2021-07-18 19:26:51 +08:00
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
2021-05-17 20:33:00 +08:00
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,
ImageShadowsocksRust,
2021-05-17 20:33:00 +08:00
ImageVmess,
2022-06-07 10:45:32 +08:00
ImageVless,
2021-05-17 20:33:00 +08:00
ImageTrojan,
2021-10-16 20:19:59 +08:00
ImageTrojanGo,
2021-05-17 20:33:00 +08:00
ImageSnell,
ImageXray,
2022-06-07 13:38:45 +08:00
ImageHysteria,
2021-05-17 20:33:00 +08:00
}
for _, image := range images {
if imageExist(image) {
continue
}
2022-05-21 17:37:06 +08:00
println("pulling image:", image)
2021-05-17 20:33:00 +08:00
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() {
2021-09-04 22:44:18 +08:00
l, err := Listen("tcp", ":10001")
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-09-05 14:25:55 +08:00
defer l.Close()
2021-05-17 20:33:00 +08:00
c, err := l.Accept()
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
buf := make([]byte, 4)
2022-05-21 17:37:06 +08:00
_, err = io.ReadFull(c, buf)
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
pingCh <- buf
2022-05-21 17:37:06 +08:00
_, err = c.Write([]byte("pong"))
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
}()
go func() {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-09-05 14:25:55 +08:00
defer c.Close()
2021-05-17 20:33:00 +08:00
2022-05-21 17:37:06 +08:00
_, err = socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil)
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
2022-05-21 17:37:06 +08:00
_, err = c.Write([]byte("ping"))
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
buf := make([]byte, 4)
2022-05-21 17:37:06 +08:00
_, err = io.ReadFull(c, buf)
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
pongCh <- buf
}()
test(t)
}
2022-06-07 10:45:32 +08:00
func testPingPongWithConn(t *testing.T, cc func() net.Conn) error {
2021-09-04 22:44:18 +08:00
l, err := Listen("tcp", ":10001")
2021-05-17 20:33:00 +08:00
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
}
}()
2022-06-07 10:45:32 +08:00
c := cc()
defer c.Close()
2021-05-17 20:33:00 +08:00
go func() {
if _, err := c.Write([]byte("ping")); err != nil {
return
}
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
2022-06-07 10:45:32 +08:00
t.Error(err)
2021-05-17 20:33:00 +08:00
return
}
pongCh <- buf
}()
return test(t)
}
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
2021-09-04 22:44:18 +08:00
l, err := ListenPacket("udp", ":10001")
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
defer l.Close()
2022-06-07 10:45:32 +08:00
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
2021-05-17 20:33:00 +08:00
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
}
2022-06-07 10:45:32 +08:00
func testLargeDataWithConn(t *testing.T, cc func() net.Conn) error {
2021-09-04 22:44:18 +08:00
l, err := Listen("tcp", ":10001")
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
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
}
2021-09-05 14:25:55 +08:00
defer c.Close()
2021-05-17 20:33:00 +08:00
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,
}
}()
2022-06-07 10:45:32 +08:00
c := cc()
defer c.Close()
2021-05-17 20:33:00 +08:00
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 {
2021-09-04 22:44:18 +08:00
l, err := ListenPacket("udp", ":10001")
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
defer l.Close()
2022-06-07 10:45:32 +08:00
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
2021-05-17 20:33:00 +08:00
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))
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
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) {
2022-06-07 10:45:32 +08:00
assert.NoError(t, testPingPongWithConn(t, func() net.Conn {
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2022-06-07 10:45:32 +08:00
return conn
}))
2021-05-17 20:33:00 +08:00
2022-06-07 10:45:32 +08:00
assert.NoError(t, testLargeDataWithConn(t, func() net.Conn {
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2022-06-07 10:45:32 +08:00
return conn
}))
2021-05-17 20:33:00 +08:00
if !proxy.SupportUDP() {
return
}
pc, err := proxy.ListenPacketContext(context.Background(), &C.Metadata{
2021-05-17 20:33:00 +08:00
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
defer pc.Close()
assert.NoError(t, testPingPongWithPacketConn(t, pc))
pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
2021-05-17 20:33:00 +08:00
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
defer pc.Close()
assert.NoError(t, testLargeDataWithPacketConn(t, pc))
pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
2021-05-17 20:33:00 +08:00
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
2022-05-21 17:37:06 +08:00
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
defer pc.Close()
assert.NoError(t, testPacketConnTimeout(t, pc))
}
2021-07-18 17:23:22 +08:00
func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
2021-09-04 22:44:18 +08:00
l, err := Listen("tcp", ":10001")
2022-05-21 17:37:06 +08:00
require.NoError(b, err)
2021-09-05 14:25:55 +08:00
defer l.Close()
2021-07-18 17:23:22 +08:00
2022-05-23 12:27:34 +08:00
chunkSize := int64(16 * 1024)
chunk := make([]byte, chunkSize)
rand.Read(chunk)
2021-07-18 17:23:22 +08:00
go func() {
c, err := l.Accept()
if err != nil {
2022-05-21 17:37:06 +08:00
return
2021-07-18 17:23:22 +08:00
}
2021-09-05 14:25:55 +08:00
defer c.Close()
2021-07-18 17:23:22 +08:00
2022-05-23 12:27:34 +08:00
go func() {
for {
_, err := c.Write(chunk)
if err != nil {
return
}
}
}()
2021-07-18 17:23:22 +08:00
io.Copy(io.Discard, c)
}()
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
2022-05-21 17:37:06 +08:00
require.NoError(b, err)
_, err = conn.Write([]byte("skip protocol handshake"))
require.NoError(b, err)
2021-07-18 17:23:22 +08:00
2022-05-23 12:27:34 +08:00
b.Run("Write", func(b *testing.B) {
b.SetBytes(chunkSize)
for i := 0; i < b.N; i++ {
conn.Write(chunk)
}
})
b.Run("Read", func(b *testing.B) {
b.SetBytes(chunkSize)
buf := make([]byte, chunkSize)
for i := 0; i < b.N; i++ {
conn.Read(buf)
}
})
2021-07-18 17:23:22 +08:00
}
func TestClash_Basic(t *testing.T) {
2021-05-17 20:33:00 +08:00
basic := `
mixed-port: 10000
log-level: silent
`
2022-05-21 17:37:06 +08:00
err := parseAndApply(basic)
require.NoError(t, err)
2021-05-17 20:33:00 +08:00
defer cleanup()
2022-05-21 17:37:06 +08:00
require.True(t, TCPing(net.JoinHostPort(localIP.String(), "10000")))
2021-05-17 20:33:00 +08:00
testPingPongWithSocksPort(t, 10000)
}
2021-07-18 19:26:51 +08:00
func Benchmark_Direct(b *testing.B) {
proxy := outbound.NewDirect()
benchmarkProxy(b, proxy)
}