5.2.1. <del> The nonce length MUST be no fewer than 4 octets. </del>  The configuration agent also selects an 16-octet AES-ECB key to use for connection ID decryption. 5.2.2.  Upon receipt of a QUIC packet, the load balancer extracts as many of the earliest octets from the destination connection ID as necessary to match the server ID.  The nonce immediately follows. The load balancer decrypts the nonce and the server ID using the <del> following three pass algorithm: Pass 1: The load balancer decrypts the server ID using 128-bit AES Electronic Codebook (ECB) mode, much like QUIC header protection. The encrypted nonce octets are zero-padded to 16 octets.  AES-ECB encrypts this encrypted nonce using its key to generate a mask which it applies to the encrypted server id.  This provides an intermediate value of the server ID, referred to as server-id intermediate. server_id_intermediate = encrypted_server_id ^ AES-ECB(key, padded- encrypted-nonce) Pass 2: The load balancer decrypts the nonce octets using 128-bit AES ECB mode, using the server-id intermediate as "nonce" for this pass.  The server-id intermediate octets are zero-padded to 16 octets.  AES-ECB encrypts this padded server-id intermediate using its key to generate a mask which it applies to the encrypted nonce.  This provides the decrypted nonce value. nonce = encrypted_nonce ^ AES-ECB(key, padded-server_id_intermediate) Pass 3: The load balancer decrypts the server ID using 128-bit AES ECB mode.  The nonce octets are zero-padded to 16 octets.  AES-ECB encrypts this nonce using its key to generate a mask which it applies to the intermediate server id.  This provides the decrypted server ID. server_id = server_id_intermediate ^ AES-ECB(key, padded-nonce) For example, if the nonce length is 10 octets and the server ID length is 2 octets, the connection ID can be as small as 13 octets. The load balancer uses the the second through eleventh octets of the connection ID for the nonce, zero-pads it to 16 octets, uses xors the result with the twelfth and thirteenth octet.  The result is padded with 14 octets of zeros and encrypted to obtain a mask that is xored with the nonce octets.  Finally, the nonce octets are padded with six octets of zeros, encrypted, and the first two octets xored with the server ID octets to obtain the actual server ID. This three-pass algorithm is a simplified version of the FFX algorithm, with the property that each encrypted nonce value depends on all server ID bits, and each encrypted server ID bit depends on all nonce bits and all server ID bits.  This mitigates attacks against stream ciphers in which attackers simply flip encrypted server-ID bits. The output of the decryption is the server ID that the load balancer uses for routing. </del>  <del> 5.2.3. </del>  <del> When generating a routable connection ID, the server writes arbitrary bits into its nonce octets, and its provided server ID into the server ID octets.  See cid-entropy for nonce generation considerations. </del>  <del> The server encrypts the server ID using exactly the algorithm as described in encrypted-short-load-balancer-actions, performing the three passes in reverse order. </del>  5.3. If servers simply increment the nonce by one with each generated connection ID, then it is safe to use the existing keys until any <del> server's nonce counter exhausts the allocated space and rolls over to zero.  Whether or not it implements this method, the server MUST NOT </del>  reuse a nonce until it switches to a configuration with new keys. Both the Plaintext CID and Encrypted Long CID algorithms send parts

Alright NAME NAME I've incorporated feedback from both of you and the one crypto review we've received. I suggest you read all of section 5.2 (## Encrypted Short CID Algorithm) to see what I've done, as this is a total rewrite.
Looks good.

5.2.1.  <ins> The nonce length MUST be no fewer than 4 octets.  The nonce SHOULD be at least as long as the server ID in order to save the load balancer an encryption pass; see below. </ins> The configuration agent also selects an 16-octet AES-ECB key to use for connection ID decryption. 5.2.2. <ins> When generating a routable connection ID, the server writes arbitrary bits into its nonce octets, and its provided server ID into the server ID octets.  See cid-entropy for nonce generation considerations. The server encrypts the server ID using the following four pass algorithm, which leverages 128-bit AES Electronic Codebook (ECB) mode, much like QUIC header protection. In the text below, ^ is the XOR function and || is concatenation. The truncate() function takes the most significant octets of its argument, so that the XOR function operates on fields of the same length.  The expand() function outputs 16 octets, with its first argument in the most significant bits, its second argument in the least significant bits, and zeros in all other positions.  Thus, The example at the end of this section helps to clarify the steps described below. The server concatenates the server ID and nonce to create plaintext_CID. The server splits plaintext_CID into components left_0 and right_0 of equal length, splitting an odd octet in half if necessary.  For example, 0x7040b81b55ccf3 would split into a left_0 of 0x7040b81 and right_0 of 0xb55ccf3. Encrypt left_0.  The encryption is 128-bit AES-ECB with the key provided by the configuration agent, and the plaintext argument is an expanded version of left_0 where left_0 constitutes the most significant bits, 0x01 is the least significant octet, and all other bits are zero. XOR the most significant bits of the ciphertext with right_0 to form right_1. Thus steps 3 and 4 can be expressed as "right_1 = right_0 ^ truncate(AES_ECB(key, expand(left_0, 0x01)) " Repeat steps 3 and 4, but use them to compute left_1 by expanding and encrypting right_1 with the least significant octet as 0x02 and XOR the results with left_0. "left_1 = left_0 ^ truncate(AES_ECB(key, expand(right_1), 0x02)) " Repeat steps 3 and 4, but use them to compute right_2 by expanding and encrypting left_1 with the least significant octet as 0x03 and XOR the results with right_1. "right_2 = right_1 ^ truncate(AES_ECB(key, expand(left_1, 0x03)) " Repeat steps 3 and 4, but use them to compute left_2 by expanding and encrypting right_2 with the least significant octet as 0x04 and XOR the results with left_1. "left_2 = left_1 ^ truncate(AES_ECB(key, expand(right_2), 0x04)) " The server concatenates left_2 with right_2 to form the ciphertext CID, which it appends to the first octet. The following example executes the steps for the provided inputs. Note that the plaintext is of odd octet length, so the middle octet will be split evenly left_0 and right_0. 5.2.3. </ins> Upon receipt of a QUIC packet, the load balancer extracts as many of the earliest octets from the destination connection ID as necessary to match the server ID.  The nonce immediately follows. The load balancer decrypts the nonce and the server ID using the  <ins> reverse of the algorithm above. </ins>  <ins> First, split the ciphertext CID (excluding the first octet) into its equal- length components left_2 and right_2.  Then follow the process below: </ins>  <ins> As the load balancer has no need for the nonce, it can conclude after 3 passes as long as the server ID is entirely contained in left_0 (i.e., the nonce is at least as large as the server ID).  If the server ID is longer, a fourth pass is necessary: "right_0 = right_1 ^ truncate(AES_ECB(key, expand(left_0, 0x01))) " </ins>  <ins> and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID. </ins> 5.3. If servers simply increment the nonce by one with each generated connection ID, then it is safe to use the existing keys until any  <ins> server's nonce counter exhausts the allocated space and rolls over. To maximize entropy, servers SHOULD start with a random nonce value, in which case the configuration is usable until the nonce value wraps around to zero and then reaches the initial value again. Whether or not it implements the counter method, the server MUST NOT </ins> reuse a nonce until it switches to a configuration with new keys. Both the Plaintext CID and Encrypted Long CID algorithms send parts


QUIC connection unless otherwise indicated.  A "load balancer" is an intermediary for that connection that does not possess QUIC connection keys, but it may rewrite IP addresses or conduct other IP <del> or UDP processing. </del>  Note that stateful load balancers that act as proxies, by terminating a QUIC connection with the client and then retrieving data from the balancers to apply the correct routing algorithm and parameters to incoming packets. <del> Servers MUST NOT generate new connection IDs using an old configuration when it has sent an Ack payload for a new configuration. </del>  <del> Load balancers SHOULD NOT use a codepoint to represent a new </del>  configuration until it takes precautions to make sure that all <del> connections using IDs with an old configuration at that codepoint have closed or transitioned.  They MAY drop connection IDs with the old configuration after a reasonable interval to accelerate this process. </del>  3.2. Note that the Plaintext CID algorithm makes no attempt to obscure the server mapping, and therefore does not address these concerns.  It exists to allow consistent CID encoding for compatibility across a <del> network infrastructure.  Servers that are running the Plaintext CID algorithm SHOULD only use it to generate new CIDs for the Server Initial Packet and SHOULD NOT send CIDs in QUIC NEW_CONNECTION_ID frames.  Doing so might falsely suggest to the client that said CIDs were generated in a secure fashion. </del>  A linkability attack would find some means of determining that two connection IDs route to the same server.  As described above, there 8.1. Any attacker might open a connection to the server infrastructure and <del> aggressively retire connection IDs to obtain a large sample of IDs that map to the same server.  It could then apply analytical techniques to try to obtain the server encoding. </del>  <del> The Encrypted CID algorithm provides robust entropy to making any sort of linkage.  The Obfuscated CID obscures the mapping and prevents trivial brute-force attacks to determine the routing </del>  parameters, but does not provide robust protection against sophisticated attacks.

I found some text that still references the in-band protocol mechanisms. I tried to update terminology here (and in Security Considerations) without changing any design decisions, so that we can bracket those discussions. In particular, would change the intent of the config-rotation bits that we are trying to enforce here. This also touches the normative text about PCID that is the subject of , though I believe this PR doesn't apply any change to the status quo.
As server clusters increase in size, the need to reallocate server identifiers becomes more acute. In one model, the configuration ID is used to indicate a stable routing configuration. Server identifiers for a given configuration ID are routed to the same server, no matter how many other instances are added or removed. In order to allow for changes in the cluster, the configuration ID is used so that old servers can be removed from consideration and new ones added. If these changes happen frequently enough, the number of bits allocated to identifying a configuration might be insufficient. Why not make the length of the identifier flexible? That might mean that you need to make the length of the length similarly configurable.
It was not the intent of these bits to support long-lived configurations, instead supporting key rotation, upgrades, and the like. I would much rather people overprovisioned the server ID space than using this tool, TBH. However, the only cost is limiting the theoretical size of CIDs. At the moment, we can support up to 64B, future-proofing the encodings against future versions of QUIC. I'm open to another bit for this, but how would a configurable number of CR bits work with multiple configurations? How does a config that needs 5 bits and one that needs 2 coexist, especially if the latter needs length self-encoding?
As we talk through the implications of mutually mistrustful servers in , I think the case for adding another bit is compelling. I'm going to remove the needs-discussion label and come up with a PR that takes another bit.
I'm returning this to needs-discussion, as NAME points out there is a privacy tradeoff here. If the config codespace is large, it's straightforward to have each mistrustful server have its own totally unique config. On the other hand, keeping this long-lived config difference leaks the type of flow. Assuming it's routed based on SNI, it leaks the SNI of each CID, and in that sense also increases linkability.
Alright, I've reflected on this a bit more. In general, different server entities will have different external IP addresses and/or ports, so the load balancer can distinguish the correct QUIC-LB config without resorting to the CR bits. As IP and port are visible to everyone, there is no privacy leakage. The problem occurs when mutually mistrustful servers share the same IP/port and are switched on something else. That "something" may be something present only in the client hello, with the classical load balancer simply using the 4tuple after that. The only thing I am aware of in practice is the SNI. If there are others, please say so in this thread. If the SNI is encrypted, the unprivileged LB envisioned in QUIC-LB does not have access to it. So we can assume this use case only applies to unencrypted SNI. Option 1: mistrustful servers share the same config. a third party will be able to extract your server mapping, but in practice it will be hard for an attacker to obtain this position on purpose. If this is the best outcome, we can stick with 2 config rotation bits. Option 2: Issue them different config rotation codepoints. So an observer can see the SNI and associate it with certain CR bits; if the client later migrates, it will still be able to associate that connection with that SNI. If this is better than Option 1, we should probably add a third config rotation bit. Other (minor) costs of having 3 config rotation bits: length self-encoding can only support 31 byte connection IDs, instead of 63 (obviously this is only relevant for hypothetical future versions of QUIC) each config can have its own set of server IDs. So there is considerably more config state at the LB. As SIDs can be up to 18B, it's 18B x (# of servers) x (# of config rotation codepoints). This memory footprint will roughly double by going from 3 to 7 codepoints. I've talked myself into Option 1 as being a mildly better situation, thus sticking with 2 CR bits.
Update: the ECHO design might allow the LB privileged access to the SNI, so it might be encrypted. However, an attacker could connect to the domains at that IP and obtain the config rotation bits. So option 2 actually circumvents ESNI entirely!
Closing -- I have received no pushback on doing nothing (leaving it at 2 bits), so I'm going to do nothing.
There is a requirement that it be difficult for a party other than the server and load balancer to guess a CID that will be accepted as valid for a target connection. This requirement needs to be validated for the schemes described in the draft. This might impose some constraints on the designs chosen. For instance, I don't believe that the plaintext algorithm meets this goal. The server ID can take all the available space, which is probably wrong. Clearly it is impossible to create sufficient connection IDs for even a single connection if there is only one valid identifier per server. However, it might be argued that even an 18 byte server ID makes it too easy to guess a valid connection ID for a connection (just 16 guesses would be enough to get a 50% chance at that). So it seems to me that a shorter connection ID is necessary. The same applies to any attempt at obfuscation. The encrypted versions might be similarly challenging to get right. The For Server Use field in the stream cipher variant needs to be sufficiently long as to avoid engineered collisions. The value used for the stream cipher is malleable, which means that an attacker isn't prevented from guessing. In many ways, this is more challenging than the plaintext variant because the nonce consumes space. The zero-padding in the block cipher mode might be the best way of preventing guessing, if it were sufficiently long. Similarly, if "Encrypted bits for server use" were sufficiently sparsely populated, then guessing can be hard enough.
What exactly does "a CID that will be accepted as valid for a target connection" mean? What is exactly making this requirement?
A connection ID that is accepted as valid for a target connection is any value that will cause packets to be routed to the node that serves that connection. The transport draft :
I think this issue is conflating two different things: 1) Is it easy to generate a CID that has a high probability of being considered valid by the LB? For all but BCID with zero-padding, the answer is clearly "yes" unless the SID space is sparsely populated. But that doesn't make them valid at the server. Moreover, if I am just trying to get random packets past the LB, it's far more productive to just generate a bunch of Initials. In the absence of an LB, all CIDs get through to the server, so I don't understand why this is a problem. 2) The transport draft language (it seems to me) exists mostly to prevent linkability in migration events. Although I've mentioned several times that linkability is a continuum, it is pretty clear that PCID fails at this and OCID makes it harder but no one would bet the house on it being truly secure -- hence the name. I think we're due for an actual WG discussion on OCID, but for PCID would it be sufficient for you, MT, if we said that servers using PCID MUST use disable_migration? To be honest, I thought I had already done this but it's not there.
I really want to push back hard on any restrictions around migration for the Plaintext CID algorithm. It is by far the simplest and cheapest algorithm to support in the LB, and IMO must remain a valid option for QUIC load balancing. As far as giving any information to an external observer, it's possible an external observer can work out the Server ID for each CID. Whether this is practically useful information depends on the number of connections being managed by the individual servers. IMO, we cannot restrict the full set of QUIC features to only work on the most complex and costly algorithms. Some solutions may never implement them.
Ah wait, I found the text in security considerations: So it's a SHOULD right now.
To summarize the issue with Plaintext CID. This is the easiest to implement, so is mostly likely to be adopted. However: 1) This makes DoS attacks on a single server quite straightforward. There is no consensus as to whether single-server DoS is a threat to mitigate or not. 2) Makes connection IDs more linkable. 3) Unless we add a server transport parameter for it, the client has no idea they're more linkable. 4) We could prohibit migration to fix the linkability problem. 5) If we prohibit migration, than the only reason to do plaintext CIDs, instead of just 5-tuple routing, is to support NAT rebinding. To some extent, Obfuscated CID might have similar issues.
The discussion at IETF 109 suggested that the level of linkability associated with the PCID algorithm did not violate the spirit of quic-transport (see NAME and NAME in the QUIC-LB segment here: URL)

