outbound_manager_test.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package amneziawgnet
  2. import (
  3. "net"
  4. "sync"
  5. "testing"
  6. "time"
  7. "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
  8. "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  9. )
  10. // egressPortBound reports whether the egress server's Port accepts TCP.
  11. func egressPortBound(t *testing.T) bool {
  12. t.Helper()
  13. conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", itoa(GetEgressServer().Port())), 500*time.Millisecond)
  14. if err != nil {
  15. return false
  16. }
  17. conn.Close()
  18. return true
  19. }
  20. func itoa(n int) string {
  21. if n == 0 {
  22. return "0"
  23. }
  24. var b [8]byte
  25. i := len(b)
  26. for n > 0 {
  27. i--
  28. b[i] = byte('0' + n%10)
  29. n /= 10
  30. }
  31. return string(b[i:])
  32. }
  33. // newTestOutboundDesired builds one runnable outbound desired state.
  34. func newTestOutboundDesired(t *testing.T, tag string) OutboundDesired {
  35. t.Helper()
  36. priv, _, err := wireguard.GenerateWireguardKeypair()
  37. if err != nil {
  38. t.Fatalf("generate keypair: %v", err)
  39. }
  40. return OutboundDesired{
  41. Instance: amneziawg.OutboundInstance{
  42. Tag: tag,
  43. Address: []string{"10.204.0.1/24"},
  44. MTU: 1420,
  45. PrivateKey: priv,
  46. ListenPort: 0,
  47. },
  48. }
  49. }
  50. // TestOutboundManagerReconcileEmptyDesiredClosesEgress verifies that an empty
  51. // desired set tears down interfaces and releases the egress port.
  52. func TestOutboundManagerReconcileEmptyDesiredClosesEgress(t *testing.T) {
  53. m := &OutboundManager{iface: map[string]*managedOutbound{}}
  54. defer m.Reconcile(nil)
  55. // Other tests in this package may leave the process-wide egress
  56. // singleton bound; converge to a known-free state before pinning.
  57. GetEgressServer().Close()
  58. if egressPortBound(t) {
  59. t.Fatal("egress port still bound after Close; Close() failed to release it")
  60. }
  61. // Non-empty: listener must come up.
  62. d := newTestOutboundDesired(t, "t1")
  63. m.Reconcile([]OutboundDesired{d})
  64. if !egressPortBound(t) {
  65. t.Fatal("egress port not bound after Reconcile with a desired outbound")
  66. }
  67. held := GetEgressServer().Port()
  68. // Empty: listener must be released so other listeners can take the port.
  69. m.Reconcile(nil)
  70. if egressPortBound(t) {
  71. t.Fatal("egress port still bound after Reconcile(nil)")
  72. }
  73. ln, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", itoa(held)))
  74. if err != nil {
  75. t.Fatalf("egress port must be free after Reconcile(nil): %v", err)
  76. }
  77. ln.Close()
  78. // Back to non-empty and empty again: Close/Listen must be repeatable.
  79. m.Reconcile([]OutboundDesired{d})
  80. if !egressPortBound(t) {
  81. t.Fatal("egress port not re-bound after a second non-empty Reconcile")
  82. }
  83. m.Reconcile(nil)
  84. if egressPortBound(t) {
  85. t.Fatal("egress port still bound after a second Reconcile(nil)")
  86. }
  87. }
  88. // TestEgressServerCloseDuringConcurrentAccepts ensures Close during
  89. // concurrent accepts shuts down cleanly without hanging wg.Wait().
  90. func TestEgressServerCloseDuringConcurrentAccepts(t *testing.T) {
  91. srv := GetEgressServer()
  92. if err := srv.Listen(); err != nil {
  93. t.Fatal(err)
  94. }
  95. addr := net.JoinHostPort("127.0.0.1", itoa(srv.Port()))
  96. stop := make(chan struct{})
  97. done := make(chan struct{})
  98. var clientWg sync.WaitGroup
  99. go func() {
  100. defer close(done)
  101. for {
  102. select {
  103. case <-stop:
  104. return
  105. default:
  106. c, err := net.DialTimeout("tcp", addr, 50*time.Millisecond)
  107. if err == nil {
  108. clientWg.Add(1)
  109. go func(conn net.Conn) {
  110. defer clientWg.Done()
  111. time.Sleep(20 * time.Millisecond)
  112. conn.Close()
  113. }(c)
  114. }
  115. time.Sleep(2 * time.Millisecond)
  116. }
  117. }
  118. }()
  119. time.Sleep(20 * time.Millisecond)
  120. closeChan := make(chan struct{})
  121. go func() {
  122. srv.Close()
  123. close(closeChan)
  124. }()
  125. select {
  126. case <-closeChan:
  127. case <-time.After(3 * time.Second):
  128. t.Fatal("srv.Close() hung waiting for connection handlers to exit")
  129. }
  130. close(stop)
  131. <-done
  132. clientWg.Wait()
  133. }