From 62310b965f8daed9b09475bf862aadbaa578ccf4 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 17 Oct 2024 09:14:26 -0500 Subject: [PATCH] remove the template parameter from the client (#73) The same UDP proxy could offer proxying functionality at different paths (and therefore different templates), and we should be able to multiplex these on the same QUIC connection. --- client.go | 20 +++++--------------- cmd/client/main.go | 3 +-- connect-udp_test.go | 23 +++++++++++++---------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/client.go b/client.go index 2d95e21..56603ea 100644 --- a/client.go +++ b/client.go @@ -20,13 +20,9 @@ import ( // This allows tunneling QUIC connections, which themselves have a minimum MTU requirement of 1200 bytes. const defaultInitialPacketSize = 1350 -// A Client establishes proxied connections to remote hosts, -// using a UDP proxy. +// A Client establishes proxied connections to remote hosts, using a UDP proxy. // Multiple flows can be proxied via the same connection to the proxy. type Client struct { - // Template is the URI template of the UDP proxy. - Template *uritemplate.Template - // TLSClientConfig is the TLS client config used when dialing the QUIC connection to the proxy. // It must set the "h3" ALPN. TLSClientConfig *tls.Config @@ -43,15 +39,12 @@ type Client struct { // DialAddr dials a proxied connection to a target server. // The target address is sent to the proxy, and the DNS resolution is left to the proxy. // The target must be given as a host:port. -func (c *Client) DialAddr(ctx context.Context, target string) (net.PacketConn, *http.Response, error) { - if c.Template == nil { - return nil, nil, errors.New("masque: no template") - } +func (c *Client) DialAddr(ctx context.Context, proxyTemplate *uritemplate.Template, target string) (net.PacketConn, *http.Response, error) { host, port, err := net.SplitHostPort(target) if err != nil { return nil, nil, fmt.Errorf("failed to parse target: %w", err) } - str, err := c.Template.Expand(uritemplate.Values{ + str, err := proxyTemplate.Expand(uritemplate.Values{ uriTemplateTargetHost: uritemplate.String(host), uriTemplateTargetPort: uritemplate.String(port), }) @@ -62,11 +55,8 @@ func (c *Client) DialAddr(ctx context.Context, target string) (net.PacketConn, * } // Dial dials a proxied connection to a target server. -func (c *Client) Dial(ctx context.Context, raddr *net.UDPAddr) (net.PacketConn, *http.Response, error) { - if c.Template == nil { - return nil, nil, errors.New("masque: no template") - } - str, err := c.Template.Expand(uritemplate.Values{ +func (c *Client) Dial(ctx context.Context, proxyTemplate *uritemplate.Template, raddr *net.UDPAddr) (net.PacketConn, *http.Response, error) { + str, err := proxyTemplate.Expand(uritemplate.Values{ uriTemplateTargetHost: uritemplate.String(escape(raddr.IP.String())), uriTemplateTargetPort: uritemplate.String(strconv.Itoa(raddr.Port)), }) diff --git a/cmd/client/main.go b/cmd/client/main.go index fd5b1a3..7a44a08 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -34,7 +34,6 @@ func main() { } cl := masque.Client{ - Template: uritemplate.MustNew(proxyURITemplate), QUICConfig: &quic.Config{ EnableDatagrams: true, InitialPacketSize: 1350, @@ -52,7 +51,7 @@ func main() { if err != nil { return nil, err } - pconn, _, err := cl.Dial(context.Background(), raddr) + pconn, _, err := cl.Dial(context.Background(), uritemplate.MustNew(proxyURITemplate), raddr) if err != nil { log.Fatal("dialing MASQUE failed:", err) } diff --git a/connect-udp_test.go b/connect-udp_test.go index 47c0a40..3ce1efa 100644 --- a/connect-udp_test.go +++ b/connect-udp_test.go @@ -83,11 +83,14 @@ func testProxyToIP(t *testing.T, addr *net.UDPAddr) { }() cl := masque.Client{ - Template: template, TLSClientConfig: &tls.Config{ClientCAs: certPool, NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true}, } defer cl.Close() - proxiedConn, _, err := cl.Dial(context.Background(), remoteServerConn.LocalAddr().(*net.UDPAddr)) + proxiedConn, _, err := cl.Dial( + context.Background(), + template, + remoteServerConn.LocalAddr().(*net.UDPAddr), + ) require.NoError(t, err) _, err = proxiedConn.WriteTo([]byte("foobar"), remoteServerConn.LocalAddr()) @@ -141,11 +144,10 @@ func TestProxyToHostname(t *testing.T) { }() cl := masque.Client{ - Template: template, TLSClientConfig: &tls.Config{ClientCAs: certPool, NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true}, } defer cl.Close() - proxiedConn, rsp, err := cl.DialAddr(context.Background(), "quic-go.net:1234") // the proxy doesn't actually resolve this hostname + proxiedConn, rsp, err := cl.DialAddr(context.Background(), template, "quic-go.net:1234") // the proxy doesn't actually resolve this hostname require.NoError(t, err) require.Equal(t, http.StatusOK, rsp.StatusCode) @@ -181,22 +183,24 @@ func TestProxyingRejected(t *testing.T) { }() cl := masque.Client{ - Template: template, TLSClientConfig: &tls.Config{ClientCAs: certPool, NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true}, } defer cl.Close() - _, rsp, err := cl.DialAddr(context.Background(), "quic-go.net:1234") // the proxy doesn't actually resolve this hostname + _, rsp, err := cl.DialAddr(context.Background(), template, "quic-go.net:1234") // the proxy doesn't actually resolve this hostname require.Error(t, err) require.Equal(t, http.StatusTeapot, rsp.StatusCode) } func TestProxyToHostnameMissingPort(t *testing.T) { cl := masque.Client{ - Template: uritemplate.MustNew("https://localhost:1234/masque?h={target_host}&p={target_port}"), TLSClientConfig: &tls.Config{ClientCAs: certPool, NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true}, } defer cl.Close() - _, rsp, err := cl.DialAddr(context.Background(), "quic-go.net") // missing port + _, rsp, err := cl.DialAddr( + context.Background(), + uritemplate.MustNew("https://localhost:1234/masque?h={target_host}&p={target_port}"), + "quic-go.net", // missing port + ) require.Nil(t, rsp) require.ErrorContains(t, err, "address quic-go.net: missing port in address") } @@ -236,11 +240,10 @@ func TestProxyShutdown(t *testing.T) { }() cl := masque.Client{ - Template: template, TLSClientConfig: &tls.Config{ClientCAs: certPool, NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true}, } defer cl.Close() - proxiedConn, rsp, err := cl.Dial(context.Background(), remoteServerConn.LocalAddr().(*net.UDPAddr)) + proxiedConn, rsp, err := cl.Dial(context.Background(), template, remoteServerConn.LocalAddr().(*net.UDPAddr)) require.NoError(t, err) require.Equal(t, http.StatusOK, rsp.StatusCode)