-
-
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
- Loading branch information
1 parent
0729886
commit 82d28a5
Showing
3 changed files
with
143 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
--- | ||
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 the [URI Templates]({{< relref "proxy#uri-templates" >}}) section on the proxy page. | ||
|
||
```go | ||
t, err := uritemplate.New("https://example.org:4443/masque?h={target_host}&p={target_port}") | ||
// ... handle error ... | ||
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 packets to the target | ||
``` | ||
|
||
## 📝 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,91 @@ | ||
--- | ||
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 of the UDP packet flow. | ||
|
||
## URI Templates | ||
|
||
HTTP clients are configured to use a 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, err := uritemplate.New("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. | ||
err = proxy.Proxy(w, mreq) | ||
// ... error handling | ||
} | ||
``` | ||
|
||
The error returned from `masque ParseRequest` is a `masque.RequestParseError`, which contains a field 'HTTPStatus'. This allows the proxy to reject | ||
invalid requests with the correct HTTP status code. | ||
|
||
The `masque Request.Target` contains the requested target as `{target_host}:{target_port}`. Proxies 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 >}} | ||
|
||
## Controlling the Socket | ||
|
||
`proxy.Proxy` 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 use `Proxy.ProxyConnectedSocket` instead of `Proxy.Proxy`: | ||
```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 ... | ||
} | ||
``` | ||
|
||
## 📝 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) |