Internet Protocols · Protocol Design

Negotiation Round Trips and What They Cost

How capability discovery is paid for in latency, why the cheapest negotiation is one that reuses state, and where a saved round trip becomes a security problem.

A negotiation is a conversation about what both sides can do, and every conversation costs at least one round trip. On a local network a round trip is a fraction of a millisecond and the cost is invisible. Across an ocean it is on the order of a hundred milliseconds, and a protocol that needs two extra round trips before the first byte of application data has added two hundred milliseconds to every connection that is not resumed.

That arithmetic is why negotiation design is mostly a study in avoiding round trips. The mechanisms that avoid them are not clever tricks; they are ways of moving the conversation to a time when the answer is already known.

The round trip budget of a connection

Consider the sequence a client performs to fetch a resource over a fresh secure connection. It resolves a name, which costs at least one round trip unless the answer is cached. It opens a transport connection, which costs one round trip for TCP’s three-way handshake. It runs a TLS 1.3 handshake, which costs one round trip before the client can send application data. It sends a request and receives a response, which costs one more.

The minimum is therefore four round trips from a cold start, and three of them are setup: the name lookup, the transport handshake, and the security handshake. Only the last round trip does application work. TCP Fast Open lets the client put data in the SYN and saves one round trip, at the cost of requiring a prior connection and server support; TLS 1.3’s early data saves another in the same way. Both are resumption mechanisms, and both leave the cold path at four.

This is the frame in which every negotiation mechanism should be evaluated. A mechanism that costs a round trip is not expensive in absolute terms; it is expensive relative to the total, which is already dominated by the unavoidable steps. Adding one round trip to a four-round-trip connection is a 25 percent increase. Adding it to a resumed connection that costs one round trip is a 100 percent increase.

The asymmetry explains why the effort goes into resumption. The first connection to a server can afford a full negotiation. The thousandth connection should not repeat it.

Carrying capability in the first flight

The cheapest place to negotiate is in a message that is already being sent. If the client’s first flight carries the full list of what it supports, the server can select from that list without asking anything further. There is no additional round trip because the flight was going to happen anyway.

TLS 1.3 is built on this. The ClientHello carries the supported version list, the cipher suite list, the key share, the signature algorithm list, and any extensions the client wants. The server selects one of each and replies with its choice in the ServerHello. The client’s capabilities are stated once, in the first flight, and the server’s selection is final.

The design constraint that makes it work is that the client’s list must be complete enough for the server to choose without further queries. That means the client sends every option it is prepared to accept, not a preference order that requires clarification. The cost is message size: a ClientHello with many key shares and extensions can exceed a kilobyte, which matters when the first flight must fit in a single datagram to avoid fragmentation.

QUIC made the constraint explicit with a fixed number. A client’s Initial packet must be at least 1200 bytes, padded if the content is smaller, so that the server can send its own response without exceeding the minimum path MTU that QUIC requires the path to support. Everything the client wants to say about its capabilities in that flight has to fit within the path’s actual MTU, or it has to be deferred to a later flight. The transport parameters, which are QUIC’s version of a capability list, are therefore compactly encoded and kept small.

The general rule is that a first-flight negotiation should be a set of independent choices, each encoded as a list, and each list short enough that the whole message fits the transport’s first-message budget. A protocol that needs a large capability list has to either compress it, split it across flights, or accept that the first message will be fragmented.

Resumption as cached negotiation

The second mechanism is to remember the result. A resumed connection skips the negotiation because both sides already agreed on parameters, and the agreement is represented by a ticket or a session identifier that the client presents.

TLS session tickets, described in RFC 5077, let the server hand the client an opaque blob containing the negotiated parameters and a key, encrypted under a key the server holds. On resumption the client sends the ticket, the server decrypts it, and both sides resume with the recorded parameters. The server keeps no per-client state, which is the point: the state is in the ticket.

TLS 1.3’s pre-shared key mode extends this to a full handshake in one round trip, and to zero round trips if the client is willing to send application data before the handshake completes. The zero-round-trip mode is the interesting one, because it removes the negotiation cost entirely and introduces a different cost.

The recorded parameters must be bound to the server’s identity and to the original handshake, or an attacker can replay a ticket to a different server. TLS 1.3 binds the ticket to the original connection’s transcript and to the server’s certificate, so a ticket issued by one server is not accepted by another. The binding is what makes the cache safe; without it, the cache is a downgrade vector, because an attacker could present a ticket that encodes weak parameters. The general form of that risk, and the reason negotiated parameters must be authenticated rather than merely exchanged, is the subject of downgrade semantics in version negotiation.

