| // 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. |
| |
| //go:build go1.21 |
| |
| 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, "") |
| } |
| } |
| } |