udp_control.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package bwtest
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "sync"
  7. "time"
  8. "github.com/vishvananda/netlink"
  9. )
  10. const udpFeedbackVersion = "receiver-v1"
  11. const udpProbeType uint8 = 4
  12. // UDP without a requested limit probes capacity using receiver feedback instead
  13. // of enqueuing an unbounded amount of traffic ahead of the reverse direction.
  14. type udpPacer struct {
  15. mu sync.Mutex
  16. limiter *RateLimiter
  17. automatic bool
  18. rate int64
  19. started, lastFeedback, lastChange time.Time
  20. delivered int64
  21. }
  22. func newUDPPacer(limit int64) *udpPacer {
  23. now := time.Now()
  24. automatic := limit <= 0
  25. if automatic {
  26. limit = 2_000_000
  27. }
  28. return &udpPacer{limiter: NewRateLimiter(limit), automatic: automatic, rate: limit, started: now, lastFeedback: now, lastChange: now}
  29. }
  30. func (p *udpPacer) wait(ctx context.Context, n int) bool {
  31. // Take only a small token-accounting lock; feedback and other streams can
  32. // update the rate while this stream waits for its next datagram.
  33. for {
  34. p.mu.Lock()
  35. if p.automatic && time.Since(p.lastFeedback) > 2*time.Second && time.Since(p.lastChange) > time.Second {
  36. p.setRate(max(int64(128000), p.rate/2))
  37. p.lastChange = time.Now()
  38. }
  39. rl := p.limiter
  40. now := time.Now()
  41. rl.tokens += now.Sub(rl.lastCheckTime).Seconds() * float64(p.rate) / 8
  42. rl.lastCheckTime = now
  43. cap := max(float64(n), float64(p.rate)/8*.02)
  44. if rl.tokens > cap {
  45. rl.tokens = cap
  46. }
  47. if rl.tokens >= float64(n) {
  48. rl.tokens -= float64(n)
  49. p.mu.Unlock()
  50. return ctx.Err() == nil
  51. }
  52. delay := time.Duration((float64(n) - rl.tokens) / (float64(p.rate) / 8) * float64(time.Second))
  53. p.mu.Unlock()
  54. if delay > 20*time.Millisecond {
  55. delay = 20 * time.Millisecond
  56. }
  57. timer := time.NewTimer(delay)
  58. select {
  59. case <-ctx.Done():
  60. timer.Stop()
  61. return false
  62. case <-timer.C:
  63. }
  64. }
  65. }
  66. func (p *udpPacer) setRate(rate int64) { p.rate = rate; p.limiter.bps = rate }
  67. func (p *udpPacer) observe(bytes int64, now time.Time) {
  68. p.mu.Lock()
  69. defer p.mu.Unlock()
  70. dt := now.Sub(p.lastFeedback).Seconds()
  71. delta := bytes - p.delivered
  72. if dt <= 0 || delta < 0 {
  73. return
  74. }
  75. p.lastFeedback = now
  76. p.delivered = bytes
  77. if !p.automatic || now.Sub(p.started) < time.Second || now.Sub(p.lastChange) < 450*time.Millisecond {
  78. return
  79. }
  80. deliveredRate := int64(float64(delta) * 8 / dt)
  81. switch {
  82. case deliveredRate < p.rate*7/10:
  83. p.setRate(max(int64(128000), deliveredRate*11/10))
  84. case deliveredRate >= p.rate*85/100:
  85. p.setRate(min(int64(10_000_000_000), p.rate*5/4))
  86. }
  87. p.lastChange = now
  88. }
  89. func udpPayloadSize(requested int, ip net.IP) (int, error) {
  90. routes, err := netlink.RouteGet(ip)
  91. if err != nil {
  92. return 0, fmt.Errorf("bandwidth target route: %w", err)
  93. }
  94. if len(routes) == 0 {
  95. return 0, fmt.Errorf("no route to bandwidth target")
  96. }
  97. link, err := netlink.LinkByIndex(routes[0].LinkIndex)
  98. if err != nil {
  99. return 0, err
  100. }
  101. mtu := link.Attrs().MTU
  102. if routes[0].MTU > 0 {
  103. mtu = min(mtu, routes[0].MTU)
  104. }
  105. overhead := 28
  106. if ip.To4() == nil {
  107. overhead = 48
  108. }
  109. size := min(requested, mtu-overhead)
  110. if size < 64 {
  111. return 0, fmt.Errorf("target MTU is too small for bandwidth datagrams")
  112. }
  113. return size, nil
  114. }
  115. func udpProbe(hash uint64) []byte {
  116. b := make([]byte, HeaderSize)
  117. EncodeHeader(b, PacketHeader{Magic: MagicBytes, Version: ProtoVer, PacketType: udpProbeType, SessionHash: hash})
  118. return b
  119. }
  120. // Each UDP stream has its own sequence space. Late packets within the bounded
  121. // reorder window undo inferred losses, and duplicate datagrams are not counted.
  122. type udpSequenceState struct {
  123. max uint64
  124. seen [1024]uint64
  125. }
  126. type udpTracker struct {
  127. streams map[uint8]*udpSequenceState
  128. lost int64
  129. }
  130. func (t *udpTracker) accept(stream uint8, seq uint64) (bool, int64) {
  131. if seq == 0 {
  132. return false, t.lost
  133. }
  134. if t.streams == nil {
  135. t.streams = make(map[uint8]*udpSequenceState)
  136. }
  137. s := t.streams[stream]
  138. if s == nil {
  139. s = &udpSequenceState{}
  140. t.streams[stream] = s
  141. }
  142. if seq > s.max {
  143. t.lost += int64(seq - s.max - 1)
  144. s.max = seq
  145. } else {
  146. if s.max-seq >= 1024 || s.seen[seq%1024] == seq {
  147. return false, t.lost
  148. }
  149. if t.lost > 0 {
  150. t.lost--
  151. }
  152. }
  153. s.seen[seq%1024] = seq
  154. return true, t.lost
  155. }