QUIC connection unless otherwise indicated.  A "load balancer" is an intermediary for that connection that does not possess QUIC connection keys, but it may rewrite IP addresses or conduct other IP  <ins> or UDP processing.  A "configuration agent" is the entity that determines the QUIC-LB configuration parameters for the network and leverages some system to distribute that configuration. </ins> Note that stateful load balancers that act as proxies, by terminating a QUIC connection with the client and then retrieving data from the balancers to apply the correct routing algorithm and parameters to incoming packets.  <ins> Configuration Agents SHOULD make an effort to deliver new configurations to load balancers before doing so to servers, so that load balancers are ready to process CIDs using the new parameters when they arrive. </ins>  <ins> A Configuration Agent SHOULD NOT use a codepoint to represent a new </ins> configuration until it takes precautions to make sure that all  <ins> connections using CIDs with an old configuration at that codepoint have closed or transitioned. Servers MUST NOT generate new connection IDs using an old configuration after receiving a new one from the configuration agent. Servers MUST send NEW_CONNECTION_ID frames that provide CIDS using the new configuration, and retire CIDs using the old configuration using the "Retire Prior To" field of that frame. </ins> 3.2. Note that the Plaintext CID algorithm makes no attempt to obscure the server mapping, and therefore does not address these concerns.  It exists to allow consistent CID encoding for compatibility across a  <ins> network infrastructure, which makes QUIC robust to NAT rebinding. Servers that are running the Plaintext CID algorithm SHOULD only use it to generate new CIDs for the Server Initial Packet and SHOULD NOT send CIDs in QUIC NEW_CONNECTION_ID frames, except that it sends one new Connection ID in the event of config rotation config-rotation. Doing so might falsely suggest to the client that said CIDs were generated in a secure fashion. </ins> A linkability attack would find some means of determining that two connection IDs route to the same server.  As described above, there 8.1. Any attacker might open a connection to the server infrastructure and  <ins> aggressively simulate migration to obtain a large sample of IDs that map to the same server.  It could then apply analytical techniques to try to obtain the server encoding. </ins>  <ins> The Stream and Block Cipher CID algorithms provide robust entropy to making any sort of linkage.  The Obfuscated CID obscures the mapping and prevents trivial brute-force attacks to determine the routing </ins> parameters, but does not provide robust protection against sophisticated attacks.


All other DCIDs are routable. Load balancers MUST forward packets with routable DCIDs to a server <del> in accordance with the chosen routing algorithm. </del>  Load balancers SHOULD drop short header packets with unroutable DCIDs. decision.  Note that the format and cryptographic protection of this information may change in future versions or extensions of TLS or QUIC, and therefore this functionality is inherently not version- <del> invariant. </del>  11.

Upon further consideration, any TP to provide CIDs for future connections should be in a different document.
LBs that are doing SNI routing are a special case. (1) If the Initial CID is routable, they can't just take that as good; they have to assume that it was random and inspect the SNI to make a decision. Section 4.1 doesn't allow this, and it should. (2) that mode doesn't work for unknown QUIC versions, obviously. (3) For QUIC versions the SNI-LB doesn't understand, I think this is the best we can do: a) For QUIC versions it understands, the LB MAY find the SNI. It MAY use the SNI for unroutable CIDs. If a routable CID, it may override the encoded SID if that SID does not correspond to the provided SNI. b) The QUIC-LB config explicitly states that the LB is doing SNI routing and the versions it supports c) The server MAY send a TP that means "next time you connect with me, please use one of the connection IDs I provided in a NEWCONNECTIONID frame." This may also be useful for some persistence use cases. The worst case is an unsupported version with a randomly generated CID. In this case, the LB will spray it and will only get lucky if it happens to get to the right tenant. Sec 11.4 may also need a revision here. Or maybe SNI routing is just incompatible with version-independent QUIC-LB?
The down side of just saying "don't do that' is that then SNI Load balancing can't work with version aliasing, which would be sad because VA helps hide the SNI.
Maybe specify a unique quic transport param is a better choice? For a web service, all requests just have same SNI, help nothing for distinguishing the Initial CID
Further more, there is a draft about encrypted client hello(URL)if it becomes RFC, maybe LB cannot get information about clientHello or there must be some ways to synchronize the server private-key to LB
synchronizing the server private-key is exactly what happens; that is what the "client-facing server" is in that draft.
I'm not sure what you're proposing here. My hope is not introduce a regression relative to current capabilities, but it sounds like you're talking about an L7 load balancer that's doing deep packet inspection. If so, that's out of scope for this document.
OK, I got it

