mirror of
https://github.com/golang/net.git
synced 2026-04-01 02:47:08 +09:00
The x/net go.mod now depends on go1.23. Change-Id: I24960949d84753f84f75fbdc97b7dfb92191a4d2 Reviewed-on: https://go-review.googlesource.com/c/net/+/664295 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> Auto-Submit: Damien Neil <dneil@google.com>
404 lines
14 KiB
Go
404 lines
14 KiB
Go
// Copyright 2023 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package quic
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// maybeSend sends datagrams, if possible.
|
|
//
|
|
// If sending is blocked by pacing, it returns the next time
|
|
// a datagram may be sent.
|
|
//
|
|
// If sending is blocked indefinitely, it returns the zero Time.
|
|
func (c *Conn) maybeSend(now time.Time) (next time.Time) {
|
|
// Assumption: The congestion window is not underutilized.
|
|
// If congestion control, pacing, and anti-amplification all permit sending,
|
|
// but we have no packet to send, then we will declare the window underutilized.
|
|
underutilized := false
|
|
defer func() {
|
|
c.loss.cc.setUnderutilized(c.log, underutilized)
|
|
}()
|
|
|
|
// Send one datagram on each iteration of this loop,
|
|
// until we hit a limit or run out of data to send.
|
|
//
|
|
// For each number space where we have write keys,
|
|
// attempt to construct a packet in that space.
|
|
// If the packet contains no frames (we have no data in need of sending),
|
|
// abandon the packet.
|
|
//
|
|
// Speculatively constructing packets means we don't need
|
|
// separate code paths for "do we have data to send?" and
|
|
// "send the data" that need to be kept in sync.
|
|
for {
|
|
limit, next := c.loss.sendLimit(now)
|
|
if limit == ccBlocked {
|
|
// If anti-amplification blocks sending, then no packet can be sent.
|
|
return next
|
|
}
|
|
if !c.sendOK(now) {
|
|
return time.Time{}
|
|
}
|
|
// We may still send ACKs, even if congestion control or pacing limit sending.
|
|
|
|
// Prepare to write a datagram of at most maxSendSize bytes.
|
|
c.w.reset(c.loss.maxSendSize())
|
|
|
|
dstConnID, ok := c.connIDState.dstConnID()
|
|
if !ok {
|
|
// It is currently not possible for us to end up without a connection ID,
|
|
// but handle the case anyway.
|
|
return time.Time{}
|
|
}
|
|
|
|
// Initial packet.
|
|
pad := false
|
|
var sentInitial *sentPacket
|
|
if c.keysInitial.canWrite() {
|
|
pnumMaxAcked := c.loss.spaces[initialSpace].maxAcked
|
|
pnum := c.loss.nextNumber(initialSpace)
|
|
p := longPacket{
|
|
ptype: packetTypeInitial,
|
|
version: quicVersion1,
|
|
num: pnum,
|
|
dstConnID: dstConnID,
|
|
srcConnID: c.connIDState.srcConnID(),
|
|
extra: c.retryToken,
|
|
}
|
|
c.w.startProtectedLongHeaderPacket(pnumMaxAcked, p)
|
|
c.appendFrames(now, initialSpace, pnum, limit)
|
|
if logPackets {
|
|
logSentPacket(c, packetTypeInitial, pnum, p.srcConnID, p.dstConnID, c.w.payload())
|
|
}
|
|
if c.logEnabled(QLogLevelPacket) && len(c.w.payload()) > 0 {
|
|
c.logPacketSent(packetTypeInitial, pnum, p.srcConnID, p.dstConnID, c.w.packetLen(), c.w.payload())
|
|
}
|
|
sentInitial = c.w.finishProtectedLongHeaderPacket(pnumMaxAcked, c.keysInitial.w, p)
|
|
if sentInitial != nil {
|
|
// Client initial packets and ack-eliciting server initial packaets
|
|
// need to be sent in a datagram padded to at least 1200 bytes.
|
|
// We can't add the padding yet, however, since we may want to
|
|
// coalesce additional packets with this one.
|
|
if c.side == clientSide || sentInitial.ackEliciting {
|
|
pad = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handshake packet.
|
|
if c.keysHandshake.canWrite() {
|
|
pnumMaxAcked := c.loss.spaces[handshakeSpace].maxAcked
|
|
pnum := c.loss.nextNumber(handshakeSpace)
|
|
p := longPacket{
|
|
ptype: packetTypeHandshake,
|
|
version: quicVersion1,
|
|
num: pnum,
|
|
dstConnID: dstConnID,
|
|
srcConnID: c.connIDState.srcConnID(),
|
|
}
|
|
c.w.startProtectedLongHeaderPacket(pnumMaxAcked, p)
|
|
c.appendFrames(now, handshakeSpace, pnum, limit)
|
|
if logPackets {
|
|
logSentPacket(c, packetTypeHandshake, pnum, p.srcConnID, p.dstConnID, c.w.payload())
|
|
}
|
|
if c.logEnabled(QLogLevelPacket) && len(c.w.payload()) > 0 {
|
|
c.logPacketSent(packetTypeHandshake, pnum, p.srcConnID, p.dstConnID, c.w.packetLen(), c.w.payload())
|
|
}
|
|
if sent := c.w.finishProtectedLongHeaderPacket(pnumMaxAcked, c.keysHandshake.w, p); sent != nil {
|
|
c.packetSent(now, handshakeSpace, sent)
|
|
if c.side == clientSide {
|
|
// "[...] a client MUST discard Initial keys when it first
|
|
// sends a Handshake packet [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9001.html#section-4.9.1-2
|
|
c.discardKeys(now, initialSpace)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1-RTT packet.
|
|
if c.keysAppData.canWrite() {
|
|
pnumMaxAcked := c.loss.spaces[appDataSpace].maxAcked
|
|
pnum := c.loss.nextNumber(appDataSpace)
|
|
c.w.start1RTTPacket(pnum, pnumMaxAcked, dstConnID)
|
|
c.appendFrames(now, appDataSpace, pnum, limit)
|
|
if pad && len(c.w.payload()) > 0 {
|
|
// 1-RTT packets have no length field and extend to the end
|
|
// of the datagram, so if we're sending a datagram that needs
|
|
// padding we need to add it inside the 1-RTT packet.
|
|
c.w.appendPaddingTo(paddedInitialDatagramSize)
|
|
pad = false
|
|
}
|
|
if logPackets {
|
|
logSentPacket(c, packetType1RTT, pnum, nil, dstConnID, c.w.payload())
|
|
}
|
|
if c.logEnabled(QLogLevelPacket) && len(c.w.payload()) > 0 {
|
|
c.logPacketSent(packetType1RTT, pnum, nil, dstConnID, c.w.packetLen(), c.w.payload())
|
|
}
|
|
if sent := c.w.finish1RTTPacket(pnum, pnumMaxAcked, dstConnID, &c.keysAppData); sent != nil {
|
|
c.packetSent(now, appDataSpace, sent)
|
|
}
|
|
}
|
|
|
|
buf := c.w.datagram()
|
|
if len(buf) == 0 {
|
|
if limit == ccOK {
|
|
// We have nothing to send, and congestion control does not
|
|
// block sending. The congestion window is underutilized.
|
|
underutilized = true
|
|
}
|
|
return next
|
|
}
|
|
|
|
if sentInitial != nil {
|
|
if pad {
|
|
// Pad out the datagram with zeros, coalescing the Initial
|
|
// packet with invalid packets that will be ignored by the peer.
|
|
// https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1-1
|
|
for len(buf) < paddedInitialDatagramSize {
|
|
buf = append(buf, 0)
|
|
// Technically this padding isn't in any packet, but
|
|
// account it to the Initial packet in this datagram
|
|
// for purposes of flow control and loss recovery.
|
|
sentInitial.size++
|
|
sentInitial.inFlight = true
|
|
}
|
|
}
|
|
// If we're a client and this Initial packet is coalesced
|
|
// with a Handshake packet, then we've discarded Initial keys
|
|
// since constructing the packet and shouldn't record it as in-flight.
|
|
if c.keysInitial.canWrite() {
|
|
c.packetSent(now, initialSpace, sentInitial)
|
|
}
|
|
}
|
|
|
|
c.endpoint.sendDatagram(datagram{
|
|
b: buf,
|
|
peerAddr: c.peerAddr,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (c *Conn) packetSent(now time.Time, space numberSpace, sent *sentPacket) {
|
|
c.idleHandlePacketSent(now, sent)
|
|
c.loss.packetSent(now, c.log, space, sent)
|
|
}
|
|
|
|
func (c *Conn) appendFrames(now time.Time, space numberSpace, pnum packetNumber, limit ccLimit) {
|
|
if c.lifetime.localErr != nil {
|
|
c.appendConnectionCloseFrame(now, space, c.lifetime.localErr)
|
|
return
|
|
}
|
|
|
|
shouldSendAck := c.acks[space].shouldSendAck(now)
|
|
if limit != ccOK {
|
|
// ACKs are not limited by congestion control.
|
|
if shouldSendAck && c.appendAckFrame(now, space) {
|
|
c.acks[space].sentAck()
|
|
}
|
|
return
|
|
}
|
|
// We want to send an ACK frame if the ack controller wants to send a frame now,
|
|
// OR if we are sending a packet anyway and have ack-eliciting packets which we
|
|
// have not yet acked.
|
|
//
|
|
// We speculatively add ACK frames here, to put them at the front of the packet
|
|
// to avoid truncation.
|
|
//
|
|
// After adding all frames, if we don't need to send an ACK frame and have not
|
|
// added any other frames, we abandon the packet.
|
|
if c.appendAckFrame(now, space) {
|
|
defer func() {
|
|
// All frames other than ACK and PADDING are ack-eliciting,
|
|
// so if the packet is ack-eliciting we've added additional
|
|
// frames to it.
|
|
if !shouldSendAck && !c.w.sent.ackEliciting {
|
|
// There's nothing in this packet but ACK frames, and
|
|
// we don't want to send an ACK-only packet at this time.
|
|
// Abandoning the packet means we wrote an ACK frame for
|
|
// nothing, but constructing the frame is cheap.
|
|
c.w.abandonPacket()
|
|
return
|
|
}
|
|
// Either we are willing to send an ACK-only packet,
|
|
// or we've added additional frames.
|
|
c.acks[space].sentAck()
|
|
if !c.w.sent.ackEliciting && c.shouldMakePacketAckEliciting() {
|
|
c.w.appendPingFrame()
|
|
}
|
|
}()
|
|
}
|
|
if limit != ccOK {
|
|
return
|
|
}
|
|
pto := c.loss.ptoExpired
|
|
|
|
// TODO: Add all the other frames we can send.
|
|
|
|
// CRYPTO
|
|
c.crypto[space].dataToSend(pto, func(off, size int64) int64 {
|
|
b, _ := c.w.appendCryptoFrame(off, int(size))
|
|
c.crypto[space].sendData(off, b)
|
|
return int64(len(b))
|
|
})
|
|
|
|
// Test-only PING frames.
|
|
if space == c.testSendPingSpace && c.testSendPing.shouldSendPTO(pto) {
|
|
if !c.w.appendPingFrame() {
|
|
return
|
|
}
|
|
c.testSendPing.setSent(pnum)
|
|
}
|
|
|
|
if space == appDataSpace {
|
|
// HANDSHAKE_DONE
|
|
if c.handshakeConfirmed.shouldSendPTO(pto) {
|
|
if !c.w.appendHandshakeDoneFrame() {
|
|
return
|
|
}
|
|
c.handshakeConfirmed.setSent(pnum)
|
|
}
|
|
|
|
// NEW_CONNECTION_ID, RETIRE_CONNECTION_ID
|
|
if !c.connIDState.appendFrames(c, pnum, pto) {
|
|
return
|
|
}
|
|
|
|
// PATH_RESPONSE
|
|
if pad, ok := c.appendPathFrames(); !ok {
|
|
return
|
|
} else if pad {
|
|
defer c.w.appendPaddingTo(smallestMaxDatagramSize)
|
|
}
|
|
|
|
// All stream-related frames. This should come last in the packet,
|
|
// so large amounts of STREAM data don't crowd out other frames
|
|
// we may need to send.
|
|
if !c.appendStreamFrames(&c.w, pnum, pto) {
|
|
return
|
|
}
|
|
|
|
if !c.appendKeepAlive(now) {
|
|
return
|
|
}
|
|
}
|
|
|
|
// If this is a PTO probe and we haven't added an ack-eliciting frame yet,
|
|
// add a PING to make this an ack-eliciting probe.
|
|
//
|
|
// Technically, there are separate PTO timers for each number space.
|
|
// When a PTO timer expires, we MUST send an ack-eliciting packet in the
|
|
// timer's space. We SHOULD send ack-eliciting packets in every other space
|
|
// with in-flight data. (RFC 9002, section 6.2.4)
|
|
//
|
|
// What we actually do is send a single datagram containing an ack-eliciting packet
|
|
// for every space for which we have keys.
|
|
//
|
|
// We fill the PTO probe packets with new or unacknowledged data. For example,
|
|
// a PTO probe sent for the Initial space will generally retransmit previously
|
|
// sent but unacknowledged CRYPTO data.
|
|
//
|
|
// When sending a PTO probe datagram containing multiple packets, it is
|
|
// possible that an earlier packet will fill up the datagram, leaving no
|
|
// space for the remaining probe packet(s). This is not a problem in practice.
|
|
//
|
|
// A client discards Initial keys when it first sends a Handshake packet
|
|
// (RFC 9001 Section 4.9.1). Handshake keys are discarded when the handshake
|
|
// is confirmed (RFC 9001 Section 4.9.2). The PTO timer is not set for the
|
|
// Application Data packet number space until the handshake is confirmed
|
|
// (RFC 9002 Section 6.2.1). Therefore, the only times a PTO probe can fire
|
|
// while data for multiple spaces is in flight are:
|
|
//
|
|
// - a server's Initial or Handshake timers can fire while Initial and Handshake
|
|
// data is in flight; and
|
|
//
|
|
// - a client's Handshake timer can fire while Handshake and Application Data
|
|
// data is in flight.
|
|
//
|
|
// It is theoretically possible for a server's Initial CRYPTO data to overflow
|
|
// the maximum datagram size, but unlikely in practice; this space contains
|
|
// only the ServerHello TLS message, which is small. It's also unlikely that
|
|
// the Handshake PTO probe will fire while Initial data is in flight (this
|
|
// requires not just that the Initial CRYPTO data completely fill a datagram,
|
|
// but a quite specific arrangement of lost and retransmitted packets.)
|
|
// We don't bother worrying about this case here, since the worst case is
|
|
// that we send a PTO probe for the in-flight Initial data and drop the
|
|
// Handshake probe.
|
|
//
|
|
// If a client's Handshake PTO timer fires while Application Data data is in
|
|
// flight, it is possible that the resent Handshake CRYPTO data will crowd
|
|
// out the probe for the Application Data space. However, since this probe is
|
|
// optional (recall that the Application Data PTO timer is never set until
|
|
// after Handshake keys have been discarded), dropping it is acceptable.
|
|
if pto && !c.w.sent.ackEliciting {
|
|
c.w.appendPingFrame()
|
|
}
|
|
}
|
|
|
|
// shouldMakePacketAckEliciting is called when sending a packet containing nothing but an ACK frame.
|
|
// It reports whether we should add a PING frame to the packet to make it ack-eliciting.
|
|
func (c *Conn) shouldMakePacketAckEliciting() bool {
|
|
if c.keysAppData.needAckEliciting() {
|
|
// The peer has initiated a key update.
|
|
// We haven't sent them any packets yet in the new phase.
|
|
// Make this an ack-eliciting packet.
|
|
// Their ack of this packet will complete the key update.
|
|
return true
|
|
}
|
|
if c.loss.consecutiveNonAckElicitingPackets >= 19 {
|
|
// We've sent a run of non-ack-eliciting packets.
|
|
// Add in an ack-eliciting one every once in a while so the peer
|
|
// lets us know which ones have arrived.
|
|
//
|
|
// Google QUICHE injects a PING after sending 19 packets. We do the same.
|
|
//
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-13.2.4-2
|
|
return true
|
|
}
|
|
// TODO: Consider making every packet sent when in PTO ack-eliciting to speed up recovery.
|
|
return false
|
|
}
|
|
|
|
func (c *Conn) appendAckFrame(now time.Time, space numberSpace) bool {
|
|
seen, delay := c.acks[space].acksToSend(now)
|
|
if len(seen) == 0 {
|
|
return false
|
|
}
|
|
d := unscaledAckDelayFromDuration(delay, ackDelayExponent)
|
|
return c.w.appendAckFrame(seen, d)
|
|
}
|
|
|
|
func (c *Conn) appendConnectionCloseFrame(now time.Time, space numberSpace, err error) {
|
|
c.sentConnectionClose(now)
|
|
switch e := err.(type) {
|
|
case localTransportError:
|
|
c.w.appendConnectionCloseTransportFrame(e.code, 0, e.reason)
|
|
case *ApplicationError:
|
|
if space != appDataSpace {
|
|
// "CONNECTION_CLOSE frames signaling application errors (type 0x1d)
|
|
// MUST only appear in the application data packet number space."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-12.5-2.2
|
|
c.w.appendConnectionCloseTransportFrame(errApplicationError, 0, "")
|
|
} else {
|
|
c.w.appendConnectionCloseApplicationFrame(e.Code, e.Reason)
|
|
}
|
|
default:
|
|
// TLS alerts are sent using error codes [0x0100,0x01ff).
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-20.1-2.36.1
|
|
var alert tls.AlertError
|
|
switch {
|
|
case errors.As(err, &alert):
|
|
// tls.AlertError is a uint8, so this can't exceed 0x01ff.
|
|
code := errTLSBase + transportError(alert)
|
|
c.w.appendConnectionCloseTransportFrame(code, 0, "")
|
|
default:
|
|
c.w.appendConnectionCloseTransportFrame(errInternal, 0, "")
|
|
}
|
|
}
|
|
}
|