aboutsummaryrefslogtreecommitdiff
path: root/proposals
diff options
context:
space:
mode:
authorMicah Elizabeth Scott <beth@torproject.org>2024-02-12 17:11:21 -0800
committerMicah Elizabeth Scott <beth@torproject.org>2024-02-12 17:46:21 -0800
commit7c31ee8b3b8b52f9b2978ecdcf947547cf077c57 (patch)
tree1d75a6cf1a965916f3e823422553bfe8bad8c069 /proposals
parentae85ab9ee421c43810277783f7a961ca73ae94bb (diff)
downloadtorspec-7c31ee8b3b8b52f9b2978ecdcf947547cf077c57.tar.gz
torspec-7c31ee8b3b8b52f9b2978ecdcf947547cf077c57.zip
Revisions to proposal 348
- history: Added a mailing list thread recalled by Nathan Freitas - TURN description revised to account for single allocation per local port. - Revised the distinction between different Tor stream usage strategies. I moved the "One Stream per Mapping" section up and included it as part of a series of "One stream per Local Port" variants. In that series I started with the standardized TURN protocol, then switched gears to Proposal 339 and a couple further iterations on its basic idea.
Diffstat (limited to 'proposals')
-rw-r--r--proposals/348-udp-app-support.md125
1 files changed, 76 insertions, 49 deletions
diff --git a/proposals/348-udp-app-support.md b/proposals/348-udp-app-support.md
index ac83fed..9482a8f 100644
--- a/proposals/348-udp-app-support.md
+++ b/proposals/348-udp-app-support.md
@@ -28,14 +28,15 @@ Status: Open
- [3rd Party Implementations](#3rd-party-implementations)
- [Future Work on Tor](#future-work-on-tor)
- [Tunneling](#tunneling)
- - [TURN Encapsulated in a Tor Stream](#turn-encapsulated-in-a-tor-stream)
+ - [TURN Encapsulated in Tor Streams](#turn-encapsulated-in-tor-streams)
- [Tor Stream Tunnel to an Exit](#tor-stream-tunnel-to-an-exit)
- [Tor Stream Tunnel to a Rendezvous Point](#tor-stream-tunnel-to-a-rendezvous-point)
- [Specific Designs Using Tor Streams](#specific-designs-using-tor-streams)
- - [One Stream per Tunnel](#one-stream-per-tunnel)
- - [One Stream per Socket](#one-stream-per-socket)
+ - [One Stream per Tunnel (VPN)](#one-stream-per-tunnel-vpn)
+ - [One Stream per Local Port (TURN)](#one-stream-per-local-port-turn)
+ - [One Stream per Local Port (Proposal 339)](#one-stream-per-local-port-proposal-339)
+ - [One Stream per Local Port (NAT Mapping)](#one-stream-per-local-port-nat-mapping)
- [One Stream per Flow](#one-stream-per-flow)
- - [One Stream per Mapping](#one-stream-per-socket)
- [Hybrid Mapping and Flow Approach](#hybrid-mapping-and-flow-approach)
- [Risks](#risks)
- [Behavior Regressions](#behavior-regressions)
@@ -78,6 +79,12 @@ This value is much too small for most applications.
It's possible these UDP protocol details would have been elaborated during design, but the proposal hit a snag elsewhere:
there was no agreement on a way to avoid facilitating new attacks against anonymity.
+#### 2014
+
+In [a thread on the tor-talk mailing list](https://tor-talk.torproject.narkive.com/Weyl6Vrq/using-udpgw-and-tun2socks-over-tor), Nathan Freitas suggested UDP tunneling over Tor using the [*BadVPN*](https://github.com/ambrop72/badvpn) project's `udpgw` protocol.
+
+This protocol was never formally documented and is no longer actively maintained, but it was very broadly similar in scope to a [RFC8656](https://www.rfc-editor.org/rfc/rfc8656) TURN relay operating over a TCP transport.
+
#### 2018
In 2018, Nick Mathewson and Mike Perry wrote a
@@ -152,7 +159,7 @@ A socket is considered *unconnected* if `connect()` has not been called.
These sockets have no default destination, and they accept datagrams from any source.
There does not need to be any particular mapping between the lifetime of these application sockets and any higher-level "connection" the application establishes.
-It's better to think of one socket as one allocated source port.
+It's better to think of one socket as one allocated local port.
A typical application may allocate only a single port (one socket) for talking to many peers.
Every datagram sent or received on the socket may have a different peer address.
@@ -366,7 +373,7 @@ VPN implementations will need to know ahead of time which tunnel circuits to bui
Additionally, it's common for UDP port numbers to be randomly assigned.
This would make highly specific Tor exit policies even less useful and even higher overhead than they are with TCP.
-#### TURN Encapsulated in a Tor Stream
+#### TURN Encapsulated in Tor Streams
The scope of this tunnel is quite similar to the existing TURN relays, used commonly by WebRTC applications to implement fallbacks for clients who cannot find a more direct connection path.
@@ -403,11 +410,26 @@ They are always allocated from the OP end but may be destroyed asynchronously by
We have an opportunity to use this additional existing multiplexing layer to serve a useful function in the new protocol, or we can opt to interact with streams as little as possible in order to keep the protocol features more orthogonal.
-### One Stream per Tunnel
+### One Stream per Tunnel (VPN)
+
+It's possible to transport arbitrary UDP traffic using only a single Tor stream ID within the circuit. In this case, both the source and destination address would need to be represented somehow within an added per-datagram header.
+
+The most common examples of this approach might be TUN/TAP forwarding over SSH, or any VPN that uses a TCP transport.
+
+This design might be preferable if 16-bit stream IDs are found to be in short supply, but otherwise we would expect some benefit from using multiple streams IDs and therefore allowing a shorter per-datagram header.
+
+### One Stream per Local port (TURN)
+
+With the standard TURN relay protocol, implemented over a TCP transport,
+the "allocation" of a relayed port can be performed once per tuple of `(protocol, source IP, source port, destination IP, destination port)`.
+Put another way, TURN does not amplify the number of local ports available.
+An application that wishes to use two relayed ports will need two separate TCP connections, or in this context two separate Tor stream IDs.
-The fewest new streams would be a single stream for all of UDP. This is the result when using an off-the-shelf stream oriented protocol like TURN-over-TCP as our UDP proxy.
+As with the SSH example above, TURN could be implemented without any new Tor message types.
+TURN adds a 4-byte "channel data" header to normal datagrams, to facilitate message framing and peer identification.
-This approach would require only a single new Tor message type:
+We could choose to implement protocol support for bundling TURN relay functionality with a normal Tor exit node.
+In that case, we would want a single new Tor message type:
- `CONNECT_TURN`
@@ -417,16 +439,22 @@ This approach would require only a single new Tor message type:
Note that RFC8656 requires authentication before data can be relayed, which is a good default best practice for the internet perhaps but is the opposite of what Tor is trying to do. We would either deviate from the specification to relax this auth requirement, or provide a way for clients to discover credentials: perhaps by fixing them ahead of time or by including them in the relay descriptor.
-### One Stream per Socket
+If authentication is used, we must consider the protocol's privacy limitations.
+STUN/TURN usernames are conveyed in plaintext unless an additional TLS layer is also in use.
+For anonymity, use a randomly generated username.
+The [`draft-uberti-behave-turn-rest-00`](https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00) document describes a method for generating time-limited credentials, as implemented through `--use-auth-secret` in `coturn`.
+Username timestamps should be randomized, to avoid exposing the precise local system clock.
-One stream **per socket** was the approach suggested in [Proposal 339](https://spec.torproject.org/proposals/339-udp-over-tor.html) by Nick Mathewson in 2020.
+### One Stream per Local Port (Proposal 339)
-In proposal 339, there would be one new type of stream and three new message types: `CONNECT_UDP`, `CONNECTED_UDP`, and `DATAGRAM`.
+One stream **per socket** was the approach suggested in [Proposal 339](https://spec.torproject.org/proposals/339-udp-over-tor.html) by Nick Mathewson in 2020, defining a per-local-port approach using slightly different terminology than TURN.
-Each stream's lifetime would match the lifetime of a local port allocation.
-There would be a single peer `(remote address, remote port)` allowed per `local port`.
-This matches the usage of BSD-style sockets on which `connect()` has completed.
-It's incompatible with many of the applications analyzed.
+Proposal 339 chooses a different UDP subset to support, but otherwise the main technical distinction vs. TURN's per-local-port stream comes down to the framing layer.
+TURN defines an additional header to be used within a TCP-like stream, whereas proposal 339 avoids the two redundant length bytes by defining new Tor message types: `CONNECT_UDP`, `CONNECTED_UDP`, and `DATAGRAM`.
+
+Similar to TURN, each stream's lifetime would match the lifetime of a local port allocation.
+Unlike TURN, there would be a single peer `(remote address, remote port)` allowed per `local port`.
+This matches the usage of BSD-style sockets on which `connect()` has completed, but it's incompatible with many of the applications analyzed.
Multiple peers are typically needed for a variety of reasons, like connectivity checks or multi-region servers.
This approach would be simplest to implement and specify, especially in the existing C tor implementation.
@@ -434,6 +462,36 @@ It also unfortunately has very limited compatibility, and no clear path toward i
A simple one-to-one mapping between streams and sockets would preclude the optimizations necessary to address [local port exhaustion](#local-port-exhaustion) risks below. Solutions under this design are possible, but only by decoupling logical protocol-level sockets from the ultimate implementation-level sockets and reintroducing much of the complexity that we attempted to avoid by choosing this design.
+### One Stream per Local Port (NAT Mapping)
+
+We could improve the compatibility of Proposal 339 while retaining its very low per-datagram overhead by updating it to align with terminology and requirements from [RFC4787](https://www.rfc-editor.org/rfc/rfc4787) where practical.
+
+This approach would use each stream to represent one **endpoint-independent mapping**, for use as a local UDP port in communication with multiple peers.
+
+A mapping would always be allocated from the OP (client) side.
+It could explicitly specify a filtering style, if we wish to allow applications to request non-port-dependent filtering for compatibility.
+Each datagram within the stream would still need to be tagged with a peer address/port in some way.
+
+This approach would involve a single new type of stream, and two new messages that pertain to these *mapping* streams:
+
+- `NEW_UDP_MAPPING`
+
+ - Always client-to-exit.
+ - Creates a new mapping, with a specified stream ID.
+ - Succeeds instantly; no reply is expected, early data is ok.
+ - Externally-visible local port number is arbitrary, and must be determined through interaction with other endpoints.
+ - Might contain an IP "don't fragment" flag.
+ - Might contain a requested filtering mode.
+ - Lifetime is until circuit teardown or `END` message.
+
+- `UDP_MAPPING_DATAGRAM`
+
+ - Conveys one datagram on a stream previously defined by `NEW_UDP_MAPPING`.
+ - Includes peer address (IPv4/IPv6) as well as datagram content.
+
+This updated approach is more compatible than Proposal 339 but it's not yet as efficient as TURN.
+Peers are almost always repeated over the course of a tunnel's lifetime, so the header space needed for addressing could be compressed by keeping some persistent information about peers as well as mappings.
+
### One Stream per Flow
One stream **per flow** has also been suggested.
@@ -446,7 +504,7 @@ This has advantages in keeping the datagram cells simple, with no additional IDs
It may also have advantages in DoS-prevention and in privacy analysis.
Stream lifetimes, in this case, would not have any specific meaning other than the lifetime of the ID itself.
-The bundle of flows associated with one source port would still all be limited to the lifetime of a Tor circuit, by scoping the source port identifier to be contained within the lifetime of its circuit.
+The bundle of flows associated with one local port would still all be limited to the lifetime of a Tor circuit, by scoping the local port identifier to be contained within the lifetime of its circuit.
It would be necessary to allocate a new stream ID any time a new `(local port, remote address, remote port)` tuple is seen.
This would most commonly happen as a result of a first datagram sent to a new peer, coinciding with the establishment of a NAT-style mapping and the possible allocation of a socket on the exit.
@@ -464,37 +522,6 @@ Even with the stricter **address and port-dependent filtering** clients may stil
This approach thus requires some attention to either correctly allocating stream IDs on both sides of the circuit, or choosing a filtering strategy and filter/mapping lifetime that does not ever leave stream IDs undefined when expecting incoming datagrams.
-### One Stream per Mapping
-
-One stream **per mapping** is an alternative which attempts to reduce the number of edge cases by merging the lifetimes of one stream and one **endpoint-independent mapping**.
-
-A mapping would always be allocated from the OP (client) side.
-It could explicitly specify a filtering style, if we wish to allow applications to request non-port-dependent filtering for compatibility.
-Each datagram within the stream would still need to be tagged with a peer address/port in some way.
-
-This approach would involve a single new type of stream, and two new messages that pertain to these *mapping* streams:
-
-- `NEW_UDP_MAPPING`
-
- - Always client-to-exit.
- - Creates a new mapping, with a specified stream ID.
- - Succeeds instantly; no reply is expected, early data is ok.
- - Externally-visible local port number is arbitrary, and must be determined through interaction with other endpoints.
- - Might contain an IP "don't fragment" flag.
- - Might contain a requested filtering mode.
- - Lifetime is until circuit teardown or `END` message.
-
-- `UDP_MAPPING_DATAGRAM`
-
- - Conveys one datagram on a stream previously defined by `NEW_UDP_MAPPING`.
- - Includes peer address (IPv4/IPv6) as well as datagram content.
-
-This puts us in a very similar design space to TURN (RFC8656).
-In that protocol, "allocations" are made explicitly on request, and assigned a random relayed port.
-TURN also uses its allocations as an opportunity to support a *Don't Fragment* flag.
-
-The principal disadvantage of this approach is in space overhead, especially the proportional overhead on small datagrams which must each carry a full-size address.
-
### Hybrid Mapping and Flow Approach
We can extend the approach above with an optimization that addresses the undesirable space overhead from redundant address headers.
@@ -708,7 +735,7 @@ Without a clear way to implement fully generic UDP support, we're left with appl
Different applications may have contrasting needs, and we can only achieve high levels of both compatibility and privacy by delegating some choices to app authors or per-app VPN settings.
This points to an alternative in which UDP support is excluded from Tor as much as possible while still supporting application requirements.
-For example, this could motivate the [TURN Encapsulated in a Tor Stream](#turn-encapsulated-in-a-tor-stream) design, or even simpler designs where TURN servers are maintained independently from the Tor exit infrastructure.
+For example, this could motivate the [TURN Encapsulated in Tor Streams](#turn-encapsulated-in-tor-streams) design, or even simpler designs where TURN servers are maintained independently from the Tor exit infrastructure.
The next prototyping step we would need at this stage is a version of `onionmasq` extended to support NAT-style UDP mappings using TURN allocations provided through TURN-over-TCP or TURN-over-TLS.
This can be a platform for further application compatibility experiments.