All other DCIDs are routable. Load balancers MUST forward packets with routable DCIDs to a server  <ins> in accordance with the chosen routing algorithm.  Exception: if the load balancer can parse the QUIC packet and makes a routing decision depending on the contents (e.g., the SNI in a TLS client hello), it MAY route in accordance with this instead.  However, load balancers MUST always route long header packets it cannot parse in accordance with the DCID (see version-invariance). </ins> Load balancers SHOULD drop short header packets with unroutable DCIDs. decision.  Note that the format and cryptographic protection of this information may change in future versions or extensions of TLS or QUIC, and therefore this functionality is inherently not version-  <ins> invariant.  See also unroutable for other considerations about this case.  Note that an SNI-aware load balancer, faced with an unknown QUIC version, might misdirect initial packets to the wrong tenant. While inefficient, this preserves the ability for tenants to deploy new versions provided they have an out-of-band means of providing a connection ID for the client to use. </ins> 11.


A server encodes one of its assigned server IDs in any CID it generates using the relevant configuration. <del> 4.4. All connection IDs use the following format: Each configuration specifies the length of the Server ID and Nonce fields, with limits defined for each algorithm.  When using a given configuration, the server MUST generate CIDs of length equal to the lengths of these three fields. The Server ID is assigned to each server in accordance with sid- allocation.  Dynamically allocated SIDs are limited to seven octets or fewer.  Statically allocated ones have different limits for each algorithm. The configuration agent assigns a server ID to every server in its pool, and determines a server ID length (in octets) sufficiently large to encode all server IDs, including potential future servers. The Nonce is selected by the server when it generates a CID.  As the name implies, a server MUST use a nonce no more than once when generating a CID for a given server ID and unique set of configuration parameters. The nonce length MUST be at least 4 octets.  Additional limits on its length are different for each algorithm.  See cid-entropy for limits on nonce generation. As QUIC version 1 limits connection IDs to 20 octets, the server ID and nonce lengths MUST sum to 19 octets or less. </del> 5. <del> Encryption in the algorithms below uses the AES-128-ECB cipher. Future standards could add new algorithms that use other ciphers to provide cryptographic agility in accordance with RFC7696.  QUIC-LB implementations SHOULD be extensible to support new algorithms. </del> 5.1. <del> The Plaintext CID Algorithm makes no attempt to obscure the mapping of connections to servers, significantly increasing linkability. </del>  <del> 5.1.1. </del>  <del> See cid-format. </del>  <del> 5.1.2. </del>  <del> On each incoming packet, the load balancer extracts consecutive octets, beginning with the second octet.  These bytes represent the server ID.  It ignores the nonce. </del>  <del> 5.1.3. </del>  <del> When a server needs a new connection ID, it encodes one of its assigned server IDs in consecutive octets beginning with the second and chooses a nonce.  This nonce MUST appear to be random (see cid- entropy). </del>  <del> 5.2. </del>  <del> The Encrypted Short CID algorithm provides cryptographic protection at the cost of additional per-packet processing at the load balancer to decrypt every incoming connection ID, unless the load balancer maintains state for the routing information of any given 4-tuple. </del>  <del> 5.2.1. </del>  <del> The nonce length MUST be no fewer than 4 octets.  The nonce SHOULD be at least as long as the server ID in order to save the load balancer an encryption pass; see below. </del>  <del> The configuration agent also selects an 16-octet AES-ECB key to use for connection ID decryption. </del>  <del> 5.2.2. </del>  <del> When generating a routable connection ID, the server writes arbitrary bits into its nonce octets, and its provided server ID into the server ID octets.  See cid-entropy for nonce generation considerations. </del>  <del> The server encrypts the server ID using the following four pass algorithm, which leverages 128-bit AES Electronic Codebook (ECB) mode, much like QUIC header protection. </del>  <del> In the text below, ^ is the XOR function and || is concatenation. The truncate() function takes the most significant octets of its argument, so that the XOR function operates on fields of the same length.  The expand() function outputs 16 octets, with its first argument in the most significant bits, its second argument in the least significant bits, and zeros in all other positions.  Thus, </del>  The example at the end of this section helps to clarify the steps described below. example, 0x7040b81b55ccf3 would split into a left_0 of 0x7040b81 and right_0 of 0xb55ccf3. <del> Encrypt left_0.  The encryption is 128-bit AES-ECB with the key provided by the configuration agent, and the plaintext argument is an expanded version of left_0 where left_0 constitutes the most significant bits, 0x01 is the least significant octet, and all other bits are zero. </del>  <del> XOR the most significant bits of the ciphertext with right_0 to </del>  form right_1. <del> Thus steps 3 and 4 can be expressed as "right_1 = right_0 ^ truncate(AES_ECB(key, expand(left_0, 0x01)) " </del>  Repeat steps 3 and 4, but use them to compute left_1 by expanding <del> and encrypting right_1 with the least significant octet as 0x02 and XOR the results with left_0. </del>  <del> "left_1 = left_0 ^ truncate(AES_ECB(key, expand(right_1), 0x02)) " </del>  Repeat steps 3 and 4, but use them to compute right_2 by expanding and encrypting left_1 with the least significant octet as 0x03 and XOR the results with right_1. <del> "right_2 = right_1 ^ truncate(AES_ECB(key, expand(left_1, 0x03)) " </del>  Repeat steps 3 and 4, but use them to compute left_2 by expanding <del> and encrypting right_2 with the least significant octet as 0x04 and XOR the results with left_1. </del>  <del> "left_2 = left_1 ^ truncate(AES_ECB(key, expand(right_2), 0x04)) " </del>  The server concatenates left_2 with right_2 to form the ciphertext CID, which it appends to the first octet. Note that the plaintext is of odd octet length, so the middle octet will be split evenly left_0 and right_0. <del> 5.2.3. </del>  <del> Upon receipt of a QUIC packet, the load balancer extracts as many of the earliest octets from the destination connection ID as necessary to match the server ID.  The nonce immediately follows. </del>  <del> The load balancer decrypts the nonce and the server ID using the reverse of the algorithm above. </del>  First, split the ciphertext CID (excluding the first octet) into its equal- length components left_2 and right_2.  Then follow the process (i.e., the nonce is at least as large as the server ID).  If the server ID is longer, a fourth pass is necessary: <del> "right_0 = right_1 ^ truncate(AES_ECB(key, expand(left_0, 0x01))) " </del>  and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID. <del> 5.3. The Encrypted Long CID Algorithm, by using a full 16 octets of plaintext and a 128-bit cipher, protects the server ID with a single encryption pass.  However, it also requires connection IDs of at least 17 octets, increasing overhead of client-to-server packets. 5.3.1. The server ID length MUST be no more than 12 octets.  The nonce and server ID MUST sum to at least 16 octets. The configuration agent also selects an 16-octet AES-ECB key to use for connection ID decryption. 5.3.2. Upon receipt of a QUIC packet, the load balancer reads the first octet to obtain the config rotation bits.  It then decrypts the subsequent 16 octets using AES-ECB decryption and the chosen key. The first octets of the plaintext contains the server id. 5.3.3. The server encrypts both its server ID and enough octets in a nonce to form a 16-octet block using the configured AES-ECB key.  Note that any remaining octets in the nonce are transmitted as plaintext, and should consider the constraints in cid-entropy. </del> 6. QUIC-LB requires no per-connection state at the load balancer.  The QUIC-LB requires common configuration to synchronize understanding of encodings and guarantee explicit consent of the server. <del> The load balancer and server MUST agree on a routing algorithm and the relevant parameters for that algorithm. All algorithm configurations can have a server ID length, nonce length, and key.  However, for Plaintext CID, there is no key. </del>  The load balancer MUST receive the full table of mappings, and each server must receive its assigned SID(s), from the configuration (IP address, UDP port, client-generated destination Connection ID) remain constant for all packets sent on the same connection. <del> Dynamic server ID allocation is dependent on client-generated Destination CIDs in Initial Packets being at least 8 octets in length.  If they are not, the load balancer may not be able to extract a valid server ID to add to its table.  Configuring a shorter server ID length can increase robustness to a change. </del> While this document does not update the commitments in RFC8999, the additional assumptions are minimal and narrowly scoped, and provide a likely set of constants that load balancers can use with minimal risk QUIC-LB is intended to prevent linkability.  Attacks would therefore attempt to subvert this purpose. <del> Note that the Plaintext CID algorithm makes no attempt to obscure the server mapping, and therefore does not address these concerns.  It exists to allow consistent CID encoding for compatibility across a network infrastructure, which makes QUIC robust to NAT rebinding. Servers that are running the Plaintext CID algorithm SHOULD only use it to generate new CIDs for the Server Initial Packet and SHOULD NOT send CIDs in QUIC NEW_CONNECTION_ID frames, except that it sends one new Connection ID in the event of config rotation config-rotation. Doing so might falsely suggest to the client that said CIDs were generated in a secure fashion. </del>  A linkability attack would find some means of determining that two connection IDs route to the same server.  As described above, there map to the same server.  It could then apply analytical techniques to try to obtain the server encoding. <del> The Encrypted CID algorithms provide robust protection against any sort of linkage.  The Plaintext CID algorithm makes no attempt to protect this encoding. </del>  Were this analysis to obtain the server encoding, then on-path observers might apply this analysis to correlating different client have each other's stateless reset token unless that key has changed in the interim. <del> The Encrypted Short and Encrypted Long algorithms need to generate different cipher text for each generated Connection ID instance to protect the Server ID.  To do so, at least four octets of the CID are reserved for a nonce that, if used only once, will result in unique cipher text for each Connection ID. </del>  If servers simply increment the nonce by one with each generated connection ID, then it is safe to use the existing keys until any Whether or not it implements the counter method, the server MUST NOT reuse a nonce until it switches to a configuration with new keys. <del> Both the Plaintext CID and Encrypted Long CID algorithms send parts of their nonce in plaintext.  Servers MUST generate nonces so that the plaintext portion appears to be random.  Observable correlations between plaintext nonces would provide trivial linkability between individual connections, rather than just to a common server. </del>  For any algorithm, configuration agents SHOULD implement an out-of- band method to discover when servers are in danger of exhausting

