0x05
The TUIC protocol relies on a multiplex-able TLS-encrypted stream. All relaying tasks are negotiated by the Header
in Command
s.
The protocol doesn't care about the underlying transport. However, it is mainly designed to be used with QUIC. See Protocol Flow for detailed mechanism.
All fields are in Big Endian unless otherwise noted.
+-----+------+----------+
| VER | TYPE | OPT |
+-----+------+----------+
| 1 | 1 | Variable |
+-----+------+----------+
where:
VER
- the TUIC protocol versionTYPE
- command typeOPT
- command type specific data
There are five types of command:
0x00
-Authenticate
- for authenticating the multiplexed stream0x01
-Connect
- for establishing a TCP relay0x02
-Packet
- for relaying (fragmented part of) a UDP packet0x03
-Dissociate
- for terminating a UDP relaying session0x04
-Heartbeat
- for keeping the QUIC connection alive
Command Connect
and Packet
carry payload (stream / packet fragment)
+------+-------+
| UUID | TOKEN |
+------+-------+
| 16 | 32 |
+------+-------+
where:
UUID
- client UUIDTOKEN
- client token. The client raw password is hashed into a 256-bit long token using TLS Keying Material Exporter on current TLS session. While exporting, thelabel
should be the client UUID and thecontext
should be the raw password.
+----------+
| ADDR |
+----------+
| Variable |
+----------+
where:
ADDR
- target address. See Address
+----------+--------+------------+---------+------+----------+
| ASSOC_ID | PKT_ID | FRAG_TOTAL | FRAG_ID | SIZE | ADDR |
+----------+--------+------------+---------+------+----------+
| 2 | 2 | 1 | 1 | 2 | Variable |
+----------+--------+------------+---------+------+----------+
where:
ASSOC_ID
- UDP relay session ID. See UDP relayingPKT_ID
- UDP packet ID. See UDP relayingFRAG_TOTAL
- total number of fragments of the UDP packetFRAG_ID
- fragment ID of the UDP packetSIZE
- length of the (fragmented) UDP packetADDR
- target (from client) or source (from server) address. See Address
+----------+
| ASSOC_ID |
+----------+
| 2 |
+----------+
where:
ASSOC_ID
- UDP relay session ID. See UDP relaying
+-+
| |
+-+
| |
+-+
Address
is a variable-length field that encodes the network address
+------+----------+----------+
| TYPE | ADDR | PORT |
+------+----------+----------+
| 1 | Variable | 2 |
+------+----------+----------+
where:
TYPE
- the address typeADDR
- the addressPORT
- the port
The address type can be one of the following:
0xff
: None0x00
: Fully-qualified domain name (the first byte indicates the length of the domain name)0x01
: IPv4 address0x02
: IPv6 address
Address type None
is used in Packet
commands that is not the first fragment of a UDP packet.
The port number is encoded in 2 bytes after the Domain name / IP address.
This section describes the protocol flow in detail with QUIC as the underlying transport.
The TUIC protocol doesn't care about how the underlying transport is managed. It can even be integrated into other existing services, such as HTTP/3.
Here is a typical flow of the TUIC protocol on a QUIC connection:
The client opens a unidirectional_stream
and sends a Authenticate
command. This procedure can be parallelized with other commands (relaying tasks).
The server receives the Authenticate
command and verifies the token. If the token is valid, the connection is authenticated and ready for other relaying tasks.
If the server receives other commands before the Authenticate
command, it should only accept the command header part and pause. After the connection is authenticated, the server should resume all the paused tasks.
Command Connect
is used for initializing a TCP relay.
The client opens a bidirectional_stream
and sends a Connect
command. After the command header transmission is completed, the client can start using the stream for TCP relaying, no need to wait for the server's response (server will never respond, actually).
The server receives the Connect
command and opens a TCP stream to the target address. After the stream is established, the server can start relaying data between the TCP stream and the bidirectional_stream
.
TUIC achieves 0-RTT Full Cone UDP forwarding by syncing UDP session ID (associate ID) between the client and the server.
Both the client and the server should create a UDP session table for each QUIC connection, mapping every associate ID to an associated UDP socket.
The associate ID is a 16-bit unsigned integer generated by the client. If the client wants to send UDP packets using the same socket of the server, the attached associate ID in the Packet
command should be the same.
When receiving a Packet
command, the server should check whether the attached associate ID is already associated with a UDP socket. If not, the server should allocate a UDP socket for the associate ID. The server will use this UDP socket to send UDP packets requested by the client, and accepting UDP packets from any destination at the same time, prefixing them with the Packet
command header then sends back to the client.
A UDP packet can be fragmented into multiple Packet
commands. Field PKT_ID
, FRAG_TOTAL
and FRAG_ID
are used to identify and reassemble the fragmented UDP packets.
As a client, a Packet
can be sent through:
- QUIC
unidirectional_stream
(UDP relay mode quic) - QUIC
datagram
(UDP relay mode native)
When the server receives the first Packet
from an UDP relay session (associate ID), it should use the same mode to send back the Packet
commands.
A UDP session can be dissociated by sending a Dissociate
command through a QUIC unidirectional_stream
by client. The server will remove the UDP session and release the associated UDP socket.
When there is any ongoing relaying task, the client should send a Heartbeat
command through a QUIC datagram
periodically to keep the QUIC connection alive.
Note that there is no response for any command. If the server receives a command that is not valid, or encounters any error during the processing (e.g. the target address is unreachable, authentication failure), there is no standard way to deal with it. The behavior is implementation-defined. The server may close the QUIC connection, or just ignore the command.
For example, if the server receives a Connect
command with an unreachable target address, it may close bidirectional_stream
to indicate the error.