123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- package main
- import (
- "context"
- "crypto/md5"
- "crypto/rand"
- "errors"
- "fmt"
- "io"
- "net"
- "net/netip"
- "os"
- "path/filepath"
- "runtime"
- "sync"
- "testing"
- "time"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/client"
- "github.com/docker/go-connections/nat"
- "github.com/metacubex/mihomo/adapter/outbound"
- C "github.com/metacubex/mihomo/constant"
- "github.com/metacubex/mihomo/hub/executor"
- "github.com/metacubex/mihomo/transport/socks5"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- const (
- ImageShadowsocks = "mritd/shadowsocks:latest"
- ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest"
- ImageVmess = "v2fly/v2fly-core:v4.45.2"
- ImageVmessLatest = "sagernet/v2fly-core:latest"
- ImageVless = "teddysun/xray:latest"
- ImageTrojan = "trojangfw/trojan:latest"
- ImageTrojanGo = "p4gefau1t/trojan-go:latest"
- ImageSnell = "ghcr.io/icpz/snell-server:latest"
- ImageXray = "teddysun/xray:latest"
- ImageHysteria = "tobyxdd/hysteria:latest"
- )
- var (
- waitTime = time.Second
- localIP = netip.MustParseAddr("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"},
- },
- }
- isDarwin = runtime.GOOS == "darwin"
- )
- 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)
- }
- }
- c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
- 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,
- ImageVmess,
- ImageVless,
- ImageTrojan,
- ImageTrojanGo,
- ImageSnell,
- ImageXray,
- ImageHysteria,
- }
- for _, image := range images {
- if imageExist(image) {
- continue
- }
- println("pulling image:", image)
- 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 := Listen("tcp", ":10001")
- require.NoError(t, err)
- defer l.Close()
- c, err := l.Accept()
- require.NoError(t, err)
- buf := make([]byte, 4)
- _, err = io.ReadFull(c, buf)
- require.NoError(t, err)
- pingCh <- buf
- _, err = c.Write([]byte("pong"))
- require.NoError(t, err)
- }()
- go func() {
- c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
- require.NoError(t, err)
- defer c.Close()
- _, err = socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil)
- require.NoError(t, err)
- _, err = c.Write([]byte("ping"))
- require.NoError(t, err)
- buf := make([]byte, 4)
- _, err = io.ReadFull(c, buf)
- require.NoError(t, err)
- pongCh <- buf
- }()
- test(t)
- }
- func testPingPongWithConn(t *testing.T, cc func() net.Conn) error {
- l, err := 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
- }
- }()
- c := cc()
- defer c.Close()
- go func() {
- if _, err := c.Write([]byte("ping")); err != nil {
- return
- }
- buf := make([]byte, 4)
- if _, err := io.ReadFull(c, buf); err != nil {
- t.Error(err)
- return
- }
- pongCh <- buf
- }()
- return test(t)
- }
- func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
- l, err := ListenPacket("udp", ":10001")
- require.NoError(t, err)
- defer l.Close()
- rAddr := &net.UDPAddr{IP: localIP.AsSlice(), 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, cc func() net.Conn) error {
- l, err := Listen("tcp", ":10001")
- require.NoError(t, 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
- }
- defer c.Close()
- 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,
- }
- }()
- c := cc()
- defer c.Close()
- 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 := ListenPacket("udp", ":10001")
- require.NoError(t, err)
- defer l.Close()
- rAddr := &net.UDPAddr{IP: localIP.AsSlice(), 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{}
- go func() {
- for i := 0; i < times; i++ {
- buf := make([]byte, chunkSize)
- if _, err := rand.Read(buf[1:]); err != nil {
- t.Log(err.Error())
- return
- }
- buf[0] = byte(i)
- hash := md5.Sum(buf)
- mux.Lock()
- hashMap[i] = hash[:]
- mux.Unlock()
- if _, err := pc.WriteTo(buf, addr); err != nil {
- t.Log(err.Error())
- return
- }
- }
- }()
- 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))
- require.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) {
- assert.NoError(t, testPingPongWithConn(t, func() net.Conn {
- conn, err := proxy.DialContext(context.Background(), &C.Metadata{
- Host: localIP.String(),
- DstPort: 10001,
- })
- require.NoError(t, err)
- return conn
- }))
- assert.NoError(t, testLargeDataWithConn(t, func() net.Conn {
- conn, err := proxy.DialContext(context.Background(), &C.Metadata{
- Host: localIP.String(),
- DstPort: 10001,
- })
- require.NoError(t, err)
- return conn
- }))
- if !proxy.SupportUDP() {
- return
- }
- pc, err := proxy.ListenPacketContext(context.Background(), &C.Metadata{
- NetWork: C.UDP,
- DstIP: localIP,
- DstPort: 10001,
- })
- require.NoError(t, err)
- defer pc.Close()
- assert.NoError(t, testPingPongWithPacketConn(t, pc))
- pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
- NetWork: C.UDP,
- DstIP: localIP,
- DstPort: 10001,
- })
- require.NoError(t, err)
- defer pc.Close()
- assert.NoError(t, testLargeDataWithPacketConn(t, pc))
- pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
- NetWork: C.UDP,
- DstIP: localIP,
- DstPort: 10001,
- })
- require.NoError(t, err)
- defer pc.Close()
- assert.NoError(t, testPacketConnTimeout(t, pc))
- }
- func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
- l, err := Listen("tcp", ":10001")
- require.NoError(b, err)
- defer l.Close()
- chunkSize := int64(16 * 1024)
- chunk := make([]byte, chunkSize)
- rand.Read(chunk)
- go func() {
- c, err := l.Accept()
- if err != nil {
- return
- }
- defer c.Close()
- go func() {
- for {
- _, err := c.Write(chunk)
- if err != nil {
- return
- }
- }
- }()
- io.Copy(io.Discard, c)
- }()
- conn, err := proxy.DialContext(context.Background(), &C.Metadata{
- Host: localIP.String(),
- DstPort: 10001,
- })
- require.NoError(b, err)
- _, err = conn.Write([]byte("skip protocol handshake"))
- require.NoError(b, err)
- 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++ {
- io.ReadFull(conn, buf)
- }
- })
- }
- func TestMihomo_Basic(t *testing.T) {
- basic := `
- mixed-port: 10000
- log-level: silent
- `
- err := parseAndApply(basic)
- require.NoError(t, err)
- defer cleanup()
- require.True(t, TCPing(net.JoinHostPort(localIP.String(), "10000")))
- testPingPongWithSocksPort(t, 10000)
- }
- func Benchmark_Direct(b *testing.B) {
- proxy := outbound.NewDirect()
- benchmarkProxy(b, proxy)
- }
|