narrows single-pass encryption to a special-case optimization, and simplifies presentation of the design.
There's a lot of needless bitshifting in the current algorithm. When reading from the right, each byte has to be shifted 4 bits and ORed with the neighboring byte, as currently written. There's an easier way. When encrypting side X (= { left, right }) and XORing with side Y. Set Y to zero. Set the index value (= 1..4) in the furthest byte that has been zeroed. Encrypt to get ciphertext XOR Y with the bits in the same position as Y.
There has quietly been some work on tightening up the QUIC-LB specification. At the moment, we are still short on implementations but I am hearing something might happen soon. Anyway, Christian Huitema has made substantial contributions to the security properties of Stream Cipher CID, which allows smallish CIDs, by making it a three-pass algorithm. We still have the "Block Cipher CID option" which requires CIDs of at least 17 bytes; AFAICT the only advantage at this point is that it can be decoded with 1 block encryption operation instead of three. In principle, QUIC-LB load balancers can be run with no per-connection state, in which case this would be a per-packet operation. I strongly suspect that real LBs will keep some per-4tuple state, as they do today; if so, this crypto operation only needs to occur once per packet where the 4-tuple is new. If so, the CPU impact is vanishingly small except in a storm of garbage packets. So AFAICT, the use case for Block Cipher is as follows: Willing to run one crypto operation per packet/new 4-tuple Not OK with doing three crypto operations satisfied with 17B + CIDs I strongly suspect this does not describe a real implementer, and am inclined to simply delete this in my effort to simplify the design. Nevertheless, I'm taking this to the list in case someone thinks this is an important use case.
I am pretty confident about the properties of the stream cipher, but I am not a cryptographer Should we ask a review from the CFRG?
I implemented block cipher cid in my proxy server. It is a bit sad to hear that it is going to be removed from the spec, but I have no strong opinion to against that. If it is removed I'll just remove the reference to quic lb spec and call it our own. The good thing of block cipher cid is that its implementation is quite easy. 17 bytes cid has good entropy for random bits to manage several cid per connection.
NAME It's not gone yet! At the very least, we're going to do the crypto review.
FWIW, h2o also uses 17-byte CID backed by AES-ECB. Our server-id is 64-bit. While that might sound a bit large, it is good to have some additional space that can be used for detecting broken packets carrying bogus CIDs. Also, we do not retain 4-tuple state for each connection, because we do not want to retain connection state for all the server cluster. Instead, we decrypt CID of each packet. Our format is different from what is in QUIC-LB (our design predates the first revision of the draft), so we do not mind support for block ciphers getting removed from the document. I'm just pointing out that there are use cases where block ciphers are sufficient.
NAME thanks for the field report. While you're not conforming to the standard today, I hope you'll consider h2o servers eventually supporting the standard (at last optionally) so they can be used with migration with commodity load balancers. Would the lack of a block cipher make a meaningful difference in your willingness to do so?
if you've implemented this, please join the #quic-lb channel and we should do interop once the spec settles down
NAME Implementing the Stream Cipher CID algorithm is easy for the backend servers. IMO the practical concerns are related to load balancers. Each of the three-pass AES-ECB operation has dependency on earlier passes. That means that it is impossible to run these passes in parallel. My napkin calculation tells me that the CPU cycles required for running Stream Cipher CID algorithm on each full-sized packet would cost like 30% of decrypting a full-sized packet. There would be at least two ways to mitigate this problem. One is process multiple packets in parallel. The other is cache information, as you've suggested in this issue. If the load balancer developers are happy to accept this complexity (for saving few bytes of CID), then I do not think I have a reason to argue against.
Please don't make decisions now about the cost of three passes. The CFRG review might very well ask us to increase the number of passes, especially if the nonce is something like a counter instead of a random number. Consider for example the usage pattern in which after a key rotation all servers in the cluster set their nonce to exactly 1. In effect, that means we are almost down to two passes, and that may arguably be too few. Or maybe not. Review will tell. If I look at the literature, 3 passes is the absolute minimum for achieving an acceptable level of mixing, but there are papers recommending 5, 6 or even 12 passes.
NAME thank you for the caution. Then I wonder if we need to decide the fate of block cipher CIDs, before the design of stream cipher CIDs become more stable.
I am not resolving this issue until the crypto review is complete.
It's unfortunately that routing by will meet some problems, image this scenario: The right way SHOULD be that quic-lb route all packets through CID, then the cpu cost can't be avoid, but I still approve the usage of stream cipher, from a safety point of view.
Good point, I'll consider this when I
reduced the Block CID (now "single-pass encryption") to the special case where noncelen + sidlen == 16.
Given the work in , I'm disinclined to remove this special case. Closing.
That works. The macros are clever, and you may want to explain the cleverness somewhere. It is obvious to me that you want to align "right" and "left" to an even number of bytes, adding a null nibble at the left or right or at the right of left. That will indeed save a lot of bit twiddling. Found an unrelated typo.

A server encodes one of its assigned server IDs in any CID it generates using the relevant configuration.  5.  5.1.  <ins> All connection IDs use the following format: </ins>  <ins> 5.2. </ins>  <ins> The configuration agent assigns a server ID to every server in its pool in accordance with sid-allocation, and determines a server ID length (in octets) sufficiently large to encode all server IDs, including potential future servers. </ins>  <ins> Each configuration specifies the length of the Server ID and Nonce fields, with limits defined for each algorithm. </ins>  <ins> Optionally, it also defines a 16-octet key.  Note that failure to define a key means that observers can determine the assigned server of any connection, significantly increasing the linkability of QUIC address migration. </ins>  <ins> The nonce length MUST be at least 4 octets.  The server ID length MUST be at least 1 octet. </ins>  <ins> As QUIC version 1 limits connection IDs to 20 octets, the server ID and nonce lengths MUST sum to 19 octets or less. </ins>  <ins> 5.3. </ins>  <ins> The server writes the first octet and its server ID into their respective fields. If there is no key in the configuration, the server MUST fill the Nonce field with bytes that appear to be random.  If there is a key, the server fills the nonce field with a nonce of its choosing.  See cid-entropy for details. The server MAY append additional bytes to the connection ID, up to the limit specified in that version of QUIC, for its own use.  These bytes MUST NOT provide observers with any information that could link two connection IDs to the same connection, client, or server.  In particular, all servers using a configuration MUST consistently add the same length to each connection ID, to preserve the linkability objectives of QUIC-LB.  Any additional bytes SHOULD appear random unless individual servers are not distinguishable (e.g. any server using that configuration appends identical bytes to every connection ID). If there is no key in the configuration, the Connection ID is complete.  Otherwise, there are further steps, as described in the two following subsections. Encryption below uses the AES-128-ECB cipher.  Future standards could add new algorithms that use other ciphers to provide cryptographic agility in accordance with RFC7696.  QUIC-LB implementations SHOULD be extensible to support new algorithms. </ins>  <ins> 5.3.1. </ins>  <ins> When the nonce length and server ID length sum to exactly 16 octets, the server MUST use a single-pass encryption algorithm.  All connection ID octets except the first form an AES-ECB block.  This block is encrypted once, and the result forms the second through seventeenth most significant bytes of the connection ID. </ins>  <ins> 5.3.2. </ins>  <ins> Any other field length requires four passes for encryption and at least three for decryption.  To understand this algorithm, it is useful to define four functions that minimize the amount of bit- shifting necessary in the event that there are an odd number of octets. </ins>  <ins> The expand_left() function outputs 16 octets, with its first argument in the most significant bits, its second argument in the least significant byte, and zeros in all other positions.  Thus, </ins>  <ins> expand_right() is similar, except that the second argument is in the most significant byte, and the first argument is in the least significant bits.  Therefore, </ins>  <ins> Similarly, truncate_left() and truncate_right() take the most significant and least significant bits, respectively, from a ciphertext.  For example, to take 28 bits of a ciphertext: </ins> The example at the end of this section helps to clarify the steps described below. example, 0x7040b81b55ccf3 would split into a left_0 of 0x7040b81 and right_0 of 0xb55ccf3.  <ins> Encrypt the result of expand_left(left_0) to obtain a ciphertext. </ins>  <ins> XOR the least significant bits of the ciphertext with right_0 to </ins> form right_1.  <ins> Thus steps 3 and 4 can be expressed as "right_1 = right_0 ^ truncate_right( AES_ECB(key, expand_left(left_0, 0x01)), len(right_0)) " </ins> Repeat steps 3 and 4, but use them to compute left_1 by expanding  <ins> and encrypting right_1 with the most significant octet as 0x02 and XOR the results with left_0. </ins>  <ins> "left_1 = left_0 ^ truncate_left( AES_ECB(key, expand_right(right_1, 0x02)), len(left_0)) " </ins> Repeat steps 3 and 4, but use them to compute right_2 by expanding and encrypting left_1 with the least significant octet as 0x03 and XOR the results with right_1.  <ins> "right_2 = right_1 ^ truncate_right( AES_ECB(key, expand_left(left_1, 0x03)), len(right_1)) " </ins> Repeat steps 3 and 4, but use them to compute left_2 by expanding  <ins> and encrypting right_2 with the most significant octet as 0x04 and XOR the results with left_1. </ins>  <ins> "left_2 = left_1 ^ truncate_left( AES_ECB(key, expand_right(right_2, 0x04)), len(left_1)) " </ins> The server concatenates left_2 with right_2 to form the ciphertext CID, which it appends to the first octet. Note that the plaintext is of odd octet length, so the middle octet will be split evenly left_0 and right_0.  <ins> 5.4. On each incoming packet, the load balancer extracts consecutive octets, beginning with the second octet.  If there is no key, the first octets correspond to the server ID. If there is a key, the load balancer takes one of two actions: 5.4.1. </ins>  <ins> If server ID length and nonce length sum to exactly 16 octets, they form a ciphertext block.  The load balancer decrypts the block using the AES-ECB key and extracts the server ID from the most significant bytes of the resulting plaintext. </ins>  <ins> 5.4.2. </ins> First, split the ciphertext CID (excluding the first octet) into its equal- length components left_2 and right_2.  Then follow the process (i.e., the nonce is at least as large as the server ID).  If the server ID is longer, a fourth pass is necessary:  <ins> "right_0 = right_1 ^ truncate_right(AES_ECB(key, expand_left(left_0, 0x01))) " </ins> and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID.  6. QUIC-LB requires no per-connection state at the load balancer.  The QUIC-LB requires common configuration to synchronize understanding of encodings and guarantee explicit consent of the server.  <ins> The load balancer and server MUST agree on the connection ID encoding parameters, which include the server ID length and nonce length.  A key is optional. </ins> The load balancer MUST receive the full table of mappings, and each server must receive its assigned SID(s), from the configuration (IP address, UDP port, client-generated destination Connection ID) remain constant for all packets sent on the same connection.  While this document does not update the commitments in RFC8999, the additional assumptions are minimal and narrowly scoped, and provide a likely set of constants that load balancers can use with minimal risk QUIC-LB is intended to prevent linkability.  Attacks would therefore attempt to subvert this purpose.  <ins> Note that without a key for the encoding, QUIC-LB makes no attempt to obscure the server mapping, and therefore does not address these concerns.  Without a key, QUIC-LB merely allows consistent CID encoding for compatibility across a' network infrastructure, which makes QUIC robust to NAT rebinding.  Servers that are encoding their server ID without a key algorithm SHOULD only use it to generate new CIDs for the Server Initial Packet and SHOULD NOT send CIDs in QUIC NEW_CONNECTION_ID frames, except that it sends one new Connection ID in the event of config rotation config-rotation.  Doing so might falsely suggest to the client that said CIDs were generated in a secure fashion. </ins> A linkability attack would find some means of determining that two connection IDs route to the same server.  As described above, there map to the same server.  It could then apply analytical techniques to try to obtain the server encoding.  <ins> An encrypted encoding provides robust protection against this.  An unencrypted one provides none. </ins> Were this analysis to obtain the server encoding, then on-path observers might apply this analysis to correlating different client have each other's stateless reset token unless that key has changed in the interim.  <ins> The encrypte mode needs to generate different cipher text for each generated Connection ID instance to protect the Server ID.  To do so, at least four octets of the CID are reserved for a nonce that, if used only once, will result in unique cipher text for each Connection ID. </ins> If servers simply increment the nonce by one with each generated connection ID, then it is safe to use the existing keys until any Whether or not it implements the counter method, the server MUST NOT reuse a nonce until it switches to a configuration with new keys.  <ins> If the nonce is sent in plaintext, servers MUST generate nonces so that they appear to be random.  Observable correlations between plaintext nonces would provide trivial linkability between individual connections, rather than just to a common server. </ins> For any algorithm, configuration agents SHOULD implement an out-of- band method to discover when servers are in danger of exhausting


