[Masque] QUIC proxying: feedback on forwarding design and documentation

Martin Thomson <mt@lowentropy.net> Wed, 12 April 2023 06:45 UTC

Return-Path: <mt@lowentropy.net>
X-Original-To: masque@ietfa.amsl.com
Delivered-To: masque@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id DC812C14CE39 for <masque@ietfa.amsl.com>; Tue, 11 Apr 2023 23:45:13 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -2.799
X-Spam-Level:
X-Spam-Status: No, score=-2.799 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H2=-0.001, RCVD_IN_ZEN_BLOCKED_OPENDNS=0.001, SPF_PASS=-0.001, URIBL_DBL_BLOCKED_OPENDNS=0.001, URIBL_ZEN_BLOCKED_OPENDNS=0.001] autolearn=ham autolearn_force=no
Authentication-Results: ietfa.amsl.com (amavisd-new); dkim=pass (2048-bit key) header.d=lowentropy.net header.b="60YFqK9+"; dkim=pass (2048-bit key) header.d=messagingengine.com header.b="d7+RAekM"
Received: from mail.ietf.org ([50.223.129.194]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 6kQcKezwBEqJ for <masque@ietfa.amsl.com>; Tue, 11 Apr 2023 23:45:09 -0700 (PDT)
Received: from wout1-smtp.messagingengine.com (wout1-smtp.messagingengine.com [64.147.123.24]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 2AC2DC14CE24 for <masque@ietf.org>; Tue, 11 Apr 2023 23:45:09 -0700 (PDT)
Received: from compute6.internal (compute6.nyi.internal [10.202.2.47]) by mailout.west.internal (Postfix) with ESMTP id 2E4D732009BC for <masque@ietf.org>; Wed, 12 Apr 2023 02:45:08 -0400 (EDT)
Received: from imap41 ([10.202.2.91]) by compute6.internal (MEProxy); Wed, 12 Apr 2023 02:45:08 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lowentropy.net; h=cc:content-type:content-type:date:date:from:from:in-reply-to :message-id:mime-version:reply-to:sender:subject:subject:to:to; s=fm1; t=1681281907; x=1681368307; bh=geX53GI+p/yVPy9CtVi7Ag1A4 a6ZXzLr800fAZDBgyE=; b=60YFqK9+TU7RzLC3wRrnoZdj9HxDZBIxZHGUTr3IS 3IUPD2xf2yKLm/m2w98wqer6oxYTHDx/x/Qr3MfzpDkpN2jhXlm93+CjYr7+npX2 z5eW1CkqimLiOu1+pHMYmpFQ7hRoleXeLabUUXTFvTlN/6JFWtU5PWqTsAZXoHtZ bChUrtk1lpR0EgtaO8juvRXEEM+G6KlHAE/DfT+RFRK4WAi/POBu3Hj8XCziwHnv 7fKFBPraNiLc36jFXuBbkcUkD0XRnYkT1aY1mq53QknwpKBcj9h89+jkrhsu2vSK 8yIBd70bgYxtnsHnmmTgtby5J9BZI3u6Ql5MOwPOP5wcw==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:message-id :mime-version:reply-to:sender:subject:subject:to:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1681281907; x=1681368307; bh=geX53GI+p/yVPy9CtVi7Ag1A4a6ZXzLr800 fAZDBgyE=; b=d7+RAekMsiKSWF1ivLzcW7zMT+Wn4kQyyodqufm+q1aZ6mn7WOz 3A13VS+BhX9y8li1+AC4xJ3XY2HUzCYXYG637O4OnPKMg0Esqlggkj1jifx5WuMP C01RNfPdAj3Vj+Ti/v/c9fp7B8CwqerkIbuG567LsLVgZS6GaKy+jJ6sOsi5Xsp8 aTGBJYWX83dY3QcTdznCbkkZDaTTzCzzllWK8MnqYxKAbIf3J7iuDMKpo00fZYEz BvCYMwLtu6yAzOCpoQef3QrEgStrqtdp3VtJ1zRgmbHur9O87Ils8VovI2MOuUpb E96rRCRjgyauEVIHTI6U4gjRxRFvl/mgW+g==
X-ME-Sender: <xms:c1M2ZNvUrTYU_OW7QVoz0aKvmBMhOL__YJaR5x3PnWs_trUozsIq5w> <xme:c1M2ZGd_GuHrf4GIx_iMykBvOwD9yoO2DGBG7HfBNjjJhaF2Vu607aQ5c9tDtexxM nyouLJMrFvQV6OpDWc>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvhedrvdekhedgudduvdcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecunecujfgurhepofgfggfkfffhvffutgesthdtre dtreertdenucfhrhhomhepfdforghrthhinhcuvfhhohhmshhonhdfuceomhhtsehlohif vghnthhrohhphidrnhgvtheqnecuggftrfgrthhtvghrnhepvefguddtgfejfeduueehke ehkeduueegheefgeevkeekgeelveevffeuudffheehnecuvehluhhsthgvrhfuihiivgep tdenucfrrghrrghmpehmrghilhhfrhhomhepmhhtsehlohifvghnthhrohhphidrnhgvth
X-ME-Proxy: <xmx:c1M2ZAwhE2PYMDi2kFP16CPDdQrCU7ZsEFOwthIrnzTN2GMOJj0MNw> <xmx:c1M2ZENGyYLjFzB8UKawDEe-U_6ywFKmhaoeBlmxnqRdT9ImxSwQrQ> <xmx:c1M2ZN_g2qCcwmkKbOkWz7GrNfWqjFb6_pPHaV_3O1v9Au0OG-xmVA> <xmx:c1M2ZALgk1gHoP-ms3TLNv3IMpwAoUD-GP1mTkFczDLBT8WG7ofgtQ>
Feedback-ID: ic129442d:Fastmail
Received: by mailuser.nyi.internal (Postfix, from userid 501) id 7643B234007B; Wed, 12 Apr 2023 02:45:07 -0400 (EDT)
X-Mailer: MessagingEngine.com Webmail Interface
User-Agent: Cyrus-JMAP/3.9.0-alpha0-334-g8c072af647-fm-20230330.001-g8c072af6
Mime-Version: 1.0
Message-Id: <6675769b-5b1f-4ce2-a216-2d2aa0349c81@betaapp.fastmail.com>
Date: Wed, 12 Apr 2023 16:44:47 +1000
From: Martin Thomson <mt@lowentropy.net>
To: masque@ietf.org
Content-Type: text/plain
Archived-At: <https://mailarchive.ietf.org/arch/msg/masque/DagVg9bjtpH9IsqrwUXILlYSAxQ>
Subject: [Masque] QUIC proxying: feedback on forwarding design and documentation
X-BeenThere: masque@ietf.org
X-Mailman-Version: 2.1.39
Precedence: list
List-Id: Multiplexed Application Substrate over QUIC Encryption <masque.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/masque>, <mailto:masque-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/masque/>
List-Post: <mailto:masque@ietf.org>
List-Help: <mailto:masque-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/masque>, <mailto:masque-request@ietf.org?subject=subscribe>
X-List-Received-Date: Wed, 12 Apr 2023 06:45:13 -0000

Hey all, I'm going through draft-pauly-masque-quic-proxy and trying to reconcile what that says with what I have in my head and I'm coming up with a pretty big mismatch, plus some new ideas.

# Expectations

After our discussion at the last meeting, I think that we generally have a fairly good shared understanding here of what the requirements look like, but I'll recap just in case.

The basic idea is that the client tunnels a QUIC connection through a proxy toward a target server.  The initial exchange is completely tunneled, but once the connection is established, the flow of packets (all of which currently have a QUIC short header) are moved outside of the tunnel onto a rapid forwarding path.

The server can be completely ignorant of anything that happens between the client and proxy.  It is able to run an unmodified QUIC stack.  (Big disclaimer here: I think that we probably do want to talk about a new extension for servers that would make this process much easier to manage, but more on that below.)

In theory at least, the proxy could just learn about connection IDs that the client and server use and just forward packets, rewriting only the IP and port on the way through, but this isn't great for many reasons.  Aside from the obvious privacy downsides (for which we agree we would use some sort of encryption), the proxy wouldn't be able to scale out without having some control over connection ID allocation.  If the proxy forwards to multiple servers, using connection IDs would not work in the case that different servers chose the same connection ID.  The same applies to the clients, which (today) tend not to use connection IDs at all, which makes forwarding on the server to client direction very difficult; of course, clients will be aware of their participation in proxying and can ensure that they use amply-sized connection IDs.

# My Understanding of the Intended Design

We can break this down into the two directions that packets flow.  In both cases, the additional work always happens between client and proxy; the server remains unmodified.

## From the Client to the Server

In this direction, the server supplies the client with connection IDs (and stateless reset tokens).  But the proxy would prefer if the client use connection IDs that it chose.  So, we have the following flow:

Server -> Client: a QUIC NEW_CONNECTION_ID frame
Client -> Proxy (as a capsule): The server wants to use this connection ID.
Proxy -> Client (as a capsule): For that connection ID, please encapsulate using this connection ID instead.

I'm not going to list stateless reset tokens in these exchanges for now; in general, you should assume that if you are providing a connection ID, you would also provide a stateless reset token along with it, in case you lose state associated with that connection ID and you want to signal that fact.

### Capsules

What I see in the protocol is a REGISTER_TARGET_CID capsule, which contains approximately what you would expect here.  The client can send that toward a proxy with a copy of the information that the server provided.  It doesn't include the QUIC sequence number, which is totally fine.

The response to that seems fine.  The connection ID that the client registered is used as a transaction identifier in the ACK_TARGET_CID capsule (the order of these would be fine).  This includes a connection ID for the client to use and a stateless reset token.  The client 

### Cleanup

For cleanup, if the client wants to retire one of these connection IDs, it can send a CLOSE_TARGET_CID capsule.  This includes the server-provided connection ID as a key.

Use of the server-provided connection ID as a means of correlating items is a bit questionable, but it should work.  I might prefer the use of sequence numbers or similar here.

Note however that these CLOSE_XX_CID capsules are not acknowledged, so the resource management stuff is a bit loose (as connection ID usage already is in QUIC).

There is no way for the proxy to inform the client of connection ID limits.  The client might forward these on to the server so that the server doesn't over-commit, or the client could at least limit its usage of connection IDs to fit within the proxy's constraints.  This applies to both what the client uses and in terms of what it advertises; presumably the proxy will want to limit how many entries it needs to track for each connection.

### Connection ID Lengths

In the client to server direction, there is a catch here that might be worth exploring further.  Forwarding is more efficient if you can ensure that the connection ID length doesn't change.  A proxy might require some minimum number of bytes for its connection IDs, which might be longer than the length chosen by servers.  In that case, it might be nice if the client could negotiate the use of longer connection IDs with the server.  Otherwise, the proxy has a set of choices, none of which are particularly nice: disable this forwarding and tunnel everything, deal with a shorter connection ID for forwarding, or move the payload when forwarding packets.

An extension to QUIC that requests a longer connection ID might be helpful here.  The proxy could inform the client of its requirements and the client could include this request.  Servers that support that feature might pad their connection IDs out in response to that.

This protocol would need a way for the proxy to signal its requirements, before the client constructs its QUIC Initial.  That potentially cuts into the low latency connection establishment, but it seems like it might be necessary.  It's something that clients can probably remember though, which means that it might be possible to avoid a round trip.  (Or it could be configured, along with the URL template for the proxy, but that seems fragile to me.)

## From the Server to the Client

Here, the client is the source of connection IDs, so the flow would look like this:

Client -> Proxy: I want to use this connection ID.
Proxy -> Client: Please tell the server to use this connection instead.
Client -> Server: NEW_CONNECTION_ID (with the choice from the proxy)

This is where my understanding and the documented designs diverge.

In the other direction, the ACK_TARGET_CID frame included a connection ID that was already decided.  But here the client is supposed to send REGISTER_CLIENT_CID with two connection IDs in it.  That doesn't match the above flow.  It looks like the client is expected to put in the two connection ID fields, which means that the client chooses connection IDs for the proxy?  That interpretation is consistent with the shape of the ACK_CLIENT_CID frame, which is effectively just an acknowledgment.

I think that this aspect of the design is not right.  The proxy should be responsible for choosing the connection IDs that the server uses in packets directed toward the proxy.

Also, while the client informs the proxy of a stateless reset token, if the proxy loses state, it appears as though there is no way to tell the server about this.  That's probably fine, because the server is ignorant of the special status of the proxy and so it would give the proxy the ability to destroy the entire connection state at the server, which we might not want.  However, this case is not explained in the document.

### Connection ID Length

In the server to client direction, again the proxy might have minimum length constraints for connection IDs.  At least in this case it can just tell the client how big connection IDs need to be.  In a lot of cases, the client is able to bind a unique port for the forwarded connection, so it can just make connection IDs as big as necessary.

However, I see no place where this item might be communicated from proxy to client, just like the above.

## Path Migration (new idea)

The proxy should also be in a position to choose a source address.  We should allow the proxy to change the address (and port) it uses for a flow once it moves from tunneling to forwarding.  While CONNECT-UDP might have stricter constraints on operation, a QUIC connection can use connection IDs, which might allow for greater density than strictly CONNECT-UDP.  In that case, it might be useful to treat a switch to forwarding mode as a strict, one-way operation.

That is, after forwarding starts, tunneling could cease.  The HTTP stream might remain active for control purposes, like these capsules, but it might be completely unusable for tunneling.  That would allow the proxy to reclaim resources.  If the proxy is permitted to forward on a new IP and port, this will look like connection migration to the proxy.  It might also allow for better separation of functions, with dedicated endpoints that manage forwarding.

That suggests a few things:

1. In the client to server direction, a switch to forwarding might entail a connection migration.  This might look like a NAT rebinding in the best case, at least to the server, but it can be a little bit better than that for the client.  The client can follow logic similar to the server preferred address and do a clean path migration to a new IP and port at the proxy.

2. In the server to client direction, the server can just switch over based on the apparent address of the client changing.  Nothing special is needed there, though it might trigger path validation (which, again, should just work).  Having the client receive packets from a new proxy address is part of the path migration, which (once again) should work fine.

3. In order for this to work, the proxy might like to tell the client where to send packets for forwarding.  Maybe it can't just rely on them arriving at the same IP and port as the tunnel itself (though that might be a reasonable default).

## Document Feedback

The presentation of this flow in the draft is incredibly hard to follow.  The way that the draft is structured around formats and not exchanges makes it very hard to understand.  I found the mappings section to be completely unhelpful here.  Perhaps that is just how my brain works, but the entirely of Sections 2.1 through 2.3 make no sense to me.

For instance: "Each client <-> proxy HTTP stream MUST be mapped to a single target-facing socket" -- I think that this assumes a great deal about what a socket is.  If this is a connected UDP socket, which binds local address and port plus remote (target) address and port, then I can maybe see how that might play out.  But these are implementation details and not explained adequately.