diff --git a/.lint.py b/.lint.py index 707f8f45b7..0f25fc3e25 100755 --- a/.lint.py +++ b/.lint.py @@ -58,7 +58,11 @@ def err(msg): # Check length length = len(line) - limit = args.maxFigureLineLength if insideFigure else args.maxLineLength + limit = ( + int(args.maxFigureLineLength) + if insideFigure + else int(args.maxLineLength) + ) if length > limit: err("Line is {0} characters; limit is {1}".format(length, limit)) diff --git a/draft-ietf-quic-http.md b/draft-ietf-quic-http.md index ae3e9074ca..d221954c00 100644 --- a/draft-ietf-quic-http.md +++ b/draft-ietf-quic-http.md @@ -386,7 +386,7 @@ While connection-level options pertaining to the core QUIC protocol are set in the initial crypto handshake, HTTP/3-specific settings are conveyed in the SETTINGS frame. After the QUIC connection is established, a SETTINGS frame ({{frame-settings}}) MUST be sent by each endpoint as the initial frame of their -respective HTTP control stream (see {{control-streams}}). +respective HTTP control stream; see {{control-streams}}. ## Connection Reuse @@ -401,9 +401,12 @@ requests with multiple different URI authority components. In general, a server is considered authoritative for all URIs with the "https" scheme for which the hostname in the URI is present in the authenticated certificate provided by the server, either as the CN field of the certificate subject or as a dNSName in the -subjectAltName field of the certificate (see {{!RFC6125}}). For a host that is +subjectAltName field of the certificate; see {{!RFC6125}}. For a host that is an IP address, the client MUST verify that the address appears as an iPAddress -in the subjectAltName field of the certificate. +in the subjectAltName field of the certificate. If the hostname or address is +not present in the certificate, the client MUST NOT consider the server +authoritative for origins containing that hostname or address. See Section 5.4 +of {{!SEMANTICS}} for more detail on authoritative access. Clients SHOULD NOT open more than one HTTP/3 connection to a given host and port pair, where the host is derived from a URI, a selected alternative service @@ -420,8 +423,8 @@ complete or terminate any necessary remaining tasks. A server that does not wish clients to reuse connections for a particular origin can indicate that it is not authoritative for a request by sending a 421 -(Misdirected Request) status code in response to the request (see Section 9.1.2 -of {{?HTTP2}}). +(Misdirected Request) status code in response to the request; see Section 9.1.2 +of {{?HTTP2}}. # HTTP Request Lifecycle @@ -433,8 +436,8 @@ A client MUST send only a single request on a given stream. A server sends zero or more interim HTTP responses on the same stream as the request, followed by a single final HTTP response, as detailed below. -Pushed responses are sent on a server-initiated unidirectional QUIC stream (see -{{push-streams}}). A server sends zero or more interim HTTP responses, followed +Pushed responses are sent on a server-initiated unidirectional QUIC stream; see +{{push-streams}}. A server sends zero or more interim HTTP responses, followed by a single final HTTP response, in the same manner as a standard response. Push is described in more detail in {{server-push}}. @@ -453,8 +456,10 @@ An HTTP message (request or response) consists of: 3. optionally, the trailer field section, if present (see Section 4.6 of {{!SEMANTICS}}), sent as a single HEADERS frame. -Receipt of DATA and HEADERS frames in any other sequence MUST be treated as a -connection error of type H3_FRAME_UNEXPECTED ({{errors}}). +Receipt of an invalid sequence of frames MUST be treated as a connection error +of type H3_FRAME_UNEXPECTED ({{errors}}). In particular, a DATA frame before +any HEADERS frame, or a HEADERS or DATA frame after the trailing HEADERS frame +is considered invalid. A server MAY send one or more PUSH_PROMISE frames (see {{frame-push-promise}}) before, after, or interleaved with the frames of a response message. These @@ -480,12 +485,6 @@ informational responses (1xx; see Section 9.2 of {{!SEMANTICS}}) precede a final response to the same request. Interim responses do not contain a payload body or trailers. -If an endpoint receives an invalid sequence of frames on either a request or -a push stream, it MUST respond with a connection error of type -H3_FRAME_UNEXPECTED ({{errors}}). In particular, a DATA frame before any -HEADERS frame, or a HEADERS or DATA frame after the trailing HEADERS frame is -considered invalid. - An HTTP request/response exchange fully consumes a client-initiated bidirectional QUIC stream. After sending a request, a client MUST close the stream for sending. Unless using the CONNECT method (see {{connect}}), clients @@ -589,8 +588,8 @@ The following pseudo-header fields are defined for requests: : To ensure that the HTTP/1.1 request line can be reproduced accurately, this pseudo-header field MUST be omitted when translating from an HTTP/1.1 - request that has a request target in origin or asterisk form (see Section - 3.2 of {{?HTTP11}}). Clients that generate HTTP/3 requests directly SHOULD + request that has a request target in origin or asterisk form; see Section + 3.2 of {{?HTTP11}}. Clients that generate HTTP/3 requests directly SHOULD use the ":authority" pseudo-header field instead of the Host field. An intermediary that converts an HTTP/3 request to HTTP/1.1 MUST create a Host field if one is not present in a request by copying the value of the @@ -599,19 +598,20 @@ The following pseudo-header fields are defined for requests: ":path": : Contains the path and query parts of the target URI (the "path-absolute" - production and optionally a '?' character followed by the "query" production - (see Sections 3.3 and 3.4 of [RFC3986]). A request in asterisk form - includes the value '*' for the ":path" pseudo-header field. + production and optionally a '?' character followed by the "query" + production; see Sections 3.3 and 3.4 of {{?URI=RFC3986}}. A request in + asterisk form includes the value '*' for the ":path" pseudo-header field. : This pseudo-header field MUST NOT be empty for "http" or "https" URIs; "http" or "https" URIs that do not contain a path component MUST include a value of '/'. The exception to this rule is an OPTIONS request for an "http" or "https" URI that does not include a path component; these MUST - include a ":path" pseudo-header field with a value of '*' (see Section 3.2.4 - of {{?HTTP11}}). + include a ":path" pseudo-header field with a value of '*'; see Section 3.2.4 + of {{?HTTP11}}. All HTTP/3 requests MUST include exactly one value for the ":method", ":scheme", -and ":path" pseudo-header fields, unless it is a CONNECT request ({{connect}}). +and ":path" pseudo-header fields, unless it is a CONNECT request; see +{{connect}}. If the ":scheme" pseudo-header field identifies a scheme which has a mandatory authority component (including "http" and "https"), the request MUST contain @@ -628,7 +628,7 @@ HTTP/3 does not define a way to carry the version identifier that is included in the HTTP/1.1 request line. For responses, a single ":status" pseudo-header field is defined that carries -the HTTP status code (see Section 9 of {{!SEMANTICS}}). This pseudo-header +the HTTP status code; see Section 9 of {{!SEMANTICS}}. This pseudo-header field MUST be included in all responses; otherwise, the response is malformed ({{malformed}}). @@ -660,7 +660,7 @@ on the uncompressed size of fields, including the length of the name and value in bytes plus an overhead of 32 bytes for each field. If an implementation wishes to advise its peer of this limit, it can be conveyed -as a number of bytes in the `SETTINGS_MAX_FIELD_SECTION_SIZE` parameter. An +as a number of bytes in the SETTINGS_MAX_FIELD_SECTION_SIZE parameter. An implementation which has received this parameter SHOULD NOT send an HTTP message header which exceeds the indicated size, as the peer will likely refuse to process it. However, because this limit is applied at each hop, messages below @@ -711,7 +711,7 @@ frames but is invalid due to: - the inclusion of invalid characters in field names or values A request or response that includes a payload body can include a -`content-length` header field. A request or response is also malformed if the +Content-Length header field. A request or response is also malformed if the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the body. A response that is defined to have no payload, as described in Section 6.3.3 of {{!SEMANTICS}} can have a non-zero @@ -746,12 +746,12 @@ A CONNECT request MUST be constructed as follows: - The ":method" pseudo-header field is set to "CONNECT" - The ":scheme" and ":path" pseudo-header fields are omitted - The ":authority" pseudo-header field contains the host and port to connect to - (equivalent to the authority-form of the request-target of CONNECT requests - (see Section 5.3 of {{?HTTP11}})) + (equivalent to the authority-form of the request-target of CONNECT requests; + see Section 5.3 of {{?HTTP11}}) The request stream remains open at the end of the request to carry the data to be transferred. A CONNECT request that does not conform to these restrictions -is malformed (see {{malformed}}). +is malformed; see {{malformed}}. A proxy that supports CONNECT establishes a TCP connection ({{!RFC0793}}) to the server identified in the ":authority" pseudo-header field. Once this connection @@ -809,7 +809,7 @@ same order, and both the name and the value in each field MUST be exact matches. Server push is only enabled on a connection when a client sends a MAX_PUSH_ID -frame (see {{frame-max-push-id}}). A server cannot use server push until it +frame; see {{frame-max-push-id}}. A server cannot use server push until it receives a MAX_PUSH_ID frame. A client sends additional MAX_PUSH_ID frames to control the number of pushes that a server can promise. A server SHOULD use Push IDs sequentially, starting at 0. A client MUST treat receipt of a push stream @@ -823,8 +823,8 @@ This allows the server push to be associated with a client request. Not all requests can be pushed. A server MAY push requests which have the following properties: -- cacheable (see Section 7.2.3 of {{!SEMANTICS}}) -- safe (see Section 7.2.1 of {{!SEMANTICS}}) +- cacheable; see Section 7.2.3 of {{!SEMANTICS}} +- safe; see Section 7.2.1 of {{!SEMANTICS}} - does not include a request body or trailer section Clients SHOULD send a CANCEL_PUSH frame upon receipt of a PUSH_PROMISE frame @@ -834,7 +834,7 @@ push stream, this MAY be treated as a stream error of type H3_STREAM_CREATION_ERROR. The server MUST include a value in the ":authority" pseudo-header field for -which the server is authoritative (see {{connection-reuse}}). A client SHOULD +which the server is authoritative; see {{connection-reuse}}. A client SHOULD send a CANCEL_PUSH frame upon receipt of a PUSH_PROMISE frame carrying a request for which it does not consider the server authoritative. If the pushed response arrives on a push stream, this MAY be treated as a stream error of type @@ -853,7 +853,7 @@ or DATA frames that reference the promised responses. This reduces the chance that a client requests a resource that will be pushed by the server. When a server later fulfills a promise, the server push response is conveyed on -a push stream (see {{push-streams}}). The push stream identifies the Push ID of +a push stream; see {{push-streams}}. The push stream identifies the Push ID of the promise that it fulfills, then contains a response to the promised request using the same format described for responses in {{request-response}}. @@ -973,7 +973,7 @@ can be cleanly shut down without losing requests. A client has more flexibility in the value it chooses for the Push ID in a GOAWAY that it sends. A value of 2^62 - 1 indicates that the server can continue fulfilling pushes which have already been promised, and the client can -continue granting push credit as needed (see {{frame-max-push-id}}). A smaller +continue granting push credit as needed; see {{frame-max-push-id}}. A smaller value indicates the client will reject pushes with Push IDs greater than or equal to this value. Like the server, the client MAY send subsequent GOAWAY frames so long as the specified Push ID is strictly smaller than all previously @@ -1112,7 +1112,7 @@ the reception of the unidirectional stream header. ### Control Streams -A control stream is indicated by a stream type of `0x00`. Data on this stream +A control stream is indicated by a stream type of 0x00. Data on this stream consists of HTTP/3 frames, as defined in {{frames}}. Each side MUST initiate a single control stream at the beginning of the @@ -1137,7 +1137,7 @@ Server push is an optional feature introduced in HTTP/2 that allows a server to initiate a response before a request has been made. See {{server-push}} for more details. -A push stream is indicated by a stream type of `0x01`, followed by the Push ID +A push stream is indicated by a stream type of 0x01, followed by the Push ID of the promise that it fulfills, encoded as a variable-length integer. The remaining data on this stream consists of HTTP/3 frames, as defined in {{frames}}, and fulfills a promised server push by zero or more interim HTTP @@ -1162,8 +1162,8 @@ client MUST treat this as a connection error of type H3_ID_ERROR. ### Reserved Stream Types {#stream-grease} -Stream types of the format `0x1f * N + 0x21` for integer values of N are -reserved to exercise the requirement that unknown types be ignored. These +Stream types of the format `0x1f * N + 0x21` for non-negative integer values of +N are reserved to exercise the requirement that unknown types be ignored. These streams have no semantics, and can be sent when application-layer padding is desired. They MAY also be sent on connections where no data is currently being transferred. Endpoints MUST NOT consider these streams to have any meaning upon @@ -1313,8 +1313,8 @@ CANCEL_PUSH Frame { {: #fig-cancel-push title="CANCEL_PUSH Frame"} The CANCEL_PUSH frame carries a Push ID encoded as a variable-length integer. -The Push ID identifies the server push that is being cancelled (see -{{frame-push-promise}}). If a CANCEL_PUSH frame is received which references a +The Push ID identifies the server push that is being cancelled; see +{{frame-push-promise}}. If a CANCEL_PUSH frame is received which references a Push ID greater than currently allowed on the connection, this MUST be treated as a connection error of type H3_ID_ERROR. @@ -1387,11 +1387,11 @@ The following settings are defined in HTTP/3: SETTINGS_MAX_FIELD_SECTION_SIZE (0x6): : The default value is unlimited. See {{header-formatting}} for usage. -Setting identifiers of the format `0x1f * N + 0x21` for integer values of N are -reserved to exercise the requirement that unknown identifiers be ignored. Such -settings have no defined meaning. Endpoints SHOULD include at least one such -setting in their SETTINGS frame. Endpoints MUST NOT consider such settings to -have any meaning upon receipt. +Setting identifiers of the format `0x1f * N + 0x21` for non-negative integer +values of N are reserved to exercise the requirement that unknown identifiers be +ignored. Such settings have no defined meaning. Endpoints SHOULD include at +least one such setting in their SETTINGS frame. Endpoints MUST NOT consider such +settings to have any meaning upon receipt. Because the setting has no defined meaning, the value of the setting can be any value the implementation selects. @@ -1571,19 +1571,19 @@ MAX_PUSH_ID Frame { {: #fig-max-push title="MAX_PUSH_ID Frame Payload"} The MAX_PUSH_ID frame carries a single variable-length integer that identifies -the maximum value for a Push ID that the server can use (see -{{frame-push-promise}}). A MAX_PUSH_ID frame cannot reduce the maximum Push ID; +the maximum value for a Push ID that the server can use; see +{{frame-push-promise}}. A MAX_PUSH_ID frame cannot reduce the maximum Push ID; receipt of a MAX_PUSH_ID that contains a smaller value than previously received MUST be treated as a connection error of type H3_ID_ERROR. ### Reserved Frame Types {#frame-reserved} -Frame types of the format `0x1f * N + 0x21` for integer values of N are reserved -to exercise the requirement that unknown types be ignored ({{extensions}}). -These frames have no semantics, and can be sent on any open stream when -application-layer padding is desired. They MAY also be sent on connections where -no data is currently being transferred. Endpoints MUST NOT consider these frames -to have any meaning upon receipt. +Frame types of the format `0x1f * N + 0x21` for non-negative integer values of N +are reserved to exercise the requirement that unknown types be ignored +({{extensions}}). These frames have no semantics, and can be sent on any open +stream when application-layer padding is desired. They MAY also be sent on +connections where no data is currently being transferred. Endpoints MUST NOT +consider these frames to have any meaning upon receipt. The payload and length of the frames are selected in any manner the implementation chooses. @@ -1607,7 +1607,7 @@ outstanding requests before making this choice. Because new error codes can be defined without negotiation (see {{extensions}}), use of an error code in an unexpected context or receipt of an unknown error code MUST be treated as equivalent to H3_NO_ERROR. However, closing a stream -can have other effects regardless of the error code (see {{request-response}}). +can have other effects regardless of the error code; see {{request-response}}. This section describes HTTP/3-specific error codes which can be used to express the cause of a connection or stream error. @@ -1673,10 +1673,11 @@ H3_VERSION_FALLBACK (0x110): : The requested operation cannot be served over HTTP/3. The peer should retry over HTTP/1.1. -Error codes of the format `0x1f * N + 0x21` for integer values of N are reserved -to exercise the requirement that unknown error codes be treated as equivalent to -H3_NO_ERROR ({{extensions}}). Implementations SHOULD select an error code from -this space with some probability when they would have sent H3_NO_ERROR. +Error codes of the format `0x1f * N + 0x21` for non-negative integer values of N +are reserved to exercise the requirement that unknown error codes be treated as +equivalent to H3_NO_ERROR ({{extensions}}). Implementations SHOULD select an +error code from this space with some probability when they would have sent +H3_NO_ERROR. # Extensions to HTTP/3 {#extensions} @@ -2008,9 +2009,9 @@ The entries in {{iana-frame-table}} are registered by this document. | ---------------- | ------ | -------------------------- | {: #iana-frame-table title="Initial HTTP/3 Frame Types"} -Additionally, each code of the format `0x1f * N + 0x21` for integer values of N -(that is, `0x21`, `0x40`, ..., through `0x3FFFFFFFFFFFFFFE`) MUST NOT be -assigned by IANA. +Additionally, each code of the format `0x1f * N + 0x21` for non-negative integer +values of N (that is, 0x21, 0x40, ..., through 0x3FFFFFFFFFFFFFFE) MUST +NOT be assigned by IANA. ### Settings Parameters {#iana-settings} @@ -2050,9 +2051,9 @@ The entries in {{iana-setting-table}} are registered by this document. | ---------------------------- | ------ | ------------------------- | --------- | {: #iana-setting-table title="Initial HTTP/3 Settings"} -Additionally, each code of the format `0x1f * N + 0x21` for integer values of N -(that is, `0x21`, `0x40`, ..., through `0x3FFFFFFFFFFFFFFE`) MUST NOT be -assigned by IANA. +Additionally, each code of the format `0x1f * N + 0x21` for non-negative integer +values of N (that is, 0x21, 0x40, ..., through 0x3FFFFFFFFFFFFFFE) MUST +NOT be assigned by IANA. ### Error Codes {#iana-error-codes} @@ -2102,9 +2103,9 @@ The entries in the {{iana-error-table}} are registered by this document. | --------------------------------- | ---------- | ---------------------------------------- | ---------------------- | {: #iana-error-table title="Initial HTTP/3 Error Codes"} -Additionally, each code of the format `0x1f * N + 0x21` for integer values of N -(that is, `0x21`, `0x40`, ..., through `0x3FFFFFFFFFFFFFFE`) MUST NOT be -assigned by IANA. +Additionally, each code of the format `0x1f * N + 0x21` for non-negative integer +values of N (that is, 0x21, 0x40, ..., through 0x3FFFFFFFFFFFFFFE) MUST +NOT be assigned by IANA. ### Stream Types {#iana-stream-types} @@ -2138,9 +2139,9 @@ The entries in the following table are registered by this document. | Push Stream | 0x01 | {{server-push}} | Server | | ---------------- | ------ | -------------------------- | ------ | -Additionally, each code of the format `0x1f * N + 0x21` for integer values of N -(that is, `0x21`, `0x40`, ..., through `0x3FFFFFFFFFFFFFFE`) MUST NOT be -assigned by IANA. +Additionally, each code of the format `0x1f * N + 0x21` for non-negative integer +values of N (that is, 0x21, 0x40, ..., through 0x3FFFFFFFFFFFFFFE) MUST +NOT be assigned by IANA. --- back @@ -2355,9 +2356,8 @@ the peers' settings to arrive before responding to other streams. See ## HTTP/2 Error Codes QUIC has the same concepts of "stream" and "connection" errors that HTTP/2 -provides. However, there is no direct portability of HTTP/2 error codes to -HTTP/3 error codes; the values are shifted in order to prevent accidental -or implicit conversion. +provides. However, the differences between HTTP/2 and HTTP/3 mean that error +codes are not directly portable between versions. The HTTP/2 error codes defined in Section 7 of {{?HTTP2}} logically map to the HTTP/3 error codes as follows: @@ -2412,11 +2412,48 @@ H3_1_1_REQUIRED (0xd): Error codes need to be defined for HTTP/2 and HTTP/3 separately. See {{iana-error-codes}}. +### Mapping Between HTTP/2 and HTTP/3 Errors + +An intermediary that converts between HTTP/2 and HTTP/3 may encounter error +conditions from either upstream. It is useful to communicate the occurrence of +error to the downstream but error codes largely reflect connection-local +problems that generally do not make sense to propagate. + +An intermediary that encounters an error from an upstream origin can indicate +this by sending an HTTP status code such as 502, which is suitable for a broad +class of errors. + +There are some rare cases where it is beneficial to propagate the error by +mapping it to the closest matching error type to the receiver. For example, an +intermediary that receives an HTTP/2 stream error of type REFUSED_STREAM from +the origin has a clear signal that the request was not processed and that the +request is safe to retry. Propagating this error condition to the client as an +HTTP/3 stream error of type H3_REQUEST_REJECTED allows the client to take the +action it deems most appropriate. In the reverse direction the intermediary +might deem it beneficial to pass on client request cancellations that are +indicated by terminating a stream with H3_REQUEST_CANCELLED. + +Conversion between errors is described in the logical mapping. The error codes +are defined in non-overlapping spaces in order to protect against accidental +conversion that could result in the use of inappropriate or unknown error codes +for the target version. An intermediary is permitted to promote stream errors to +connection errors but they should be aware of the cost to the connection for +what might be a temporary or intermittent error. + # Change Log > **RFC Editor's Note:** Please remove this section prior to publication of a > final version of this document. +## Since draft-ietf-quic-http-27 + +- Updated text to refer to latest HTTP revisions +- Use the HTTP definition of authority for establishing and coalescing + connections (#253, #2223, #3558) +- Define use of GOAWAY from both endpoints (#2632, #3129) +- Require either :authority or Host if the URI scheme has a mandatory + authority component (#3408, #3475) + ## Since draft-ietf-quic-http-26 - No changes @@ -2661,5 +2698,35 @@ None. The original authors of this specification were Robbie Shade and Mike Warres. -A substantial portion of Mike's contribution was supported by Microsoft during -his employment there. +The IETF QUIC Working Group received an enormous amount of support from many +people. Among others, the following people provided substantial contributions to +this document: + +- Bence Béky +- Daan De Meyer +- Martin Duke +- Roy Fielding +- Alan Frindell +- Alessandro Ghedini +- Nick Harper +- Ryan Hamilton +- Christian Huitema +- Subodh Iyengar +- Robin Marx +- Patrick McManus +- Luca Nicco +- +- Lucas Pardue +- Roberto Peon +- Julian Reschke +- Eric Rescorla +- Martin Seemann +- Ben Schwartz +- Ian Swett +- Willy Taureau +- Martin Thomson +- Dmitri Tikhonov +- Tatsuhiro Tsujikawa + +A portion of Mike's contribution was supported by Microsoft during his +employment there. diff --git a/draft-ietf-quic-invariants.md b/draft-ietf-quic-invariants.md index 1abd81910e..ec40a0ab10 100644 --- a/draft-ietf-quic-invariants.md +++ b/draft-ietf-quic-invariants.md @@ -129,8 +129,7 @@ version-specific and of arbitrary length. ## Long Header -Long headers take the form described in {{fig-long}}. Bits that have -version-specific semantics are marked with an X. +Long headers take the form described in {{fig-long}}. ~~~ Long Header Packet { @@ -138,9 +137,9 @@ Long Header Packet { Version-Specific Bits (7), Version (32), DCID Len (8), - Destination Connection ID (0..160), + Destination Connection ID (0..2040), SCID Len (8), - Source Connection ID (0..160), + Source Connection ID (0..2040), Version-Specific Data (..), } ~~~ @@ -156,18 +155,17 @@ The next byte contains the length in bytes of the Destination Connection ID (see unsigned integer. The Destination Connection ID field follows the DCID Len field and is between 0 and 255 bytes in length. -The next byte contains the length in bytes -of the Source Connection ID field that follows it. This length is encoded as -a 8-bit unsigned integer. The Source Connection ID field follows the SCID Len -field and is between 0 and 255 bytes in length. +The next byte contains the length in bytes of the Source Connection ID field +that follows it. This length is encoded as a 8-bit unsigned integer. The +Source Connection ID field follows the SCID Len field and is between 0 and 255 +bytes in length. The remainder of the packet contains version-specific content. ## Short Header -Short headers take the form described in {{fig-short}}. Bits that have -version-specific semantics are marked with an X. +Short headers take the form described in {{fig-short}}. ~~~~~ Short Header Packet { @@ -235,9 +233,9 @@ Version Negotiation Packet { Unused (7), Version (32) = 0, DCID Len (8), - Destination Connection ID (0..160), + Destination Connection ID (0..2040), SCID Len (8), - Source Connection ID (0..160), + Source Connection ID (0..2040), Supported Version (32) ..., } ~~~ diff --git a/draft-ietf-quic-qpack.md b/draft-ietf-quic-qpack.md index a21c923e2f..58f8931065 100644 --- a/draft-ietf-quic-qpack.md +++ b/draft-ietf-quic-qpack.md @@ -61,6 +61,30 @@ normative: org: Mozilla role: editor +informative: + + CRIME: + target: http://en.wikipedia.org/w/index.php?title=CRIME&oldid=660948120 + title: "CRIME" + author: + - + org: Wikipedia + date: May, 2015 + + + PETAL: + target: http://www.pdl.cmu.edu/PDL-FTP/associated/CMU-PDL-13-106.pdf + title: "PETAL: Preset Encoding Table Information Leakage" + author: + - + ins: J. Tan + name: Jiaqi Tan + - + ins: J. Nahata + name: Jayvardhan Nahata + date: April, 2013 + + --- abstract @@ -222,7 +246,7 @@ evictable, the encoder MUST NOT insert that entry into the dynamic table (including duplicates of existing entries). In order to avoid this, an encoder that uses the dynamic table has to keep track of each dynamic table entry referenced by each field section until those representations are acknowledged by -the decoder (see {{entry-acknowledgement}}). +the decoder; see {{entry-acknowledgement}}. #### Avoiding Prohibited Insertions @@ -244,14 +268,14 @@ references to those entries will eventually become zero, allowing them to be evicted. ~~~~~~~~~~ drawing - +----------+---------------------------------+--------+ - | Draining | Referenceable | Unused | - | Entries | Entries | Space | - +----------+---------------------------------+--------+ - ^ ^ ^ - | | | - Dropping Draining Index Insertion Point - Point + +--------+---------------------------------+----------+ + | Unused | Referenceable | Draining | + | Space | Entries | Entries | + +--------+---------------------------------+----------+ + ^ ^ ^ + | | | + Insertion Point Draining Index Dropping + Point ~~~~~~~~~~ {:#fig-draining-index title="Draining Dynamic Table Entries"} @@ -644,11 +668,11 @@ and follows the definitions in [RFC7541] without modification. QPACK defines two unidirectional stream types: - - An encoder stream is a unidirectional stream of type `0x02`. + - An encoder stream is a unidirectional stream of type 0x02. It carries an unframed sequence of encoder instructions from encoder to decoder. - - A decoder stream is a unidirectional stream of type `0x03`. + - A decoder stream is a unidirectional stream of type 0x03. It carries an unframed sequence of decoder instructions from decoder to encoder. @@ -952,12 +976,12 @@ value of 4 indicates that the Required Insert Count is 9 for the field section. #### Base {#base} -The `Base` is used to resolve references in the dynamic table as described in +The Base is used to resolve references in the dynamic table as described in {{relative-indexing}}. To save space, the Base is encoded relative to the Required Insert Count using a -one-bit sign ('S') and the `Delta Base` value. A sign bit of 0 indicates that -the Base is greater than or equal to the value of the Required Insert Count; the +one-bit sign ('S') and the Delta Base value. A sign bit of 0 indicates that the +Base is greater than or equal to the value of the Required Insert Count; the decoder adds the value of Delta Base to the Required Insert Count to determine the value of the Base. A sign bit of 1 indicates that the Base is less than the Required Insert Count; the decoder subtracts the value of Delta Base from the @@ -1158,7 +1182,197 @@ QPACK_DECODER_STREAM_ERROR (0x202): # Security Considerations -TBD. Also see Section 7.1 of [RFC7541]. + +This section describes potential areas of security concern with QPACK: + + * Use of compression as a length-based oracle for verifying guesses about + secrets that are compressed into a shared compression context. + * Denial of service resulting from exhausting processing or memory capacity at + a decoder. + +## Probing Dynamic Table State + +QPACK reduces the length of header field encodings by exploiting the redundancy +inherent in protocols like HTTP. The ultimate goal of this is to reduce the +amount of data that is required to send HTTP requests or responses. + +The compression context used to encode header fields can be probed by an +attacker who can both define header fields to be encoded and transmitted and +observe the length of those fields once they are encoded. When an attacker can +do both, they can adaptively modify requests in order to confirm guesses about +the dynamic table state. If a guess is compressed into a shorter length, the +attacker can observe the encoded length and infer that the guess was correct. + +This is possible even over the Transport Layer Security Protocol (TLS, see +{{?RFC5246}}), because while TLS provides confidentiality protection for +content, it only provides a limited amount of protection for the length of that +content. + +Note: + +: Padding schemes only provide limited protection against an attacker with these +capabilities, potentially only forcing an increased number of guesses to learn +the length associated with a given guess. Padding schemes also work directly +against compression by increasing the number of bits that are transmitted. + +Attacks like CRIME [CRIME] demonstrated the existence of these general attacker +capabilities. The specific attack exploited the fact that DEFLATE {{?RFC1951}} +removes redundancy based on prefix matching. This permitted the attacker to +confirm guesses a character at a time, reducing an exponential-time attack into +a linear-time attack. + +## Applicability to QPACK and HTTP + +QPACK mitigates but does not completely prevent attacks modeled on CRIME [CRIME] +by forcing a guess to match an entire header field value, rather than individual +characters. An attacker can only learn whether a guess is correct or not, so is +reduced to a brute force guess for the header field values. + +The viability of recovering specific header field values therefore depends on +the entropy of values. As a result, values with high entropy are unlikely to be +recovered successfully. However, values with low entropy remain vulnerable. + +Attacks of this nature are possible any time that two mutually distrustful +entities control requests or responses that are placed onto a single HTTP/3 +connection. If the shared QPACK compressor permits one entity to add entries to +the dynamic table, and the other to access those entries, then the state of the +table can be learned. + +Having requests or responses from mutually distrustful entities occurs when an +intermediary either: + + * sends requests from multiple clients on a single connection toward an origin + server, or + + * takes responses from multiple origin servers and places them on a shared + connection toward a client. + +Web browsers also need to assume that requests made on the same connection by +different web origins {{?RFC6454}} are made by mutually distrustful entities. + +## Mitigation + +Users of HTTP that require confidentiality for header fields can use values with +entropy sufficient to make guessing infeasible. However, this is impractical as +a general solution because it forces all users of HTTP to take steps to mitigate +attacks. It would impose new constraints on how HTTP is used. + +Rather than impose constraints on users of HTTP, an implementation of QPACK can +instead constrain how compression is applied in order to limit the potential for +dynamic table probing. + +An ideal solution segregates access to the dynamic table based on the entity +that is constructing header fields. Header field values that are added to the +table are attributed to an entity, and only the entity that created a particular +value can extract that value. + +To improve compression performance of this option, certain entries might be +tagged as being public. For example, a web browser might make the values of the +Accept-Encoding header field available in all requests. + +An encoder without good knowledge of the provenance of header fields might +instead introduce a penalty for a header field with many different values, such +that a large number of attempts to guess a header field value results in the +header field not being compared to the dynamic table entries in future messages, +effectively preventing further guesses. + +Note: + +: Simply removing entries corresponding to the header field from the dynamic +table can be ineffectual if the attacker has a reliable way of causing values to +be reinstalled. For example, a request to load an image in a web browser +typically includes the Cookie header field (a potentially highly valued target +for this sort of attack), and web sites can easily force an image to be loaded, +thereby refreshing the entry in the dynamic table. + +This response might be made inversely proportional to the length of the header +field value. Disabling access to the dynamic table for a header field might +occur for shorter values more quickly or with higher probability than for longer +values. + +## Never Indexed Literals + +Implementations can also choose to protect sensitive header fields by not +compressing them and instead encoding their value as literals. + +Refusing to insert a header field into the dynamic table is only +effective if doing so is avoided on all hops. The never indexed literal bit (see +{{literal-name-reference}}) can be used to signal to intermediaries that a +particular value was intentionally sent as a literal. + +An intermediary MUST NOT re-encode a value that uses a literal representation +with the 'N' bit set with another representation that would index it. If QPACK +is used for re-encoding, a literal representation with the 'N' bit set MUST be +used. If HPACK is used for re-encoding, the never indexed literal +representation (see Section 6.2.3 of [RFC7541]) MUST be used. + +The choice to mark that a header field should never be indexed +depends on several factors. Since QPACK doesn't protect against guessing an +entire header field value, short or low-entropy values are more readily +recovered by an adversary. Therefore, an encoder might choose not to index +values with low entropy. + +An encoder might also choose not to index values for header fields that are +considered to be highly valuable or sensitive to recovery, such as the Cookie or +Authorization header fields. + +On the contrary, an encoder might prefer indexing values for header fields that +have little or no value if they were exposed. For instance, a User-Agent header +field does not commonly vary between requests and is sent to any server. In that +case, confirmation that a particular User-Agent value has been used provides +little value. + +Note that these criteria for deciding to use a never indexed literal +representation will evolve over time as new attacks are discovered. + +## Static Huffman Encoding + +There is no currently known attack against a static Huffman encoding. A study +has shown that using a static Huffman encoding table created an information +leakage, however this same study concluded that an attacker could not take +advantage of this information leakage to recover any meaningful amount of +information (see [PETAL]). + +## Memory Consumption + +An attacker can try to cause an endpoint to exhaust its memory. QPACK is +designed to limit both the peak and stable amounts of memory allocated by an +endpoint. + +The amount of memory used by the encoder is limited by the protocol using +QPACK through the definition of the maximum size of the dynamic table, and the +maximum number of blocking streams. In HTTP/3, these values are controlled by +the decoder through the settings parameters SETTINGS_QPACK_MAX_TABLE_CAPACITY +and SETTINGS_QPACK_BLOCKED_STREAMS, respectively (see +{{maximum-dynamic-table-capacity}} and {{blocked-streams}}). The limit on the +size of the dynamic table takes into account the size of the data stored in the +dynamic table, plus a small allowance for overhead. The limit on the number of +blocked streams is only a proxy for the maximum amount of memory required by the +decoder. The actual maximum amount of memory will depend on how much memory the +decoder uses to track each blocked stream. + +A decoder can limit the amount of state memory used for the dynamic table by +setting an appropriate value for the maximum size of the dynamic table. In +HTTP/3, this is realized by setting an appropriate value for the +SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter. An encoder can limit the amount of +state memory it uses by signaling a lower dynamic table size than the decoder +allows (see {{eviction}}). + +A decoder can limit the amount of state memory used for blocked streams by +setting an appropriate value for the maximum number of blocked streams. In +HTTP/3, this is realized by setting an appropriate value for the +QPACK_BLOCKED_STREAMS parameter. An encoder can limit the amount of state +memory by only using as many blocked streams as it wishes to support; no +signaling to the decoder is required. + +The amount of temporary memory consumed by an encoder or decoder can be limited +by processing header fields sequentially. A decoder implementation does not need +to retain a complete list of header fields while decoding a header block. An +encoder implementation does not need to retain a complete list of header fields +while encoding a header block if it is using a single-pass algorithm. Note +that it might be necessary for an application to retain a complete +header list for other reasons; even if QPACK does not force this to occur, +application constraints might make this necessary. While the negotiated limit on the dynamic table size accounts for much of the memory that can be consumed by a QPACK implementation, data which cannot be @@ -1169,6 +1383,19 @@ to an excess of unsent data might include limiting the ability of the peer to open new streams, reading only from the encoder stream, or closing the connection. + +## Implementation Limits + +An implementation of QPACK needs to ensure that large values for integers, long +encoding for integers, or long string literals do not create security +weaknesses. + +An implementation has to set a limit for the values it accepts for integers, as +well as for the encoded length (see {{prefixed-integers}}). In the same way, it +has to set a limit to the length it accepts for string literals (see +{{string-literals}}). + + # IANA Considerations ## Settings Registration @@ -1213,6 +1440,13 @@ are registered in the "HTTP/3 Error Code" registry established in {{HTTP3}}. # Static Table +This table was generated by analyzing actual internet traffic in 2018 and +including the most common headers, after filtering out some unsupported and +non-standard values. Due to this methodology, some of the entries may be +inconsistent or appear multiple times with similar but not identical values. +The order of the entries is optimized to encode the most common headers with the +smallest number of bytes. + | Index | Name | Value | | ----- | -------------------------------- | ----------------------------------------------------------- | | 0 | :authority | | @@ -1318,54 +1552,62 @@ are registered in the "HTTP/3 Error Code" registry established in {{HTTP3}}. # Sample One Pass Encoding Algorithm Pseudo-code for single pass encoding, excluding handling of duplicates, -non-blocking mode, and reference tracking. +non-blocking mode, available encoder stream flow control and reference tracking. ~~~ -baseIndex = dynamicTable.baseIndex -largestReference = 0 +base = dynamicTable.getInsertCount() +requiredInsertCount = 0 for line in field_lines: - staticIdx = staticTable.getIndex(line) - if staticIdx: - encodeIndexReference(streamBuffer, staticIdx) + staticIndex = staticTable.findIndex(line) + if staticIndex is not None: + encodeIndexReference(streamBuffer, staticIndex) continue - dynamicIdx = dynamicTable.getIndex(line) - if !dynamicIdx: + dynamicIndex = dynamicTable.findIndex(line) + if dynamicIndex is None: # No matching entry. Either insert+index or encode literal - nameIdx = getNameIndex(line) + staticNameIndex = staticTable.findName(line.name) + if staticNameIndex is None: + dynamicNameIndex = dynamicTable.findName(line.name) + if shouldIndex(line) and dynamicTable.canIndex(line): - encodeLiteralWithIncrementalIndex(controlBuffer, nameIdx, - line) - dynamicTable.add(line) - dynamicIdx = dynamicTable.baseIndex + encodeInsert(encoderBuffer, staticNameIndex, + dynamicNameIndex, line) + dynamicIndex = dynamicTable.add(line) - if !dynamicIdx: + if dynamicIndex is None: # Couldn't index it, literal - if nameIdx <= staticTable.size: + if nameIndex is None or isStaticName: + # Encodes a literal with a static name or literal name encodeLiteral(streamBuffer, nameIndex, line) else: - # encode literal, possibly with nameIdx above baseIndex - encodeDynamicLiteral(streamBuffer, nameIndex, baseIndex, - line) - largestReference = max(largestReference, - dynamicTable.toAbsolute(nameIdx)) + # encode literal with dynamic name, possibly above base + encodeDynamicLiteral(streamBuffer, nameIndex, base, line) + requiredInsertCount = max(requiredInsertCount, nameIndex) else: # Dynamic index reference - assert(dynamicIdx) - largestReference = max(largestReference, dynamicIdx) - # Encode dynamicIdx, possibly with dynamicIdx above baseIndex - encodeDynamicIndexReference(streamBuffer, dynamicIdx, - baseIndex) + assert(dynamicIndex is not None) + requiredInsertCount = max(requiredInsertCount, dynamicIndex) + # Encode dynamicIndex, possibly above base + encodeDynamicIndexReference(streamBuffer, dynamicIndex, base) # encode the prefix -encodeInteger(prefixBuffer, 0x00, largestReference, 8) -if baseIndex >= largestReference: - encodeInteger(prefixBuffer, 0, baseIndex - largestReference, 7) +if requiredInsertCount == 0: + encodeIndexReference(prefixBuffer, 0, 0, 8) + encodeIndexReference(prefixBuffer, 0, 0, 7) else: - encodeInteger(prefixBuffer, 0x80, - largestReference - baseIndex, 7) + wireRIC = ( + requiredInsertCount + % (2 * getMaxEntries(maxTableCapacity)) + ) + 1; + encodeInteger(prefixBuffer, 0x00, wireRIC, 8) + if base >= requiredInsertCount: + encodeInteger(prefixBuffer, 0, base - requiredInsertCount, 7) + else: + encodeInteger(prefixBuffer, 0x80, + requiredInsertCount - base - 1, 7) -return controlBuffer, prefixBuffer + streamBuffer +return encoderBuffer, prefixBuffer + streamBuffer ~~~ # Change Log @@ -1373,6 +1615,10 @@ return controlBuffer, prefixBuffer + streamBuffer > **RFC Editor's Note:** Please remove this section prior to publication of a > final version of this document. +## Since draft-ietf-quic-qpack-14 + +Added security considerations + ## Since draft-ietf-quic-qpack-13 No changes @@ -1462,22 +1708,29 @@ Editorial changes only # Acknowledgments {:numbered="false"} -This draft draws heavily on the text of {{!RFC7541}}. The indirect input of -those authors is gratefully acknowledged, as well as ideas from: +The IETF QUIC Working Group received an enormous amount of support from many +people. -* Ryan Hamilton +The compression design team did substantial work exploring the problem space and +influencing the initial draft. The contributions of design team members Roberto +Peon, Martin Thomson, and Dmitri Tikhonov are gratefully acknowledged. -* Patrick McManus +The following people also provided substantial contributions to this document: -* Kazuho Oku +- Bence Béky +- Alessandro Ghedini +- Ryan Hamilton +- Robin Marx +- Patrick McManus +- +- Lucas Pardue +- Biren Roy +- Ian Swett -* Biren Roy - -* Ian Swett - -* Dmitri Tikhonov +This draft draws heavily on the text of {{!RFC7541}}. The indirect input of +those authors is also gratefully acknowledged. Buck's contribution was supported by Google during his employment there. -A substantial portion of Mike's contribution was supported by Microsoft during -his employment there. +A portion of Mike's contribution was supported by Microsoft during his +employment there. diff --git a/draft-ietf-quic-recovery.md b/draft-ietf-quic-recovery.md index 1f5f5f38f2..e95c165253 100644 --- a/draft-ietf-quic-recovery.md +++ b/draft-ietf-quic-recovery.md @@ -218,7 +218,7 @@ forward progress without relying on timeouts. QUIC endpoints measure the delay incurred between when a packet is received and when the corresponding acknowledgment is sent, allowing a peer to maintain a -more accurate round-trip time estimate (see Section 13.2 of {{QUIC-TRANSPORT}}). +more accurate round-trip time estimate; see Section 13.2 of {{QUIC-TRANSPORT}}. ### Probe Timeout Replaces RTO and TLP @@ -245,6 +245,19 @@ QUIC specifies a time-based definition to ensure one or more packets are sent prior to a dramatic decrease in congestion window; see {{persistent-congestion}}. +### The Minimum Congestion Window is Two Packets + +TCP uses a minimum congestion window of one packet. However, loss of +that single packet means that the sender needs to waiting for a PTO +({{pto}}) to recover, which can be much longer than a round-trip time. +Sending a single ack-eliciting packet also increases the chances of incurring +additional latency when a receiver delays its acknowledgement. + +QUIC therefore recommends that the minimum congestion window be two +packets. While this increases network load, it is considered safe, since the +sender will still reduce its sending rate exponentially under persistent +congestion ({{pto}}). + # Estimating the Round-Trip Time {#compute-rtt} @@ -421,8 +434,11 @@ The RECOMMENDED initial value for the packet reordering threshold less than 3, to keep in line with TCP {{?RFC5681}}. Some networks may exhibit higher degrees of reordering, causing a sender to -detect spurious losses. Implementers MAY use algorithms developed for TCP, such -as TCP-NCR {{?RFC4653}}, to improve QUIC's reordering resilience. +detect spurious losses. Algorithms that increase the reordering threshold after +spuriously detecting losses, such as TCP-NCR {{?RFC4653}}, have proven to be +useful in TCP and are expected to at least as useful in QUIC. Re-ordering +could be more common with QUIC than TCP, because network elements cannot observe +and fix the order of out-of-order packets. ### Time Threshold {#time-threshold} @@ -462,8 +478,13 @@ and larger thresholds increase loss detection delay. A Probe Timeout (PTO) triggers sending one or two probe datagrams when ack-eliciting packets are not acknowledged within the expected period of -time or the handshake has not been completed. A PTO enables a connection to -recover from loss of tail packets or acknowledgements. +time or the server may not have validated the client's address. A PTO enables +a connection to recover from loss of tail packets or acknowledgements. + +A PTO timer expiration event does not indicate packet loss and MUST NOT cause +prior unacknowledged packets to be marked as lost. When an acknowledgement +is received that newly acknowledges packets, loss detection proceeds as +dictated by packet and time threshold mechanisms; see {{ack-loss-detection}}. As with loss detection, the probe timeout is per packet number space. The PTO algorithm used in QUIC implements the reliability functions of @@ -533,16 +554,14 @@ previous RTT is available, the initial RTT SHOULD be set to 333ms, resulting in a 1 second initial timeout, as recommended in {{?RFC6298}}. A connection MAY use the delay between sending a PATH_CHALLENGE and receiving a -PATH_RESPONSE to set the initial RTT (see kInitialRtt in {{pto-handshake}}) -for a new path, but the delay SHOULD NOT be considered an RTT sample. +PATH_RESPONSE to set the initial RTT (see kInitialRtt in +{{constants-of-interest}}) for a new path, but the delay SHOULD NOT be +considered an RTT sample. Prior to handshake completion, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client. To allow the client to improve its RTT -estimate, the new packet that it sends MUST be ack-eliciting. If Handshake -keys are available to the client, it MUST send a Handshake packet, and -otherwise it MUST send an Initial packet in a UDP datagram of at least 1200 -bytes. +estimate, the new packet that it sends MUST be ack-eliciting. Initial packets and Handshake packets could be never acknowledged, but they are removed from bytes in flight when the Initial and Handshake keys are discarded, @@ -556,7 +575,7 @@ have been set for a now discarded packet number space. Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of {{QUIC-TRANSPORT}}. If no additional data can be -sent, the server's PTO alarm MUST NOT be armed until datagrams have been +sent, the server's PTO timer MUST NOT be armed until datagrams have been received from the client, because packets sent on PTO count against the anti-amplification limit. Note that the server could fail to validate the client's address even if 0-RTT is accepted. @@ -567,6 +586,14 @@ until it is certain that the server has finished its address validation (see Section 8 of {{QUIC-TRANSPORT}}). That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. +If Handshake keys are available to the client, it MUST send a Handshake +packet, and otherwise it MUST send an Initial packet in a UDP datagram of +at least 1200 bytes. + +A client could have received and acknowledged a Handshake packet, causing it to +discard state for the Initial packet number space, but not sent any +ack-eliciting Handshake packets. In this case, the PTO is set from the current +time. ### Speeding Up Handshake Completion @@ -597,7 +624,11 @@ All probe packets sent on a PTO MUST be ack-eliciting. In addition to sending data in the packet number space for which the timer expired, the sender SHOULD send ack-eliciting packets from other packet -number spaces with in-flight data, coalescing packets if possible. +number spaces with in-flight data, coalescing packets if possible. This is +particularly valuable when the server has both Initial and Handshake data +in-flight or the client has both Handshake and ApplicationData in-flight, +because the peer might only have receive keys for one of the two packet number +spaces. If the sender wants to elicit a faster acknowledgement on PTO, it can skip a packet number to eliminate the ack delay. @@ -635,16 +666,6 @@ and ensures the highest priority frames arrive first. Sending different payloads each time reduces the chances of spurious retransmission. -### Loss Detection {#pto-loss} - -Delivery or loss of packets in flight is established when an ACK frame is -received that newly acknowledges one or more packets. - -A PTO timer expiration event does not indicate packet loss and MUST NOT cause -prior unacknowledged packets to be marked as lost. When an acknowledgement -is received that newly acknowledges packets, loss detection proceeds as -dictated by packet and time threshold mechanisms; see {{ack-loss-detection}}. - ## Handling Retry Packets A Retry packet causes a client to send another Initial packet, effectively @@ -672,7 +693,7 @@ all recovery state associated with those packets and MUST remove them from the count of bytes in flight. Endpoints stop sending and receiving Initial packets once they start exchanging -Handshake packets (see Section 17.2.2.1 of {{QUIC-TRANSPORT}}). At this point, +Handshake packets; see Section 17.2.2.1 of {{QUIC-TRANSPORT}}. At this point, recovery state for all in-flight Initial packets is discarded. When 0-RTT is rejected, recovery state for all in-flight 0-RTT packets is @@ -684,8 +705,8 @@ is expected to be infrequent. It is expected that keys are discarded after packets encrypted with them would be acknowledged or declared lost. Initial secrets however might be destroyed -sooner, as soon as handshake keys are available (see Section 4.10.1 of -{{QUIC-TLS}}). +sooner, as soon as handshake keys are available; see Section 4.11.1 of +{{QUIC-TLS}}. # Congestion Control {#congestion-control} @@ -711,7 +732,7 @@ window in bytes. An endpoint MUST NOT send a packet if it would cause bytes_in_flight (see {{vars-of-interest}}) to be larger than the congestion window, unless the packet -is sent on a PTO timer expiration (see {{pto}}). +is sent on a PTO timer expiration; see {{pto}}. ## Explicit Congestion Notification {#congestion-ecn} @@ -729,6 +750,12 @@ twice the maximum datagram size. This follows the analysis and recommendations in {{?RFC6928}}, increasing the byte limit to account for the smaller 8 byte overhead of UDP compared to the 20 byte overhead for TCP. +Prior to validating the client's address, the server can be further limited by +the anti-amplification limit as specified in Section 8.1 of {{QUIC-TRANSPORT}}. +Though the anti-amplification limit can prevent the congestion window from +being fully utilized and therefore slow down the increase in congestion window, +it does not directly affect the congestion window. + The minimum congestion window is the smallest value the congestion window can decrease to as a response to loss, ECN-CE, or persistent congestion. The RECOMMENDED value is 2 * max_datagram_size. @@ -817,22 +844,26 @@ This duration is computed as follows: For example, assume: - smoothed_rtt = 1 - rttvar = 0 - max_ack_delay = 0 - kPersistentCongestionThreshold = 3 +~~~ +smoothed_rtt = 1 +rttvar = 0 +max_ack_delay = 0 +kPersistentCongestionThreshold = 3 +~~~ If an ack-eliciting packet is sent at time t = 0, the following scenario would illustrate persistent congestion: - t=0 | Send Pkt #1 (App Data) - t=1 | Send Pkt #2 (PTO 1) - t=3 | Send Pkt #3 (PTO 2) - t=7 | Send Pkt #4 (PTO 3) - t=8 | Recv ACK of Pkt #4 +| Time | Action | +|:-----|:-----------------------| +| t=0 | Send Pkt #1 (App Data) | +| t=1 | Send Pkt #2 (PTO 1) | +| t=3 | Send Pkt #3 (PTO 2) | +| t=7 | Send Pkt #4 (PTO 3) | +| t=8 | Recv ACK of Pkt #4 | The first three packets are determined to be lost when the acknowledgement of -packet 4 is received at t=8. The congestion period is calculated as the time +packet 4 is received at t = 8. The congestion period is calculated as the time between the oldest and newest lost packets: (3 - 0) = 3. The duration for persistent congestion is equal to: (1 * kPersistentCongestionThreshold) = 3. Because the threshold was reached and because none of the packets between the @@ -862,17 +893,39 @@ Timely delivery of ACK frames is important for efficient loss recovery. Packets containing only ACK frames SHOULD therefore not be paced, to avoid delaying their delivery to the peer. -Sending multiple packets into the network without any delay between them -creates a packet burst that might cause short-term congestion and losses. -Implementations MUST either use pacing or limit such bursts to the initial -congestion window, which is recommended to be the minimum of -10 * max_datagram_size and max(2* max_datagram_size, 14720)), where -max_datagram_size is the current maximum size of a datagram for the connection, -not including UDP or IP overhead. +Endpoints can implement pacing as they choose. A perfectly paced sender spreads +packets exactly evenly over time. For a window-based congestion controller, such +as the one in this document, that rate can be computed by averaging the +congestion window over the round-trip time. Expressed as a rate in bytes: + +~~~ +rate = N * congestion_window / smoothed_rtt +~~~ + +Or, expressed as an inter-packet interval: + +~~~ +interval = smoothed_rtt * packet_size / congestion_window / N +~~~ -As an example of a well-known and publicly available implementation of a flow -pacer, implementers are referred to the Fair Queue packet scheduler (fq qdisc) -in Linux (3.11 onwards). +Using a value for `N` that is small, but at least 1 (for example, 1.25) ensures +that variations in round-trip time don't result in under-utilization of the +congestion window. Values of 'N' larger than 1 ultimately result in sending +packets as acknowledgments are received rather than when timers fire, provided +the congestion window is fully utilized and acknowledgments arrive at regular +intervals. + +Practical considerations, such as packetization, scheduling delays, and +computational efficiency, can cause a sender to deviate from this rate over time +periods that are much shorter than a round-trip time. Sending multiple packets +into the network without any delay between them creates a packet burst that +might cause short-term congestion and losses. Implementations MUST either use +pacing or limit such bursts to the initial congestion window; see +{{initial-cwnd}}. + +One possible implementation strategy for pacing uses a leaky bucket algorithm, +where the capacity of the "bucket" is limited to the maximum burst size and the +rate the "bucket" fills is determined by the above function. ## Under-utilizing the Congestion Window @@ -882,7 +935,7 @@ the congestion window SHOULD NOT be increased in either slow start or congestion avoidance. This can happen due to insufficient application data or flow control limits. -A sender MAY use the pipeACK method described in section 4.3 of {{?RFC7661}} +A sender MAY use the pipeACK method described in Section 4.3 of {{?RFC7661}} to determine if the congestion window is sufficiently utilized. A sender that paces packets (see {{pacing}}) might delay sending packets @@ -983,7 +1036,7 @@ time_sent: : The time the packet was sent. -## Constants of interest +## Constants of Interest {#constants-of-interest} Constants used in loss recovery are based on a combination of RFCs, papers, and common practice. @@ -1032,7 +1085,7 @@ smoothed_rtt: {{smoothed-rtt}}. rttvar: -: The RTT variation, computed as described in {{smoothed-rtt}} +: The RTT variation, computed as described in {{smoothed-rtt}}. min_rtt: : The minimum RTT seen in the connection, ignoring ack delay, as described @@ -1109,6 +1162,22 @@ Pseudocode for OnPacketSent follows: SetLossDetectionTimer() ~~~ +## On Receiving a Datagram + +When a server is blocked by anti-amplification limits, receiving +a datagram unblocks it, even if none of the packets in the +datagram are successfully processed. In such a case, the PTO +timer will need to be re-armed. + +Pseudocode for OnDatagramReceived follows: + +~~~ +OnDatagramReceived(datagram): + // If this datagram unblocks the server, arm the + // PTO timer to avoid deadlock. + if (server was at anti-amplification limit): + SetLossDetectionTimer() +~~~ ## On Receiving an Acknowledgment @@ -1224,7 +1293,7 @@ SetLossDetectionTimer(): return if (server is at anti-amplification limit): - // The server's alarm is not set if nothing can be sent. + // The server's timer is not set if nothing can be sent. loss_detection_timer.cancel() return @@ -1236,16 +1305,23 @@ SetLossDetectionTimer(): loss_detection_timer.cancel() return + // Determine which PN space to arm PTO for. + sent_time, pn_space = GetEarliestTimeAndSpace( + time_of_last_sent_ack_eliciting_packet) + // Don't arm PTO for ApplicationData until handshake complete. + if (pn_space == ApplicationData && + handshake is not confirmed): + loss_detection_timer.cancel() + return + if (sent_time == 0): + assert(!PeerCompletedAddressValidation()) + sent_time = now() + // Calculate PTO duration timeout = smoothed_rtt + max(4 * rttvar, kGranularity) + max_ack_delay timeout = timeout * (2 ^ pto_count) - sent_time, _ = GetEarliestTimeAndSpace( - time_of_last_sent_ack_eliciting_packet) - if (sent_time == 0) - assert(!PeerCompletedAddressValidation()) - sent_time = now() loss_detection_timer.update(sent_time + timeout) ~~~ @@ -1542,6 +1618,21 @@ OnPacketNumberSpaceDiscarded(pn_space): Issue and pull request numbers are listed with a leading octothorp. +## Since draft-ietf-quic-recovery-27 + +- Added recommendations for speeding up handshake under some loss conditions + (#3078, #3080) +- PTO count is reset when handshake progress is made (#3272, #3415) +- PTO count is not reset by a client when the server might be awaiting + address validation (#3546, #3551) +- Recommend repairing losses immediately after entering the recovery period + (#3335, #3443) +- Clarified what loss conditions can be ignored during the handshake (#3456, + #3450) +- Allow, but don't recommend, using RTT from previous connection to seed RTT + (#3464, #3496) +- Recommend use of adaptive loss detection thresholds (#3571, #3572) + ## Since draft-ietf-quic-recovery-26 No changes. diff --git a/draft-ietf-quic-tls.md b/draft-ietf-quic-tls.md index 7304b5f9f2..70d675e34c 100644 --- a/draft-ietf-quic-tls.md +++ b/draft-ietf-quic-tls.md @@ -188,10 +188,6 @@ TLS provides two basic handshake modes of interest to QUIC: self-contained trigger for any non-idempotent action. A simplified TLS handshake with 0-RTT application data is shown in {{tls-full}}. -Note that this omits the EndOfEarlyData message, which is not used in QUIC (see -{{remove-eoed}}). Likewise, neither ChangeCipherSpec nor KeyUpdate messages are -used by QUIC; ChangeCipherSpec is redundant in TLS 1.3 and QUIC has defined its -own key update mechanism {{key-update}}. ~~~ Client Server @@ -213,6 +209,11 @@ own key update mechanism {{key-update}}. ~~~ {: #tls-full title="TLS Handshake with 0-RTT"} +{{tls-full}} omits the EndOfEarlyData message, which is not used in QUIC; see +{{remove-eoed}}. Likewise, neither ChangeCipherSpec nor KeyUpdate messages are +used by QUIC. ChangeCipherSpec is redundant in TLS 1.3; see {{compat-mode}}. +QUIC has its own key update mechanism; see {{key-update}}. + Data is protected using a number of encryption levels: - Initial Keys @@ -446,7 +447,7 @@ network, it proceeds as follows: find the proper location in the data sequence. If the result of this process is that new data is available, then it is delivered to TLS in order. -- If the packet is from a previously installed encryption level, it MUST not +- If the packet is from a previously installed encryption level, it MUST NOT contain data which extends past the end of previously received data in that flow. Implementations MUST treat any violations of this requirement as a connection error of type PROTOCOL_VIOLATION. @@ -740,19 +741,25 @@ QUIC implementations SHOULD instead use the Retry feature (see Section 8.1 of {{QUIC-TRANSPORT}}). HelloRetryRequest is still used to request key shares. -## TLS Errors +## TLS Errors {#tls-errors} If TLS experiences an error, it generates an appropriate alert as defined in Section 6 of {{!TLS13}}. -A TLS alert is turned into a QUIC connection error by converting the one-byte -alert description into a QUIC error code. The alert description is added to -0x100 to produce a QUIC error code from the range reserved for CRYPTO_ERROR. -The resulting value is sent in a QUIC CONNECTION_CLOSE frame of type 0x1c. +A TLS alert is converted into a QUIC connection error. The alert description is +added to 0x100 to produce a QUIC error code from the range reserved for +CRYPTO_ERROR. The resulting value is sent in a QUIC CONNECTION_CLOSE frame of +type 0x1c. The alert level of all TLS alerts is "fatal"; a TLS stack MUST NOT generate alerts at the "warning" level. +QUIC permits the use of a generic code in place of a specific error code; see +Section 11 of {{QUIC-TRANSPORT}}. For TLS alerts, this includes replacing any +alert with a generic alert, such as handshake_failure (0x128 in QUIC). +Endpoints MAY use a generic error code to avoid possibly exposing confidential +information. + ## Discarding Unused Keys @@ -1554,7 +1561,7 @@ Handshake packets, but because that tampering requires modifying TLS handshake messages, that tampering will cause the TLS handshake to fail. -# QUIC-Specific Additions to the TLS Handshake +# QUIC-Specific Adjustments to the TLS Handshake QUIC uses the TLS handshake for more than just negotiation of cryptographic parameters. The TLS handshake provides preliminary values for QUIC transport @@ -1567,12 +1574,13 @@ QUIC requires that the cryptographic handshake provide authenticated protocol negotiation. TLS uses Application Layer Protocol Negotiation (ALPN) {{!ALPN=RFC7301}} to select an application protocol. Unless another mechanism is used for agreeing on an application protocol, endpoints MUST use ALPN for -this purpose. When using ALPN, endpoints MUST immediately close a connection -(see Section 10.3 in {{QUIC-TRANSPORT}}) if an application protocol is not -negotiated with a no_application_protocol TLS alert (QUIC error code 0x178, see -{{tls-errors}}). While {{!ALPN}} only specifies that servers use this alert, -QUIC clients MUST also use it to terminate a connection when ALPN negotiation -fails. +this purpose. + +When using ALPN, endpoints MUST immediately close a connection (see Section +10.3 of {{QUIC-TRANSPORT}}) with a no_application_protocol TLS alert (QUIC error +code 0x178; see {{tls-errors}}) if an application protocol is not negotiated. +While {{!ALPN}} only specifies that servers use this alert, QUIC clients MUST +use error 0x178 to terminate a connection when ALPN negotiation fails. An application protocol MAY restrict the QUIC versions that it can operate over. Servers MUST select an application protocol compatible with the QUIC version @@ -1597,7 +1605,7 @@ protection for these values. } ExtensionType; ~~~ -The `extension_data` field of the quic_transport_parameters extension contains a +The extension_data field of the quic_transport_parameters extension contains a value that is defined by the version of QUIC that is in use. The quic_transport_parameters extension is carried in the ClientHello and the @@ -1631,6 +1639,21 @@ PROTOCOL_VIOLATION. As a result, EndOfEarlyData does not appear in the TLS handshake transcript. +## Prohibit TLS Middlebox Compatibility Mode {#compat-mode} + +Appendix D.4 of {{!TLS13}} describes an alteration to the TLS 1.3 handshake as +a workaround for bugs in some middleboxes. The TLS 1.3 middlebox compatibility +mode involves setting the legacy_session_id field to a 32-byte value in the +ClientHello and ServerHello, then sending a change_cipher_spec record. Both +field and record carry no semantic content and are ignored. + +This mode has no use in QUIC as it only applies to middleboxes that interfere +with TLS over TCP. QUIC also provides no means to carry a change_cipher_spec +record. A client MUST NOT request the use of the TLS 1.3 compatibility mode. A +server SHOULD treat the receipt of a TLS ClientHello that with a non-empty +legacy_session_id field as a connection error of type PROTOCOL_VIOLATION. + + # Security Considerations All of the security considerations that apply to TLS also apply to the use of @@ -1907,7 +1930,7 @@ The unprotected header includes the connection ID and a 4 byte packet number encoding for a packet number of 2: ~~~ -c3ff00001b088394c8f03e5157080000449e00000002 +c3ff00001c088394c8f03e5157080000449e00000002 ~~~ Protecting the payload produces output that is sampled for header protection. @@ -1924,13 +1947,13 @@ header[0] ^= mask[0] & 0x0f = c0 header[18..21] ^= mask[1..4] = 3b343aa8 -header = c0ff00001b088394c8f03e5157080000449e3b343aa8 +header = c0ff00001c088394c8f03e5157080000449e3b343aa8 ~~~ The resulting protected packet is: ~~~ -c0ff00001b088394c8f03e5157080000 449e3b343aa8535064a4268a0d9d7b1c +c0ff00001c088394c8f03e5157080000 449e3b343aa8535064a4268a0d9d7b1c 9d250ae355162276e9b1e3011ef6bbc0 ab48ad5bcc2681e953857ca62becd752 4daac473e68d7405fbba4e9ee616c870 38bdbe908c06d9605d9ac49030359eec b1d05a14e117db8cede2bb09d0dbbfee 271cb374d8f10abec82d0f59a1dee29f @@ -1967,7 +1990,7 @@ eaf45a9bf27dc0c1e784161691220913 13eb0e87555abd706626e557fc36a04f cd191a58829104d6075c5594f627ca50 6bf181daec940f4a4f3af0074eee89da acde6758312622d4fa675b39f728e062 d2bee680d8f41a597c262648bb18bcfc 13c8b3d97b1a77b2ac3af745d61a34cc 4709865bac824a94bb19058015e4e42d -38d3b779d72edc00c5cd088eff802b05 +ea5388b911e76d2856d68cf6cf394185 ~~~ @@ -1987,7 +2010,7 @@ The header from the server includes a new connection ID and a 2-byte packet number encoding for a packet number of 1: ~~~ -c1ff00001b0008f067a5502a4262b50040740001 +c1ff00001c0008f067a5502a4262b50040740001 ~~~ As a result, after protection, the header protection sample is taken starting @@ -1996,17 +2019,17 @@ from the third protected octet: ~~~ sample = 7002596f99ae67abf65a5852f54f58c3 mask = 38168a0c25 -header = c9ff00001b0008f067a5502a4262b5004074168b +header = c9ff00001c0008f067a5502a4262b5004074168b ~~~ The final protected packet is then: ~~~ -c9ff00001b0008f067a5502a4262b500 4074168bf22b7002596f99ae67abf65a +c9ff00001c0008f067a5502a4262b500 4074168bf22b7002596f99ae67abf65a 5852f54f58c37c808682e2e40492d8a3 899fb04fc0afe9aabc8767b18a0aa493 537426373b48d502214dd856d63b78ce e37bc664b3fe86d487ac7a77c53038a3 -cd32f0b5004d9f5754c4f7f2d1f35cf3 f7116351c92bd8c3a9528d2b6aca20f0 -8047d9f017f0 +cd32f0b5004d9f5754c4f7f2d1f35cf3 f7116351c92bda5b23c81034ab74f54c +b1bd72951256 ~~~ @@ -2018,8 +2041,8 @@ connection ID value of 0x8394c8f03e515708, but that value is not included in the final Retry packet: ~~~ -ffff00001b0008f067a5502a4262b574 6f6b656ea523cb5ba524695f6569f293 -a1359d8e +ffff00001c0008f067a5502a4262b574 6f6b656ef71a5f12afe3ecf8001a920e +6fdf1d63 ~~~ @@ -2030,9 +2053,15 @@ a1359d8e Issue and pull request numbers are listed with a leading octothorp. +## Since draft-ietf-quic-tls-27 + +- Allowed CONNECTION_CLOSE in any packet number space, with restrictions on + use of the application-specific variant (#3430, #3435, #3440) +- Prohibit the use of the compatibility mode from TLS 1.3 (#3594, #3595) + ## Since draft-ietf-quic-tls-26 -- Updated examples +- No changes ## Since draft-ietf-quic-tls-25 @@ -2196,23 +2225,24 @@ No significant changes. The IETF QUIC Working Group received an enormous amount of support from many people. The following people provided substantive contributions to this document: -Adam Langley, -Alessandro Ghedini, -Christian Huitema, -Christopher Wood, -David Schinazi, -Dragana Damjanovic, -Eric Rescorla, -Ian Swett, -Jana Iyengar, , -Marten Seemann, -Martin Duke, -Mike Bishop, , -Nick Banks, -Nick Harper, -Roberto Peon, -Rui Paulo, -Ryan Hamilton, -and Victor Vasiliev. + +- Adam Langley +- Alessandro Ghedini +- Christian Huitema +- Christopher Wood +- David Schinazi +- Dragana Damjanovic +- Eric Rescorla +- Ian Swett +- Jana Iyengar +- +- Marten Seemann +- Martin Duke +- Mike Bishop +- +- Nick Banks +- Nick Harper +- Roberto Peon +- Rui Paulo +- Ryan Hamilton +- Victor Vasiliev diff --git a/draft-ietf-quic-transport.md b/draft-ietf-quic-transport.md index 104e711cc3..7676baed6c 100644 --- a/draft-ietf-quic-transport.md +++ b/draft-ietf-quic-transport.md @@ -190,8 +190,8 @@ QUIC packet: Ack-eliciting Packet: : A QUIC packet that contains frames other than ACK, PADDING, and - CONNECTION_CLOSE. These cause a recipient to send an acknowledgment (see - {{sending-acknowledgements}}). + CONNECTION_CLOSE. These cause a recipient to send an acknowledgment; see + {{sending-acknowledgements}}. Out-of-order packet: @@ -256,7 +256,7 @@ x (i): x (A..B): : Indicates that x can be any length from A to B; A can be omitted to indicate - a mimumum of zero bits and B can be omitted to indicate no set upper limit; + a minimum of zero bits and B can be omitted to indicate no set upper limit; values in this format always end on an octet boundary x (?) = C: @@ -311,7 +311,7 @@ means of ensuring ordering between bytes on different streams. QUIC allows for an arbitrary number of streams to operate concurrently and for an arbitrary amount of data to be sent on any stream, subject to flow control -constraints (see {{flow-control}}) and stream limits. +constraints and stream limits; see {{flow-control}}. ## Stream Types and Identifiers {#stream-id} @@ -916,8 +916,8 @@ An endpoint MUST NOT send data on a stream at or beyond the final size. Once a final size for a stream is known, it cannot change. If a RESET_STREAM or STREAM frame is received indicating a change in the final size for the stream, -an endpoint SHOULD respond with a FINAL_SIZE_ERROR error (see -{{error-handling}}). A receiver SHOULD treat receipt of data at or beyond the +an endpoint SHOULD respond with a FINAL_SIZE_ERROR error; see +{{error-handling}}. A receiver SHOULD treat receipt of data at or beyond the final size as a FINAL_SIZE_ERROR error, even after a stream is closed. Generating these errors is not mandatory, but only because requiring that an endpoint generate these errors also means that the endpoint needs to maintain @@ -1047,11 +1047,18 @@ connection ID issued by the server via the preferred_address transport parameter. An endpoint SHOULD ensure that its peer has a sufficient number of available and -unused connection IDs. Endpoints store received connection IDs for future use -and advertise the number of connection IDs they are willing to store with the -active_connection_id_limit transport parameter. An endpoint MUST NOT provide -more connection IDs than the peer's limit. An endpoint that receives more -connection IDs than its advertised active_connection_id_limit MUST close the +unused connection IDs. Endpoints advertise the number of active connection IDs +they are willing to maintain using the active_connection_id_limit transport +parameter. An endpoint MUST NOT provide more connection IDs than the peer's +limit. An endpoint MAY send connection IDs that temporarily exceed a peer's +limit if the NEW_CONNECTION_ID frame also requires the retirement of any excess, +by including a sufficiently large value in the Retire Prior To field. + +A NEW_CONNECTION_ID frame might cause an endpoint to add some active connection +IDs and retire others based on the value of the Retire Prior To field. After +processing a NEW_CONNECTION_ID frame and adding and retiring active connection +IDs, if the number of active connection IDs exceeds the value advertised in its +active_connection_id_limit transport parameter, an endpoint MUST close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. An endpoint SHOULD supply a new connection ID when the peer retires a connection @@ -1071,7 +1078,7 @@ peer to use a new connection ID on migration, as the peer will close the connection if the pool is exhausted. -### Consuming and Retiring Connection IDs {#retiring-cids} +### Consuming and Retiring Connection IDs {#retire-cid} An endpoint can change the connection ID it uses for a peer to another available one at any time during the connection. An endpoint consumes connection IDs in @@ -1085,8 +1092,9 @@ be used again and requests that the peer replace it with a new connection ID using a NEW_CONNECTION_ID frame. As discussed in {{migration-linkability}}, endpoints limit the use of a -connection ID to a single network path where possible. Endpoints SHOULD retire -connection IDs when no longer actively using the network path on which the +connection ID to packets sent from a single local address to a single +destination address. Endpoints SHOULD retire connection IDs when they are no +longer actively using either the local or destination address for which the connection ID was used. An endpoint might need to stop accepting previously issued connection IDs in @@ -1096,16 +1104,28 @@ field. The endpoint SHOULD continue to accept the previously issued connection IDs until they are retired by the peer. If the endpoint can no longer process the indicated connection IDs, it MAY close the connection. -Upon receipt of an increased Retire Prior To field, the peer MUST stop using the -corresponding connection IDs and retire them with RETIRE_CONNECTION_ID frames -before adding the newly provided connection ID to the set of active connection -IDs. This ordering allows an endpoint that has already supplied its peer with as -many connection IDs as allowed by the active_connection_id_limit transport -parameter to replace those connection IDs with new ones as necessary. Failure -to cease using the connection IDs when requested can result in connection -failures, as the issuing endpoint might be unable to continue using the -connection IDs with the active connection. - +Upon receipt of an increased Retire Prior To field, the peer MUST stop using +the corresponding connection IDs and retire them with RETIRE_CONNECTION_ID +frames before adding the newly provided connection ID to the set of active +connection IDs. This ordering allows an endpoint to replace all active +connection IDs without the possibility of a peer having no available connection +IDs and without exceeding the limit the peer sets in the +active_connection_id_limit transport parameter; see +{{transport-parameter-definitions}}. Failure to cease using the connection IDs +when requested can result in connection failures, as the issuing endpoint might +be unable to continue using the connection IDs with the active connection. + +An endpoint SHOULD limit the number of connection IDs it has retired locally and +have not yet been acknowledged. An endpoint SHOULD allow for sending and +tracking a number of RETIRE_CONNECTION_ID frames of at least twice the +active_connection_id limit. An endpoint MUST NOT forget a connection ID without +retiring it, though it MAY choose to treat having connection IDs in need of +retirement that exceed this limit as a connection error of type +CONNECTION_ID_LIMIT_ERROR. + +Endpoints SHOULD NOT issue updates of the Retire Prior To field before receiving +RETIRE_CONNECTION_ID frames that retire all connection IDs indicated by the +previous Retire Prior To value. ## Matching Packets to Connections {#packet-handling} @@ -1193,6 +1213,29 @@ SHOULD ignore any such packets. Servers MUST drop incoming packets under all other circumstances. +### Considerations for Simple Load Balancers + +A server deployment could load balance among servers using only source and +destination IP addresses and ports. Changes to the client's IP address or port +could result in packets being forwarded to the wrong server. Such a server +deployment could use one of the following methods for connection continuity +when a client's address changes. + +* Servers could use an out-of-band mechanism to forward packets to the correct + server based on Connection ID. + +* If servers can use a dedicated server IP address or port, other than the one + that the client initially connects to, they could use the preferred_address + transport parameter to request that clients move connections to that dedicated + address. Note that clients could choose not to use the preferred address. + +A server in a deployment that does not implement a solution to +maintain connection continuity during connection migration +SHOULD disallow migration using the disable_active_migration transport +parameter. + +Server deployments that use this simple form of load balancing MUST avoid the +creation of a stateless reset oracle; see {{reset-oracle}}. ## Life of a QUIC Connection {#connection-lifecycle} @@ -1343,16 +1386,15 @@ MUST NOT be used outside of draft implementations. ## Using Reserved Versions For a server to use a new version in the future, clients need to correctly -handle unsupported versions. To help ensure this, a server SHOULD include a -version that is reserved for forcing version negotiation (0x?a?a?a?a as defined -in {{versions}}) when generating a Version Negotiation packet. +handle unsupported versions. Some version numbers (0x?a?a?a?a as defined in +{{versions}}) are reserved for inclusion in fields that contain version +numbers. -The design of version negotiation permits a server to avoid maintaining state -for packets that it rejects in this fashion. - -A client MAY send a packet using a version that is reserved for forcing version -negotiation. This can be used to solicit a list of supported versions from a -server. +Endpoints MAY add reserved versions to any field where unknown or unsupported +versions are ignored to test that a peer correctly ignores the value. For +instance, an endpoint could include a reserved version in a Version Negotiation +packet; see {{packet-version}}. Endpoints MAY send packets with a reserved +version to test that a peer correctly discards the packet. # Cryptographic and Transport Handshake {#handshake} @@ -1418,9 +1460,9 @@ the first packet is of type Initial, with packet number 0, and contains a CRYPTO frame carrying the ClientHello. Note that multiple QUIC packets -- even of different packet types -- can be -coalesced into a single UDP datagram (see {{packet-coalesce}}), and so this -handshake may consist of as few as 4 UDP datagrams, or any number more. For -instance, the server's first flight contains Initial packets, +coalesced into a single UDP datagram; see {{packet-coalesce}}). As a result, +this handshake may consist of as few as 4 UDP datagrams, or any number more. +For instance, the server's first flight contains Initial packets, Handshake packets, and "0.5-RTT data" in 1-RTT packets with a short header. ~~~~ @@ -1436,8 +1478,8 @@ Initial[1]: ACK[0] Handshake[0]: CRYPTO[FIN], ACK[0] 1-RTT[0]: STREAM[0, "..."], ACK[0] -> - 1-RTT[1]: STREAM[3, "..."], ACK[0] - <- Handshake[1]: ACK[0] + Handshake[1]: ACK[0] + <- 1-RTT[1]: STREAM[3, "..."], ACK[0] ~~~~ {: #tls-1rtt-handshake title="Example 1-RTT Handshake"} @@ -1460,8 +1502,8 @@ Initial[1]: ACK[0] Handshake[0]: CRYPTO[FIN], ACK[0] 1-RTT[1]: STREAM[0, "..."] ACK[0] -> - 1-RTT[1]: STREAM[3, "..."], ACK[1] - <- Handshake[1]: ACK[0] + Handshake[1]: ACK[0] + <- 1-RTT[1]: STREAM[3, "..."], ACK[1] ~~~~ {: #tls-0rtt-handshake title="Example 0-RTT Handshake"} @@ -1497,11 +1539,11 @@ Source Connection ID values as the client's first Initial packet. Upon first receiving an Initial or Retry packet from the server, the client uses the Source Connection ID supplied by the server as the Destination Connection ID -for subsequent packets, including all subsequent 0-RTT packets. This means that -a client might have to change the connection ID it sets in the Destination -Connection ID field twice during connection establishment: once in response to a -Retry, and once in response to an Initial packet from the server. Once a client -has received an Initial packet from the server, it MUST discard any subsequent +for subsequent packets, including any 0-RTT packets. This means that a client +might have to change the connection ID it sets in the Destination Connection ID +field twice during connection establishment: once in response to a Retry, and +once in response to an Initial packet from the server. Once a client has +received a valid Initial packet from the server, it MUST discard any subsequent packet it receives with a different Source Connection ID. A client MUST change the Destination Connection ID it uses for sending packets @@ -1519,6 +1561,91 @@ lifetime of a connection, especially in response to connection migration ({{migration}}); see {{issue-cid}} for details. +## Authenticating Connection IDs {#cid-auth} + +The choice each endpoint makes about connection IDs during the handshake is +authenticated by including all values in transport parameters; see +{{transport-parameters}}. This ensures that all connection IDs used for the +handshake are also authenticated by the cryptographic handshake. + +Each endpoint includes the value of the Source Connection ID field from the +first Initial packet it sent in the initial_source_connection_id transport +parameter; see {{transport-parameter-definitions}}. A server includes the +Destination Connection ID field from the first Initial packet it received from +the client in the original_destination_connection_id transport parameter; if +the server sent a Retry packet this refers to the first Initial packet received +before sending the Retry packet. If it sends a Retry packet, a server also +includes the Source Connection ID field from the Retry packet in the +retry_source_connection_id transport parameter. + +The values provided by a peer for these transport parameters MUST match the +values that an endpoint used in the Destination and Source Connection ID fields +of Initial packets that it sent. Including connection ID values in transport +parameters and verifying them ensures that that an attacker cannot influence +the choice of connection ID for a successful connection by injecting packets +carrying attacker-chosen connection IDs during the handshake. An endpoint MUST +treat any of the following as a connection error of type PROTOCOL_VIOLATION: + +* absence of the initial_source_connection_id transport parameter from either + endpoint, + +* absence of the original_destination_connection_id transport parameter from + the server, + +* absence of the retry_source_connection_id transport parameter from the server + after receiving a Retry packet, + +* presence of the retry_source_connection_id transport parameter when no Retry + packet was received, or + +* a mismatch between values received from a peer in these transport parameters + and the value sent in the corresponding Destination or Source Connection ID + fields of Initial packets. + +If a zero-length connection ID is selected, the corresponding transport +parameter is included with a zero-length value. + +{{fig-auth-cid}} shows the connection IDs that are used in a complete +handshake. The exchange of Initial packets is shown, plus the later exchange of +1-RTT packets that includes the connection ID established during the handshake. + +~~~ +Client Server + +Initial: DCID=S1, SCID=C1 -> + <- Initial: DCID=C1, SCID=S3 + ... +1-RTT: DCID=S3 -> + <- 1-RTT: DCID=C1 +~~~ +{: #fig-auth-cid title="Use of Connection IDs in a Handshake"} + +{{fig-auth-cid-retry}} shows a similar handshake that includes a Retry packet. + +~~~ +Client Server + +Initial: DCID=S1, SCID=C1 -> + <- Retry: DCID=C1, SCID=S2 +Initial: DCID=S2, SCID=C1 -> + <- Initial: DCID=C1, SCID=S3 + ... +1-RTT: DCID=S3 -> + <- 1-RTT: DCID=C1 +~~~ +{: #fig-auth-cid-retry title="Use of Connection IDs in a Handshake with Retry"} + +For the handshakes in {{fig-auth-cid}} and {{fig-auth-cid-retry}} the client +sets the value of the initial_source_connection_id transport parameter to `C1`. +In {{fig-auth-cid-retry}}, the server sets original_destination_connection_id +to `S1`, retry_source_connection_id to `S2`, and initial_source_connection_id +to `S3`. In {{fig-auth-cid}}, the server sets +original_destination_connection_id to `S1`, initial_source_connection_id to +`S3`, and does not include retry_source_connection_id. Each endpoint validates +the transport parameters set by their peer, including the client confirming +that retry_source_connection_id is absent if no Retry packet was processed. + + ## Transport Parameters {#transport-parameters} During connection establishment, both endpoints make authenticated declarations @@ -1547,9 +1674,9 @@ An endpoint MUST NOT send a parameter more than once in a given transport parameters extension. An endpoint SHOULD treat receipt of duplicate transport parameters as a connection error of type TRANSPORT_PARAMETER_ERROR. -A server MUST include the original_connection_id transport parameter -({{transport-parameter-definitions}}) if it sent a Retry packet to enable -validation of the Retry, as described in {{packet-retry}}. +Endpoints use transport parameters to authenticate the negotiation of +connection IDs during the handshake; see {{cid-auth}}. + ### Values of Transport Parameters for 0-RTT {#zerortt-parameters} @@ -1566,9 +1693,11 @@ specify whether they MUST, MAY, or MUST NOT be stored for 0-RTT. A client need not store a transport parameter it cannot process. A client MUST NOT use remembered values for the following parameters: -ack_delay_exponent, max_ack_delay, original_connection_id, preferred_address, -and stateless_reset_token. The client MUST use the server's new values in the -handshake instead, and absent new values from the server, the default value. +ack_delay_exponent, max_ack_delay, initial_source_connection_id, +original_destination_connection_id, preferred_address, +retry_source_connection_id, and stateless_reset_token. The client MUST use the +server's new values in the handshake instead, and absent new values from the +server, the default value. A client that attempts to send 0-RTT data MUST remember all other transport parameters used by the server. The server can remember these transport @@ -1582,13 +1711,13 @@ limits or alter any values that might be violated by the client with its values for the following parameters ({{transport-parameter-definitions}}) that are smaller than the remembered value of the parameters. +* active_connection_id_limit * initial_max_data * initial_max_stream_data_bidi_local * initial_max_stream_data_bidi_remote * initial_max_stream_data_uni * initial_max_streams_bidi * initial_max_streams_uni -* active_connection_id_limit Omitting or setting a zero value for certain transport parameters can result in 0-RTT data being enabled, but not usable. The applicable subset of transport @@ -1731,13 +1860,13 @@ its own address (see {{token-integrity}}) and the client is able to return that token, it proves to the server that it received the token. A server can also use a Retry packet to defer the state and processing costs of -connection establishment. Requiring the server to provide a different -connection ID, along with the original_connection_id transport parameter defined -in {{transport-parameter-definitions}}, forces the server to demonstrate that -it, or an entity it cooperates with, received the original Initial packet from -the client. Providing a different connection ID also grants a server some -control over how subsequent packets are routed. This can be used to direct -connections to a different server instance. +connection establishment. Requiring the server to provide a different +connection ID, along with the original_destination_connection_id transport +parameter defined in {{transport-parameter-definitions}}, forces the server to +demonstrate that it, or an entity it cooperates with, received the original +Initial packet from the client. Providing a different connection ID also grants +a server some control over how subsequent packets are routed. This can be used +to direct connections to a different server instance. If a server receives a client Initial that can be unprotected but contains an invalid Retry token, it knows the client will not accept another Retry token. @@ -1866,7 +1995,7 @@ connection properties. Attackers could replay tokens to use servers as amplifiers in DDoS attacks. To protect against such attacks, servers SHOULD ensure that tokens sent in Retry packets are only accepted for a short time. Tokens that are provided in -NEW_TOKEN frames (see {{frame-new-token}}) need to be valid for longer, but +NEW_TOKEN frames ({{frame-new-token}}) need to be valid for longer, but SHOULD NOT be accepted multiple times in a short period. Servers are encouraged to allow tokens to be used only once, if possible. @@ -2022,7 +2151,7 @@ duration of the handshake. An endpoint MUST NOT initiate connection migration before the handshake is confirmed, as defined in section 4.1.2 of {{QUIC-TLS}}. An endpoint also MUST NOT send packets from a different local address, actively -initiating migration, if the peer sent the `disable_active_migration` transport +initiating migration, if the peer sent the disable_active_migration transport parameter during the handshake. An endpoint which has sent this transport parameter, but detects that a peer has nonetheless migrated to a different network MUST either drop the incoming packets on that path without generating a @@ -2093,9 +2222,10 @@ verifies ECN capability as described in {{ecn}}. Receiving acknowledgments for data sent on the new path serves as proof of the peer's reachability from the new address. Note that since acknowledgments may -be received on any path, return reachability on the new path is not -established. To establish return reachability on the new path, an endpoint MAY -concurrently initiate path validation {{migrate-validate}} on the new path. +be received on any path, return reachability on the new path is not established. +To establish return reachability on the new path, an endpoint MAY concurrently +initiate path validation {{migrate-validate}} on the new path or it MAY choose +to wait for the peer to send the next non-probing frame to its new address. ## Responding to Connection Migration {#migration-response} @@ -2145,8 +2275,8 @@ congestion window's worth of data per estimated round-trip time (kMinimumWindow, as defined in {{QUIC-RECOVERY}}). In the absence of this limit, an endpoint risks being used for a denial of service attack against an unsuspecting victim. Note that since the endpoint will not have any round-trip time measurements to -this address, the estimate SHOULD be the default initial value (see -{{QUIC-RECOVERY}}). +this address, the estimate SHOULD be the default initial value; see +{{QUIC-RECOVERY}}. If an endpoint skips validation of a peer address as described in {{migration-response}}, it does not need to limit its sending rate. @@ -2277,11 +2407,23 @@ linked by any other entity. At any time, endpoints MAY change the Destination Connection ID they send to a value that has not been used on another path. -An endpoint MUST use a new connection ID if it initiates connection migration as -described in {{initiating-migration}} or probes a new network path as described -in {{probing}}. An endpoint MUST use a new connection ID in response to a -change in the address of a peer if the packet with the new peer address uses an -active connection ID that has not been previously used by the peer. +An endpoint MUST NOT reuse a connection ID when sending from more than one local +address, for example when initiating connection migration as described in +{{initiating-migration}} or when probing a new network path as described in +{{probing}}. + +Similarly, an endpoint MUST NOT reuse a connection ID when sending to more than +one destination address. Due to network changes outside the control of its +peer, an endpoint might receive packets from a new source address with the same +destination connection ID, in which case it MAY continue to use the current +connection ID with the new remote address while still sending from the same +local address. + +These requirements regarding connection ID reuse apply only to the sending of +packets, as unintentional changes in path without a change in connection ID are +possible. For example, after a period of network inactivity, NAT rebinding +might cause packets to be sent on a new path when the client resumes sending. +An endpoint responds to such an event as described in {{migration-response}}. Using different connection IDs for packets sent in both directions on each new network path eliminates the use of the connection ID for linking packets from @@ -2290,9 +2432,15 @@ that packet numbers cannot be used to correlate activity. This does not prevent other properties of packets, such as timing and size, from being used to correlate activity. -Unintentional changes in path without a change in connection ID are possible. -For example, after a period of network inactivity, NAT rebinding might cause -packets to be sent on a new path when the client resumes sending. +An endpoint SHOULD NOT initiate migration with a peer that has requested a +zero-length connection ID, because traffic over the new path might be trivially +linkable to traffic over the old one. If the server is able to route packets +with a zero-length connection ID to the right connection, it means that the +server is using other information to demultiplex packets. For example, a server +might provide a unique address to every client, for instance using HTTP +alternative services {{?ALTSVC=RFC7838}}. Information that might allow correct +routing of packets across multiple network paths will also allow activity on +those paths to be linked by entities other than the peer. A client might wish to reduce linkability by employing a new connection ID and source UDP port when sending traffic after a period of inactivity. Changing the @@ -2334,10 +2482,11 @@ transport parameter in the TLS handshake. Servers MAY communicate a preferred address of each address family (IPv4 and IPv6) to allow clients to pick the one most suited to their network attachment. -Once the handshake is finished, the client SHOULD select one of the two +Once the handshake is confirmed, the client SHOULD select one of the two server's preferred addresses and initiate path validation (see -{{migrate-validate}}) of that address using the connection ID provided in the -preferred_address transport parameter. +{{migrate-validate}}) of that address using any previously unused active +connection ID, taken from either the preferred_address transport parameter or +a NEW_CONNECTION_ID frame. If path validation succeeds, the client SHOULD immediately begin sending all future packets to the new server address using the new connection ID and @@ -2363,6 +2512,11 @@ begins sending non-probing packets to the client exclusively from its preferred IP address. It SHOULD drop packets for this connection received on the old IP address, but MAY continue to process delayed packets. +The addresses that a server provides in the preferred_address transport +parameter are only valid for the connection in which they are provided. A +client MUST NOT use these for other connections, including connections that are +resumed from the current connection. + ### Interaction of Client Migration and Preferred Address @@ -2391,6 +2545,12 @@ address before path validation is complete. A client that migrates to a new address SHOULD use a preferred address from the same address family for the server. +The connection ID provided in the preferred_address transport parameter is not +specific to the addresses that are provided. This connection ID is provided to +ensure that the client has a connection ID available for migration, but the +client MAY use this connection ID on any path. + + ## Use of IPv6 Flow-Label and Migration {#ipv6-flow-label} Endpoints that send data using IPv6 SHOULD apply an IPv6 flow label @@ -2846,7 +3006,7 @@ are sent; such failures might only be detected by other means, such as timers. An endpoint that detects an error SHOULD signal the existence of that error to its peer. Both transport-level and application-level errors can affect an -entire connection (see {{connection-errors}}), while only application-level +entire connection; see {{connection-errors}}. Only application-level errors can be isolated to a single stream; see {{stream-errors}}. The most appropriate error code ({{error-codes}}) SHOULD be included in the @@ -2913,18 +3073,18 @@ prematurely cancelled by either endpoint. # Packets and Frames {#packets-frames} QUIC endpoints communicate by exchanging packets. Packets have confidentiality -and integrity protection (see {{packet-protected}}) and are carried in UDP -datagrams (see {{packet-coalesce}}). +and integrity protection; see {{packet-protected}}. Packets are carried in UDP +datagrams; see {{packet-coalesce}}. -This version of QUIC uses the long packet header (see {{long-header}}) during -connection establishment. Packets with the long header are Initial +This version of QUIC uses the long packet header during connection +establishment; see {{long-header}}. Packets with the long header are Initial ({{packet-initial}}), 0-RTT ({{packet-0rtt}}), Handshake ({{packet-handshake}}), and Retry ({{packet-retry}}). Version negotiation uses a version-independent -packet with a long header (see {{packet-version}}). +packet with a long header; see {{packet-version}}. -Packets with the short header ({{short-header}}) are designed for minimal -overhead and are used after a connection is established and 1-RTT keys are -available. +Packets with the short header are designed for minimal overhead and are used +after a connection is established and 1-RTT keys are available; see +{{short-header}}. ## Protected Packets {#packet-protected} @@ -3223,7 +3383,7 @@ contract: an endpoint promises to never intentionally delay acknowledgments of an ack-eliciting packet by more than the indicated value. If it does, any excess accrues to the RTT estimate and could result in spurious or delayed retransmissions from the peer. For Initial and Handshake packets, -a max_ack_delay of 0 is used. The sender uses the receiver's `max_ack_delay` +a max_ack_delay of 0 is used. The sender uses the receiver's max_ack_delay value in determining timeouts for timer-based retransmission, as detailed in Section 5.2.1 of {{QUIC-RECOVERY}}. @@ -3297,7 +3457,12 @@ to fit within a single QUIC packet. If it does not, then older ranges (those with the smallest packet numbers) are omitted. {{ack-tracking}} and {{ack-limiting}} describe an exemplary approach for -determining what packets to acknowledge in each ACK frame. +determining what packets to acknowledge in each ACK frame. Though the goal of +these algorithms is to generate an acknowledgment for every packet that is +processed, it is still possible for acknowledgments to be lost. A sender cannot +expect to receive an acknowledgment for every packet that the receiver +processes. + ### Receiver Tracking of ACK Frames {#ack-tracking} @@ -3328,9 +3493,20 @@ necessary if an ACK frame would be too large to fit in a packet, however receivers MAY also limit ACK frame size further to preserve space for other frames. -When discarding unacknowledged ACK Ranges, a receiver MUST retain the largest -received packet number. A receiver SHOULD retain ACK Ranges containing newly -received packets or higher-numbered packets. +A receiver MUST retain an ACK Range unless it can ensure that it will not +subsequently accept packets with numbers in that range. Maintaining a minimum +packet number that increases as ranges are discarded is one way to achieve this +with minimal state. + +Receivers can discard all ACK Ranges, but they MUST retain the largest packet +number that has been successfully processed as that is used to recover packet +numbers from subsequent packets; see {{packet-encoding}}. + +A receiver SHOULD include an ACK Range containing the largest received packet +number in every ACK frame. The Largest Acknowledged field is used in ECN +validation at a sender and including a lower value than what was included in a +previous ACK frame could cause ECN to be unnecessarily disabled; see +{{ecn-validation}}. A receiver that sends only non-ack-eliciting packets, such as ACK frames, might not receive an acknowledgement for a long period of time. This could cause the @@ -3464,8 +3640,8 @@ containing that information is acknowledged. * The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged. Endpoints SHOULD prioritize retransmission of data over sending new data, unless -priorities specified by the application indicate otherwise (see -{{stream-prioritization}}). +priorities specified by the application indicate otherwise; see +{{stream-prioritization}}. Even though a sender is encouraged to assemble frames containing up-to-date information every time it sends a packet, it is not forbidden to retransmit @@ -3628,17 +3804,22 @@ supports a reasonable Maximum Transmission Unit (MTU). Padding datagrams also helps reduce the amplitude of amplification attacks caused by server responses toward an unverified client address; see {{address-validation}}. +Enforcement of the max_udp_payload_size transport parameter +({{transport-parameter-definitions}}) might act as an additional limit on +packet size. Exceeding this limit can be avoided once the value is known. +However, prior to learning the value of the transport parameter, endpoints risk +datagrams being lost if they send packets larger than 1200 bytes. + Datagrams containing Initial packets MAY exceed 1200 bytes if the client -believes that the Path Maximum Transmission Unit (PMTU) supports the size that -it chooses. +believes that the network path and peer both support the size that it chooses. UDP datagrams MUST NOT be fragmented at the IP layer. In IPv4 {{!IPv4=RFC0791}}, the DF bit MUST be set to prevent fragmentation on the path. -A server MUST discard an Initial packet that is carried in a UDP datagram that -is smaller than 1200 bytes. A server MAY also immediately close the connection -by sending a CONNECTION_CLOSE frame with an error code of PROTOCOL_VIOLATION; -see {{immediate-close-hs}}. +A server MUST discard an Initial packet that is carried in a UDP datagram with +a payload that is less than 1200 bytes. A server MAY also immediately close the +connection by sending a CONNECTION_CLOSE frame with an error code of +PROTOCOL_VIOLATION; see {{immediate-close-hs}}. The server MUST also limit the number of bytes it sends before validating the address of the client; see {{address-validation}}. @@ -3646,11 +3827,12 @@ address of the client; see {{address-validation}}. ## Path Maximum Transmission Unit (PMTU) -The PMTU is the maximum size of the entire IP packet including the IP header, -UDP header, and UDP payload. The UDP payload includes the QUIC packet header, -protected payload, and any authentication fields. The PMTU can depend upon the -current path characteristics. Therefore, the current largest UDP payload an -implementation will send is referred to as the QUIC maximum packet size. +The Path Maximum Transmission Unit (PMTU) is the maximum size of the entire IP +packet including the IP header, UDP header, and UDP payload. The UDP payload +includes the QUIC packet header, protected payload, and any authentication +fields. The PMTU can depend on path characteristics, and can therefore change +over time. The largest UDP payload an endpoint sends at any given time is +referred to as the endpoint's maximum packet size. QUIC depends on a PMTU of at least 1280 bytes. This is the IPv6 minimum size {{?RFC8200}} and is also supported by most modern IPv4 networks. All QUIC @@ -3707,20 +3889,12 @@ associate the message with a corresponding transport connection {{!DPLPMTUD}}. ICMP message validation MUST include matching IP addresses and UDP ports {{!RFC8085}} and, when possible, connection IDs to an active QUIC session. -Further validation can also be provided: - -* An IPv4 endpoint could set the Don't Fragment (DF) bit on a small proportion - of packets, so that most invalid ICMP messages arrive when there are no DF - packets outstanding, and can therefore be identified as spurious. - -* An endpoint could store additional information from the IP or UDP headers to - use for validation (for example, the IP ID or UDP checksum). - The endpoint SHOULD ignore all ICMP messages that fail validation. -An endpoint MUST NOT increase PMTU based on ICMP messages. Any reduction in the -QUIC maximum packet size MAY be provisional until QUIC's loss detection -algorithm determines that the quoted packet has actually been lost. +An endpoint MUST NOT increase PMTU based on ICMP messages; see Section 3, clause +6 of {{!DPLPMTUD}}. Any reduction in the QUIC maximum packet size in response +to ICMP messages MAY be provisional until QUIC's loss detection algorithm +determines that the quoted packet has actually been lost. ## Datagram Packetization Layer PMTU Discovery @@ -3746,18 +3920,21 @@ apply if these messages are used by DPLPMTUD. ### PMTU Probes Containing Source Connection ID {#pmtu-probes-src-cid} -Endpoints that rely on the destination connection ID for routing QUIC packets -are likely to require that the connection ID be included in PMTU probe packets -to route any resulting ICMP messages ({{icmp-pmtud}}) back to the correct -endpoint. However, only long header packets ({{long-header}}) contain source -connection IDs, and long header packets are not decrypted or acknowledged by -the peer once the handshake is complete. One way to construct a PMTU probe is -to coalesce (see {{packet-coalesce}}) a Handshake packet ({{packet-handshake}}) -with a short header packet in a single UDP datagram. If the UDP datagram -reaches the endpoint, the Handshake packet will be ignored, but the short header -packet will be acknowledged. If the UDP datagram elicits an ICMP message, that -message will likely contain the source connection ID within the quoted portion -of the UDP datagram. +Endpoints that rely on the destination connection ID for routing incoming QUIC +packets are likely to require that the connection ID be included in PMTU probe +packets to route any resulting ICMP messages ({{icmp-pmtud}}) back to the +correct endpoint. However, only long header packets ({{long-header}}) contain +source connection IDs, and long header packets are not decrypted or acknowledged +by the peer once the handshake is complete. + +One way to construct a probe for the path MTU is to coalesce (see +{{packet-coalesce}}) a Handshake packet ({{packet-handshake}}) with a short +header packet in a single UDP datagram. If the UDP datagram reaches the +endpoint, the Handshake packet will be ignored, but the short header packet will +be acknowledged. If the UDP datagram causes an ICMP message to be sent, the +first part of the datagram will be quoted in that message. If the source +connection ID is within the quoted portion of the UDP datagram, that could be +used for routing. # Versions {#versions} @@ -3991,13 +4168,13 @@ contain these additional fields: Reserved Bits: : Two bits (those with a mask of 0x0c) of byte 0 are reserved across multiple - packet types. These bits are protected using header protection (see Section - 5.4 of {{QUIC-TLS}}). The value included prior to protection MUST be set to 0. + packet types. These bits are protected using header protection; see Section + 5.4 of {{QUIC-TLS}}. The value included prior to protection MUST be set to 0. An endpoint MUST treat receipt of a packet that has a non-zero value for these bits, after removing both packet and header protection, as a connection error of type PROTOCOL_VIOLATION. Discarding such a packet after only removing - header protection can expose the endpoint to attacks (see Section 9.3 of - {{QUIC-TLS}}). + header protection can expose the endpoint to attacks; see Section 9.3 of + {{QUIC-TLS}}. Packet Number Length: @@ -4006,7 +4183,7 @@ Packet Number Length: number, encoded as an unsigned, two-bit integer that is one less than the length of the packet number field in bytes. That is, the length of the packet number field is the value of this field, plus one. These bits are protected - using header protection (see Section 5.4 of {{QUIC-TLS}}). + using header protection; see Section 5.4 of {{QUIC-TLS}}. Length: @@ -4019,7 +4196,7 @@ Packet Number: : The packet number field is 1 to 4 bytes long. The packet number has confidentiality protection separate from packet protection, as described in Section 5.4 of {{QUIC-TLS}}. The length of the packet number field is encoded - in the Packet Number Length bits of byte 0 (see above). + in the Packet Number Length bits of byte 0; see above. ### Version Negotiation Packet {#packet-version} @@ -4038,9 +4215,9 @@ Version Negotiation Packet { Unused (7), Version (32) = 0, DCID Length (8), - Destination Connection ID (0..160), + Destination Connection ID (0..2040), SCID Length (8), - Source Connection ID (0..160), + Source Connection ID (0..2040), Supported Version (32) ..., } ~~~ @@ -4102,6 +4279,7 @@ Initial Packet { Source Connection ID (0..160), Token Length (i), Token (..), + Length (i), Packet Number (8..32), Packet Payload (..), } @@ -4170,7 +4348,7 @@ when it receives its first Handshake packet. Though packets might still be in flight or awaiting acknowledgment, no further Initial packets need to be exchanged beyond this point. Initial packet protection keys are discarded (see Section 4.10.1 of {{QUIC-TLS}}) along with any loss recovery and congestion -control state (see Section 6.5 of {{QUIC-RECOVERY}}). +control state; see Section 6.5 of {{QUIC-RECOVERY}}. Any data in CRYPTO frames is discarded - and no longer retransmitted - when Initial keys are discarded. @@ -4198,6 +4376,7 @@ limitations. Destination Connection ID (0..160), SCID Length (8), Source Connection ID (0..160), + Length (i), Packet Number (8..32), Packet Payload (..), } @@ -4254,8 +4433,7 @@ Handshake Packet { Destination Connection ID (0..160), SCID Length (8), Source Connection ID (0..160), - Token Length (i), - Token (..), + Length (i), Packet Number (8..32), Packet Payload (..), } @@ -4268,8 +4446,8 @@ to the server. The Destination Connection ID field in a Handshake packet contains a connection ID that is chosen by the recipient of the packet; the Source Connection ID -includes the connection ID that the sender of the packet wishes to use (see -{{negotiating-connection-ids}}). +includes the connection ID that the sender of the packet wishes to use; see +{{negotiating-connection-ids}}. Handshake packets are their own packet number space, and thus the first Handshake packet sent by a server contains a packet number of 0. @@ -4294,11 +4472,12 @@ Retry Packet { Header Form (1) = 1, Fixed Bit (1) = 1, Long Packet Type (2) = 3, - Type-Specific Bits (4), + Unused (4), Version (32), DCID Length (8), Destination Connection ID (0..160), SCID Length (8), + Source Connection ID (0..160), Retry Token (..), Retry Integrity Tag (128), } @@ -4359,8 +4538,8 @@ value from the Source Connection ID in the Retry packet. Changing Destination Connection ID also results in a change to the keys used to protect the Initial packet. It also sets the Token field to the token provided in the Retry. The client MUST NOT change the Source Connection ID because the server could include -the connection ID as part of its token validation logic (see -{{token-integrity}}). +the connection ID as part of its token validation logic; see +{{token-integrity}}. A Retry packet does not include a packet number and cannot be explicitly acknowledged by a client. @@ -4384,15 +4563,16 @@ A client MUST NOT reset the packet number for any packet number space after processing a Retry packet; {{packet-0rtt}} contains more information on this. A server acknowledges the use of a Retry packet for a connection using the -original_connection_id transport parameter (see -{{transport-parameter-definitions}}). If the server sends a Retry packet, it -MUST include the Destination Connection ID field from the client's first -Initial packet in the transport parameter. +retry_source_connection_id transport parameter; see +{{transport-parameter-definitions}}. If the server sends a Retry packet, it +also subsequently includes the value of the Source Connection ID field from +the Retry packet in its retry_source_connection_id transport parameter. If the client received and processed a Retry packet, it MUST validate that the -original_connection_id transport parameter is present and correct; otherwise, it -MUST validate that the transport parameter is absent. A client MUST treat a -failed validation as a connection error of type TRANSPORT_PARAMETER_ERROR. +retry_source_connection_id transport parameter is present and correct; +otherwise, it MUST validate that the transport parameter is absent. A client +MUST treat a failed validation as a connection error of type +PROTOCOL_VIOLATION. ## Short Header Packets {#short-header} @@ -4435,13 +4615,13 @@ as described in {{spin-bit}}. Reserved Bits: : The next two bits (those with a mask of 0x18) of byte 0 are reserved. These - bits are protected using header protection (see Section 5.4 of - {{QUIC-TLS}}). The value included prior to protection MUST be set to 0. An + bits are protected using header protection; see Section 5.4 of + {{QUIC-TLS}}. The value included prior to protection MUST be set to 0. An endpoint MUST treat receipt of a packet that has a non-zero value for these bits, after removing both packet and header protection, as a connection error of type PROTOCOL_VIOLATION. Discarding such a packet after only removing - header protection can expose the endpoint to attacks (see Section 9.3 of - {{QUIC-TLS}}). + header protection can expose the endpoint to attacks; see Section 9.3 of + {{QUIC-TLS}}. Key Phase: @@ -4456,7 +4636,7 @@ Packet Number Length: the length of the packet number, encoded as an unsigned, two-bit integer that is one less than the length of the packet number field in bytes. That is, the length of the packet number field is the value of this field, plus one. These - bits are protected using header protection (see Section 5.4 of {{QUIC-TLS}}). + bits are protected using header protection; see Section 5.4 of {{QUIC-TLS}}. Destination Connection ID: @@ -4532,7 +4712,7 @@ connection. # Transport Parameter Encoding {#transport-parameter-encoding} -The `extension_data` field of the quic_transport_parameters extension defined in +The extension_data field of the quic_transport_parameters extension defined in {{QUIC-TLS}} contains the QUIC transport parameters. They are encoded as a sequence of transport parameters, as shown in {{transport-parameter-sequence}}: @@ -4555,7 +4735,7 @@ Transport Parameter { ~~~ {: #transport-parameter-encoding-fig title="Transport Parameter Encoding"} -The Transport Param Length field contains the length of the Transport +The Transport Parameter Length field contains the length of the Transport Parameter Value field. QUIC encodes transport parameters into a sequence of bytes, which are then @@ -4576,18 +4756,16 @@ This section details the transport parameters defined in this document. Many transport parameters listed here have integer values. Those transport parameters that are identified as integers use a variable-length integer -encoding (see {{integer-encoding}}) and have a default value of 0 if the -transport parameter is absent, unless otherwise stated. +encoding; see {{integer-encoding}}. Transport parameters have a default value +of 0 if the transport parameter is absent unless otherwise stated. The following transport parameters are defined: -original_connection_id (0x00): +original_destination_connection_id (0x00): : The value of the Destination Connection ID field from the first Initial packet - sent by the client. This transport parameter is only sent by a server. This - is the same value sent in the "Original Destination Connection ID" field of a - Retry packet; see {{packet-retry}}. A server MUST include the - original_connection_id transport parameter if it sent a Retry packet. + sent by the client; see {{cid-auth}}. This transport parameter is only sent + by a server. max_idle_timeout (0x01): @@ -4711,11 +4889,19 @@ preferred_address (0x0d): network byte order. : The Connection ID field and the Stateless Reset Token field contain an - alternative connection ID that has a sequence number of 1 ({{issue-cid}}). + alternative connection ID that has a sequence number of 1; see {{issue-cid}}. Having these values bundled with the preferred address ensures that there will be at least one unused active connection ID when the client initiates migration to the preferred address. +: The Connection ID and Stateless Reset Token fields of a preferred address are + identical in syntax and semantics to the corresponding fields of a + NEW_CONNECTION_ID frame ({{frame-new-connection-id}}). A server that chooses + a zero-length connection ID MUST NOT provide a preferred address. Similarly, + a server MUST NOT include a zero-length connection ID in this transport + parameter. A client MUST treat violation of these requirements as a + connection error of type TRANSPORT_PARAMETER_ERROR. + ~~~ Preferred Address { IPv4 Address (32), @@ -4736,11 +4922,24 @@ active_connection_id_limit (0x0e): to store. This value includes the connection ID received during the handshake, that received in the preferred_address transport parameter, and those received in NEW_CONNECTION_ID frames. - Unless a zero-length connection ID is being used, the value of the - active_connection_id_limit parameter MUST be no less than 2. If this - transport parameter is absent, a default of 2 is assumed. - When a zero-length connection ID is being used, the active_connection_id_limit - parameter MUST NOT be sent. + The value of the active_connection_id_limit parameter MUST be at least 2. + An endpoint that receives a value less than 2 MUST close the connection + with an error of type TRANSPORT_PARAMETER_ERROR. + If this transport parameter is absent, a default of 2 is assumed. If an + endpoint issues a zero-length connection ID, it will never send a + NEW_CONNECTION_ID frame and therefore ignores the active_connection_id_limit + value received from its peer. + +initial_source_connection_id (0x0f): + +: The value that the endpoint included in the Source Connection ID field of the + first Initial packet it sends for the connection; see {{cid-auth}}. + +retry_source_connection_id (0x10): + +: The value that the the server included in the Source Connection ID field of a + Retry packet; see {{cid-auth}}. This transport parameter is only sent by a + server. If present, transport parameters that set initial flow control limits (initial_max_stream_data_bidi_local, initial_max_stream_data_bidi_remote, and @@ -4749,10 +4948,11 @@ initial_max_stream_data_uni) are equivalent to sending a MAX_STREAM_DATA frame immediately after opening. If the transport parameter is absent, streams of that type start with a flow control limit of 0. -A client MUST NOT include server-only transport parameters -(original_connection_id, stateless_reset_token, or preferred_address). A server -MUST treat receipt of any of these transport parameters as a connection error of -type TRANSPORT_PARAMETER_ERROR. +A client MUST NOT include any server-only transport parameter: +original_destination_connection_id, preferred_address, +retry_source_connection_id, or stateless_reset_token. A server MUST treat +receipt of any of these transport parameters as a connection error of type +TRANSPORT_PARAMETER_ERROR. # Frame Types and Formats {#frame-formats} @@ -4851,7 +5051,7 @@ ACK Delay: when this ACK was sent and when the largest acknowledged packet, as indicated in the Largest Acknowledged field, was received by this peer. The value of the ACK Delay field is scaled by multiplying the encoded value by 2 to the - power of the value of the `ack_delay_exponent` transport parameter set by the + power of the value of the ack_delay_exponent transport parameter set by the sender of the ACK frame; see {{transport-parameter-definitions}}. Scaling in this fashion allows for a larger range of values with a shorter encoding at the cost of lower resolution. Because the receiver doesn't use the ACK Delay @@ -5196,8 +5396,8 @@ STREAM frames contain the following fields: Stream ID: -: A variable-length integer indicating the stream ID of the stream (see - {{stream-id}}). +: A variable-length integer indicating the stream ID of the stream; see + {{stream-id}}. Offset: @@ -5261,8 +5461,8 @@ the initial limits; see {{zerortt-parameters}}. The MAX_STREAM_DATA frame (type=0x11) is used in flow control to inform a peer of the maximum amount of data that can be sent on a stream. -A MAX_STREAM_DATA frame can be sent for streams in the Recv state (see -{{stream-send-states}}). Receiving a MAX_STREAM_DATA frame for a +A MAX_STREAM_DATA frame can be sent for streams in the Recv state; see +{{stream-send-states}}. Receiving a MAX_STREAM_DATA frame for a locally-initiated stream that has not yet been created MUST be treated as a connection error of type STREAM_STATE_ERROR. An endpoint that receives a MAX_STREAM_DATA frame for a receive-only stream MUST terminate the connection @@ -5349,8 +5549,8 @@ includes streams that have been closed as well as those that are open. ## DATA_BLOCKED Frame {#frame-data-blocked} A sender SHOULD send a DATA_BLOCKED frame (type=0x14) when it wishes to send -data, but is unable to due to connection-level flow control (see -{{flow-control}}). DATA_BLOCKED frames can be used as input to tuning of flow +data, but is unable to due to connection-level flow control; see +{{flow-control}}. DATA_BLOCKED frames can be used as input to tuning of flow control algorithms; see {{fc-credit}}. The DATA_BLOCKED frame is shown in {{fig-data-blocked}}. @@ -5464,8 +5664,8 @@ Sequence Number: Retire Prior To: -: A variable-length integer indicating which connection IDs should be retired. - See {{retiring-cids}}. +: A variable-length integer indicating which connection IDs should be retired; + see {{retire-cid}}. Length: @@ -5502,7 +5702,7 @@ IDs, the endpoint MAY treat that receipt as a connection error of type PROTOCOL_VIOLATION. The Retire Prior To field counts connection IDs established during connection -setup and the preferred_address transport parameter; see {{retiring-cids}}. The +setup and the preferred_address transport parameter; see {{retire-cid}}. The Retire Prior To field MUST be less than or equal to the Sequence Number field. Receiving a value greater than the Sequence Number MUST be treated as a connection error of type FRAME_ENCODING_ERROR. @@ -5518,6 +5718,7 @@ NEW_CONNECTION_ID frame MUST send a corresponding RETIRE_CONNECTION_ID frame that retires the newly received connection ID, unless it has already done so for that sequence number. + ## RETIRE_CONNECTION_ID Frame {#frame-retire-connection-id} An endpoint sends a RETIRE_CONNECTION_ID frame (type=0x19) to indicate that it @@ -5544,8 +5745,7 @@ RETIRE_CONNECTION_ID frames contain the following fields: Sequence Number: -: The sequence number of the connection ID being retired. See - {{retiring-cids}}. +: The sequence number of the connection ID being retired; see {{retire-cid}}. Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number greater than any previously sent to the peer MUST be treated as a connection error of @@ -6495,7 +6695,7 @@ The initial contents of this registry are shown in {{iana-tp-table}}. | Value| Parameter Name | Specification | |:-----|:----------------------------|:------------------------------------| -| 0x00 | original_connection_id | {{transport-parameter-definitions}} | +| 0x00 | original_destination_connection_id | {{transport-parameter-definitions}} | | 0x01 | max_idle_timeout | {{transport-parameter-definitions}} | | 0x02 | stateless_reset_token | {{transport-parameter-definitions}} | | 0x03 | max_udp_payload_size | {{transport-parameter-definitions}} | @@ -6510,10 +6710,12 @@ The initial contents of this registry are shown in {{iana-tp-table}}. | 0x0c | disable_active_migration | {{transport-parameter-definitions}} | | 0x0d | preferred_address | {{transport-parameter-definitions}} | | 0x0e | active_connection_id_limit | {{transport-parameter-definitions}} | +| 0x0f | initial_source_connection_id | {{transport-parameter-definitions}} | +| 0x10 | retry_source_connection_id | {{transport-parameter-definitions}} | {: #iana-tp-table title="Initial QUIC Transport Parameters Entries"} Additionally, each value of the format `31 * N + 27` for integer values of N -(that is, `27`, `58`, `89`, ...) are reserved and MUST NOT be assigned by IANA. +(that is, 27, 58, 89, ...) are reserved and MUST NOT be assigned by IANA. ## QUIC Frame Type Registry {#iana-frames} @@ -6538,9 +6740,9 @@ Frame Name: In addition to the advice in {{iana-policy}}, specifications for new permanent registrations SHOULD describe the means by which an endpoint might determine that it can send the identified type of frame. An accompanying transport -parameter registration (see {{iana-transport-parameters}}) is expected for most -registrations. Specifications for permanent registrations also needs to -describe the format and assigned semantics of any fields in the frame. +parameter registration is expected for most registrations; see +{{iana-transport-parameters}}. Specifications for permanent registrations also +needs to describe the format and assigned semantics of any fields in the frame. The initial contents of this registry are tabulated in {{frame-types}}. @@ -6675,6 +6877,32 @@ incurred. Issue and pull request numbers are listed with a leading octothorp. +## Since draft-ietf-quic-transport-27 + +- Allowed CONNECTION_CLOSE in any packet number space, with a requirement to + use a new transport-level error for application-specific errors in Initial + and Handshake packets (#3430, #3435, #3440) +- Clearer requirements for address validation (#2125, #3327) +- Security analysis of handshake and migration (#2143, #2387, #2925) +- The entire payload of a datagram is used when counting bytes for + mitigating amplification attacks (#3333, #3470) +- Connection IDs can be used at any time, including in the handshake (#3348, + #3560, #3438, #3565) +- Only one ACK should be sent for each instance of reordering (#3357, #3361) +- Remove text allowing a server to proceed with a bad Retry token (#3396, + #3398) +- Ignore active_connection_id_limit with a zero-length connection ID (#3427, + #3426) +- Require active_connection_id_limit be remembered for 0-RTT (#3423, #3425) +- Require ack_delay not be remembered for 0-RTT (#3433, #3545) +- Redefined max_packet_size to max_udp_datagram_size (#3471, #3473) +- Guidance on limiting outstanding attempts to retire connection IDs (#3489, + #3509, #3557, #3547) +- Restored text on dropping bogus Version Negotiation packets (#3532, #3533) +- Clarified that largest acknowledged needs to be saved, but not necessarily + signaled in all cases (#3541, #3581) +- Addressed linkability risk with the use of preferred_address (#3559, #3563) + ## Since draft-ietf-quic-transport-26 - Change format of transport parameters to use varints (#3294, #3169) @@ -7178,34 +7406,35 @@ work by Jim Roskind {{EARLY-DESIGN}}. The IETF QUIC Working Group received an enormous amount of support from many people. The following people provided substantive contributions to this document: -Alessandro Ghedini, -Alyssa Wilk, -Antoine Delignat-Lavaud, -Brian Trammell, -Christian Huitema, -Colin Perkins, -David Schinazi, -Dmitri Tikhonov, -Eric Kinnear, -Eric Rescorla, -Gorry Fairhurst, -Ian Swett, -Igor Lubashev, , -Lucas Pardue, -Magnus Westerlund, -Marten Seemann, -Martin Duke, -Mike Bishop, , , -Nick Banks, -Nick Harper, -Patrick McManus, -Roberto Peon, -Ryan Hamilton, -Subodh Iyengar, -Tatsuhiro Tsujikawa, -Ted Hardie, -Tom Jones, -and Victor Vasiliev. + +- Alessandro Ghedini +- Alyssa Wilk +- Antoine Delignat-Lavaud +- Brian Trammell +- Christian Huitema +- Colin Perkins +- David Schinazi +- Dmitri Tikhonov +- Eric Kinnear +- Eric Rescorla +- Gorry Fairhurst +- Ian Swett +- Igor Lubashev +- +- Lucas Pardue +- Magnus Westerlund +- Marten Seemann +- Martin Duke +- Mike Bishop +- +- +- Nick Banks +- Nick Harper +- Patrick McManus +- Roberto Peon +- Ryan Hamilton +- Subodh Iyengar +- Tatsuhiro Tsujikawa +- Ted Hardie +- Tom Jones +- Victor Vasiliev diff --git a/protection-samples.js b/protection-samples.js index 9acff6bc5d..9a4993784b 100755 --- a/protection-samples.js +++ b/protection-samples.js @@ -6,19 +6,25 @@ 'use strict'; -var buffer = require('buffer'); +require('buffer'); var crypto = require('crypto'); -var assert = require('assert'); var INITIAL_SALT = Buffer.from('c3eef712c72ebb5a11a7d2432bb46365bef9f502', 'hex'); var SHA256 = 'sha256'; var AES_GCM = 'aes-128-gcm'; var AES_ECB = 'aes-128-ecb'; -var version = 'ff00001b'; +const draft_version = 28; +var version = 'ff0000' + draft_version.toString(16); + +function chunk(s, n) { + return (new Array(Math.ceil(s.length / n))) + .fill() + .map((_, i) => s.slice(i * n, i * n + n)); +} function log(m, k) { - console.log(m + ' [' + k.length + ']: ' + k.toString('hex')); + console.log(m + ' [' + k.length + ']: ' + chunk(k.toString('hex'), 32).join(' ')); }; class HMAC { @@ -132,7 +138,7 @@ class InitialProtection { log('hp sample', sample); // var ctr = crypto.createCipheriv('aes-128-ctr', this.hp, sample); // var mask = ctr.update(Buffer.alloc(5)); - var ecb = crypto.createCipheriv('aes-128-ecb', this.hp, Buffer.alloc(0)); + var ecb = crypto.createCipheriv(AES_ECB, this.hp, Buffer.alloc(0)); var mask = ecb.update(sample); log('hp mask', mask); return mask; diff --git a/workflow.xml b/workflow.xml deleted file mode 100644 index aa0c254010..0000000000 --- a/workflow.xml +++ /dev/null @@ -1 +0,0 @@ -7Vxbc5w2FP41O20f7AHEbR/jdZymk3ZS2500Tx0taBc1gChibW9+fSWQWEDYi73c7ElmkiwHxOWc8507LMAqeviQwiT4nfgoXBia/7AAlwvDWOou+5cT9gXBMeyCsE2xX5D0A+EGf0eCqAnqDvuI1g7MCAkznNSJHolj5GU1GkxTcl8/bEPC+lUTuEUK4caDoUr9gv0sKKiufApO/xXhbSCvrNvLYs8aet+2KdnF4noLA2zyP8XuCMpziQelAfTJfYUE3i/AKiUkK35FDysUctZKthXrrh7ZW953iuKsy4LIodruu34d3NFb/Tq5D+Lf/jnTNfE0NNtLjiCfMUhskjQLyJbEMHx/oF7kT434eTW2dTjmEyEJI+qM+C/Ksr2QNtxlhJGCLArFXvSAs7/58nNLbH2t7Ll8EGfON/ZyI87SfWUR3/xa3XdYlm/JdcXz8Yd6lHGCRMku9dAT3FoKpchgukXZk2x1Sgkz4CASIXZLbGWKQpjhu/qtQKHC2/K4gxjZDyHJ50jVMd+6VFHsv+PoZ5teCCnFXkG8wqG8nQ2JsysY4ZCvWTHZYpQynvyB7qfSCseYUivkfd7BcCcuxXlhaB8pZYSmwtTV4T7AGbpJYM6Je+YJ6qLfMLavSEjSfC3wIXI3XCI0S8k3VNljey5ab0oB3KE0Qw9Pi0Dll1hgmsK0CtcDhC27P9hxXVrfoGLDbW0o3OmOwuIURRDHdFI8VtB4wGY7HnvExelGUCz9TDC79kHsy7rYdXNZP0UBRLGqIdLyNk6R8rQ+8zV5TKEDXYyjbk3qMsswbRKh6m9VqOakQtVUe3ybYp4RKN4uINF6R5/t6Vi8b3itns6317Zl9+Tp7IbJs1VX57Z4OncwT2dYryXC7FPxT1bndo9mG41Axhnbo5kKTlhSjrcxX1lEh3bImHWxZhG0vc1Ktr40XuwBE1YDE4YzefhnGq8FFHPwIrrV1YuARzRhJC8ib7SCDuTjjDBPEs4YIMCYHUAMZ2KAVBGinS+fzoX4xmeUYvbwKJ0RcMo66VHgFFXM6WJqXXUrmHo7SjGJVU0IQ5xQLnEawIQTvZDs/JkWH5ruB3RF13AxGVCj3T2iHSzTGBh8aZ2v1blp5y5wn43dPjFod06BAJgWg2rVL0lJQij3XU3N+HlHdzAM+e1B9vfz9S8dtEei1SfeLso5eQyw60KbPq0nRrBdQ7ApkTkdgq1pihDHa+l9QufkaK5bWXBpj5xEAdXb/UBaayRqaebUSAPurLzgueaUhEc7XmxrSJdWOKpOLm3aqp680QrQViSmKKY7Fu1oKxiWaZmvSPkgQ32mxb6mX3KMjn5puLxNdtZ+5G0nAczsDLBpeyGgpRz4dvK2BrqWk0d9pmrPYjKTtO0F/akymIxJjGqRZO+AOhUm7ZGk49TDFVMfOZI0pzW3z5D1YMM7/atJF7trTmt3LdXuMvDDiNvNeE35f0VBp0bqYCmYuczqwk4Rxd/hOj+AiyThypw/kHWxsC4ZBYa8/8MkxBjO3egFN7vYg+E7sSPCvp9rXgjXKLwoxyHrARMfiHzCcIuxTnEni3Jasirop0DyqJ0/0851VzZkBJDPxEVfah/kIWSzoWigJFLtNQSQFowSES64UoPaWXevLbcuBrfN5WotLrckDuBz26DWtLltZkrp0Ry3USpjKg9utTy3pPXtx4yGHytMp+LH1P60+zKHyLgH95XDhJHpfMNguWxIujhjv5CzpCOZRylgXr7T7O473Ul9p6kazgilfNJH+3ytGswTqgC+hVzfbDOarrEGdl8jP6BRzuyapww43uAqLGaZHkWLl/TAXwvfdW16xluqbkte8yF3tmcDBYzt/3b8hZKL+rx7SZaCkYXpsxRBfy9Pxu6uOF+fYSSzfLQwgnqXqDLj9vOCMqXA8fY2N6Zn5lTKYJv1YoFluaou6C26YAynC2o/T45qDZMGtMsvRJvsOdJ76l2MSYLSpmiBZOyR2GxAyart+8qY0RsRbonbbQp9zGR1iVOW//G6IrhEkGZPQlouquy7ury6Wq360QnQaCECS53pcKxRdUJ1uSwdPDskg29FL0bKRB294dy1jqnocCKWYUOLcw90KdVPMENnNxks4tmUeIjSitc+HCiJiSTcBpjmAdqi6DTfYd54xhTmmDM0suHRfcDP++dfH9kzaF9I+o0Jkv36wMK15Ce+MuTXp+L6iby+tiGpDP7oeeV+kmdoY13gomZc1Q5BEvrFCJaibcZjE5WEqcsmzLOlAPs+ipXUrY+QsWE1VJVq9SQ6GE6n1FEERRJHgm9Ik+J16w1+4MyaplvTKAjYLbwFLawdkLPG8JwdJbRtctaZnLNqF6xvzo7jZBqcdTQ1iBiZs2qtE3rC/K/z+ly85zaWnScunYFHomgX42w/o4CiB+GUr7qUFsVRrfXIAYCa3dfFU2QB6ivEr1wQVsMAqYIYOxJTU+u6ILwA4nTeMfcAEHH0lmm9kSUzTUN8xFEGe5h35a1GNdNsymjoUQZHjUT5bEsfeJl5f9o+2p8Gmpz82deuOeP2tN32gYu8gixyQY8PX76+DrXTqPN2dUYDdqjteU0oL460JWf6no7R+eM8A1lg22iolunUT9G5Cd84URkwjWbK2wpVTQ09MjJRUS/NBTUFO7d0cEzJThuB7/C+5KTzGkeddVdVKRfKCK7ZmhxcVcAc47WxPgh2+ne+2sUKGmM4ZaVhNLGqxa8yN2Wen+dJDPU4KnPWygvyavo6/1mAZmW3rNUPMArANg/fgiwkdvjeJnj/Pw== \ No newline at end of file