[tor-dev] Hackathon UDP Support

Matthew Finkel sysrqb at torproject.org
Wed Apr 14 14:28:46 UTC 2021


Hello!

During the Hackweek, I spent a little time hacking support of
UDP-over-Tor. The goal of the project was supporting UDP onion services,
and leaving Exit support for another time.

I didn't have a working implementation by the end of the week, but life
moves one.

This is an initial mail about the general idea, and I'll follow up with
a concrete proposal in the not-too-distant future.

I have a (currently broken) branch `feature_udp_data` in my gitlab repo
[0]. I started simplifying/refactoring the hack job, but it's still a
work in progress.

I'll briefly describe how the working version [1] is designed. Overall,
this mostly just-works. While UDP is not a reliable routed protocol,
datagrams are not dropped within the network. We delegate drops to the
edges' recv/send queues (and any intermediate network equipment).


Client Side:

The patch continued our dependence on SOCKS5 [2] and (maybe correctly)
implemented the UDP ASSOCIATE command. The client sends a normal SOCKS5
handshake, except the new command is 0x03. The tor client then:
  - Establishes a rendezvous circuit with the specified onion service
    hostname in the handshake
  - On completing that connection, creates and binds a datagram socket
  - Sends the application a Success response and includes the datagram
    socket's bound address and port in that message.

The client then sends all datagrams to the provided (bound)
address:port, and the TCP socket used in the SOCKS5 handshake is only
kept open as a way of tracking the UDP socket's lifetime. Tor knows that
any datagram payload should be transported as a datagram instead of a
stream.


Tor Internal:

The patch introduces two relay cell commands: DATAGRAM and
DATAGRAM_FRAG. These relay cell types are stateless. Simply, when a tor
client (or general edge, in the future) receives a DATAGRAM_FRAG relay
cell, it knows that the payload is only a fragment of an entire
datagram. As a result, tor queues the data and does not send it to the
application. When a DATAGRAM cell is received, then tor knows the
contained payload is either:
  1) a complete datagram that fit into a single relay cell
  2) the final payload of a fragmented datagram

In either case, tor appends the payload to the (possibly empty) queue
and then sends it to the application. Tor doesn't need to care which one
is the current case. Also, Tor doesn't take into account a MTU mismatch
between the client-side and server-side - future work.

As an aside, I considered introducing only a single DATAGRAM relay cell,
and reserving the first byte for metadata (and using a bit indicating if
the payload is a fragment). However, I couldn't convince myself that
losing one byte of payload capacity was worth the advantages. I'll leave
this as a valuable discussion during the proposal process.


Onion Service Side:

The onion service is configured normally, except now there is a
HiddenServiceType configuration option. When it isn't provided, the
default value is "stream", otherwise it should be "dgram". This
configuration tells tor that a "stream" opened to this onion service (on
the specified port) is actually connectionless, and any payload data
should be sent and received as datagrams. Following from the previous
section, when a DATAGRAM relay cell is received, the queued payload is
packaged as a datagram and sent to the configured onion service
address:port.


Future design considerations:
  - Better client-side interface than SOCKS5 UDP Associate?
  - Relay cell design improvements
  - Cleaner onion service configuration?
  - MTU difference on the client/server-sides
  - What's missing before we can support this on Exits?


[0] https://gitlab.torproject.org/sysrqb/tor/
[1] commit ebfb4a97713d49a5e7f61709658b9d55ac21ec95
[2] https://tools.ietf.org/html/rfc1928


More information about the tor-dev mailing list