The expand_left() function outputs 16 octets, with its first argument in the most significant bits, its second argument in the least <del> significant byte, and zeros in all other positions.  Thus, </del>  expand_right() is similar, except that the second argument is in the <del> most significant byte, and the first argument is in the least significant bits.  Therefore, </del>  Similarly, truncate_left() and truncate_right() take the most significant and least significant bits, respectively, from a example, 0x7040b81b55ccf3 would split into a left_0 of 0x7040b81 and right_0 of 0xb55ccf3. <del> Encrypt the result of expand_left(left_0) to obtain a ciphertext. </del>  XOR the least significant bits of the ciphertext with right_0 to form right_1. Thus steps 3 and 4 can be expressed as "right_1 = right_0 ^ <del> truncate_right( AES_ECB(key, expand_left(left_0, 0x01)), </del>  len(right_0)) " Repeat steps 3 and 4, but use them to compute left_1 by expanding <del> and encrypting right_1 with the most significant octet as 0x02 and XOR the results with left_0. </del>  "left_1 = left_0 ^ truncate_left( AES_ECB(key, <del> expand_right(right_1, 0x02)), len(left_0)) " </del>  Repeat steps 3 and 4, but use them to compute right_2 by expanding <del> and encrypting left_1 with the least significant octet as 0x03 and XOR the results with right_1. </del>  "right_2 = right_1 ^ truncate_right( AES_ECB(key, <del> expand_left(left_1, 0x03)), len(right_1)) " </del>  Repeat steps 3 and 4, but use them to compute left_2 by expanding <del> and encrypting right_2 with the most significant octet as 0x04 and XOR the results with left_1. </del>  "left_2 = left_1 ^ truncate_left( AES_ECB(key, <del> expand_right(right_2, 0x04)), len(left_1)) " </del>  The server concatenates left_2 with right_2 to form the ciphertext CID, which it appends to the first octet. server ID is longer, a fourth pass is necessary: "right_0 = right_1 ^ truncate_right(AES_ECB(key, expand_left(left_0, <del> 0x01))) " </del>  and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID. design decisions that could impact the operation of QUIC-LB. The maximum Connection ID length could be below the minimum necessary <del> for one or more encoding algorithms. </del>  unroutable provides guidance about how load balancers should handle unroutable DCIDs.  This guidance, and the implementation of an

in accordance with the 2nd crypto review
In the second crypto review () there was a note that plaintexts of different lengths might end up with similar results, if the extra bits were zeros. The suggestion to fix this was to update the path index to also include the length.
Looks good, except for the missing parenthesis.

The expand_left() function outputs 16 octets, with its first argument in the most significant bits, its second argument in the least  <ins> significant byte, its third argument in the second least significant byte, and zeros in all other positions.  Thus, </ins> expand_right() is similar, except that the second argument is in the  <ins> most significant byte, the third is in the second most significant byte, and the first argument is in the least significant bits. Therefore, </ins> Similarly, truncate_left() and truncate_right() take the most significant and least significant bits, respectively, from a example, 0x7040b81b55ccf3 would split into a left_0 of 0x7040b81 and right_0 of 0xb55ccf3.  <ins> Encrypt the result of expand_left(left_0, index)) to obtain a ciphertext, where 'index' is one octet: the two most significant bits of which are 0b00, and the six least significant bits are the length of the resulting connection ID in bytes, cid_len. </ins> XOR the least significant bits of the ciphertext with right_0 to form right_1. Thus steps 3 and 4 can be expressed as "right_1 = right_0 ^  <ins> truncate_right( AES_ECB(key, expand_left(left_0, cid_len, 1)), </ins> len(right_0)) " Repeat steps 3 and 4, but use them to compute left_1 by expanding  <ins> and encrypting right_1 with the most significant octet as the concatenation of 0b01 and cid_len, and XOR the results with left_0. </ins> "left_1 = left_0 ^ truncate_left( AES_ECB(key,  <ins> expand_right(right_1, cid_len, 2)), len(left_0)) " </ins> Repeat steps 3 and 4, but use them to compute right_2 by expanding  <ins> and encrypting left_1 with the least significant octet as the concatenation of 0b10 and cid_len, and XOR the results with right_1. </ins> "right_2 = right_1 ^ truncate_right( AES_ECB(key,  <ins> expand_left(left_1, cid_len, 3), len(right_1)) " </ins> Repeat steps 3 and 4, but use them to compute left_2 by expanding  <ins> and encrypting right_2 with the most significant octet as the concatenation of 0b11 ands cid_len, and XOR the results with left_1. </ins> "left_2 = left_1 ^ truncate_left( AES_ECB(key,  <ins> expand_right(right_2, cid_len, 4), len(left_1)) " </ins> The server concatenates left_2 with right_2 to form the ciphertext CID, which it appends to the first octet. server ID is longer, a fourth pass is necessary: "right_0 = right_1 ^ truncate_right(AES_ECB(key, expand_left(left_0,  <ins> 1 + cid_len))) " </ins> and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID. design decisions that could impact the operation of QUIC-LB. The maximum Connection ID length could be below the minimum necessary  <ins> to use all or part of this specification.  The minimum Connection ID length could exceed the limits in this specification. </ins> unroutable provides guidance about how load balancers should handle unroutable DCIDs.  This guidance, and the implementation of an


2.1.  Initial Packets are especially effective at consuming server resources because they cause the server to create connection state. Even when mitigating this load with Retry Packets, the act of

