Change:
In order to avoid sending anti-deadlock packets too quickly when the server doesn't yet have anything to send. Also sets ptocount to 0 in pseudocode when Initial or Handshake keys are dropped, lining it up with existing normative text.(this could be split into a separate non-design PR if people prefer) Adds the term 'backoff' which is usually reset upon receiving an acknowledgement. The lines up more closely with 'ptocount' in the pseudocode.
It looks like under the current PTO algorithm PTO probes are sent too frequently under certain condition. To create such a condition, suppose that client never send Handshake packets. According to the -27 spec, client sends ack-eliciting anti-deadlock packet on ptocount=1. Then it gets ACK from server for it, which resets ptocount to 0. After next pto timeout, it sends anti-deadlock packet again. This process repeats indefinitely until either endpoint gives up handshake. The problem is that depending RTT, PTO probes are sent too frequently. With RTT=6ms, it sends PTO probe once per ~33ms. And both sides do this. ACK+PADDING packets are also sent without delay in this period if remote endpoint does PTO. I'm not sure TCP does the similar things, but it sounds like PTO probe is too aggressive. If both endpoints are implemented properly, this probably does not happen, but in practice, we don't know the remote endpoint is broken in this case, and have no means to check that out.
If the server keeps quickly ACKing packets but not sending handshake data, then yes, the client will keep sending full-sized packets, believing the server is blocked by the anti-amplification limit. At first I thought this couldn't happen in a real-world scenario, but possibly if one had async TLS handshaking so the ServerHello could not be sent immediately upon receipt of the ClientHello and the server and client were very close, a packet could be exchanged as quickly as 1 packet each direction per kGranularity time until the async process returned? TCP doesn't have an anti-amplification limit and it doesn't require Handshake packets to provide address validation. I would characterize this as an issue with the client's anti-deadlock response, rather than a problem with PTO.
I think the most intellectually clean thing to do here would be to not reset PTO count when an ACK is received and the PTO is only armed for anti-amplification purposes. This is because even though at a transport level, everything is working(ACKs are quickly received), the client is arming the PTO for a very different reason(anti-deadlock). And the PTO packets aren't allowing the server to make any more progress, because it's not blocked. But I don't think that suggestion is very easy to implement, so I'm not sure it's what we want. I'd suggest restricting the client to one anti-deadlock packet per second or some arbitrary period, which would be easier, but means someone has to pick a magic number. I mention 1 second because it's a magic number already in QUIC recovery(default handshake timeout) and in TCP. This basically amounts to anti-anti-amplification attack prevention I think :(
Does it make simpler if we reset ptocount only when ack-eliciting not pure probe packet (which includes lost data or new data, not just PING) is received without special casing for anti-deadlock packet? This ensures that ptocount is reset only when endpoint has made a progress and delaying consecutive PTO if it only receives ACK for pure probe packet. Server might be in the same situation if client somehow only sends ACK frame in Initial and server keeps sending Initial packet in PTO
This is an interesting issue and I think NAME is correct in pointing out that this could be a real problem for slow devices doing public key operations in an asynchronous manner. I like NAME suggestion to not reset the PTO timer. Maybe we can always increment it, regardless of the packet received from peer being ACK-eliciting or not.
There are other cases when we send ack-eliciting packets including only PINGs or other non-application data and we do want to set ptocount back to 0 when those are acknowledged. We could specifically recommend sending non-ack-eliciting packets for the anti-deadlock packet, but then you lose an opportunity for an RTT measurement early in the connection, which is unfortunate, though not a dealbreaker. For full-sized Initial packets, it's likely they'd never be acknowledged if they weren't ack-eliciting, because no ack-eliciting Initial packets would be sent after them. NAME I believe always incrementing ptocount would be detrimental to performance, unless it's under some very narrow circumstances.
Sorry if I was unclear, I meant not resetting PTO timer when the client receives an Initial packet. Assuming that the server sends only a few (most likely one), I think not resetting is fine. Note that in case the server is going to send a fair number of Initial packets (e.g., post quantum) it should be sending a retry regardless of what we decide here, as relying on CC wold be better than relying on 3x limit.
We only reset pto_count upon receiving an Initial ACK, not any Initial packet, but I think your suggestion could work well. It amounts to continuing with exponential backoff until the client has Handshake keys. It's fairly simple to implement, and I wouldn't expect it to cause a noticeable regression, so unless someone comes up with something better, let's do it.
So the operating principle here is that we are expecting progress to Handshake to be made, which means that we need to align with our existing logic that enables PTO in that case. For reference, the check we use to enable PTO during the anti-amplification phase is the inverse of the following: Which follows the "server at anti-amplification limit" check. So maybe is the check you want rather than .
I updated the PR to only do this for the client, since I think that's the right thing to do. I did the Initial check because I thought it was slightly simpler than the check, which I believe is equivalent, since receiving an Initial ACK means you still have Initial keys, so the server is awaiting address validation. As soon as a Handshake ACK is received by the client, it knows the server has completed address validation. But I could also use the PeerNotAwaitingAddressValidation() method that's already defined(and mistakenly not used, see ) Then I think the check would be:
Looks good to me.
Thanks, Ian, a couple of comments.Looks great!

