github.com/ooni/psiphon/tunnel-core@v0.0.0-20230105123940-fe12a24c96ee/oovendor/quic-go/mtu_discoverer.go (about)

     1  package quic
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/ooni/psiphon/tunnel-core/oovendor/quic-go/internal/ackhandler"
     7  	"github.com/ooni/psiphon/tunnel-core/oovendor/quic-go/internal/protocol"
     8  	"github.com/ooni/psiphon/tunnel-core/oovendor/quic-go/internal/utils"
     9  	"github.com/ooni/psiphon/tunnel-core/oovendor/quic-go/internal/wire"
    10  )
    11  
    12  type mtuDiscoverer interface {
    13  	ShouldSendProbe(now time.Time) bool
    14  	NextProbeTime() time.Time
    15  	GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount)
    16  }
    17  
    18  const (
    19  	// At some point, we have to stop searching for a higher MTU.
    20  	// We're happy to send a packet that's 10 bytes smaller than the actual MTU.
    21  	maxMTUDiff = 20
    22  	// send a probe packet every mtuProbeDelay RTTs
    23  	mtuProbeDelay = 5
    24  )
    25  
    26  type mtuFinder struct {
    27  	lastProbeTime time.Time
    28  	probeInFlight bool
    29  	mtuIncreased  func(protocol.ByteCount)
    30  
    31  	rttStats *utils.RTTStats
    32  	current  protocol.ByteCount
    33  	max      protocol.ByteCount // the maximum value, as advertised by the peer (or our maximum size buffer)
    34  }
    35  
    36  var _ mtuDiscoverer = &mtuFinder{}
    37  
    38  func newMTUDiscoverer(rttStats *utils.RTTStats, start, max protocol.ByteCount, mtuIncreased func(protocol.ByteCount)) mtuDiscoverer {
    39  	return &mtuFinder{
    40  		current:       start,
    41  		rttStats:      rttStats,
    42  		lastProbeTime: time.Now(), // to make sure the first probe packet is not sent immediately
    43  		mtuIncreased:  mtuIncreased,
    44  		max:           max,
    45  	}
    46  }
    47  
    48  func (f *mtuFinder) done() bool {
    49  	return f.max-f.current <= maxMTUDiff+1
    50  }
    51  
    52  func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
    53  	if f.probeInFlight || f.done() {
    54  		return false
    55  	}
    56  	return !now.Before(f.NextProbeTime())
    57  }
    58  
    59  // NextProbeTime returns the time when the next probe packet should be sent.
    60  // It returns the zero value if no probe packet should be sent.
    61  func (f *mtuFinder) NextProbeTime() time.Time {
    62  	if f.probeInFlight || f.done() {
    63  		return time.Time{}
    64  	}
    65  	return f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT())
    66  }
    67  
    68  func (f *mtuFinder) GetPing() (ackhandler.Frame, protocol.ByteCount) {
    69  	size := (f.max + f.current) / 2
    70  	f.lastProbeTime = time.Now()
    71  	f.probeInFlight = true
    72  	return ackhandler.Frame{
    73  		Frame: &wire.PingFrame{},
    74  		OnLost: func(wire.Frame) {
    75  			f.probeInFlight = false
    76  			f.max = size
    77  		},
    78  		OnAcked: func(wire.Frame) {
    79  			f.probeInFlight = false
    80  			f.current = size
    81  			f.mtuIncreased(size)
    82  		},
    83  	}, size
    84  }