A different fix for . Avoids any client involvement, and covers all but extreme corner cases.
There are cases where an offload might send Retry on the second initial but not the first: it turns on between the two packets it is in a stochastic mode where it doesn't always send Retry There is one failure mode and one degraded performance issue: 1) If the server sends HRR, if the client's response triggers Retry it is likely to blackhole the connection. 2) Under certain conditions, dropping the client's second Initial packet will cause either a client or server PTO. (Specifically, there will be no penalty if the client's 2nd flight has a datagram that (a) does not contain an initial, and (b) contains a HS ACK.) We ought to write this down in the draft. For , it might be good practice for servers to send CONNECTION_CLOSE instead of HRR under two conditions: 1) There is no Retry token in the Initial, and 2) Other recent initials contained Retry tokens.
The amplification limit is a problem here. If the client's second flight is one packet, including an Initial, which keeps getting dropped, and the server is at its amplification limit, there is no way for the connection to progress.
As I mentioned in OR , I think we need to handle this via an extension to the QUIC protocol itself. I recommend we define an extension that does the following: The server includes a new transport parameter that contains the value of the initial token that clients should use for any subsequent initial packets they send. If the client understands this extension/TP, it acts accordingly. If it does not, then it may hit the issues you describe in this PR and eventually time out.
So the problem with using a TP is that it isn't delivered until the Encrypted Extensions messages, which is far too late for most of the failure modes here. However, I've thought more carefully about this and have realized some things. Offload statefulness doesn't completely solve it, as you could have a shared-state retry where an anti-DDoS service turns on and suddenly has all packets route through it. If an offload is going to randomly send Retry while being stateless, it really ought to be pseudorandom based on the address/port or whatever. Therefore, the two Initial packets will get the same treatment in the usual case. That reduces to the problem to "service turns on mid-handshake". Case 1 is where Retry offload breaks the Initial + Handshake flight. This isn't actually a black hole. The client will retransmit handshake packets and, per RFC9001 4.9.1, MUST NOT send Initial packets once it's sent a handshake. So there might be a PTO, which is not ideal, but not catastrophic. In the normal case, there are multiple server handshake packets, and if the client is following spec and not doing ack delay in the handshake, it is unlikely the entire Handshake ACK will be coalesced with Initial. So let's forget about this "problem", and we don't need to do any weird packet truncation as in PR . Case 2 is HRR or Multi-packet SHLO: this is the harder case as Initial ACKs are critical to progress. HRR is an edge case but post-quantum we may need multi-packet SHLO, and it would be good for this design to be robust to that. I don't see a realistic fix here right now but I'll think about it some more.
Crap. You're obviously totally right. I don't know how I forgot. I still thing the right way to handle this is to somehow give the client the along with the server's initial payload. I might propose a slight modification of my original plan: The client includes a new transport parameter (with no payload) that advertises support for this new extension. If the server supports this extension, it includes a frame in all its Initial packets, and then also includes the transport parameter that also contains the value of the initial token (so that it can be securely validated). Clients should use the frame value for any subsequent initial packets they send.
That will work, but I am very skeptical of client support, particularly in Chrome. I just reworked things completely with . Reasoning about the Handshake is hard, but I think this works pretty well unless the handshake is also suffering losses for reasons other than the Offload dropping stuff. Basically, there's a "transition mode" when going active where you send Retry and forward the packet to the server anyway. The handshake logic basically works out, which is surprising until you think it through.
I'm very hesitant to push a solution that doesn't work in all scenarios. From some tests I've done in recent past, HRR is a lot more common in some scenarios. Also, post-quantum crypto is coming.
But this works!
LGTM

2.1. <ins> Retry Offloads SHOULD treat Initial packets from the same connection with a uniform policy.  Initial packets of the first and second client flight can be difficult to distinguish without expensive decryption of the contents, which is unsuitable under the conditions of a DDoS attack.  If the first packet of a connection is admitted without Retry, but the second triggers a Retry, that Retry packet will be ignored and the loss of an Initial coalesced with other packets can impair performance.  In some situations, the client does not yet have handshake keys, and dropping further client Initial packets creates a deadlock where the connection cannot progress. The simplest means to ensure this is to require, when active, a Retry Token for all incoming Initial packets, and send a Retry packet otherwise.  If the Retry Offload is to be more selective, one technique keeps state on which address/port 4-tuples have been admitted.  Another would be to apply a secure hash to the source IP address, port, and connection ID to deterministically compute whether the Initial requires a Retry Token or not.  These source values remain consistent over the handshake. However, even with these techniques there is a potential problem when a Retry Offload switches from inactive to active mode.  The Retry Offload could admit the first packet while in inactive mode, and then drop subsequent Initials in active mode. If the Retry Offload is always on-path, it MAY keep state on incoming connections while in inactive mode to avoid this problem.  If it cannot or will not keep such state, it SHOULD implement "transition mode" for an interval chosen to include the likely Initial packet exchange of most clients (200ms is a sensible default). In transition mode, Retry Offloads process Initial packets with Retry tokens as in active mode.  When the Retry Offload receives an Initial packet with no token, it issues a Retry AND forwards the packet to the server.  If the client has already received a packet from the server, it will ignore the Retry and the connection will progress normally.  If not, the client will reconnect based on the Retry, the server's response to the first initial will be discarded, and the connection will progress normally based on the client's second Initial.  mid-handshake explores the various possible packet sequences in transition mode. Note that transition mode provides no actual DDoS relief to the server, so its duration should be as short as possible.  The Retry Offload can choose not to implement transition mode and cause some client connections to fail. Servers operating behind a Retry Offload SHOULD implement a mechanism that operates whenever a client Initial arrives with a valid Retry token.  If there is another connection with identical client Connection ID, IP, and Port, but with an unvalidated address, that connection is immediately and silently terminated.  This mechanism eliminates incorrect connection state that is an artifact of transition mode, as explained in mid-handshake. 2.2. </ins> Initial Packets are especially effective at consuming server resources because they cause the server to create connection state. Even when mitigating this load with Retry Packets, the act of


If a server has not received a valid QUIC-LB configuration, and believes that low-state, Connection-ID aware load balancers are in the path, it SHOULD generate connection IDs with the config rotation <del> bits set to '11' and SHOULD use the "disable_active_migration" </del>  transport parameter in all new QUIC connections.  It SHOULD NOT send NEW_CONNECTION_ID frames with new values. A load balancer that sees a connection ID with config rotation bits <del> set to '11' MUST revert to 5-tuple routing.  These connection IDs may be of any length; however, see cid-entropy for limits on this length. </del>  2.3. receive similar treatment regardless of why they're unroutable: The config rotation bits (config-rotation) may not correspond to <del> an active configuration.  Note: a packet with a DCID that indicates 5-tuple routing (see config-failover) is always routable. </del>  The DCID might not be long enough for the decoder to process. each incoming packet and route that packet accordingly. However, once the routing decision has been made, the load balancer <del> MAY associate the 4-tuple with the decision.  This has two advantages: </del>  <del> The load balancer only extracts the server ID once per incoming 4-tuple.  When the CID is encrypted, this substantially reduces computational load. </del>  Incoming Stateless Reset packets and ICMP messages are easily routed to the correct origin server. exposing information about the target domain to the entire network; or <del> tenants can use 4-tuple routing in their CIDs (in which case they SHOULD disable migration in their connections), which neutralizes the value of QUIC-LB but preserves privacy. </del>  When configuring QUIC-LB, administrators must evaluate the privacy tradeoff considering the relative value of each of these properties,

The '11' codepoint might be better defined. It would be good, for instance, if a stateful load balancer actually stored the observed CID so that the connection can survive a NAT rebinding.
Actually this is a bit harder than we'd like. If the load balancer is stateless, then the short-header processing would have to be based on the destination connection ID to be robust to NAT rebinding. But then, as the connection ID will change mid-handshake, long-header processing needs a different approach. I don't see how, logically, a stateless LB can maintain consistency between these two results unless we force the unconfigured server to retain most of the client-generated CID.
Having reflected on this a bit more, this is what I think we should do: Keep 0b11 as 4-tuple routing. However Load Balancers MAY observe destination connection IDs and keep state on them, to later survive a NAT rebinding. I see two use cases for 0b11: (1) a general purpose server that always uses QUIC-LB, whether or not it's behind an LB, for simplicity. So when it's not behind a Layer 4 LB it just generates random CIDs except for the first byte. As an added bonus, if there is an unknown L4 LB in front of it the behavior will not be pathological, at least until there's a migration. But in general, the rebinding performance is not all that important. (2) As a panic mode when things are misconfigured or the server has run out of nonces. Again, I'm not sure rebinding performance is super-critical. In other words, if a server is behind an L4 load balancer is and is using 0b11, something has gone very wrong.

If a server has not received a valid QUIC-LB configuration, and believes that low-state, Connection-ID aware load balancers are in the path, it SHOULD generate connection IDs with the config rotation  <ins> bits set to 0b11 and SHOULD use the "disable_active_migration" </ins> transport parameter in all new QUIC connections.  It SHOULD NOT send NEW_CONNECTION_ID frames with new values. A load balancer that sees a connection ID with config rotation bits  <ins> set to 0b11 MUST route using an algorithm based solely on the address/port 4-tuple, which is consistent well beyond the QUIC handshake.  However, a load balancer MAY observe the connection IDs used during the handshake and populate a connection ID table that allows the connection to survive a NAT rebinding, and reduces the probability of connection failure due to a change in the number of servers. When using codepoint 0b11, all bytes but the first SHOULD have no larger of a chance of collision as random bytes.  The connection ID SHOULD be of at least length 8 to provide 7 bytes of entropy after the first octet with a low chance of collision.  Furthermore, servers in a pool SHOULD also use a consistent connection ID length to simplify the load balancer's extraction of a connection ID from short headers. </ins> 2.3. receive similar treatment regardless of why they're unroutable: The config rotation bits (config-rotation) may not correspond to  <ins> an active configuration.  Note: a packet with a DCID with config ID codepoint 0b11 (see config-failover) is always routable. </ins> The DCID might not be long enough for the decoder to process. each incoming packet and route that packet accordingly. However, once the routing decision has been made, the load balancer  <ins> MAY associate the 4-tuple or connection ID with the decision.  This has two advantages: </ins>  <ins> The load balancer only extracts the server ID once until the 4-tuple or connection ID changes.  When the CID is encrypted, this might reduce computational load. </ins> Incoming Stateless Reset packets and ICMP messages are easily routed to the correct origin server. exposing information about the target domain to the entire network; or  <ins> tenants can use the 0b11 codepoint in their CIDs (in which case they SHOULD disable migration in their connections), which neutralizes the value of QUIC-LB but preserves privacy. </ins> When configuring QUIC-LB, administrators must evaluate the privacy tradeoff considering the relative value of each of these properties,


