udp_control_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package bwtest
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "net"
  7. "strings"
  8. "testing"
  9. "time"
  10. )
  11. func TestUDPPacerUsesDeliveryFeedback(t *testing.T) {
  12. p := newUDPPacer(0)
  13. initial := p.rate
  14. now := p.started.Add(2 * time.Second)
  15. p.observe(10000, 10, 0, now)
  16. if p.rate >= initial {
  17. t.Fatal("automatic rate did not fall after severe loss")
  18. }
  19. lower := p.rate
  20. p.observe(10000+lower/8, 110, 0, now.Add(time.Second))
  21. if p.rate <= lower {
  22. t.Fatal("automatic rate did not grow after successful delivery")
  23. }
  24. fixed := newUDPPacer(1234567)
  25. fixed.observe(0, 0, 0, fixed.started.Add(2*time.Second))
  26. if fixed.rate != 1234567 {
  27. t.Fatal("receiver feedback changed explicit rate limit")
  28. }
  29. }
  30. func TestAutomaticUDPPacerBacksOffForPacketLoss(t *testing.T) {
  31. p := newUDPPacer(0)
  32. now := p.started.Add(2 * time.Second)
  33. // Delivery remains high enough that byte-only feedback would increase rate.
  34. p.observe(480000, 1000, 20, now)
  35. if p.rate >= 2_000_000 {
  36. t.Fatalf("rate did not fall on loss: %d", p.rate)
  37. }
  38. lower := p.rate
  39. p.observe(480000+lower/8, 2000, 20, now.Add(time.Second))
  40. if p.rate > lower {
  41. t.Fatalf("rate rose during loss cooldown: %d", p.rate)
  42. }
  43. p.observe(480000+3*lower/8, 3000, 20, now.Add(3*time.Second))
  44. if p.rate <= lower {
  45. t.Fatalf("rate failed to recover after cooldown: %d", p.rate)
  46. }
  47. fixed := newUDPPacer(2_000_000)
  48. fixed.observe(480000, 1000, 100, fixed.started.Add(2*time.Second))
  49. if fixed.rate != 2_000_000 {
  50. t.Fatal("fixed rate changed on loss")
  51. }
  52. legacy := newUDPPacer(0)
  53. legacy.observe(500000, 0, 0, legacy.started.Add(2*time.Second))
  54. if legacy.rate <= 2_000_000 {
  55. t.Fatal("byte-only feedback from older peer no longer works")
  56. }
  57. }
  58. func TestUDPNoDeliveryReportsError(t *testing.T) {
  59. ln, err := net.Listen("tcp", "127.0.0.1:0")
  60. if err != nil {
  61. t.Fatal(err)
  62. }
  63. defer ln.Close()
  64. u, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
  65. if err != nil {
  66. t.Fatal(err)
  67. }
  68. defer u.Close()
  69. go func() {
  70. for {
  71. conn, e := ln.Accept()
  72. if e != nil {
  73. return
  74. }
  75. go func() {
  76. defer conn.Close()
  77. rd := bufio.NewReader(conn)
  78. if _, e := rd.ReadBytes('\n'); e != nil {
  79. return
  80. }
  81. json.NewEncoder(conn).Encode(SessionHandshake{Type: "accept", UDPFeedback: udpFeedbackVersion, UDPPort: u.LocalAddr().(*net.UDPAddr).Port, PacketSize: 1200})
  82. for {
  83. if _, e := rd.ReadBytes('\n'); e != nil {
  84. return
  85. }
  86. }
  87. }()
  88. }
  89. }()
  90. for _, direction := range []string{"tx", "rx", "both"} {
  91. c := NewClient()
  92. if err := c.Run(context.Background(), SessionRequest{Target: "127.0.0.1", Port: ln.Addr().(*net.TCPAddr).Port, Protocol: "udp", Direction: direction, Duration: 2, PacketSize: 1200}); err != nil {
  93. t.Fatal(err)
  94. }
  95. deadline := time.Now().Add(4 * time.Second)
  96. for c.GetStatus().Running && time.Now().Before(deadline) {
  97. time.Sleep(10 * time.Millisecond)
  98. }
  99. st := c.GetStatus()
  100. c.Stop()
  101. if st.Running || !strings.Contains(st.Error, "No UDP") {
  102. t.Fatalf("%s no-data not reported: %+v", direction, st)
  103. }
  104. }
  105. }
  106. func TestUDPPacerCancellation(t *testing.T) {
  107. p := newUDPPacer(1)
  108. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
  109. defer cancel()
  110. if p.wait(ctx, 1472) {
  111. t.Fatal("rate limiter ignored cancellation")
  112. }
  113. }
  114. func TestUDPTrackerStreamsReorderingDuplicates(t *testing.T) {
  115. var tracker udpTracker
  116. for _, v := range []struct {
  117. stream uint8
  118. seq uint64
  119. lost int64
  120. }{{0, 1, 0}, {1, 1, 0}, {0, 3, 1}, {1, 2, 1}, {0, 2, 0}, {0, 4, 0}} {
  121. ok, lost := tracker.accept(v.stream, v.seq)
  122. if !ok || lost != v.lost {
  123. t.Fatalf("stream %d seq %d: accepted=%v lost=%d want=%d", v.stream, v.seq, ok, lost, v.lost)
  124. }
  125. }
  126. if ok, lost := tracker.accept(0, 3); ok || lost != 0 {
  127. t.Fatal("duplicate counted as data or loss")
  128. }
  129. }
  130. func TestBandwidthUDPDirectionsAndAutomaticPacing(t *testing.T) {
  131. s := testServer(t)
  132. for _, direction := range []string{"rx", "both", "tx"} {
  133. t.Run(direction, func(t *testing.T) {
  134. c := NewClient()
  135. t.Cleanup(c.Stop)
  136. if err := c.Run(context.Background(), SessionRequest{Target: "127.0.0.1", Port: s.config.Port, Protocol: "udp", Direction: direction, Streams: 4, PacketSize: 1472, Duration: 2}); err != nil {
  137. t.Fatal(err)
  138. }
  139. deadline := time.Now().Add(5 * time.Second)
  140. for c.GetStatus().Running && time.Now().Before(deadline) {
  141. time.Sleep(10 * time.Millisecond)
  142. }
  143. st := c.GetStatus()
  144. if st.Running || st.Error != "" || st.Summary == nil {
  145. t.Fatalf("UDP test failed: %+v", st)
  146. }
  147. if direction != "rx" && st.Summary.TotalTxBytes < 100000 {
  148. t.Fatalf("no confirmed TX: %+v", st.Summary)
  149. }
  150. if direction != "tx" && st.Summary.TotalRxBytes < 100000 {
  151. t.Fatalf("no reverse UDP: %+v", st.Summary)
  152. }
  153. if st.Summary.AvgLossPct != 0 {
  154. t.Fatalf("streams or reordered packets falsely counted as loss: %+v", st.Summary)
  155. }
  156. if st.Summary.AvgTxMbps > 20 || st.Summary.AvgRxMbps > 20 {
  157. t.Fatal("automatic UDP started with an unbounded flood")
  158. }
  159. if direction == "tx" && st.Summary.TotalRxBytes != 0 {
  160. t.Fatal("TX delivery reports mislabeled as downloaded data")
  161. }
  162. })
  163. }
  164. }