Discussion:
The PTO value MUST be set to at least kGranularity, to avoid the timer expiring immediately. <del> A sender computes its PTO timer every time an ack-eliciting packet is sent.  When ack-eliciting packets are in-flight in multiple packet number spaces, the timer MUST be set for the packet number space with the earliest timeout, except for ApplicationData, which MUST be ignored until the handshake completes; see Section 4.1.1 of QUIC-TLS. Not arming the PTO for ApplicationData prevents a client from retransmitting a 0-RTT packet on a PTO expiration before confirming that the server is able to decrypt 0-RTT packets, and prevents a server from sending a 1-RTT packet on a PTO expiration before it has the keys to process an acknowledgement. When a PTO timer expires, the PTO period MUST be set to twice its current value.  This exponential reduction in the sender's rate is important because consecutive PTOs might be caused by loss of packets or acknowledgements due to severe congestion.  Even when there are ack-eliciting packets in-flight in multiple packet number spaces, the </del> <ins> A sender recomputes and may need to reset its PTO timer every time an ack-eliciting packet is sent.  When ack-eliciting packets are in- flight in multiple packet number spaces, the timer MUST be set for the packet number space with the earliest timeout, except for ApplicationData, which MUST be ignored until the handshake completes; see Section 4.1.1 of QUIC-TLS.  Not arming the PTO for ApplicationData prevents a client from retransmitting a 0-RTT packet on a PTO expiration before confirming that the server is able to decrypt 0-RTT packets, and prevents a server from sending a 1-RTT packet on a PTO expiration before it has the keys to process an acknowledgement. When a PTO timer expires, the PTO backoff MUST be increased, resulting in the PTO period being set to twice its current value. The PTO period is set based on the latest RTT information after receiving an acknowledgement.  The PTO backoff is reset upon receiving an acknowledgement unless it's a client unsure if the the server has validated the client's address.  Not resetting the backoff during peer address validation ensures the client's anti-deadlock timer is not set too aggressively when the server is slow in responding with handshake data. This exponential reduction in the sender's rate is important because consecutive PTOs might be caused by loss of packets or acknowledgements due to severe congestion.  Even when there are ack- eliciting packets in-flight in multiple packet number spaces, the </ins> exponential increase in probe timeout occurs across all spaces to prevent excess load on the network.  For example, a timeout in the Initial packet number space doubles the length of the timeout in the

pto-handshake) for a new path, but the delay SHOULD NOT be considered an RTT sample. <del> Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of QUIC-TRANSPORT.  If no data can be sent, then the PTO alarm MUST NOT be armed until datagrams have been received from the client. Since the server could be blocked until more packets are received from the client, it is the client's responsibility to send packets to unblock the server until it is certain that the server has finished its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. </del> Prior to handshake completion, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client.  To allow the client to

keys indicates forward progress and the loss detection timer might have been set for a now discarded packet number space. <ins> 5.2.2.1. Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of QUIC-TRANSPORT.  If no additional data can be sent, the server's PTO alarm MUST NOT be armed until datagrams have been received from the client, because packets sent on PTO count against the anti-amplification limit.  Note that the server could fail to validate the client's address even if 0-RTT is accepted. Since the server could be blocked until more packets are received from the client, it is the client's responsibility to send packets to unblock the server until it is certain that the server has finished its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. </ins> 5.2.3. When a server receives an Initial packet containing duplicate CRYPTO

is no data available to send.  An endpoint MAY send up to two full- sized datagrams containing ack-eliciting packets, to avoid an expensive consecutive PTO expiration due to a single lost datagram or <del> transmit data from multiple packet number spaces. </del> <ins> transmit data from multiple packet number spaces.  All probe packets sent on a PTO MUST be ack-eliciting. </ins> In addition to sending data in the packet number space for which the timer expired, the sender SHOULD send ack-eliciting packets from

skip a packet number to eliminate the ack delay. When the PTO timer expires, and there is new or previously sent <del> unacknowledged data, it MUST be sent. </del> <ins> unacknowledged data, it MUST be sent.  A probe packet SHOULD carry new data when possible.  A probe packet MAY carry retransmitted unacknowledged data when new data is unavailable, when flow control does not permit new data to be sent, or to opportunistically reduce loss recovery delay.  Implementations MAY use alternative strategies for determining the content of probe packets, including sending new or retransmitted data based on the application's priorities. </ins> It is possible the sender has no new or previously-sent data to send. As an example, consider the following sequence of events: new

expiration increases resilience to packet drops, thus reducing the probability of consecutive PTO events. <del> Probe packets sent on a PTO MUST be ack-eliciting.  A probe packet SHOULD carry new data when possible.  A probe packet MAY carry retransmitted unacknowledged data when new data is unavailable, when flow control does not permit new data to be sent, or to opportunistically reduce loss recovery delay.  Implementations MAY use alternative strategies for determining the content of probe packets, including sending new or retransmitted data based on the application's priorities. </del> When the PTO timer expires multiple times and new data cannot be sent, implementations must choose between sending the same payload every time or sending different payloads.  Sending the same payload