Take the MASQUE off: exploring the new HTTP/3 proxies

Translations

Overview

In this article, I invite you to discover the MASQUE proxies, which enable the proxification of Web traffic relying on HTTP/3 or even the tunneling of communications with HTTP/3. MASQUE provides compatibility of the proxy or VPN function for modern HTTP/3 flows.

Introduction

As data transfers intensify over time on the Internet, the network protocols that enable these transfers are adapting to offer higher performance and increased security. The HyperText Transfer Protocol (HTTP) is the primary protocol for Web communications. Its latest version, HTTP/3, is quite disruptive in its operation, even though it is built upon foundations established by previous versions. Nearly a third of HTTP transfers are now done in HTTP/3. New mechanisms currently under standardization are emerging to make the multiple use cases of HTTP compatible with HTTP/3 while leveraging its advantages. Among the most common use cases are the proxification function for secure browsing and remote access through public networks.
First, we will see how HTTP/3 works, then we will explore the Extended CONNECT method and the MASQUE proxy that enable the proxification and tunneling of communications for remote access. Finally, I will show you a demonstration of the usage of MASQUE for the proxification of HTTP/3 traffic.

HTTP/3 and QUIC

After HTTP/1 and HTTP/2, HTTP/3, standardized in 2022, is the third major version of the HyperText Transfer Protocol (HTTP). It uses the QUIC protocol, which itself relies on the User Datagram Protocol (UDP) transport protocol, unlike previous HTTP versions such as HTTP/2, which rely directly on the Transmission Control Protocol (TCP). Let’s start by better understanding what QUIC is.
QUIC was standardized in 2021 and aims to provide a new connection-oriented transport protocol based on UDP, which is itself not connection-oriented. It incorporates an adaptation of the Transport Layer Security (TLS) protocol for securing messages through authentication and encryption of QUIC packets. The use of secure communications via TLS is mandatory in QUIC.
The information exchanged with QUIC transits via streams, which are sequences of bytes and can be unidirectional or bidirectional. To ensure connection reliability (as TCP does), QUIC ensures packet delivery and congestion control. An extension of QUIC also allows sending packets unreliably, similar to UDP, where individual datagrams can be sent without congestion control or retransmission upon loss.

