Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Retry packet #1498

Merged
merged 13 commits into from Jul 31, 2018
3 changes: 3 additions & 0 deletions draft-ietf-quic-tls.md
Expand Up @@ -745,6 +745,9 @@ handled separately.
sample_offset = 6 + len(destination_connection_id) +
len(source_connection_id) +
len(payload_length) + 4
if packet_type == Initial:
sample_offset += len(token_length) +
len(token)
~~~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I am missing something, len(token_length) is only available after the PN has been decrypted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have a circular dependency when decrypting. See issue #1535.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. This is actually addressed later, as the token has now moved in front of the PN.

To ensure that this process does not sample the packet number, packet number
Expand Down
246 changes: 154 additions & 92 deletions draft-ietf-quic-transport.md
Expand Up @@ -532,6 +532,87 @@ See {{version-negotiation}} for a description of the version negotiation
process.


## Retry Packet {#packet-retry}

A Retry packet uses a long packet header with a type value of 0x7E. It carries
an address validation token created by the server. It is used by a server that
wishes to perform a stateless retry (see {{stateless-retry}}).

~~~
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+
|1| 0x7e |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Version (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|DCIL(4)|SCIL(4)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Connection ID (0/32..144) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Connection ID (0/32..144) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ODCIL(8) | Original Destination Connection ID (*) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Retry Token (*) ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm realizing the lack of length means one can't 'coalesce' a RETRY and another packet type in a single datagram. I can't think why this is a practical issue, but it wouldn't surprise me if there was a use for it at some point, so I think it makes sense to add a length.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, you could; just that, like a short-header packet, the Retry would have to come last. But as you say, I can't imagine a use for it in this version of QUIC, and since these are version-specific packets....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit esoteric, but here's possible use cases:
Let's say a server only support QUIC v1, and it receives an Initial packet for a different version. The server would then send a Version Negotiation Packet, and could coalesce that with a Retry packet, in order to save one round trip.
I admit feels kind of clumsy though, since the Retry would have to go in front of the VNP (since the VNP doesn't have a length field).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marten: I am not sure this is a valid use case. The main use case for the retry packet is that it is sent by some kind of firewall as part of DOS defense.

The VN is sent end-to-end, and already carries a token of sorts with the Source CID. I don't see the use case of sending both retry and VN end to end.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In p2p, you won't have a separate firewall, but a peer might still want to use the Retry mechanism if it's under attack. I'm aware that this is not a very strong argument, since in that case you'd probably be ok with incurring an additional RTT, on the other hand, adding a varint to the header doesn't cost us a lot either.

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~~~
{: #retry-format title="Retry Packet"}

A Retry packet (shown in {{retry-format}}) only uses the invariant portion of
the long packet header {{QUIC-INVARIANTS}}; that is, the fields up to and
including the Destination and Source Connection ID fields. The contents of the
Retry packet are not protected. Like Version Negotiation, a Retry packet
contains the long header including the connection IDs, but omits the Length,
Packet Number, and Payload fields. These are replaced with:

ODCIL:

: The length of the Original Destination Connection ID field. The length is
encoded in the least significant 4 bits of the octet, using the same encoding
as the DCIL and SCIL fields. The most significant 4 bits of this octet are
reserved. Unless a use for these bits has been negotiated, endpoints SHOULD
send randomized values and MUST ignore any value that it receives.

Original Destination Connection ID:

: The Original Destination Connection ID contains the value of the Destination
Connection ID from the Initial packet that this Retry is in response to. The
length of this field is given in ODCIL.

Retry Token:

: An opaque token that the server can use to validate the client's address.

The server populates the Destination Connection ID with the connection ID that
the client included in the Source Connection ID of the Initial packet.

The server includes a connection ID of its choice in the Source Connection ID
field. The client MUST use this connection ID in the Destination Connection ID
of subsequent packets that it sends.

A Retry packet does not include a packet number and cannot be explictly
acknowledged by a client.

A server MUST only send a Retry in response to a client Initial packet.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a server not send a Retry in response to a 0-RTT packet? Say the Initial was lost/reordered, and the 0-RTT arrived first.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The server cannot decrypt 0-RTT, so it's only option is to

  1. buffer it in the hopes that it might complete the handshake when it is done, or

  2. discard it, which seems the most likely choice if the server is predisposed to send a Retry.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for a DDoS mitigation device that just sees a 0-RTT packet (assuming the Initial was lost/reordered) it should just drop it instead of sending a Retry? That works, but I just wonder why not use that extra info to do the Retry immediately. Otherwise, the client will eventually retransmit the Initial, and then have to pay an additional RTT for the retry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Martin has is good, but I can imagine a DDoS device doing what you said, especially if it kept a small amount of state to ensure it didn't sent too many RETRY packets to a given IP/port. So this could be a SHOULD instead of a MUST.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not even sure it's necessary for it to be a SHOULD; there's no interoperability risk if it sends a Retry in response to a lone 0-RTT packet, is there? In which case it sounds more like servers MAY omit sending Retry in response to 0-RTT when they haven't seen the Initial.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's much value in sending Retry in response to 0RTT packets. Sending it without decrypting the incoming packet makes me queasy, and sending it a few times after receiving the Initial sounds like a strange retransmission strategy. It's easy to see a server generating a ton of Retry packets in response to Initial + 0RTT packets, so it seems reasonable to recommend better behavior here, and I like the text that's there. I don't mind weakening it to a SHOULD though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump for changing this to a SHOULD for now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to leave this as SHOULD for now, but will open a new issue. This wasn't the focus of this change (this is not my text), so let's have an explicit discussion about it.


If the Original Destination Connection ID field does not match the Destination
Connection ID from the most recent Initial packet it sent, clients MUST discard
the packet. This prevents an off-path attacker from injecting a Retry packet.

The client responds to a Retry packet with an Initial packet that includes the
provided Retry Token to continue connection establishment.

A server that might send another Retry packet in response to a subsequent
Initial packet MUST set the Source Connection ID to a new value of at least 8
octets in length. This allows clients to distinguish between Retry packets when
the server sends multiple rounds of Retry packets. Consequently, a valid Retry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of requiring the server to use a new CID, why not just make the client use a new random CID for every new token it sends in the Initial packet? That way the client has direct control to be able to differentiate between the possibly Retry responses? And no additional complexity on the server side.

Going further, I was thinking during the talks today, if we are going to recommend that initial packet routing not take the client Initial's DCID into account (because it would be an attack surface) then I don't see any reason to support the Retry packet changing the CID at all. The primary reason for adding that support initially was for routing/load balancing; but that seems to be ill advised now. It just adds unnecessary complexity in the client code, IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nibanks

Instead of requiring the server to use a new CID, why not just make the client use a new random CID for every new token it sends in the Initial packet?

The clients source SCID must be new. The clients original DCID is always new and random in an initial packet. Only the source is reflected back by the server. this is why it must be new. The alternative would be to have a separate nonce in the handshake, separate from SCID, which is what I suggest.

On the going further: The server must set is own SCID - otherwise the server is forced to use a client chosen SCID which is not only a load balancer issue and it also goes against the point of a retry. A retry is a redirect to another server, not try again until you get lucky, although that can also be implemented if the server randomizes its SCID.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikkelfj if the client uses a new SCID then it would be considered an entirely new connection, and might elicit another Retry. I am talking about removing the requirement from the server in generating new random CID, and instead have the client generate a new random DCID for each try.

Also, on the going further, I am only talking about Retry. After that, in the handshake the server can/will change the CID to whatever it wants.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is nothing wrong with getting a new retry, the problem is stopping when too many happen. If the client changes SCID each time, it can stop after 1-3 retries - but it helps if it can know that all retries are from the same chain. I think a nonce is better for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client should not change its SCID each time. See #1474 and the associated PR #1491.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clients original DCID disappears once it reaches the server. Since the server sets its own SCID and the client is not allowed to change its SCID, the context is lost.
While I agree that not changing SCID is a good thing, I wondered why that was made a requirement before solving this loss of context here.
Your proposal to modify servers SCID seems like a workaround for the fundamental problem that some nonce is required. But I dont' recall all the concerns related to this right now.

Copy link
Member

@nibanks nibanks Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am considering only the Retry scenario right now. During the handshake, after the Retry, the server has the opportunity to change its SCID once again (independent of if it was changed during Retry). I am trying to make two points:

  1. I don't see any reason for the server to generate a new, random SCID (client's DCID) with Retry. The client itself can handle creating a new random DCID when it retries.
  2. Since we are going to recommend NOT load balancing off the client's initial SCID, then there is no reason to use Retry (for load balancing purposes) to change the CID at all.

So, Retry just shouldn't change the CID at all, IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nibanks, I think that the idea was to give the server better control over where to send subsequent Retry packets. That is, it can choose a value that is better for load, DoS prevention, or whatever it needs. The server can use the token to validate that the value was its own choice as well, removing concerns about the client stacking load on certain servers.

(I thought that this was one of your design constraints, but I realize now I don't know where that idea came from.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe @mcmanus originally requested the ability to change CID with the (old) Retry packet. For DoS, I only require the Token field.

Is it true or not that we recommend not using the client Initial CID for load balancing decisions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack.

I don't think that we have a concrete statement about using the Destination Connection ID from the client Initial. With a token from Retry, you could; without, it's fraught, I agree.

packet will always have an Original Destinagion Connection ID that is at least 8
octets long; clients MUST discard Retry packets that include a shorter value. A
server that will not send additional Retry packets can set the Source Connection
ID to any value.


## Cryptographic Handshake Packets {#handshake-packets}

Once version negotiation is complete, the cryptographic handshake is used to
Expand All @@ -548,58 +629,83 @@ provide confidentiality or integrity against on-path attackers, but
provides some level of protection against off-path attackers.


### Initial Packet {#packet-initial}
## Initial Packet {#packet-initial}

The Initial packet uses long headers with a type value of 0x7F. It carries the
first CRYPTO frames sent by the client and server to perform key exchange, and
may carry ACKs in either direction. The Initial packet is protected by Initial
carries ACKs in either direction. The Initial packet is protected by Initial
keys as described in {{QUIC-TLS}}.

The Initial packet has two additional header fields that follow the normal Long
Header.
The Initial packet (shown in {{initial-format}}) has two additional header
fields that are added to the Long Header before the Length field.

~~~
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+
|1| 0x7f |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Version (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|DCIL(4)|SCIL(4)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token Length (i) ...
| Destination Connection ID (0/32..144) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Connection ID (0/32..144) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token Length (i) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (*) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length (i) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Packet Number (8/16/32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload (*) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

This comment was marked as resolved.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This new design solves issue #1535.

~~~
{: #initial-format title="Initial Packet"}

These fields include the token that was previously provided in a Retry packet or
NEW_TOKEN frame:

Token Length:

: A variable-length integer specifying the length of the Token field, in bytes.
It may be zero if no token is present. Initial packets sent by the server
MUST include a zero-length token.
This value is zero if no token is present. Initial packets sent by the server
MUST set the Token Length field to zero; clients that receive an Initial
packet with a non-zero Token Length field MUST either discard the packet or
generate a connection error of type PROTOCOL_VIOLATION.

Token:

: An optional token blob previously received in either a Retry packet or
NEW_TOKEN frame.
: The value of the token.

The client and server use the Initial packet type for any packet that contains
an initial cryptographic handshake message. In addition to the first
packet(s). This includes all cases where a new packet containing the initial
cryptographic message needs to be created, such as the packets sent after
receiving a Version Negotiation ({{packet-version}}) or Retry packet
({{packet-retry}}).
an initial cryptographic handshake message. This includes all cases where a new
packet containing the initial cryptographic message needs to be created, such as
the packets sent after receiving a Version Negotiation ({{packet-version}}) or
Retry packet ({{packet-retry}}).

A server sends its first Initial packet in response to a client Initial. A
server may send multiple Initial packets. The cryptographic key exchange could
require multiple round trips or retransmissions of this data.

The payload of an Initial packet includes a CRYPTO frame (or frames) containing
a cryptographic handshake message, ACK frames, or both. The first CRYPTO frame
sent always begins at an offset of 0 (see {{handshake}}). The client's complete
first message MUST fit in a single packet (see {{handshake}}). Note that if the
server sends a HelloRetryRequest, the client will send a second Initial packet
with a CRYPTO frame with an offset starting at the end of the CRYPTO stream in
the first Initial.
sent always begins at an offset of 0 (see {{handshake}}).

The first packet sent by a client always includes a CRYPTO frame that contains
the entirety of the first cryptographic handshake message. This packet, and the
cryptographic handshake message, MUST fit in a single UDP datagram (see
{{handshake}}).

Note that if the server sends a HelloRetryRequest, the client will send a second
Initial packet. This Initial packet will continue the cryptographic handshake
and will contain a CRYPTO frame with an offset matching the size of the CRYPTO
frame sent in the first Initial packet. Cryptographic handshake messages
subsequent to the first do not need to fit within a single UDP datagram.


#### Connection IDs
### Connection IDs

When an Initial packet is sent by a client which has not previously received a
Retry packet from the server, it populates the Destination Connection ID field
Expand All @@ -624,10 +730,24 @@ server, it MUST discard any packet it receives with a different Source
Connection ID.


#### Tokens
### Tokens

If the client has a suitable token available from a previous connection, it
SHOULD populate the Token field.
If the client has a token received in a NEW_TOKEN frame on a previous connection
to what it believes to be the same server, it can include that value in the
Token field of its Initial packet.

A token allows a server to correlate activity between connections.
Specifically, the connection where the token was issued, and any connection
where it is used. Clients that want to break continuity of identity with a
server MAY discard tokens provided using the NEW_TOKEN frame. Tokens obtained
in Retry packets MUST NOT be discarded.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prohibition below is on using a token on two different network paths. But since the token is likely to contain address validation information, should we recommend against using the token from a different address than the one where it was received? Or is that solely a risk of server tracking and it's covered in the privacy-from-server paragraph here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the intent of this paragraph is to cover that. The paragraph below is privacy-from-network.


A client SHOULD NOT reuse a token. Reusing a token on allows connections to be
linked by entities on the network path (see {{migration-linkability}}). A
client MUST NOT reuse a token if it believes that its point of network
attachment has changed since the token was last used; that is, if there is a
change in its local IP address or network interface. A client needs to start
the connection process over if it migrates prior to completing the handshake.

If the client received a Retry packet from the server and sends an Initial
packet in response, then it sets the Destination Connection ID to the value from
Expand All @@ -649,85 +769,27 @@ Note:
the packet is that the client might have received the token in a previous
connection using the NEW_TOKEN frame, and if the server has lost state, it
might be unable to validate the token at all, leading to connection failure if
the packet is discarded.
the packet is discarded. A server MAY encode tokens provided with NEW_TOKEN
frames and Retry packets differently, and validate the latter more strictly.


#### Starting Packet Numbers
### Starting Packet Numbers

The first Initial packet contains a packet number of 0. Each packet sent after
the Initial packet is associated with a packet number space and its packet
number increases monotonically in that space (see {{packet-numbers}}).
The first Initial packet sent by either endpoint contains a packet number of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHOULD contain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or MUST contain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly not MUST. Consider the case where the Initial packet is resent after a version negotiation, or repeated after time-out. Then the "natural" value of the sequence number will be 1. I don't see how saying "must be zero" would improve interoperability.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Christian got it. MUST doesn't work, and throwing normative language at this is too heavy. I think that this is fine.

0. The packet number MUST increase monotonically thereafter. Initial packets
are in a different packet number space to other packets (see
{{packet-numbers}}).


#### Minimum Packet Size
### Minimum Packet Size

The payload of a UDP datagram carrying the Initial packet MUST be expanded to at
least 1200 octets (see {{packetization}}), by adding PADDING frames to the
Initial packet and/or by combining the Initial packet with a 0-RTT packet (see
{{packet-coalesce}}).


### Retry Packet {#packet-retry}

A Retry packet uses long headers with a type value of 0x7E. It carries an
address validation token created by the server. It is used by a server that
wishes to perform a stateless retry (see {{stateless-retry}}).

~~~
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ODCIL(8 | Original Destination Connection ID (*) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Retry Token (*) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~~~

A Retry packet is not encrypted at all. Instead, the payload of a
Retry packet contains two values in the clear.

ODCIL:

: The length of the Original Destination Connection ID.

Original Destination Connection ID:

: The Destination Connection ID from the Initial packet that this
Retry is in response to. The length of this field is given in DCIL.

Retry Token:

: An opaque token that the server can use to validate the client's
address.

The server populates the Destination Connection ID with the connection ID that
the client included in the Source Connection ID of the Initial packet. This
might be a zero-length value.

The server includes a connection ID of its choice in the Source Connection ID
field. The client MUST use this connection ID in the Destination Connection ID
of subsequent packets that it sends.

The Packet Number field of a Retry packet MUST be set to 0. This value is
subsequently protected as normal. \[\[Editor's Note: This isn't ideal, because
it creates a "cheat" where the client assumes a value. That's a problem, so I'm
tempted to suggest that this include any value less than 2^30 so that normal
processing works - and can be properly exercised.]]

A Retry packet is never explicitly acknowledged in an ACK frame by a client.

A server MUST only send a Retry in response to a client Initial packet.

If the Original Destination Connection ID field does not match the
Destination Connection ID from most recent the Initial packet it sent,
clients MUST discard the packet. This prevents an off-path attacker
from injecting a Retry packet with a bogus new Source Connection ID.

Otherwise, the client SHOULD respond with a new Initial
packet with the Token field set to the token received in the Retry packet.


### Handshake Packet {#packet-handshake}
## Handshake Packet {#packet-handshake}

A Handshake packet uses long headers with a type value of 0x7D. It is
used to carry acknowledgments and cryptographic handshake messages from the
Expand Down