-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
0729886
commit c492d8d
Showing
4 changed files
with
180 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(<certfile>, <keyfile>) | ||
``` | ||
|
||
`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) |