From c492d8d7d0011003171fb54a236ac102d36eba8e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 19 Sep 2024 21:23:11 +0800 Subject: [PATCH] add documentation for masque-go's CONNECT-UDP implementation (#71) * add documentation for masque-go's CONNECT-UDP implementation * start the HTTP/3 server * add a high-level protocol overview * add a Mermaid diagram * add masque-go to README * improve the proxy section * use uritemplate.MustNew * minor changes to the client page --- README.md | 3 +- content/docs/masque/_index.md | 23 +++++++- content/docs/masque/client.md | 48 +++++++++++++++ content/docs/masque/proxy.md | 108 ++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 content/docs/masque/client.md create mode 100644 content/docs/masque/proxy.md diff --git a/README.md b/README.md index a96678c..3a14808 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ This is documentation for the [quic-go.net](https://quic-go.net), the documentation website for the quic-go project, covering: * [quic-go](https://github.com/quic-go/quic-go) (both the QUIC and HTTP/3 layer) * [webtransport-go](https://github.com/quic-go/webtransport-go) +* [masque-go](https://github.com/quic-go/masque-go) ## Version Compatibility -This documentation is focused **exclusively** on the most recent releases of quic-go and webtransport-go. +This documentation is focused **exclusively** on the most recent releases of the libraries. No effort is made to document older versions. ## Running locally diff --git a/content/docs/masque/_index.md b/content/docs/masque/_index.md index 746ba7d..ba9cdfe 100644 --- a/content/docs/masque/_index.md +++ b/content/docs/masque/_index.md @@ -4,4 +4,25 @@ toc: true weight: 4 --- -CONNECT-UDP ([RFC 9298](https://datatracker.ietf.org/doc/html/rfc9298)) is being implemented in [masque-go](https://github.com/quic-go/masque-go). Documentation will be added once the API has become stable. +CONNECT-UDP ([RFC 9298](https://datatracker.ietf.org/doc/html/rfc9298)) enables the proxying of UDP packets over HTTP/3. It is implemented in [masque-go](https://github.com/quic-go/masque-go). + +## Protocol Overview + +A client establishes an HTTP/3 connection to a proxy. It requests the proxying of UDP packets to a remote server by sending an Extended CONNECT ([RFC 9220](https://datatracker.ietf.org/doc/html/rfc9220)) HTTP request. If the proxy accepts the proxying request, it opens a UDP socket to the target and forwards UDP packets between the client and the target. Between the client and the proxy, UDP datagrams are sent on the QUIC connection using HTTP Datagrams ([RFC 9279](https://datatracker.ietf.org/doc/html/rfc9279)). + +```mermaid +sequenceDiagram + participant Client + participant Proxy + participant Server as Target Server + + Client->>Proxy: Extended CONNECT Proxying Request + Proxy-->>Proxy: Open UDP Socket to Server + + loop UDP Proxying + Client->>Proxy: UDP payload sent in HTTP Datagram + Proxy->>Server: UDP Packet + Server-->>Proxy: UDP Packet + Proxy->>Client: UDP payload sent in HTTP Datagram + end +``` diff --git a/content/docs/masque/client.md b/content/docs/masque/client.md new file mode 100644 index 0000000..2481c0f --- /dev/null +++ b/content/docs/masque/client.md @@ -0,0 +1,48 @@ +--- +title: Running a Client +toc: true +weight: 2 +--- + +## Setting up a Proxied Connection + +A client needs to be configured with the same URI template as the proxy. For more information on URI templates, see [URI Templates]({{< relref "proxy#uri-templates" >}}). + +```go +t := uritemplate.MustNew("https://example.org:4443/masque?h={target_host}&p={target_port}") +cl := masque.Client{ + Template: t, +} +``` + +`Client.DialAddr` can then be used establish proxied connections to servers by hostname. +In this case, DNS resolution is handled by the proxy: +```go +// dial a target with a hostname +conn, rsp, err := cl.DialAddr(ctx, "quic-go.net:443") +``` + +`Client.Dial` can be used to establish proxied connections to servers by IP address: +```go +conn, rsp, err := cl.Dial(ctx, <*net.UDPAddr>) +``` + +The `net.PacketConn` returned from these methods is only non-nil if the proxy accepted the proxying request. +This is the case if the HTTP status code is in the 2xx range: +```go +conn, rsp, err := cl.DialAddr(ctx, "quic-go.net:443") +// ... handle error ... +if rsp.StatusCode < 200 && rsp.StatusCode > 299 { + // proxying request rejected + // The response status code and body might contain more information. + return +} +// use conn to send and receive UDP datagrams to the target +``` + +Multiple UDP flows can be proxied over the same QUIC connection to the proxy by calling `DialAddr` and / or `Dial` multiple times on the same `Client`. + +## 📝 Future Work + +* Logging / Tracing: [#59](https://github.com/quic-go/masque-go/issues/59) +* Proxying IP packets over HTTP ([RFC 9484](https://datatracker.ietf.org/doc/html/rfc9484)): [#63](https://github.com/quic-go/masque-go/issues/63) diff --git a/content/docs/masque/proxy.md b/content/docs/masque/proxy.md new file mode 100644 index 0000000..51581ff --- /dev/null +++ b/content/docs/masque/proxy.md @@ -0,0 +1,108 @@ +--- +title: Running a Proxy +toc: true +weight: 1 +--- + +To create a MASQUE proxy server, the following steps are necessary: + +1. Set up an HTTP/3 server that defines an `http.Handler` for the URI template. +2. Decode the client's request and create a socket to the target. +3. Use the `masque.Proxy` to handle proxying UDP of the UDP packet flow. + +## URI Templates + +HTTP clients are configured to use a CONNECT-UDP proxy with a URI Template ([RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570)). +This URI template encodes the target host and port number. + +For example, for a proxy running on `https://proxy.example.com`, these are possible URI templates: +* `https://proxy.example.org:4443/masque?h={target_host}&p={target_port}` +* `https://proxy.example.org:4443/masque/{target_host}/{target_port}` + +The `target_host` can either be a hostname or an IP address. In case a hostname is used, DNS resolution is handled by the proxy. + +When receiving a request at the specified HTTP handler, the server decodes the URI template and opens a UDP socket to the requested target. + +## Handling Proxying Requests + +To run a CONNECT-UDP proxy on `https://example.org:4443` with the URI template `https://example.org:4443/masque?h={target_host}&p={target_port}`: + +```go +t := uritemplate.MustNew("https://example.org:4443/masque?h={target_host}&p={target_port}") +// ... error handling +var proxy masque.Proxy +http.Handle("/masque", func(w http.ResponseWriter, r *http.Request) { + // parse the UDP proxying request + mreq, err := masque.ParseRequest(r, t) + if err != nil { + var perr *masque.RequestParseError + if errors.As(err, &perr) { + w.WriteHeader(perr.HTTPStatus) + return + } + w.WriteHeader(http.StatusBadRequest) + return + } + + // optional: whitelisting / blacklisting logic + + // start proxying UDP datagrams back and forth + err = proxy.Proxy(w, mreq) + // ... error handling +} + +// set up HTTP/3 server on :4443 +s := http3.Server{Addr: ":4443"} +s.ListenAndServeTLS(, ) +``` + +`masque.ParseRequest` parses the Extended CONNECT request, and extracts the target host and port from the URI template. If parsing of the request fails, it returns a `masque.RequestParseError`. This struct contains a field 'HTTPStatus', allowing the application to reject invalid requests with the correct HTTP status code. + +The `masque Request.Target` contains the requested target encoded as `{target_host}:{target_port}`. Applications can implement custom logic to decide which proxying requests are permissible. + +{{< callout type="warning" >}} + Applications may add custom header fields to the response header, but must not call `WriteHeader` on the `http.ResponseWriter` + The header is sent when `Proxy.Proxy` is called. +{{< /callout >}} + +For more details on how to set up and configure an HTTP/3 server, see [Serving HTTP/3]({{< relref "../http3/server.md" >}}). + + +## Managing UDP Sockets + +The `proxy.Proxy` function used above creates a new connected UDP socket on `:0` to send UDP datagrams to the target. + +An application that wishes a more fine-grained control over the socket can instead use `Proxy.ProxyConnectedSocket`: +```go +http.Handle("/masque", func(w http.ResponseWriter, r *http.Request) { + // parse the UDP proxying request + mreq, err := masque.ParseRequest(r, t) + // ... handle error, as above ... + + // custom logic to resolve and create a UDP socket + addr, err := net.ResolveUDPAddr("udp", mreq.Target) + // ... handle error ... + conn, err := net.DialUDP("udp", addr) + // ... handle error ... + + err = proxy.ProxyConnectedSocket(w, mreq, conn) + // ... handle error ... +} +``` + +The `net.UDPConn` passed to `ProxyConnectedSocket` is closed by the proxy after proxying is done. + +{{< callout type="warning" >}} + Note that it is currently not possible to use unconnected UDP sockets (issue [#3](https://github.com/quic-go/masque-go/issues/3)). + It is invalid to pass an unconnected socket to `ProxyConnectedSocket`. +{{< /callout >}} + + + +## 📝 Future Work + +* Unconnected UDP sockets: [#3](https://github.com/quic-go/masque-go/issues/3) +* Use the Proxy-Status HTTP header ([RFC 9209](https://datatracker.ietf.org/doc/html/rfc9209)) to communicate failures: [#2](https://github.com/quic-go/masque-go/issues/2) +* Use GSO and GRO to speed up UDP packet processing: [#31](https://github.com/quic-go/masque-go/issues/31) and [#32](https://github.com/quic-go/masque-go/issues/32) +* Logging / Tracing: [#59](https://github.com/quic-go/masque-go/issues/59) +* Proxying IP packets over HTTP ([RFC 9484](https://datatracker.ietf.org/doc/html/rfc9484)): [#63](https://github.com/quic-go/masque-go/issues/63)