Internet-Draft | CONNECT-UDP Bind | October 2024 |
Schinazi & Singh | Expires 21 April 2025 | [Page] |
The mechanism to proxy UDP in HTTP only allows each UDP Proxying request to transmit to a specific host and port. This is well suited for UDP client-server protocols such as HTTP/3, but is not sufficient for some UDP peer-to-peer protocols like WebRTC. This document proposes an extension to UDP Proxying in HTTP that enables such use-cases.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://ietf-wg-masque.github.io/draft-ietf-masque-connect-udp-listen/draft-ietf-masque-connect-udp-listen.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-ietf-masque-connect-udp-listen/.¶
Discussion of this document takes place on the MASQUE Working Group mailing list (mailto:[email protected]), which is archived at https://mailarchive.ietf.org/arch/browse/masque/. Subscribe at https://www.ietf.org/mailman/listinfo/masque/.¶
Source for this draft and an issue tracker can be found at https://github.com/ietf-wg-masque/draft-ietf-masque-connect-udp-listen.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 21 April 2025.¶
Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The mechanism to proxy UDP in HTTP [CONNECT-UDP] allows creating tunnels for communicating UDP payloads [UDP] to a fixed host and port. Combined with the HTTP CONNECT method (see Section 9.3.6 of [HTTP]), it allows proxying the majority of a Web Browser's HTTP traffic. However WebRTC [WebRTC] relies on ICE [ICE] to provide connectivity between two Web browsers, and ICE relies on the ability to send and receive UDP packets to multiple hosts. While in theory it might be possible to accomplish this using multiple UDP Proxying HTTP requests, HTTP semantics [HTTP] do not guarantee that distinct requests will be handled by the same server. This can lead to the UDP packets being sent from distinct IP addresses, thereby preventing ICE from operating correctly. Consequently, UDP Proxying requests cannot enable WebRTC connectivity between peers.¶
This document describes an extension to UDP Proxying in HTTP that allows sending and receiving UDP payloads to multiple hosts within the scope of a single UDP Proxying HTTP request.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses terminology from [CONNECT-UDP] and notational conventions from [QUIC]. This document uses the terms Boolean, Integer, and List from Section 3 of [STRUCTURED-FIELDS] to specify syntax and parsing. This document uses Augmented Backus-Naur Form and parsing/serialization behaviors from [ABNF]¶
In unextended UDP Proxying requests, the target host is encoded in the HTTP request path or query. For Bound UDP Proxying, the target is either conveyed in each HTTP Datagram (see Section 4.1), or registered via capsules and then compressed (see Section 5.2).¶
When performing URI Template Expansion of the UDP Proxying template (see Section 3 of [CONNECT-UDP]), the client sets both the target_host and the target_port variables to the '*' character (ASCII character 0x2A).¶
When sending the UDP Proxying request to the proxy, the client adds the "Connect-UDP-Bind" header field to identify it as such. If the proxy accepts the CONNECT UDP Bind request, it adds the allocated public IP:port tuples for the client to the response; see Section 8.¶
Endpoints exchange COMPRESSION_ASSIGN capsules in order to establish which IP a given context ID corresponds to. The context ID can correspond to both compressed and uncompressed payloads to/from any target and are configured as defined in Section 5.¶
This extension leverages context IDs (see Section 4 of [CONNECT-UDP]) to compress the target IP address and port when encoding datagrams on the wire. Endpoint start by registering a context ID and the IP/ports it's associated with by sending a COMPRESSION_ASSIGN capsule to its peer. The peer will then echo that capsule to indicate it's received it and estabished its own mapping. From then on, both endpoints are aware of the context ID and can send compressed datagrams. Later, any endpoint can decide to close the compression context by sending a COMPRESSION_CLOSE capsule.¶
The context ID 0 was reserved by unextended connect-udp and is not used by this extension. Once an endpoint has ascertained that the peer supports this extension (see Section 7), the endpoint MUST NOT send any datagrams with context ID set to 0, and MUST silently drop any received datagrams with context ID set to 0.¶
As mandated in Section 4 of [CONNECT-UDP], clients will allocate even context IDs while proxies will allocate odd ones. They MAY pre-emptively use Context IDs not yet acknowledged by the other party, knowing that those packets can be lost since the COMPRESSION_ASSIGN request receiving proxy or client is not guaranteed to be ready to accept payloads until a COMPRESSION_ASSIGN response is echoed back.¶
If the client wishes to send or receive uncompressed datagrams, it MUST first exchange the COMPRESSION_ASSIGN capsule (see Figure 3) with the proxy with an unused Context ID defined in Section 3 with the IP Version set to zero.¶
When HTTP Datagrams [HTTP-DGRAM] are associated with a Bound UDP Proxying request, the format of their UDP Proxying Payload field (see Section 5 of [CONNECT-UDP]) is defined by Figure 1 when uncompressed; every datagram carries addressing information.¶
It contains the following fields:¶
The IP Version of the following IP Address field. MUST be 4 or 6.¶
The IP Address of this proxied UDP packet. When sent from client to proxy, this is the target host to which the proxy will send this UDP payload. When sent from proxy to client, this represents the source IP address of the UDP packet received by the proxy. This field has a length of 32 bits when the corresponding IP Version field value is 4, and 128 when the IP Version is 6.¶
The UDP Port of this proxied UDP packet in network byte order. When sent from client to proxy, this is the target port to which the proxy will send this UDP payload. When sent from proxy to client, this represents the source UDP port of the UDP packet received by the proxy.¶
The unmodified UDP Payload of this proxied UDP packet (referred to as "data octets" in [UDP]).¶
If an uncompressed Context ID was set (via Section 4), the client MAY at any point request the proxy reject all traffic from uncompressed targets by using COMPRESSION_CLOSE (see Section 6.2) on said Context ID. Then the proxy effectively acts as a firewall against unwanted or unknown IPs.¶
Endpoints MAY choose to compress the IP and port information per datagram for a given target using Context IDs. In that case, the endpoint sends a COMPRESSION_ASSIGN capsule (see Figure 3) with the target information it wishes to compress and its peer responds with either a COMPRESSION_ASSIGN capsule if it accepts the compression request, or a COMPRESSION_CLOSE with the context ID (see Figure 4) if it doesn't wish to support compression for the given Context ID (For example, due to the memory cost of establishing a list of mappings per target per client). If the compression was rejected, the client and proxy will instead use an uncompressed context ID (See Section 4) to exhange UDP payloads for the given target, if those have been enabled.¶
When an endpoint receives a COMPRESSION_ASSIGN capsule with a non-zero IP length, it MUST decide whether to accept or reject the compression mapping:¶
if it accepts the mapping, first the receiver MUST save the mapping from context ID to address and port. Second, the receiver MUST echo an identical COMPRESSION_ASSIGN capsule back to its peer.¶
if it rejects the mapping, the receiver MUST respond by sending a COMPRESSION_CLOSE capsule with the context ID set to the one from the received COMPRESSION_ASSIGN capsule.¶
The endpoint MAY choose to close any context that it registered or was registered with it respectively using COMPRESSION_CLOSE (For example when a mapping is unused for a long time). Another potential use is Section 4.2.¶
When HTTP Datagrams [HTTP-DGRAM] are associated with this Bound UDP Proxying request, the format of their UDP Proxying Payload field (see Section 5 of [CONNECT-UDP]) is defined by Figure 1 when the context ID is set to one previously registered for compressed payloads. (See Section 3 for compressed and uncompressed assignments.)¶
It contains the following fields:¶
This document defines new capsule types that deal with registering context IDs.¶
The Compression Assign capsule has two purposes. Either to request the assignment of a Context ID (see Section 3) to a corresponding target IP:Port. Or to accept a COMPRESSION_ASSIGN request from the other party.¶
The IP Length, Address and Port fields in Figure 3 are the same as those defined in Section 4.1.¶
When the IP Version is set to 0, the IP Address and UDP Port fields are omitted. This allows registering an uncompressed Context ID, as described in Section 3.¶
The Compression Close capsule serves two purposes. As a response to reject a COMPRESSION_ASSIGN request and to close or to clean up any existing compression mappings. Once an endpoint has either sent or received a COMPRESSION_CLOSE for a given context ID, it MUST NOT send any further datagrams with that Context ID.¶
As mandated in Section 4 of [CONNECT-UDP], clients can only allocate even context IDs, while proxies can only allocate odd ones. This makes the registration capsules above unambiguous. For example, if a client receives a COMPRESSION_ASSIGN capsule with an even context ID, it knows that this has to be an echo of a capsule it already sent.¶
The "Connect-UDP-Bind" header field’s value is a Boolean Structured Field set to true. Clients and proxy both indicate support for this extension by sending the Connect-UDP-Bind header field with a value of ?1. Once an endpoint has both sent and received the Connect-UDP-Bind header field set to true, this extension is enabled. Any other value type MUST be handled as if the field were not present by the recipients (for example, if this field is defined multiple times, its type becomes a List and therefore is to be ignored). This document does not define any parameters for the Connect-UDP-Bind header field value, but future documents might define parameters. Receivers MUST ignore unknown parameters.¶
Upon accepting the request, the proxy MUST select at least one public IP address to bind. The proxy MAY assign more addresses. For each selected address, it MUST select an open port to bind to this request. From then and until the tunnel is closed, the proxy SHALL send packets received on these IP-port tuples to the client. The proxy MUST communicate the selected addresses and ports to the client using the "Proxy-Public-Address" header. The header is defined as a List of IP-Port-tuples. The format of the tuple is defined using IP-literal, IPv4address, IPv6address and port from Section 3.2 of [URI].¶
When a single IP-Port tuple is provided in the Proxy-Public-Address field, the proxy MUST use the same public IP and Port for the remainder of the connection. When multiple tuples are provided, maintaining address stability per address family is RECOMMENDED.¶
Note that since the addresses are conveyed in HTTP response headers, a subsequent change of addresses on the proxy cannot be conveyed to the client.¶
After accepting the Connect-UDP Binding proxying request, the proxy uses an assigned IP:port to transmit UDP payloads received from the client to the target IP Address and UDP Port specified in each binding Datagram Payload received from the client. The proxy uses the same ports to listen for UDP packets from any authorized target and encapsulates the packets in the Binding Datagram Payload format, and forwards it to the client if a corresponding Context ID mapping exists for the target.¶
If the proxy receives UDP payloads that don't correspond to any mapping i.e. no compression for the given target was ever established and a mapping for uncompressed or any target is missing, the proxy will either drop the datagram or temporarily buffer it (see Section 5 of [CONNECT-UDP]).¶
The security considerations described in Section 7 of [CONNECT-UDP] also apply here. Since TURN can be run over this mechanism, implementors should review the security considerations in Section 21 of [TURN].¶
Since unextended UDP Proxying requests carry the target as part of the request, the proxy can protect unauthorized targets by rejecting requests before creating the tunnel, and communicate the rejection reason in response header fields. The uncompressed context allows transporting datagrams to and from any target. Clients that keep the uncompressed context open need to be able to receive from all targets. If the UDP proxy would reject unextended UDP proxying requests to some targets (as recommended in Section 7 of [CONNECT-UDP]), then for bound UDP proxying requests where the uncompressed context is open, the UDP proxy needs to perform checks on the target of each uncompressed context datagram it receives.¶
Note that if the compression response (COMPRESSION_ASSIGN OR COMPRESSION_CLOSE) cannot be immediately sent due to flow or congestion control, an upper limit on how many compression responses the endpoint is willing to buffer MUST be set to prevent memory exhaustion. The proxy MAY close the connection if such conditions occur.¶
Connect-UDP-Bind¶
None¶
provisional (permanent if this document is approved)¶
This document¶
None¶
This document also requests IANA to register the following new items to the "HTTP Capsule Types" registry maintained at <https://www.iana.org/assignments/masque>:¶
Value | Capsule Type |
---|---|
0x1C0FE323 | COMPRESSION_ASSIGN |
0x1C0FE324 | COMPRESSION_CLOSE |
All of these new entries use the following values for these fields:¶
In the example below, the client is configured with URI Template "https://example.org/.well-known/masque/udp/{target_host}/{target_port}/" and listens for traffic on the proxy, eventually decides that it no longer wants to listen for connections from new targets, and limits its communication with only 203.0.113.11:4321 and no other UDP target.¶
Client Server STREAM(44): HEADERS --------> :method = CONNECT :protocol = connect-udp :scheme = https :path = /.well-known/masque/udp/*/*/ :authority = proxy.example.org connect-udp-bind = ?1 capsule-protocol = ?1 <-------- STREAM(44): HEADERS :status = 200 connect-udp-bind = ?1 capsule-protocol = ?1 proxy-public-address = 192.0.2.45:54321, \ [2001:db8::1234]:54321 /* Request Context ID 2 to be used for uncompressed UDP payloads from/to any target */ CAPSULE --------> Type = COMPRESSION_ASSIGN Context ID = 2 IP Version = 0 /* Proxy confirms registration. */ <-------- CAPSULE Type = COMPRESSION_ASSIGN Context ID = 2 IP Version = 0 /* Target talks to Client using the uncompressed context */ <-------- DATAGRAM Quarter Stream ID = 11 Context ID = 2 IP Version = 4 IP Address = 192.0.2.42 UDP Port = 1234 UDP Payload = Encapsulated UDP Payload / * Client responds on the same uncompressed context */ DATAGRAM --------> Quarter Stream ID = 11 Context ID = 2 IP Version = 4 IP Address = 192.0.2.42 UDP Port = 1234 UDP Payload = Encapsulated UDP Payload /* Another target talks to Client using the uncompressed context */ <-------- DATAGRAM Quarter Stream ID = 11 Context ID = 2 IP Version = 4 IP Address = 203.0.113.11 UDP Port = 4321 UDP Payload = Encapsulated UDP Payload / * Client responds on the same uncompressed context */ DATAGRAM --------> Quarter Stream ID = 11 Context ID = 2 IP Version = 4 IP Address = 203.0.113.11 UDP Port = 4321 UDP Payload = Encapsulated UDP Payload /* Register 203.0.113.11:4321 to compress it in the future */ CAPSULE --------> Type = COMPRESSION_ASSIGN Context ID = 4 IP Version = 4 IP Address = 203.0.113.11 UDP Port = 4321 /* Proxy confirms registration.*/ <-------- CAPSULE Type = COMPRESSION_ASSIGN Context ID = 4 IP Version = 4 IP Address = 203.0.113.11 UDP Port = 4321 /* Omit IP and Port for future packets intended for*/ /* 203.0.113.11:4321 hereon */ DATAGRAM --------> Context ID = 4 UDP Payload = Encapsulated UDP Payload <-------- DATAGRAM Context ID = 4 UDP Payload = Encapsulated UDP Payload /* Request packets without a corresponding compressed Context */ /* to be dropped by closing the uncompressed Context */ CAPSULE --------> Type = COMPRESSION_CLOSE Context ID = 2 /* Proxy confirms unmapped IP rejection. */ <-------- CAPSULE Type = COMPRESSION_CLOSE Context ID = 2 /* Context ID 4 = 203.0.113.11:4321 traffic is accepted, */ /* And the rest is dropped at the proxy */¶
While the use-cases described in Section 1 could be supported using IP Proxying in HTTP [CONNECT-IP], it would require that every HTTP Datagram carries a complete IP header. This would lead to both inefficiencies in the wire encoding and reduction in available Maximum Transmission Unit (MTU). Furthermore, Web browsers would need to support IPv4 and IPv6 header generation, parsing, validation and error handling.¶
This proposal is the result of many conversations with MASQUE working group participants.¶