120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
package quic
|
|
|
|
import (
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/quic-go/quic-go/internal/ackhandler"
|
|
"github.com/quic-go/quic-go/internal/protocol"
|
|
"github.com/quic-go/quic-go/internal/utils"
|
|
"github.com/quic-go/quic-go/internal/wire"
|
|
)
|
|
|
|
type mtuDiscoverer interface {
|
|
// Start starts the MTU discovery process.
|
|
// It's unnecessary to call ShouldSendProbe before that.
|
|
Start(maxPacketSize protocol.ByteCount)
|
|
ShouldSendProbe(now time.Time) bool
|
|
CurrentSize() protocol.ByteCount
|
|
GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount)
|
|
}
|
|
|
|
const (
|
|
// At some point, we have to stop searching for a higher MTU.
|
|
// We're happy to send a packet that's 10 bytes smaller than the actual MTU.
|
|
maxMTUDiff = 20
|
|
// send a probe packet every mtuProbeDelay RTTs
|
|
mtuProbeDelay = 5
|
|
)
|
|
|
|
func getMaxPacketSize(addr net.Addr) protocol.ByteCount {
|
|
maxSize := protocol.ByteCount(protocol.MinInitialPacketSize)
|
|
// If this is not a UDP address, we don't know anything about the MTU.
|
|
// Use the minimum size of an Initial packet as the max packet size.
|
|
if udpAddr, ok := addr.(*net.UDPAddr); ok {
|
|
if utils.IsIPv4(udpAddr.IP) {
|
|
maxSize = protocol.InitialPacketSizeIPv4
|
|
} else {
|
|
maxSize = protocol.InitialPacketSizeIPv6
|
|
}
|
|
}
|
|
return maxSize
|
|
}
|
|
|
|
type mtuFinder struct {
|
|
lastProbeTime time.Time
|
|
mtuIncreased func(protocol.ByteCount)
|
|
|
|
rttStats *utils.RTTStats
|
|
inFlight protocol.ByteCount // the size of the probe packet currently in flight. InvalidByteCount if none is in flight
|
|
current protocol.ByteCount
|
|
max protocol.ByteCount // the maximum value, as advertised by the peer (or our maximum size buffer)
|
|
}
|
|
|
|
var _ mtuDiscoverer = &mtuFinder{}
|
|
|
|
func newMTUDiscoverer(rttStats *utils.RTTStats, start protocol.ByteCount, mtuIncreased func(protocol.ByteCount)) *mtuFinder {
|
|
return &mtuFinder{
|
|
inFlight: protocol.InvalidByteCount,
|
|
current: start,
|
|
rttStats: rttStats,
|
|
mtuIncreased: mtuIncreased,
|
|
}
|
|
}
|
|
|
|
func (f *mtuFinder) done() bool {
|
|
return f.max-f.current <= maxMTUDiff+1
|
|
}
|
|
|
|
func (f *mtuFinder) Start(maxPacketSize protocol.ByteCount) {
|
|
f.lastProbeTime = time.Now() // makes sure the first probe packet is not sent immediately
|
|
f.max = maxPacketSize
|
|
}
|
|
|
|
func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
|
|
if f.max == 0 || f.lastProbeTime.IsZero() {
|
|
return false
|
|
}
|
|
if f.inFlight != protocol.InvalidByteCount || f.done() {
|
|
return false
|
|
}
|
|
return !now.Before(f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT()))
|
|
}
|
|
|
|
func (f *mtuFinder) GetPing() (ackhandler.Frame, protocol.ByteCount) {
|
|
size := (f.max + f.current) / 2
|
|
f.lastProbeTime = time.Now()
|
|
f.inFlight = size
|
|
return ackhandler.Frame{
|
|
Frame: &wire.PingFrame{},
|
|
Handler: (*mtuFinderAckHandler)(f),
|
|
}, size
|
|
}
|
|
|
|
func (f *mtuFinder) CurrentSize() protocol.ByteCount {
|
|
return f.current
|
|
}
|
|
|
|
type mtuFinderAckHandler mtuFinder
|
|
|
|
var _ ackhandler.FrameHandler = &mtuFinderAckHandler{}
|
|
|
|
func (h *mtuFinderAckHandler) OnAcked(wire.Frame) {
|
|
size := h.inFlight
|
|
if size == protocol.InvalidByteCount {
|
|
panic("OnAcked callback called although there's no MTU probe packet in flight")
|
|
}
|
|
h.inFlight = protocol.InvalidByteCount
|
|
h.current = size
|
|
h.mtuIncreased(size)
|
|
}
|
|
|
|
func (h *mtuFinderAckHandler) OnLost(wire.Frame) {
|
|
size := h.inFlight
|
|
if size == protocol.InvalidByteCount {
|
|
panic("OnLost callback called although there's no MTU probe packet in flight")
|
|
}
|
|
h.max = size
|
|
h.inFlight = protocol.InvalidByteCount
|
|
}
|