The size of the cache matters for the same reason. A ticket that encodes the full negotiated state is a few hundred bytes, and it must be stored by the client and, in the stateful variant, by the server. A server with a million active clients and a per-client ticket store needs a store that can be evicted, which means the resumption can fail and the client must fall back to a full handshake. The fallback is correct but it reintroduces the round trip at the worst possible time.

When a saved round trip becomes a downgrade

Zero-round-trip resumption is the clearest case where latency and safety trade directly. The client sends application data in its first flight, encrypted under keys derived from the ticket. The server can process it immediately, or it can reject it and require a full handshake.

The risk is replay. An attacker who captures the client’s first flight can resend it, and the server may process the application data twice. For a request that is safe to repeat, such as a GET, this is harmless. For a request that changes state, it is not, and the protocol cannot tell the difference because the request’s semantics are above the transport.

The mitigations are at the application layer: make the request idempotent, or use a single-use token. The transport cannot provide replay protection for early data without keeping per-ticket state, which defeats the purpose of stateless resumption. TLS 1.3 states the problem explicitly and gives the server a per-ticket ceiling: the maximum early-data size is carried in the ticket, and the client must not exceed it. The ceiling bounds the damage of a replay rather than preventing it.

There is a second risk that is easier to miss. Early data is sent before the server has proven its identity, so a client that sends early data to an attacker’s server has revealed the data. The client’s defence is to send early data only to servers it has previously authenticated, which is a policy the protocol cannot enforce. The practical consequence is that early data is used for requests whose content is not sensitive, and the sensitivity judgement is made by the application.

The pattern generalises: a negotiation that is skipped is a check that is not performed, and the check has to be replaced by something. Resumption replaces the full handshake with a ticket, and the ticket’s integrity is the replacement. Early data replaces the identity check with a prior identity check, and the prior check is the replacement. A protocol that removes the round trip without naming the replacement has removed a security property rather than a cost.

The cost of negotiation on a constrained path

Round trips are not the only cost. Each negotiation message is bytes on the wire, and on a link with a small MTU or a high loss rate the bytes matter as much as the trips.

A TLS ClientHello with a full set of extensions and several key shares can approach the size of a small web page. On a satellite link with a 1500-byte MTU and a round trip of several hundred milliseconds, a ClientHello that fragments into two packets has doubled its exposure to loss: if either fragment is lost, the whole flight is retransmitted, and the retransmission costs another round trip. Keeping the first flight under one MTU is a real optimisation, and it is why key share lists are limited and extensions are ordered by likely usefulness.

Loss interacts with negotiation in a way that is easy to underestimate. A negotiation that requires two flights has twice the exposure to a lost packet as one that requires a single flight, and the cost is not additive but multiplicative when the loss rate is high. A protocol designed for a lossy link should therefore minimise the number of flights, even at the cost of larger messages, because a larger message that arrives is better than two smaller ones where either can be lost.

The extreme case is a link where the round trip is measured in seconds. There, a negotiation that costs an extra flight is a visible delay, and the design pressure is toward protocols that carry everything in one message and accept the redundancy. That is the shape of many constrained-network designs, and it is why the “send everything, let the receiver ignore what it does not need” pattern survives despite its bandwidth cost.

Negotiation that never happens

The cheapest negotiation is the one that is not needed because the parameters are fixed. A protocol can declare a single mandatory configuration and refuse to speak anything else. That eliminates the negotiation entirely, and it also eliminates the ability to change the configuration, which is why it is only appropriate for protocols that are not expected to evolve.

Between the fixed protocol and the fully negotiated one there is a useful middle: negotiate once, cache forever, and provide a way to invalidate. That is what a capability advertisement with a long lifetime does. The client learns the server’s capabilities on the first connection and reuses them until a failure suggests the cache is stale. The invalidation is the hard part, because the server may have changed and the client has no way to know until a request fails.

The practical resolution is to make the cached parameters conservative: cache the minimum the server is known to support, not the maximum. A request that uses only the minimum will work even if the server has changed, and the cost is that the client does not exploit new capabilities until it re-negotiates. That trade favours reliability over optimality, and it is the right default for a cache that cannot be invalidated cheaply.

The alternative is to re-negotiate on every connection but to make the negotiation cheap by keeping it in the first flight. That is the TLS 1.3 model, and it works because the first flight was going to happen anyway. The two approaches converge on the same principle: put the negotiation where it is free, and if it cannot be free, cache it and make the cache safe. The mechanisms that let a peer use a capability before it has been confirmed are covered in optimistic extension use.

References

  1. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 — IETF
  2. RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport — IETF
  3. RFC 9114: HTTP/3 — IETF
  4. RFC 9110: HTTP Semantics — IETF
  5. RFC 5077: Transport Layer Security (TLS) Session Resumption without Server-Side State — IETF