add loss.go (#168)

This commit is contained in:
Pat Whittingslow
2026-07-22 13:09:12 -03:00
committed by GitHub
parent 1bbfd5eac3
commit 41c7e3c445
6 changed files with 436 additions and 6 deletions
+64 -6
View File
@@ -31,7 +31,15 @@ type Handler struct {
optcodec OptionCodec
// reasm tracks out-of-order segments staged in bufRx's free region. Always
// enabled once buffers are set (see [Handler.SetBuffers]).
reasm reassembly
reasm reassembly
// loss is the optional packet-loss recovery algorithm (RTO, congestion
// control, ...) driven from the rx/tx hooks. nil disables loss recovery, in
// which case the connection behaves as if no timing existed. nanotime is the
// monotonic time source (nanoseconds) passed to those hooks; it is non-nil
// whenever loss is non-nil (enforced by [Conn.Configure]). See [LossRecovery].
loss LossRecovery
nanotime func() int64
closing bool
shutdownRx bool
// nRetransmit stores the number of times the oldest packet was retransmit.
@@ -71,6 +79,28 @@ func (h *Handler) SetBuffers(txbuf, rxbuf []byte, packets int) error {
return h.bufTx.ResetOrReuse(txbuf, packets, 0)
}
// SetLossRecovery installs the packet-loss recovery algorithm and the monotonic
// time source (nanoseconds, the func() int64 convention used across lneto) that
// drives it. The tcp package keeps no clock of its own; nanotime is read only to
// stamp the rx/tx hooks (see [LossRecovery]). Passing loss == nil disables loss
// recovery. It should be set before the connection is opened.
func (h *Handler) SetLossRecovery(loss LossRecovery, nanotime func() int64) {
h.loss = loss
h.nanotime = nanotime
}
func (h *Handler) lossEnabled() bool { return h.loss != nil }
// NextDeadline returns the monotonic-nanosecond instant at which the connection
// must next be serviced by a transmit attempt (e.g. an RTO expiry), or 0 when
// there is no deadline or no loss recovery is configured. See [LossRecovery].
func (h *Handler) NextDeadline() int64 {
if h.loss == nil {
return 0
}
return h.loss.NextDeadline()
}
// LocalPort returns the local port of the connection. Returns 0 if the connection is closed and uninitialized.
func (h *Handler) LocalPort() uint16 {
return h.localPort
@@ -129,15 +159,22 @@ func (h *Handler) reset(localPort, remotePort uint16, iss Value) {
*h = Handler{
connid: h.connid + 1,
scb: h.scb,
bufTx: h.bufTx,
bufRx: h.bufRx,
localPort: localPort,
remotePort: remotePort,
validator: h.validator,
logger: h.logger,
reasm: h.reasm,
closing: false,
shutdownRx: false,
// Persist configuration across reopen:
validator: h.validator,
loss: h.loss,
nanotime: h.nanotime,
logger: h.logger,
// persist memory across repoen:
bufTx: h.bufTx,
bufRx: h.bufRx,
reasm: h.reasm,
}
if h.lossEnabled() {
h.loss.Reset()
}
h.reasm.clear() // preserve metadata capacity across reopen, drop held segments.
h.bufTx.ResetOrReuse(nil, 0, iss)
@@ -175,6 +212,12 @@ func (h *Handler) Recv(incomingPacket []byte) error {
return nil
}
// Notify loss recovery of the received segment (RTT sampling, timer
// management) and let it drop the segment before processing if it asks to.
if h.lossEnabled() && !h.loss.PreRx(segIncoming, h.nanotime()).Keep {
return nil
}
// Out-of-order reassembly: buffer in-window data that arrived ahead of the
// next expected sequence number before the ControlBlock (sequential-only)
// would reject it. Buffered segments live in bufRx's free region.
@@ -337,6 +380,18 @@ func (h *Handler) Send(b []byte) (int, error) {
if h.IsTxOver() {
return 0, net.ErrClosed
}
var now int64
if h.lossEnabled() {
now = h.nanotime()
if h.loss.PreTx(now).RetransmitAll {
// Go-back-N retransmission directed by loss recovery: rewind the
// send sequence and transmit buffer so unacknowledged data is resent
// from snd.UNA. Done before the early short-circuit below so an
// expired RTO retransmits even with no new data queued.
h.scb.RetransmitAll()
h.bufTx.RetransmitFromUNA()
}
}
awaitingSyn := h.AwaitingSynSend()
requeueControl := h.requeueControl
buffered := h.bufTx.BufferedUnsent()
@@ -419,6 +474,9 @@ func (h *Handler) Send(b []byte) (int, error) {
} else if prevState != h.scb.State() && h.logenabled(slog.LevelInfo) {
h.info("tcp.Handler:tx-statechange", slog.Uint64("port", uint64(h.localPort)), slog.String("oldState", prevState.String()), slog.String("newState", h.scb.State().String()), slog.String("txflags", segment.Flags.String()))
}
if h.lossEnabled() {
h.loss.PostTx(segment, now)
}
h.requeueControl = false
tfrm.SetSourcePort(h.localPort)
tfrm.SetDestinationPort(h.remotePort)