diff options
Diffstat (limited to 'spec/tor-spec/create-created-cells.md')
-rw-r--r-- | spec/tor-spec/create-created-cells.md | 620 |
1 files changed, 620 insertions, 0 deletions
diff --git a/spec/tor-spec/create-created-cells.md b/spec/tor-spec/create-created-cells.md new file mode 100644 index 0000000..3208909 --- /dev/null +++ b/spec/tor-spec/create-created-cells.md @@ -0,0 +1,620 @@ +<a id="tor-spec.txt-5.1"></a> + +# CREATE and CREATED cells{#CREATE} + +Users set up circuits incrementally, one hop at a time. To create a +new circuit, OPs send a CREATE/CREATE2 cell to the first node, with +the first half of an authenticated handshake; that node responds with +a CREATED/CREATED2 cell with the second half of the handshake. To +extend a circuit past the first hop, the OP sends an EXTEND/EXTEND2 +relay message (see [EXTEND and EXTENDED messages](#EXTEND) which instructs the last +node in the circuit to send a CREATE/CREATE2 cell to extend the circuit. + +There are two kinds of CREATE and CREATED cells: The older +"CREATE/CREATED" format, and the newer "CREATE2/CREATED2" format. The +newer format is extensible by design; the older one is not. + +A CREATE2 cell contains: + +| Field | Description | Size | +| ----- | ----------- | ---- | +| `HTYPE` | Client Handshake Type | 2 bytes | +| `HLEN` | Client Handshake Data Len | 2 bytes | +| `HDATA` | Client Handshake Data | `HLEN` bytes | + +A CREATED2 cell contains: + +| Field | Description | Size | +| ----- | ----------- | ---- | +| `HLEN` | Server Handshake Data Len | 2 bytes | +| `HDATA` | Server Handshake Data | `HLEN` bytes | + + +Recognized HTYPEs (handshake types) are: + +| Value | Description | +| ----- | ----------- | +| 0x0000 | TAP -- the original Tor handshake; see [The "TAP" handshake](#TAP) | +| 0x0001 | reserved | +| 0x0002 | ntor -- the ntor+curve25519+sha256 handshake; see [The "ntor" handshake](#ntor) | +| 0x0003 | ntor-v3 -- ntor extended with extra data; see [The "ntor-v3" handshake](#ntor-v3) | + +The format of a CREATE cell is one of the following: + +| Field | Description | Size | +| ----- | ----------- | ---- | +| `HDATA` | Client Handshake Data | `TAP_C_HANDSHAKE_LEN` bytes | + +or + +| Field | Description | Size | +| ----- | ----------- | ---- | +| `HTAG` | Client Handshake Type Tag | 16 bytes | +| `HDATA` | Client Handshake Data | `TAP_C_HANDSHAKE_LEN`-16 bytes | + +The first format is equivalent to a CREATE2 cell with HTYPE of 'tap' +and length of `TAP_C_HANDSHAKE_LEN`. The second format is a way to +encapsulate new handshake types into the old CREATE cell format for +migration. See ["EXTEND and EXTENDED messages"](#EXTEND) below. Recognized HTAG values are: + +| Value | Description | +| ----- | ----------- | +| 'ntorNTORntorNTOR' | ntor | + +The format of a CREATED cell is: + +| Field | Description | Size | +| ----- | ----------- | ---- | +| `HDATA` | Server Handshake Data | `TAP_S_HANDSHAKE_LEN` bytes | + +(It's equivalent to a CREATED2 cell with length of `TAP_S_HANDSHAKE_LEN`.) + +As usual with DH, `x` and `y` MUST be generated randomly. + +In general, clients SHOULD use CREATE whenever they are using the TAP +handshake, and CREATE2 otherwise. Clients SHOULD NOT send the +second format of CREATE cells (the one with the handshake type tag) +to a server directly. + +Servers always reply to a successful CREATE with a CREATED, and to a +successful CREATE2 with a CREATED2. On failure, a server sends a +DESTROY cell to tear down the circuit. + +\[CREATE2 is handled by Tor 0.2.4.7-alpha and later.\] + +<a id="tor-spec.txt-5.1.1"></a> + +## Choosing circuit IDs in create cells {#choosing-circid} + +The CircID for a CREATE/CREATE2 cell is a nonzero integer, selected +by the node (OP or OR) that sends the CREATE/CREATED2 cell. +Depending on the link protocol version, there are certain rules for +choosing the value of CircID which MUST be obeyed, as implementations +MAY decide to refuse in case of a violation. In link protocol 3 or +lower, CircIDs are 2 bytes long; in protocol 4 or higher, CircIDs are +4 bytes long. + +In link protocol version 3 or lower, the nodes choose from only one +half of the possible values based on the ORs' public identity keys, +in order to avoid collisions. If the sending node has a lower key, +it chooses a CircID with an MSB of 0; otherwise, it chooses a CircID +with an MSB of 1. (Public keys are compared numerically by modulus.) +A client with no public key MAY choose any CircID it wishes, since +clients never need to process CREATE/CREATE2 cells. + +In link protocol version 4 or higher, whichever node initiated the +connection MUST set its MSB to 1, and whichever node didn't initiate +the connection MUST set its MSB to 0. + +The CircID value 0 is specifically reserved for cells that do not +belong to any circuit: CircID 0 MUST not be used for circuits. No +other CircID value, including 0x8000 or 0x80000000, is reserved. + +Existing Tor implementations choose their CircID values at random from +among the available unused values. To avoid distinguishability, new +implementations should do the same. Implementations MAY give up and stop +attempting to build new circuits on a channel, if a certain number of +randomly chosen CircID values are all in use (today's Tor stops after 64). + +<a id="tor-spec.txt-5.1.2"></a> + +## EXTEND and EXTENDED messagess {#EXTEND} + +To extend an existing circuit, the client sends an EXTEND or EXTEND2 +message, in a RELAY_EARLY cell, to the last node in the circuit. + +The body of an EXTEND2 message contains: + +| Field | Description | Size | +| ----- | ----------- | ---- | +| `NSPEC` | Number of link specifiers | 1 byte +| `NSPEC` times: | +| - `LSTYPE` | Link specifier type | 1 byte +| - `LSLEN` | Link specifier length | 1 byte +| - `LSPEC` | Link specifier | LSLEN bytes +| `HTYPE` | Client Handshake Type | 2 bytes +| `HLEN` | Client Handshake Data Len | 2 bytes +| `HDATA` | Client Handshake Data | HLEN bytes + +Link specifiers describe the next node in the circuit and how to +connect to it. Recognized specifiers are: + +| Value | Description +| ----- | ----------- +| \[00\] | TLS-over-TCP, IPv4 address. A four-byte IPv4 address plus two-byte ORPort. +| \[01\] | TLS-over-TCP, IPv6 address. A sixteen-byte IPv6 address plus two-byte ORPort. +| \[02\] | Legacy identity. A 20-byte SHA1 identity fingerprint. At most one may be listed. +| \[03\] | Ed25519 identity. A 32-byte Ed25519 identity fingerprint. At most one may be listed. + +Nodes MUST ignore unrecognized specifiers, and MUST accept multiple +instances of specifiers other than 'legacy identity' and +'Ed25519 identity'. (Nodes SHOULD reject link specifier lists +that include multiple instances of either one of those specifiers.) + +For purposes of indistinguishability, implementations SHOULD send +these link specifiers, if using them, in this order: \[00\], \[02\], \[03\], +\[01\]. + +The body for an EXTEND relay message consists of: + +| Field | Size +| ----- | ---- +| Address | 4 bytes +| Port | 2 bytes +| Onion skin | `TAP_C_HANDSHAKE_LEN` bytes +| Identity fingerprint | `HASH_LEN` bytes + +The "legacy identity" and "identity fingerprint" fields are the +SHA1 hash of the PKCS#1 ASN1 encoding of the next onion router's +identity (signing) key. (See ["Preliminaries » Ciphers"](./preliminaries.md#ciphers)) +The "Ed25519 identity" +field is the Ed25519 identity key of the target node. Including +this key information allows the extending OR verify that it is +indeed connected to the correct target OR, and prevents certain +man-in-the-middle attacks. + +Extending ORs MUST check _all_ provided identity keys (if they +recognize the format), and and MUST NOT extend the circuit if the +target OR did not prove its ownership of any such identity key. +If only one identity key is provided, but the extending OR knows +the other (from directory information), then the OR SHOULD also +enforce the key in the directory. + +If an extending OR has a channel with a given Ed25519 ID and RSA +identity, and receives a request for that Ed25519 ID and a +different RSA identity, it SHOULD NOT attempt to make another +connection: it should just fail and DESTROY the circuit. + +The client MAY include multiple IPv4 or IPv6 link specifiers in an +EXTEND message; current OR implementations only consider the first +of each type. + +After checking relay identities, extending ORs generate a +CREATE/CREATE2 cell from the contents of the EXTEND/EXTEND2 message. +See [Creating circuits](./creating-circuits.md#creating-circuits) +for details. + +The body of an EXTENDED message is the same as the body of a +CREATED cell. + +The body of an EXTENDED2 message is the same as the body of a +CREATED2 cell. + +\[Support for EXTEND2/EXTENDED2 was added in Tor 0.2.4.8-alpha.\] + +Clients SHOULD use the EXTEND format whenever sending a TAP +handshake, and MUST use it whenever the EXTEND message will be handled +by a node running a version of Tor too old to support EXTEND2. In +other cases, clients SHOULD use EXTEND2. + +When generating an EXTEND2 message, clients SHOULD include the target's +Ed25519 identity whenever the target has one, and whenever the +target supports LinkAuth subprotocol version "3". (See [LinkAuth](./subprotocol-versioning.md#link-auth)). + +When encoding a non-TAP handshake in an EXTEND message, clients SHOULD +use the format with 'client handshake type tag'. + +<a id="tor-spec.txt-5.1.3"></a> + +## The "TAP" handshake {#TAP} + +This handshake uses Diffie-Hellman in Z<sub>p</sub> and RSA to compute a set of +shared keys which the client knows are shared only with a particular +server, and the server knows are shared with whomever sent the +original handshake (or with nobody at all). It's not very fast and +not very good. (See Goldberg's "On the Security of the Tor +Authentication Protocol".) + +Define `TAP_C_HANDSHAKE_LEN` as `DH_LEN+KEY_LEN+KP_PAD_LEN`. +Define `TAP_S_HANDSHAKE_LEN` as `DH_LEN+HASH_LEN`. + +The body for a CREATE cell is an 'onion skin', which consists of +the first step of the DH handshake data (also known as `g^x`). This +value is encrypted using the "legacy hybrid encryption" algorithm +(see ["Preliminaries » A bad hybrid encryption algorithm..."](./preliminaries.md#legacy-hybrid-encryption)) +to the server's onion key, giving a client handshake: + +| Field | Size +| ----- | ---- +| KP-encrypted: +| - Padding | `KP_PAD_LEN` bytes +| - Symmetric key | `KEY_LEN` bytes +| - First part of `g^x` | `KP_ENC_LEN-KP_PAD_LEN-KEY_LEN` bytes +| Symmetrically encrypted +| - Second part of `g^x` | `DH_LEN-(KP_ENC_LEN-KP_PAD_LEN-KEY_LEN)` bytes + +The body for a CREATED cell, or the body for an +EXTENDED relay message, contains: + +| Field | Size +| ----- | ---- +| DH data (`g^y`) | `DH_LEN` bytes +| Derivative key data (`KH`) | `HASH_LEN` bytes (see ["Setting Circuit Keys"](./setting-circuit-keys.md)) + + +Once the handshake between the OP and an OR is completed, both can +now calculate `g^xy` with ordinary DH. Before computing `g^xy`, both parties +MUST verify that the received `g^x` or `g^y` value is not degenerate; +that is, it must be strictly greater than `1` and strictly less than `p-1` +where `p` is the DH modulus. Implementations MUST NOT complete a handshake +with degenerate keys. Implementations MUST NOT discard other "weak" +`g^x` values. + +(Discarding degenerate keys is critical for security; if bad keys +are not discarded, an attacker can substitute the OR's CREATED +cell's `g^y` with `0` or `1`, thus creating a known `g^xy` and impersonating +the OR. Discarding other keys may allow attacks to learn bits of +the private key.) + +Once both parties have `g^xy`, they derive their shared circuit keys +and 'derivative key data' value via the +[KDF-TOR function](./setting-circuit-keys.md#kdf-tor). + +<a id="tor-spec.txt-5.1.4"></a> + +## The "ntor" handshake {#ntor} + +This handshake uses a set of DH handshakes to compute a set of +shared keys which the client knows are shared only with a particular +server, and the server knows are shared with whomever sent the +original handshake (or with nobody at all). Here we use the +"curve25519" group and representation as specified in "Curve25519: +new Diffie-Hellman speed records" by D. J. Bernstein. + +\[The ntor handshake was added in Tor 0.2.4.8-alpha.\] + +In this section, define: + +```text +H(x,t) as HMAC_SHA256 with message x and key t. +H_LENGTH = 32. +ID_LENGTH = 20. +G_LENGTH = 32 +PROTOID = "ntor-curve25519-sha256-1" +t_mac = PROTOID | ":mac" +t_key = PROTOID | ":key_extract" +t_verify = PROTOID | ":verify" +G = The preferred base point for curve25519 ([9]) +KEYGEN() = The curve25519 key generation algorithm, returning + a private/public keypair. +m_expand = PROTOID | ":key_expand" +KEYID(A) = A +EXP(a, b) = The ECDH algorithm for establishing a shared secret. +``` + +To perform the handshake, the client needs to know an identity key +digest for the server, and an ntor onion key (a curve25519 public +key) for that server. Call the ntor onion key `B`. The client +generates a temporary keypair: + +```text +x,X = KEYGEN() +``` + +and generates a client-side handshake with contents: + +| Field | Value | Size +| ----- | ----- | ---- +| `NODEID` | Server identity digest | `ID_LENGTH` bytes +| `KEYID` | KEYID(B) | `H_LENGTH` bytes +| `CLIENT_KP` | X | `G_LENGTH` bytes + +The server generates a keypair of `y,Y = KEYGEN()`, and uses its ntor +private key `b` to compute: + +```text +secret_input = EXP(X,y) | EXP(X,b) | ID | B | X | Y | PROTOID +KEY_SEED = H(secret_input, t_key) +verify = H(secret_input, t_verify) +auth_input = verify | ID | B | Y | X | PROTOID | "Server" +``` + +The server's handshake reply is: + +| Field | Value | Size +| ----- | ----- | ---- +| `SERVER_KP` | `Y` | `G_LENGTH` bytes +| `AUTH` | `H(auth_input, t_mac)` | `H_LENGTH` bytes + +The client then checks `Y` is in G<sup>*</sup> \[see NOTE below\], and computes + +```text +secret_input = EXP(Y,x) | EXP(B,x) | ID | B | X | Y | PROTOID +KEY_SEED = H(secret_input, t_key) +verify = H(secret_input, t_verify) +auth_input = verify | ID | B | Y | X | PROTOID | "Server" +``` + +The client verifies that `AUTH == H(auth_input, t_mac)`. + +Both parties check that none of the `EXP()` operations produced the +point at infinity. \[NOTE: This is an adequate replacement for +checking `Y` for group membership, if the group is curve25519.\] + +Both parties now have a shared value for `KEY_SEED`. They expand this +into the keys needed for the Tor relay protocol, using the KDF +described in ["KDF-RFC5869"](./setting-circuit-keys.md#kdf-rfc5869) +and the tag `m_expand`. + +<a id="tor-spec.txt-5.1.4.1"></a> + +### The "ntor-v3" handshake {#ntor-v3} + +This handshake extends the ntor handshake to include support +for extra data transmitted as part of the handshake. Both +the client and the server can transmit extra data; in both cases, +the extra data is encrypted, but only server data receives +forward secrecy. + +To advertise support for this handshake, servers advertise the +"Relay=4" subprotocol version. To select it, clients use the +'ntor-v3' HTYPE value in their CREATE2 cells. + +In this handshake, we define: + +```text +PROTOID = "ntor3-curve25519-sha3_256-1" +t_msgkdf = PROTOID | ":kdf_phase1" +t_msgmac = PROTOID | ":msg_mac" +t_key_seed = PROTOID | ":key_seed" +t_verify = PROTOID | ":verify" +t_final = PROTOID | ":kdf_final" +t_auth = PROTOID | ":auth_final" + +`ENCAP(s)` -- an encapsulation function. We define this +as `htonll(len(s)) | s`. (Note that `len(ENCAP(s)) = len(s) + 8`). + +`PARTITION(s, n1, n2, n3, ...)` -- a function that partitions a +bytestring `s` into chunks of length `n1`, `n2`, `n3`, and so +on. Extra data is put into a final chunk. If `s` is not long +enough, the function fails. + +H(s, t) = SHA3_256(ENCAP(t) | s) +MAC(k, msg, t) = SHA3_256(ENCAP(t) | ENCAP(k) | s) +KDF(s, t) = SHAKE_256(ENCAP(t) | s) +ENC(k, m) = AES_256_CTR(k, m) + +EXP(pk,sk), KEYGEN: defined as in curve25519 + +DIGEST_LEN = MAC_LEN = MAC_KEY_LEN = ENC_KEY_LEN = PUB_KEY_LEN = 32 + +ID_LEN = 32 (representing an ed25519 identity key) + +For any tag "t_foo": + H_foo(s) = H(s, t_foo) + MAC_foo(k, msg) = MAC(k, msg, t_foo) + KDF_foo(s) = KDF(s, t_foo) +``` + +Other notation is as in the [ntor description above](#ntor). + +The client begins by knowing: + +```text +B, ID -- The curve25519 onion key and Ed25519 ID of the server that it + wants to use. +CM -- A message it wants to send as part of its handshake. +VER -- An optional shared verification string: +``` + +The client computes: + +```text +x,X = KEYGEN() +Bx = EXP(B,x) +secret_input_phase1 = Bx | ID | X | B | PROTOID | ENCAP(VER) +phase1_keys = KDF_msgkdf(secret_input_phase1) +(ENC_K1, MAC_K1) = PARTITION(phase1_keys, ENC_KEY_LEN, MAC_KEY_LEN) +encrypted_msg = ENC(ENC_K1, CM) +msg_mac = MAC_msgmac(MAC_K1, ID | B | X | encrypted_msg) +``` + + The client then sends, as its CREATE handshake: + +| Field | Value | Size +| ----- | ----- | ---- +| `NODEID` | `ID` | `ID_LEN` bytes +| `KEYID` | `B` | `PUB_KEY_LEN` bytes +| `CLIENT_PK` | `X` | `PUB_KEY_LEN` bytes +| `MSG` | `encrypted_msg` | `len(CM)` bytes +| `MAC` | `msg_mac` | `MAC_LEN` bytes + +The client remembers `x`, `X`, `B`, `ID`, `Bx`, and `msg_mac`. + +When the server receives this handshake, it checks whether `NODEID` is as +expected, and looks up the `(b,B)` keypair corresponding to `KEYID`. If the +keypair is missing or the `NODEID` is wrong, the handshake fails. + +Now the relay uses `X=CLIENT_PK` to compute: + +```text +Xb = EXP(X,b) +secret_input_phase1 = Xb | ID | X | B | PROTOID | ENCAP(VER) +phase1_keys = KDF_msgkdf(secret_input_phase1) +(ENC_K1, MAC_K1) = PARTITION(phase1_keys, ENC_KEY_LEN, MAC_KEY_LEN) + +expected_mac = MAC_msgmac(MAC_K1, ID | B | X | MSG) +``` + +If `expected_mac` is not `MAC`, the handshake fails. Otherwise +the relay computes `CM` as: + +```text +CM = DEC(MSG, ENC_K1) +``` + +The relay then checks whether `CM` is well-formed, and in response +composes `SM`, the reply that it wants to send as part of the +handshake. It then generates a new ephemeral keypair: + +```text +y,Y = KEYGEN() +``` + +and computes the rest of the handshake: + +```text +Xy = EXP(X,y) +secret_input = Xy | Xb | ID | B | X | Y | PROTOID | ENCAP(VER) +ntor_key_seed = H_key_seed(secret_input) +verify = H_verify(secret_input) + +RAW_KEYSTREAM = KDF_final(ntor_key_seed) +(ENC_KEY, KEYSTREAM) = PARTITION(RAW_KEYSTREAM, ENC_KEY_LKEN, ...) + +encrypted_msg = ENC(ENC_KEY, SM) + +auth_input = verify | ID | B | Y | X | MAC | ENCAP(encrypted_msg) | + PROTOID | "Server" +AUTH = H_auth(auth_input) +``` + +The relay then sends as its CREATED handshake: + +| Field | Value | Size +| ----- | ----- | ---- +| `Y` | `Y` | `PUB_KEY_LEN` bytes +| `AUTH` | `AUTH` | `DIGEST_LEN` bytes +| `MSG` | `encrypted_msg` | `len(SM)` bytes, up to end of the message + +Upon receiving this handshake, the client computes: + +```text +Yx = EXP(Y, x) +secret_input = Yx | Bx | ID | B | X | Y | PROTOID | ENCAP(VER) +ntor_key_seed = H_key_seed(secret_input) +verify = H_verify(secret_input) + +auth_input = verify | ID | B | Y | X | MAC | ENCAP(MSG) | + PROTOID | "Server" +AUTH_expected = H_auth(auth_input) +``` + +If `AUTH_expected` is equal to `AUTH`, then the handshake has +succeeded. The client can then calculate: + +```text +RAW_KEYSTREAM = KDF_final(ntor_key_seed) +(ENC_KEY, KEYSTREAM) = PARTITION(RAW_KEYSTREAM, ENC_KEY_LKEN, ...) + +SM = DEC(ENC_KEY, MSG) +``` + +SM is the message from the relay, and the client uses KEYSTREAM to +generate the shared secrets for the newly created circuit. + +Now both parties share the same KEYSTREAM, and can use it to generate +their circuit keys. + +<a id="tor-spec.txt-5.1.5"></a> + +## CREATE_FAST/CREATED_FAST cells {#create_fast} + +When creating a one-hop circuit, the OP has already +established the OR's identity and negotiated a secret key using TLS. +Because of this, it is not necessary for the OP to perform the +public key operations to create a circuit. +In this case, the OP MAY send a CREATE_FAST cell instead of a CREATE cell. +The OR responds with a CREATED_FAST cell, and the circuit is +created. + +A CREATE_FAST cell contains: + +| Field | Size +| ----- | ---- +| Key material (`X`) | `HASH_LEN` bytes + +A CREATED_FAST cell contains: + +| Field | Size +| ----- | ---- +| Key material (`Y`) | `HASH_LEN` bytes +| Derivative key data | `HASH_LEN` bytes (See [KDF-TOR](./setting-circuit-keys.md#kdf-tor)) + +The values of `X` and `Y` must be generated randomly. + +Once both parties have `X` and `Y`, they derive their shared circuit keys +and 'derivative key data' value via the [KDF-TOR function](./setting-circuit-keys.md#kdf-tor). + +Parties SHOULD NOT use CREATE_FAST except for creating one-hop circuits. + +<a id="tor-spec.txt-5.1.6"></a> + +## Additional data in CREATE/CREATED cells {#additional-data} + +Some handshakes (currently ntor-v3 defined above) allow the client or the +relay to send additional data as part of the handshake. When used in a +CREATE/CREATED handshake, this additional data must have the following +format: + +| Field | Size +| ----- | ---- +| `N_EXTENSIONS` | one byte +| `N_EXTENSIONS` times: | +| - `EXT_FIELD_TYPE` | one byte +| - `EXT_FIELD_LEN` | one byte +| - `EXT_FIELD` | `EXT_FIELD_LEN` bytes + +(`EXT_FIELD_LEN` may be zero, in which case `EXT_FIELD` is absent.) + +All parties MUST reject messages that are not well-formed per the +rules above. + +We do not specify specific TYPE semantics here; we leave those for +other proposals and specifications. + +Parties MUST ignore extensions with `EXT_FIELD_TYPE` bodies they do not +recognize. + +Unless otherwise specified in the documentation for an extension type: + * Each extension type SHOULD be sent only once in a message. + * Parties MUST ignore any occurrences all occurrences of an extension + with a given type after the first such occurrence. + * Extensions SHOULD be sent in numerically ascending order by type. + +(The above extension sorting and multiplicity rules are only defaults; +they may be overridden in the description of individual extensions.) + +Currently supported extensions are: + + * 1 -- `CC_FIELD_REQUEST` \[Client to server\] + + Contains an empty body. Signifies that the client + wants to use the extended congestion control described + in [proposal 324]. + + * 2 -- `CC_FIELD_RESPONSE` \[Server to client\] + + Indicates that the relay will use the congestion control + of [proposal 324], as requested by the client. One byte + in length: + + `sendme_inc [1 byte]` + + * 3 -- Subprotocol Request \[Client to Server\] + + (RESERVED) Tells the endpoint what protocol version to use on the + circuit ([proposal 346]). + +[proposal 324]: ../proposals/324-rtt-congestion-control.txt +[proposal 346]: ../proposals/346-protovers-again.md |