clash_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. package main
  2. import (
  3. "context"
  4. "crypto/md5"
  5. "crypto/rand"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net"
  10. "net/netip"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "sync"
  15. "testing"
  16. "time"
  17. "github.com/docker/docker/api/types"
  18. "github.com/docker/docker/client"
  19. "github.com/docker/go-connections/nat"
  20. "github.com/metacubex/mihomo/adapter/outbound"
  21. C "github.com/metacubex/mihomo/constant"
  22. "github.com/metacubex/mihomo/hub/executor"
  23. "github.com/metacubex/mihomo/transport/socks5"
  24. "github.com/stretchr/testify/assert"
  25. "github.com/stretchr/testify/require"
  26. )
  27. const (
  28. ImageShadowsocks = "mritd/shadowsocks:latest"
  29. ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest"
  30. ImageVmess = "v2fly/v2fly-core:v4.45.2"
  31. ImageVmessLatest = "sagernet/v2fly-core:latest"
  32. ImageVless = "teddysun/xray:latest"
  33. ImageTrojan = "trojangfw/trojan:latest"
  34. ImageTrojanGo = "p4gefau1t/trojan-go:latest"
  35. ImageSnell = "ghcr.io/icpz/snell-server:latest"
  36. ImageXray = "teddysun/xray:latest"
  37. ImageHysteria = "tobyxdd/hysteria:latest"
  38. )
  39. var (
  40. waitTime = time.Second
  41. localIP = netip.MustParseAddr("127.0.0.1")
  42. defaultExposedPorts = nat.PortSet{
  43. "10002/tcp": struct{}{},
  44. "10002/udp": struct{}{},
  45. }
  46. defaultPortBindings = nat.PortMap{
  47. "10002/tcp": []nat.PortBinding{
  48. {HostPort: "10002", HostIP: "0.0.0.0"},
  49. },
  50. "10002/udp": []nat.PortBinding{
  51. {HostPort: "10002", HostIP: "0.0.0.0"},
  52. },
  53. }
  54. isDarwin = runtime.GOOS == "darwin"
  55. )
  56. func init() {
  57. currentDir, err := os.Getwd()
  58. if err != nil {
  59. panic(err)
  60. }
  61. homeDir := filepath.Join(currentDir, "config")
  62. C.SetHomeDir(homeDir)
  63. if isDarwin {
  64. localIP, err = defaultRouteIP()
  65. if err != nil {
  66. panic(err)
  67. }
  68. }
  69. c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
  70. if err != nil {
  71. panic(err)
  72. }
  73. defer c.Close()
  74. list, err := c.ImageList(context.Background(), types.ImageListOptions{All: true})
  75. if err != nil {
  76. panic(err)
  77. }
  78. imageExist := func(image string) bool {
  79. for _, item := range list {
  80. for _, tag := range item.RepoTags {
  81. if image == tag {
  82. return true
  83. }
  84. }
  85. }
  86. return false
  87. }
  88. images := []string{
  89. ImageShadowsocks,
  90. ImageShadowsocksRust,
  91. ImageVmess,
  92. ImageVless,
  93. ImageTrojan,
  94. ImageTrojanGo,
  95. ImageSnell,
  96. ImageXray,
  97. ImageHysteria,
  98. }
  99. for _, image := range images {
  100. if imageExist(image) {
  101. continue
  102. }
  103. println("pulling image:", image)
  104. imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{})
  105. if err != nil {
  106. panic(err)
  107. }
  108. io.Copy(io.Discard, imageStream)
  109. }
  110. }
  111. var clean = `
  112. port: 0
  113. socks-port: 0
  114. mixed-port: 0
  115. redir-port: 0
  116. tproxy-port: 0
  117. dns:
  118. enable: false
  119. `
  120. func cleanup() {
  121. parseAndApply(clean)
  122. }
  123. func parseAndApply(cfgStr string) error {
  124. cfg, err := executor.ParseWithBytes([]byte(cfgStr))
  125. if err != nil {
  126. return err
  127. }
  128. executor.ApplyConfig(cfg, true)
  129. return nil
  130. }
  131. func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {
  132. pingCh := make(chan []byte)
  133. pongCh := make(chan []byte)
  134. test := func(t *testing.T) error {
  135. defer close(pingCh)
  136. defer close(pongCh)
  137. pingOpen := false
  138. pongOpen := false
  139. var recv []byte
  140. for {
  141. if pingOpen && pongOpen {
  142. break
  143. }
  144. select {
  145. case recv, pingOpen = <-pingCh:
  146. assert.True(t, pingOpen)
  147. assert.Equal(t, []byte("ping"), recv)
  148. case recv, pongOpen = <-pongCh:
  149. assert.True(t, pongOpen)
  150. assert.Equal(t, []byte("pong"), recv)
  151. case <-time.After(10 * time.Second):
  152. return errors.New("timeout")
  153. }
  154. }
  155. return nil
  156. }
  157. return pingCh, pongCh, test
  158. }
  159. func newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error) {
  160. pingCh := make(chan hashPair)
  161. pongCh := make(chan hashPair)
  162. test := func(t *testing.T) error {
  163. defer close(pingCh)
  164. defer close(pongCh)
  165. pingOpen := false
  166. pongOpen := false
  167. var serverPair hashPair
  168. var clientPair hashPair
  169. for {
  170. if pingOpen && pongOpen {
  171. break
  172. }
  173. select {
  174. case serverPair, pingOpen = <-pingCh:
  175. assert.True(t, pingOpen)
  176. case clientPair, pongOpen = <-pongCh:
  177. assert.True(t, pongOpen)
  178. case <-time.After(10 * time.Second):
  179. return errors.New("timeout")
  180. }
  181. }
  182. assert.Equal(t, serverPair.recvHash, clientPair.sendHash)
  183. assert.Equal(t, serverPair.sendHash, clientPair.recvHash)
  184. return nil
  185. }
  186. return pingCh, pongCh, test
  187. }
  188. func testPingPongWithSocksPort(t *testing.T, port int) {
  189. pingCh, pongCh, test := newPingPongPair()
  190. go func() {
  191. l, err := Listen("tcp", ":10001")
  192. require.NoError(t, err)
  193. defer l.Close()
  194. c, err := l.Accept()
  195. require.NoError(t, err)
  196. buf := make([]byte, 4)
  197. _, err = io.ReadFull(c, buf)
  198. require.NoError(t, err)
  199. pingCh <- buf
  200. _, err = c.Write([]byte("pong"))
  201. require.NoError(t, err)
  202. }()
  203. go func() {
  204. c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
  205. require.NoError(t, err)
  206. defer c.Close()
  207. _, err = socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil)
  208. require.NoError(t, err)
  209. _, err = c.Write([]byte("ping"))
  210. require.NoError(t, err)
  211. buf := make([]byte, 4)
  212. _, err = io.ReadFull(c, buf)
  213. require.NoError(t, err)
  214. pongCh <- buf
  215. }()
  216. test(t)
  217. }
  218. func testPingPongWithConn(t *testing.T, cc func() net.Conn) error {
  219. l, err := Listen("tcp", ":10001")
  220. if err != nil {
  221. return err
  222. }
  223. defer l.Close()
  224. pingCh, pongCh, test := newPingPongPair()
  225. go func() {
  226. c, err := l.Accept()
  227. if err != nil {
  228. return
  229. }
  230. buf := make([]byte, 4)
  231. if _, err := io.ReadFull(c, buf); err != nil {
  232. return
  233. }
  234. pingCh <- buf
  235. if _, err := c.Write([]byte("pong")); err != nil {
  236. return
  237. }
  238. }()
  239. c := cc()
  240. defer c.Close()
  241. go func() {
  242. if _, err := c.Write([]byte("ping")); err != nil {
  243. return
  244. }
  245. buf := make([]byte, 4)
  246. if _, err := io.ReadFull(c, buf); err != nil {
  247. t.Error(err)
  248. return
  249. }
  250. pongCh <- buf
  251. }()
  252. return test(t)
  253. }
  254. func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
  255. l, err := ListenPacket("udp", ":10001")
  256. require.NoError(t, err)
  257. defer l.Close()
  258. rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
  259. pingCh, pongCh, test := newPingPongPair()
  260. go func() {
  261. buf := make([]byte, 1024)
  262. n, rAddr, err := l.ReadFrom(buf)
  263. if err != nil {
  264. return
  265. }
  266. pingCh <- buf[:n]
  267. if _, err := l.WriteTo([]byte("pong"), rAddr); err != nil {
  268. return
  269. }
  270. }()
  271. go func() {
  272. if _, err := pc.WriteTo([]byte("ping"), rAddr); err != nil {
  273. return
  274. }
  275. buf := make([]byte, 1024)
  276. n, _, err := pc.ReadFrom(buf)
  277. if err != nil {
  278. return
  279. }
  280. pongCh <- buf[:n]
  281. }()
  282. return test(t)
  283. }
  284. type hashPair struct {
  285. sendHash map[int][]byte
  286. recvHash map[int][]byte
  287. }
  288. func testLargeDataWithConn(t *testing.T, cc func() net.Conn) error {
  289. l, err := Listen("tcp", ":10001")
  290. require.NoError(t, err)
  291. defer l.Close()
  292. times := 100
  293. chunkSize := int64(64 * 1024)
  294. pingCh, pongCh, test := newLargeDataPair()
  295. writeRandData := func(conn net.Conn) (map[int][]byte, error) {
  296. buf := make([]byte, chunkSize)
  297. hashMap := map[int][]byte{}
  298. for i := 0; i < times; i++ {
  299. if _, err := rand.Read(buf[1:]); err != nil {
  300. return nil, err
  301. }
  302. buf[0] = byte(i)
  303. hash := md5.Sum(buf)
  304. hashMap[i] = hash[:]
  305. if _, err := conn.Write(buf); err != nil {
  306. return nil, err
  307. }
  308. }
  309. return hashMap, nil
  310. }
  311. go func() {
  312. c, err := l.Accept()
  313. if err != nil {
  314. return
  315. }
  316. defer c.Close()
  317. hashMap := map[int][]byte{}
  318. buf := make([]byte, chunkSize)
  319. for i := 0; i < times; i++ {
  320. _, err := io.ReadFull(c, buf)
  321. if err != nil {
  322. t.Log(err.Error())
  323. return
  324. }
  325. hash := md5.Sum(buf)
  326. hashMap[int(buf[0])] = hash[:]
  327. }
  328. sendHash, err := writeRandData(c)
  329. if err != nil {
  330. t.Log(err.Error())
  331. return
  332. }
  333. pingCh <- hashPair{
  334. sendHash: sendHash,
  335. recvHash: hashMap,
  336. }
  337. }()
  338. c := cc()
  339. defer c.Close()
  340. go func() {
  341. sendHash, err := writeRandData(c)
  342. if err != nil {
  343. t.Log(err.Error())
  344. return
  345. }
  346. hashMap := map[int][]byte{}
  347. buf := make([]byte, chunkSize)
  348. for i := 0; i < times; i++ {
  349. _, err := io.ReadFull(c, buf)
  350. if err != nil {
  351. t.Log(err.Error())
  352. return
  353. }
  354. hash := md5.Sum(buf)
  355. hashMap[int(buf[0])] = hash[:]
  356. }
  357. pongCh <- hashPair{
  358. sendHash: sendHash,
  359. recvHash: hashMap,
  360. }
  361. }()
  362. return test(t)
  363. }
  364. func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
  365. l, err := ListenPacket("udp", ":10001")
  366. require.NoError(t, err)
  367. defer l.Close()
  368. rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
  369. times := 50
  370. chunkSize := int64(1024)
  371. pingCh, pongCh, test := newLargeDataPair()
  372. writeRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) {
  373. hashMap := map[int][]byte{}
  374. mux := sync.Mutex{}
  375. go func() {
  376. for i := 0; i < times; i++ {
  377. buf := make([]byte, chunkSize)
  378. if _, err := rand.Read(buf[1:]); err != nil {
  379. t.Log(err.Error())
  380. return
  381. }
  382. buf[0] = byte(i)
  383. hash := md5.Sum(buf)
  384. mux.Lock()
  385. hashMap[i] = hash[:]
  386. mux.Unlock()
  387. if _, err := pc.WriteTo(buf, addr); err != nil {
  388. t.Log(err.Error())
  389. return
  390. }
  391. }
  392. }()
  393. return hashMap, nil
  394. }
  395. go func() {
  396. var rAddr net.Addr
  397. hashMap := map[int][]byte{}
  398. buf := make([]byte, 64*1024)
  399. for i := 0; i < times; i++ {
  400. _, rAddr, err = l.ReadFrom(buf)
  401. if err != nil {
  402. t.Log(err.Error())
  403. return
  404. }
  405. hash := md5.Sum(buf[:chunkSize])
  406. hashMap[int(buf[0])] = hash[:]
  407. }
  408. sendHash, err := writeRandData(l, rAddr)
  409. if err != nil {
  410. t.Log(err.Error())
  411. return
  412. }
  413. pingCh <- hashPair{
  414. sendHash: sendHash,
  415. recvHash: hashMap,
  416. }
  417. }()
  418. go func() {
  419. sendHash, err := writeRandData(pc, rAddr)
  420. if err != nil {
  421. t.Log(err.Error())
  422. return
  423. }
  424. hashMap := map[int][]byte{}
  425. buf := make([]byte, 64*1024)
  426. for i := 0; i < times; i++ {
  427. _, _, err := pc.ReadFrom(buf)
  428. if err != nil {
  429. t.Log(err.Error())
  430. return
  431. }
  432. hash := md5.Sum(buf[:chunkSize])
  433. hashMap[int(buf[0])] = hash[:]
  434. }
  435. pongCh <- hashPair{
  436. sendHash: sendHash,
  437. recvHash: hashMap,
  438. }
  439. }()
  440. return test(t)
  441. }
  442. func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
  443. err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
  444. require.NoError(t, err)
  445. errCh := make(chan error, 1)
  446. go func() {
  447. buf := make([]byte, 1024)
  448. _, _, err := pc.ReadFrom(buf)
  449. errCh <- err
  450. }()
  451. select {
  452. case <-errCh:
  453. return nil
  454. case <-time.After(time.Second * 10):
  455. return errors.New("timeout")
  456. }
  457. }
  458. func testSuit(t *testing.T, proxy C.ProxyAdapter) {
  459. assert.NoError(t, testPingPongWithConn(t, func() net.Conn {
  460. conn, err := proxy.DialContext(context.Background(), &C.Metadata{
  461. Host: localIP.String(),
  462. DstPort: 10001,
  463. })
  464. require.NoError(t, err)
  465. return conn
  466. }))
  467. assert.NoError(t, testLargeDataWithConn(t, func() net.Conn {
  468. conn, err := proxy.DialContext(context.Background(), &C.Metadata{
  469. Host: localIP.String(),
  470. DstPort: 10001,
  471. })
  472. require.NoError(t, err)
  473. return conn
  474. }))
  475. if !proxy.SupportUDP() {
  476. return
  477. }
  478. pc, err := proxy.ListenPacketContext(context.Background(), &C.Metadata{
  479. NetWork: C.UDP,
  480. DstIP: localIP,
  481. DstPort: 10001,
  482. })
  483. require.NoError(t, err)
  484. defer pc.Close()
  485. assert.NoError(t, testPingPongWithPacketConn(t, pc))
  486. pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
  487. NetWork: C.UDP,
  488. DstIP: localIP,
  489. DstPort: 10001,
  490. })
  491. require.NoError(t, err)
  492. defer pc.Close()
  493. assert.NoError(t, testLargeDataWithPacketConn(t, pc))
  494. pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
  495. NetWork: C.UDP,
  496. DstIP: localIP,
  497. DstPort: 10001,
  498. })
  499. require.NoError(t, err)
  500. defer pc.Close()
  501. assert.NoError(t, testPacketConnTimeout(t, pc))
  502. }
  503. func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
  504. l, err := Listen("tcp", ":10001")
  505. require.NoError(b, err)
  506. defer l.Close()
  507. chunkSize := int64(16 * 1024)
  508. chunk := make([]byte, chunkSize)
  509. rand.Read(chunk)
  510. go func() {
  511. c, err := l.Accept()
  512. if err != nil {
  513. return
  514. }
  515. defer c.Close()
  516. go func() {
  517. for {
  518. _, err := c.Write(chunk)
  519. if err != nil {
  520. return
  521. }
  522. }
  523. }()
  524. io.Copy(io.Discard, c)
  525. }()
  526. conn, err := proxy.DialContext(context.Background(), &C.Metadata{
  527. Host: localIP.String(),
  528. DstPort: 10001,
  529. })
  530. require.NoError(b, err)
  531. _, err = conn.Write([]byte("skip protocol handshake"))
  532. require.NoError(b, err)
  533. b.Run("Write", func(b *testing.B) {
  534. b.SetBytes(chunkSize)
  535. for i := 0; i < b.N; i++ {
  536. conn.Write(chunk)
  537. }
  538. })
  539. b.Run("Read", func(b *testing.B) {
  540. b.SetBytes(chunkSize)
  541. buf := make([]byte, chunkSize)
  542. for i := 0; i < b.N; i++ {
  543. io.ReadFull(conn, buf)
  544. }
  545. })
  546. }
  547. func TestMihomo_Basic(t *testing.T) {
  548. basic := `
  549. mixed-port: 10000
  550. log-level: silent
  551. `
  552. err := parseAndApply(basic)
  553. require.NoError(t, err)
  554. defer cleanup()
  555. require.True(t, TCPing(net.JoinHostPort(localIP.String(), "10000")))
  556. testPingPongWithSocksPort(t, 10000)
  557. }
  558. func Benchmark_Direct(b *testing.B) {
  559. proxy := outbound.NewDirect()
  560. benchmarkProxy(b, proxy)
  561. }