(i.e., the nonce is at least as large as the server ID).  If the server ID is longer, a fourth pass is necessary: <del> "right_0 = right_1 ^ truncate_right(AES_ECB(key, expand_left(left_0, 1 + cid_len))) " </del>  and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID. have each other's stateless reset token unless that key has changed in the interim. <del> The encrypte mode needs to generate different cipher text for each </del>  generated Connection ID instance to protect the Server ID.  To do so, at least four octets of the CID are reserved for a nonce that, if used only once, will result in unique cipher text for each Connection therefore compare the expected rate at which servers will generate CIDs with the lifetime of a configuration.  9. There are no IANA requirements.

Discuss Distinguishing Attack in security section. Fix specification of Four Passes decryption Fix number of kramdown issues.
NAME I think this simple PR is ready.
Thank you, this is perfect.
We're awaiting more data to fully understand this, but we may need to allow 4, 6, or 12 passes as configuration options.
Fixed by , but we're instead going to go with in lieu of doing this.
The analysis in the second crypto review should be in the security considerations.
I believe there is an edge case where a partially chosen plaintext is possible. 1) The load balancer is supporting both an encrypted and unencrypted config, on different codepoints, that happen to use the same server ID mapping 2) The server is generating encrypted CIDs. 3) The attacker sends an initial packet sends an unencrypted CID. 4) Any server generated CID is therefore using the unencrypted server ID as plaintext. The random nonce should generally protect us, but maybe not? Anyway, security considerations could say something like "unencrypted and encrypted CIDs MUST NOT use the same server ID assignments because...".
Fixed by

(i.e., the nonce is at least as large as the server ID).  If the server ID is longer, a fourth pass is necessary:  <ins> "right_0 = right_1 ^ truncate_right( AES_ECB(key, expand_left(left_0, cid_len, 1), len(right_1)) " </ins> and the load balancer has to concatenate left_0 and right_0 to obtain the complete server ID. have each other's stateless reset token unless that key has changed in the interim.  <ins> The encrypted mode needs to generate different cipher text for each </ins> generated Connection ID instance to protect the Server ID.  To do so, at least four octets of the CID are reserved for a nonce that, if used only once, will result in unique cipher text for each Connection therefore compare the expected rate at which servers will generate CIDs with the lifetime of a configuration. <ins> 8.7. The Four Pass Encryption algorithm is structured as a 4-round Feistel network with non-bijective round function.  As such, it does not offer a very high security level against distinguishing attacks, as explained in Patarin2008.  Attackers can mount these attacks if they are in possession of O(SQRT(len/2)) pairs of ciphertext and known corresponding plain text, where "len" is the sum of the lengths of the Server ID and the Nonce. The authors considered increasing the number of passes from 4 to 12, which would definitely block these attacks.  However, this would require 12 round of AES decryption by load balancers accessing the CID, a cost deemed prohibitive in the planned deployments. The attacks described in Patarin2008 rely on known plain text.  In a normal deployment, the plain text is only known by the server that generates the ID and by the load balancer that decrypts the content of the CID.  Attackers would have to compensate by guesses about the allocation of server identifiers or the generation of nonces.  The these attacks are thus mitigated by making nonces hard to guess, as specified in cid-entropy, and by rules related to mixed deployments that use both clear text CID and encrypted CID, for example when transitioning from clear text to encryption.  Such deployments MUST use different server ID allocations for the clear text and the encrypted versions. These attacks cannot be mounted against the Single Pass Encryption algorithm. </ins> 9. There are no IANA requirements.


6.2.  Some deployments may transparently move a connection from one server to another.  The means of transferring connection state between servers is out of scope of this document.

Many server architectures have multiple processes. There are many ways to properly route packets between them, but many have severely limited capabilities. Add some non-normative text about options here: run QUIC-LB at the demultiplexer, packet toss, register connection IDs at the demultiplexer, or rewrite the CID with the decrypted one at the LB (which has security limitations)

6.2. <ins> QUIC servers might have QUIC running on multiple processes listening on the same address, and have a need to demultiplex between them.  In principle, this demultiplexer is a Layer 4 load balancer, and the guidance in lb-chains applies.  However, in many deployments the demultiplexer lacks the capability to perform decryption operations. There are some other techniques that might work, depending on the capabilities of the server architecture. Some bytes of the server ID are reserved to encode the process ID. The demultiplexer might operate based on the 4-tuple or other legacy indicator, but the receiving server process extracts the server ID, and if it does not match the one for that process, the process could "toss" the packet to the correct destination process. Each process could register the connection IDs it generates with the demultiplexer, which routes those connection IDs accordingly. Some bytes of the server ID are reserved to encode the process ID. The load balancer writes the decrypted connection ID into the packet.  The demultiplexer reads the process ID from the relevant bytes and routes it accordingly.  The receiving server process immediately re-encrypts the connection ID in deliver the packet to the correct connection and successfully authenticate the packet. While this requires coordination between load balancer and server, this document does not standardize the technique because it is unsafe to use unless the path between load balancer and server is fully secure.  An observer at that point can determine the server mapping via the IP address of the destination server, but the process ID provides additional linkability information. 6.3. </ins> Some deployments may transparently move a connection from one server to another.  The means of transferring connection state between servers is out of scope of this document.


Local hardware cryptographic offload devices may accelerate QUIC servers by receiving keys from the QUIC implementation indexed to the connection ID.  However, on physical devices operating multiple QUIC <del> servers, it is impractical to efficiently lookup these keys if the connection ID does not self-encode its own length. </del>  Note that this is a function of particular server devices and is irrelevant to load balancers.  As such, load balancers MAY omit this

This only applies if the CID varies in length (so say that). Also, it only applies if the common stem doesn't otherwise distinguish itself enough to allow an efficient lookup (so say "might" rather than get into all that). In other words, it's probably inefficient, but not necessarily so.

Local hardware cryptographic offload devices may accelerate QUIC servers by receiving keys from the QUIC implementation indexed to the connection ID.  However, on physical devices operating multiple QUIC  <ins> servers, it might be impractical to efficiently lookup keys if the connection ID varies in length and does not self-encode its own length. </ins> Note that this is a function of particular server devices and is irrelevant to load balancers.  As such, load balancers MAY omit this


If these assumptions are not valid, this specification is likely to lead to loss of packets that contain unroutable DCIDs, and in extreme <del> cases connection failure. Some load balancers might inspect elements of the Server Name Indication (SNI) extension in the TLS Client Hello to make a routing decision.  Note that the format and cryptographic protection of this information may change in future versions or extensions of TLS or QUIC, and therefore this functionality is inherently not version- invariant.  See also unroutable for other considerations about this case.  Note that an SNI-aware load balancer, faced with an unknown QUIC version, might misdirect initial packets to the wrong tenant. While inefficient, this preserves the ability for tenants to deploy new versions provided they have an out-of-band means of providing a connection ID for the client to use. </del>  8.

THe addition to the first paragraph here seems necessary. The basic idea is that if a QUIC version violates the (reasonable) assumptions this section makes, then it just doesn't work if you try to apply this design. You will need to update your load balancer and probably update the CID allocation/negotiation/configuration you are using. The second paragraph I'm less sure about. I think that the intent was to say that if you follow the advice here you still get a functioning system even if the load balancer gets it wrong. So I said that. I think that I agree, provided that the tenant (new term?) chooses a good connection ID for any new ones. So that's not just Section 3.1 you need to refer to, but the whole document.
So what I'm trying to say at the end of the second paragraph is this: 1) Say, for example you have a LB that routes the Client Inital by SNI 2) For other packets, it uses a QUIC-LB config with 8B CIDs. 3) A client Initial arrives to URL with an (unknown) QUIC version 4) This initial also has an 8B CID that accidentally happens to route to a URL tenant 5) Since the LB can't read the CHLO, it ends up using the CID to route and things fail. Maybe you got all that from the text, but I'm making sure. This scenario isn't great but also isn't any worse than the pre-QUIC-LB status quo. shrug Still, your editorial change makes things clearer.

If these assumptions are not valid, this specification is likely to lead to loss of packets that contain unroutable DCIDs, and in extreme  <ins> cases connection failure.  A QUIC version that violates the assumptions in this section therefore cannot be safely deployed with a load balancer that follows this specification.  An updated or alternative version of this specification might address these shortcomings for such a QUIC version. Some load balancers might inspect version-specific elements of packets to make a routing decision.  This might include the Server Name Indication (SNI) extension in the TLS Client Hello.  The format and cryptographic protection of this information may change in future versions or extensions of TLS or QUIC, and therefore this functionality is inherently not version-invariant.  Such a load balancer, when it receives packets from an unknown QUIC version, might misdirect initial packets to the wrong tenant.  While this can be inefficient, the design in this document preserves the ability for tenants to deploy new versions provided they have an out-of-band means of providing a connection ID for the client to use. </ins> 8.


If the number of necessary server IDs per next hop is uniform, a simple implementation would use successively longer server IDs at each tier of load balancing, and the server configuration would match <del> the last tier.  The forward load balancers would simply treat the least significant bits of the server ID as part of the nonce. </del>  6.2.

Better not to deal with creating a new definition here. Especially one that uses the word "forward". Reword.

If the number of necessary server IDs per next hop is uniform, a simple implementation would use successively longer server IDs at each tier of load balancing, and the server configuration would match  <ins> the last tier.  Load balancers closer to the client can then treat any parts of the server ID they did not use as part of the nonce. </ins> 6.2.