Features of HTTP/2 and HTTP/3 protocol stacks (source: https://gcore.com/learning/what-is-http-3/)

QUIC streams are materialized as frames for data transmission, themselves encapsulated within a QUIC packet residing in a UDP datagram. QUIC streams are identified using a stream ID.

Let’s get back to HTTP/3. It succeeds HTTP/2, which in 2015 had already brought performance improvements by enabling request multiplexing. This means that multiple HTTP requests can occur within the same TCP connection (very common when visiting a website, for example). The advent of TLS 1.3 had also accelerated the establishment of cryptographic sessions compared to TLS 1.2, and we thus speak of establishing connections with zero round-trip time (0-RTT). With HTTP/3, this 0-RTT concept is also applied to the transport layer, and thus, the establishment of the transport session is combined with the one of the cryptographic session. HTTP/3 mandates encryption by default with TLS 1.3.
Comparing the establishment of an HTTP/2 session with that of an HTTP/3 session is a bit like comparing this conversation:

  • Hi!
  • Hi!
  • How are you?
  • I’m fine, how are you?
  • I’m fine, thank you.
  • I wanted to talk to you about something…

With this conversation:

  • Hi, how are you?
  • Hi, I’m fine and you?
  • I’m fine, thank you. I wanted to talk to you about something…

Looking more closely, below is a session establishment with HTTP/2 and TLS 1.2:

HTTP/2 session establishment with TLS 1.2

Here is a session establishment with HTTP/2 and TLS 1.3:

HTTP/2 session establishment with TLS 1.3

And here is a session establishment with HTTP/3:

HTTP/3 session establishment

Note that similar initiatives also exist at the TCP level with TCP Fast Open, where the principle is to start sending data in the first TCP SYN packet. However, this solution has not been widely adopted, especially by Web browsers.

Also note that HTTP/3 allows the elimination of the packet loss effect inherent in TCP or Head-of-line blocking, where the sequential processing of segments can cause a large packet waiting for processing in the queue to block the processing of smaller packets. Thus, with HTTP/3, independent streams can be multiplexed within the same connection and potentially not impact other streams.

The unit of communication in HTTP/3 is the frame, which can be of type HEADERS or DATA for data transfer, or other types like SETTINGS for transfer control. HTTP/3 requests can be multiplexed thanks to QUIC streams. Each HTTP request-response pair corresponds to a QUIC stream, and these streams are independent of each other.

HTTP/3 frame encapsulation

As HTTP/3 is increasingly adopted for all the listed advantages and its nature changes the way data is transported, new mechanisms are required to allow the transfer of flows on the Internet in more advanced use cases than simple Web access.

Extended CONNECT and MASQUE Proxy

There are many use cases where the HTTP and TLS protocols are involved to enable secure Web browsing through Web proxy servers, or even remote connections via Virtual Private Networks (VPN).
The HTTP CONNECT method is an HTTP method, just like the well-known GET (to obtain the representation of a resource) or POST (to submit an entity to a resource). CONNECT is used to request the recipient to establish a TCP connection to a target resource, that is, to ask the recipient to create a TCP socket: a half-connection that serves as an interface for transferring data via TCP and thus establishing complete TCP connections. The main initial need is to be able to create TLS tunnels to enable HTTPS connections through Web proxies. Thanks to CONNECT, other protocols based on TCP can also be passed through: Secure SHell (SSH), File Transfer Protocol (FTP)…

HTTP CONNECT with a Web proxy

Since QUIC uses UDP, it has been proposed to extend CONNECT to support UDP traffic proxification. This is referred to as Extended CONNECT and particularly CONNECT-UDP. This concept was introduced for HTTP/2 for WebSockets (bidirectional communications between a client and a server within the same connection) and is now also being implemented in HTTP/3. It is in this context that Multiplexed Application Substrate over QUIC Encryptionn(MASQUE) has emerged, proposing a generic proxy function to relay end-to-end secure connections over HTTP. It is based on Extended CONNECT.

HTTP/3 Extended CONNECT-UDP with a MASQUE proxy for an HTTP/3 connection

One of the intended uses of a MASQUE proxy, beyond relaying connections to Web servers in HTTP/3, is to be able to establish client-to-site VPNs to allow remote user access to a site (i.e. a location) via HTTP. Expanding the reflection, we can also consider being able to proxy any type of traffic based on the IP protocol or even the Ethernet protocol. This would allow addressing, in addition to TCP and UDP, protocols such as Internet Control Message Protocol (ICMP) and Encapsulating Security Payload (ESP). This is referred to as CONNECT-IP. For Ethernet, it’s called CONNECT-ETHERNET, where a network broadcast domain can thus be extended through HTTP.

HTTP/3 Extended CONNECT-IP with a MASQUE proxy for a VPN connection

Moreover, the Capsule protocol governs how data packets and control messages are encapsulated and transmitted. Control messages allow, for example in the case of a VPN, assigning a network configuration to clients.

Demonstration

MASQUE implementations are rare and still in an experimental state. Web browsers or popular command-line tools like cURL or wget do not yet offer it at the time of writing this article.
That said, I found several interesting proofs of concept such as the following:

I tested the first one, masque-go, which is part of a software suite, quic-go, written in the Go language and implementing the QUIC protocol with extensions including MASQUE.
Here, I propose testing the CONNECT-UDP method, and therefore recommend that you read the documentation associated with UDP proxification with HTTP.

Here is the architecture I used to test the MASQUE proxy on my machine.

Architecture used for the demonstration

The MASQUE proxy, listening on UDP port 3128, and the MASQUE client share the same local virtual network interface lo0. The MASQUE proxy uses the physical interface en0 to connect to the Internet and, upon the client’s request, specifically to the site https://cloudflare-quic.com, which supports HTTP/3.

First, download the masque-go project:

$ git clone https://github.com/quic-go/masque-go.github
$ cd masque-go

In order to carry out my tests locally without deploying complex configurations, I slightly modified the client code to disable the verification of the TLS certificate provided by the server (do not do this in a production environment!). I therefore added the argument InsecureSkipVerify: true during the instantiation of the client’s TLS configuration. Also, in order to be able to analyze the encrypted QUIC exchanges between the client and the proxy and thus be able to retrieve the generated TLS session keys in a log file (here sslkeylog.txt), I created a KeyLogWriter that I added via the argument KeyLogWriter: sslKeyLogFile during the instantiation of the client’s TLS configuration. Here are the modifications associated with the client.go file:

sslKeyLogFileName := "sslkeylog.txt"
sslKeyLogFile, err := os.OpenFile(sslKeyLogFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
    log.Fatalf("Failed to open SSL key log file: %v", err)
}
defer sslKeyLogFile.Close()

tlsConf := c.TLSClientConfig
if tlsConf == nil {
    tlsConf = &tls.Config{NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true, KeyLogWriter: sslKeyLogFile}
}

Generate an RSA private key and a self-signed TLS certificate for the MASQUE proxy:

$ cd cmd/proxy
$ openssl genpkey -algorithm RSA -out private.key -pkeyopt rsa_keygen_bits:4096
$ openssl req -new -x509 -key private.key -out certificate.crt -days 365 -subj "/C=FR/ST=State/L=Home/O=Local/OU=IT/CN=localhost"

Launch the MASQUE proxy on UDP port 3128 using the previously generated certificate and private key:

$ go run main.go -b 127.0.0.1:3128 -c certificate.crt -k private.key -t "https://localhost:3128/masque?h={target_host}&p={target_port}"

In another terminal, launch the HTTP/3 client to contact the site https://cloudflare-quic.com using the MASQUE proxy listening locally on UDP port 3128:

$ cd masque-go/cmd/client/
$ go run main.go -t "https://localhost:3128/masque?h={target_host}&p={target_port}" https://cloudflare-quic.com
You should get the HTML response from the website https://cloudflare-quic.com. Of course, the network to which your MASQUE proxy is connected must allow QUIC flows.

I then performed a packet capture with Wireshark in parallel. To be able to decrypt the encrypted part in QUIC with TLS, in Wireshark I specified the path to the sslkeylog.txt file in Preferences... > Protocols > TLS > (Pre)-Master-Secret log filename. To improve visibility, I applied the following Wireshark filter:

quic or http3

I have reported the QUIC packets exchanged between the client, proxy, and website in the following graph.

Packet exchanges between the client, proxy, and website

In practice, we see that there are many exchanges involved. Here are the associated explanations.

The first exchanges, in blue on the graph, correspond to the QUIC connection negotiation, started by an Initial type packet, and the TLS negotiation (contained within QUIC CRYPTO frames). It should be noted that several packets are sent from the proxy to the client to respond to the TLS negotiation. This is due to the size of the certificate, which is spread across 3 packets. In addition, PADDING frames are used to add padding and increase the size of the QUIC packet during the establishment of communication, notably to ensure that the communication can indeed support the transfer of packets of this size. QUIC ACK frames indicate the integration of the Explicit Congestion Notification (ECN) feature for improved detection and control of network congestion. PING frames are used to verify and maintain the ongoing QUIC connection between the client and the proxy.

Next, in red on the graph, we find the first HTTP/3 SETTINGS frame indicating to the client the support for the transport of UDP datagrams individually and unreliably, as well as the support for the Extended CONNECT method. Then, an HTTP/3 HEADERS frame performs a CONNECT-UDP with the target path /masque?h=172.67.9.235&p=443 (the client performed a DNS resolution beforehand to resolve cloudflare-quic.com, which is associated with the IP address 172.67.9.235). A QUIC ACK frame with ECN and an HTTP/3 HEADERS frame sending an HTTP status of 200 OK confirms the acceptance of the connection request.

First part of the packet capture between the client, proxy, and website with the detail of the HTTP/3 CONNECT-UDP packet

Then, in orange on the graph, we find the QUIC streams exchanged between the client and the website via the proxy, where mainly the TLS negotiation takes place.

Finally, in yellow, we find the actual HTTP data exchanges.

Second part of the packet capture between the client, proxy, and website with the detail of the HTTP/3 GET packet

Conclusion

In this article, we discovered together what a MASQUE proxy is, basically enabling the proxification and the tunnelisation of flows over the HTTP/3 protocol which relies on QUIC. We also saw a concrete demonstration its usage in Go language order to proxify an HTTP/3 connection towards a website through a MASQUE proxy.

Sources

Translations