imposes other operational constraints.  The other requires shared key, but has no such constraints. <del> Retry services MUST forward all non-Initial QUIC packets, as well as Initial packets from the server. </del>  5.1. Regardless of mechanism, a retry service has an active mode, where it is generating Retry packets, and an inactive mode, where it is not, based on its assessment of server load and the likelihood an attack <del> is underway.  The choice of mode MAY be made on a per-packet basis, through a stochastic process or based on client address. </del>  A retry service MUST forward all packets for a QUIC version it does not understand.  Note that if servers support versions the retry <del> service does not, this may unacceptably increase loads on the servers.  However, dropping these packets would introduce chokepoints to block deployment of new QUIC versions.  Note that future versions of QUIC might not have Retry packets, or require different information. </del>  5.2. The no-shared-state retry service requires no coordination, except <del> that the server must be configured to accept this service.  The scheme uses the first bit of the token to distinguish between tokens from Retry packets (codepoint '0') and tokens from NEW_TOKEN frames (codepoint '1'). </del>  5.2.1.  A no-shared-state retry service MUST be present on all paths from potential clients to the server.  These paths MUST fail to pass QUIC traffic should the service fail for any reason.  That is, if the Retry tokens generated by the service MUST have the format below. <del> The first bit of retry tokens generated by the service must be zero. </del>  The token has the following additional fields: ODCIL: The length of the original destination connection ID from the normal QUIC signal to the client that there is an unauthorized man in the middle. <del> 5.2.2. </del>  A server behind a non-shared-state retry service MUST NOT send Retry <del> packets. </del>  Tokens sent in NEW_TOKEN frames MUST have the first bit be set to '1'. The tokens have the following fields: ODCIL: The original destination connection ID length.  Tokens in <del> NEW_TOKEN frames SHOULD set this field to zero. </del>  Original Destination Connection ID: This is copied from the field in the client Initial packet.

Fixes URL
Some load balancers today switch based on the SNI. Obviously, this is not version-invariant. We should add some language about this. What should such an LB when it encounters an unknown version? It probably has no choice but to forward it based on CID and hope for the best.
If the differing domains trust each other, the servers could helpfully generate Retry packets to redirect the client to the right place. This assumes a bunch of shared state, and that those servers support the same version while the LB does not. Of course, if version support differs, the VN process has serious problems.
Maybe encrypt sni will be the real standard later(see URL), I just think that there is no need for quic lb considering sni

imposes other operational constraints.  The other requires shared key, but has no such constraints.  <ins> Retry services MUST forward all QUIC packets that are not of type Initial or 0-RTT.  Other packets types might involve changed IP addresses or connection IDs, so it is not practical for Retry Services to identify such packets as valid or invalid. </ins> 5.1. Regardless of mechanism, a retry service has an active mode, where it is generating Retry packets, and an inactive mode, where it is not, based on its assessment of server load and the likelihood an attack  <ins> is underway.  The choice of mode MAY be made on a per-packet or per- connection basis, through a stochastic process or based on client address. </ins> A retry service MUST forward all packets for a QUIC version it does not understand.  Note that if servers support versions the retry  <ins> service does not, this may increase load on the servers.  However, dropping these packets would introduce chokepoints to block deployment of new QUIC versions.  Note that future versions of QUIC might not have Retry packets, require different information in Retry, or use different packet type indicators. </ins> 5.2. The no-shared-state retry service requires no coordination, except  <ins> that the server must be configured to accept this service and know which QUIC versions the retry service supports.  The scheme uses the first bit of the token to distinguish between tokens from Retry packets (codepoint '0') and tokens from NEW_TOKEN frames (codepoint '1'). </ins> 5.2.1. <ins> The configuration agent distributes a list of QUIC versions to be served by the Retry Service. 5.2.2. </ins> A no-shared-state retry service MUST be present on all paths from potential clients to the server.  These paths MUST fail to pass QUIC traffic should the service fail for any reason.  That is, if the Retry tokens generated by the service MUST have the format below.  <ins> The first bit of retry tokens generated by the service MUST be zero. </ins> The token has the following additional fields: ODCIL: The length of the original destination connection ID from the normal QUIC signal to the client that there is an unauthorized man in the middle.  <ins> 5.2.3. </ins> A server behind a non-shared-state retry service MUST NOT send Retry  <ins> packets for a QUIC version the retry service understands.  It MAY send Retry for QUIC versions the Retry Service does not understand. </ins> Tokens sent in NEW_TOKEN frames MUST have the first bit be set to '1'. The tokens have the following fields: ODCIL: The original destination connection ID length.  Tokens in  <ins> NEW_TOKEN frames MUST set this field to zero. </ins> Original Destination Connection ID: This is copied from the field in the client Initial packet.


All connection IDs use the following format:  4.2. The configuration agent assigns a server ID to every server in its

In (Section 4.1](URL), these fields are not defined: They probably should be.

All connection IDs use the following format: <ins> The First Octet field serves one or two purposes, as defined in first-octet. The Server ID field encodes the information necessary for the load balancer to route a packet with that connection ID.  It is often encrypted. The server uses the Nonce field to make sure that each connection ID it generates is unique, even though they all use the same Server ID. </ins> 4.2. The configuration agent assigns a server ID to every server in its


To support a handover, a server involved in the transition could issue CIDs that map to the new server via a NEW_CONNECTION_ID frame, <del> and retire CIDs associated with the new server using the "Retire </del>  Prior To" field in that frame. <del> Alternately, if the old server is going offline, the load balancer could simply map its server ID to the new server's address. </del> 7. The server ID encodings, and requirements for their handling, are

This only works if the cutover is immediate. "going offline" suggests something progressive, which means that this strategy only works if you suddenly start holding a bunch of state. There a lots of caveats here, to the point that I'd probably just drop this whole paragraph.

To support a handover, a server involved in the transition could issue CIDs that map to the new server via a NEW_CONNECTION_ID frame,  <ins> and retire CIDs associated with the old server using the "Retire </ins> Prior To" field in that frame.  7. The server ID encodings, and requirements for their handling, are


complete.  Otherwise, there are further steps, as described in the two following subsections. <del> Encryption below uses the AES-128-ECB cipher.  Future standards could add new algorithms that use other ciphers to provide cryptographic agility in accordance with RFC7696.  QUIC-LB implementations SHOULD be extensible to support new algorithms. </del>  4.3.1.

This needs a normative reference. There is likely a NIST SP with a number you can use somewhere.

complete.  Otherwise, there are further steps, as described in the two following subsections.  <ins> Encryption below uses the AES-128-ECB cipher NIST-AES-ECB.  Future standards could add new algorithms that use other ciphers to provide cryptographic agility in accordance with RFC7696.  QUIC-LB implementations SHOULD be extensible to support new algorithms. </ins> 4.3.1.


8.2. Attackers in this privileged position are intrinsically able to map <del> two connection IDs to the same server.  The QUIC-LB algorithms do prevent the linkage of two connection IDs to the same individual connection if servers make reasonable selections when generating new IDs for that connection. </del>  8.3.

I would think that you instead want to say if the nonce part is chosen in a manner that is unpredictable to the adversary, or encrypted.

8.2. Attackers in this privileged position are intrinsically able to map  <ins> two connection IDs to the same server.  These algorithms ensure that two connection IDs for the same connection cannot be identified as such as long as the server chooses the first octet and any plaintext nonce correctly. </ins> 8.3.


1.2. All wire formats will be depicted using the notation defined in <del> Section 1.3 of RFC9000.  There is one addition: the function len() refers to the length of a field which can serve as a limit on a different field, so that the lengths of two fields can be concisely defined as limited to a sum, for example: x(A..B) y(C..B-len(x)) indicates that x can be of any length between A and B, and y can be of any length between C and B provided that (len(x) + len(y)) does not exceed B. The example below illustrates the basic framework: </del>  2.

Fixing . I love deleting this much text!
This might be achieved as follows:
I'll should also point out that there is optional and you can also do: For any value of , though presumably is at least , it could reasonably be any value up to .

1.2. All wire formats will be depicted using the notation defined in  <ins> Section 1.3 of RFC9000. </ins> 2.


QUIC.  For example, a non-compliant DCID might be converted to an integer and divided by the number of servers, with the modulus used to forward the packet.  The number of servers is usually consistent <del> on the time scale of a QUIC connection handshake. </del>  Load balancers SHOULD drop packets with non-compliant DCIDs in a short header. The use of client-generated incoming DCIDs does not persist beyond a few round trips in the connection. <del> In this interval, some exposed fields (e.g.  UDP address and port, source and client-generated destination Connection ID) remain constant for all packets in the connection.  For example, 0RTT and Initial packets use the same CIDs and ports. </del>  If these assumptions are invalid, this specification is likely to lead to loss of packets that contain non-compliant DCIDs, and in

Carefully phrased language that considers NAME 's suggestion that SCID may not be stable across versions, and tries to suggest (IP, port, DCID) as a suitable long header hash without making promises on behalf of quic-invariants.
NAME sayeth on the list: It would be useful to include something like this in Section 4 as a non-normative hint on what fields to use, since our hint currently consists of an example that just uses the DCID. Similarly, make that assumption clearer in Section 8.
For what it's worth, Google's load balancer only uses destination CIDs for routing. We always pick 64bit CIDs during the handshake so we can assume that short headers always carry 64bit CIDs.
Do all CIDs resolve to a valid server ID?
NAME do you not care about an attacker specifically crafting destination CIDs to attack a particular server?
NAME We do not have a concept of server ID, but yes every CID routes to a server. NAME We do not care about that attack. I haven't seen any indication that it can actually cause harm in real life.
NAME very well, then you have no non-compliant DCIDs. You would only need to upgrade your LBs for new versions if they allowed client-chosen DCIDs < 8 bytes.

QUIC.  For example, a non-compliant DCID might be converted to an integer and divided by the number of servers, with the modulus used to forward the packet.  The number of servers is usually consistent  <ins> on the time scale of a QUIC connection handshake.  See also version- invariance. </ins> Load balancers SHOULD drop packets with non-compliant DCIDs in a short header. The use of client-generated incoming DCIDs does not persist beyond a few round trips in the connection.  <ins> While the client is using DCIDs it generated, some exposed fields (IP address, UDP port, client-generated destination Connection ID) remain constant for all packets sent on the same connection. While this document does not update the commitments in QUIC- INVARIANTS, the additional assumptions are minimal and narrowly scoped, and provide a likely set of constants that load balancers can use with minimal risk of version- dependence. </ins> If these assumptions are invalid, this specification is likely to lead to loss of packets that contain non-compliant DCIDs, and in