Version
0x05
Overview
The TUIC protocol relies on a multiplex-able TLS-encrypted stream. All relaying tasks are negotiated by the Header in Commands.
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.
Command
+-----+------+----------+
| VER | TYPE | OPT |
+-----+------+----------+
| 1 | 1 | Variable |
+-----+------+----------+
where:
VER- the TUIC protocol versionTYPE- command typeOPT- command type specific data
Command Types
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).
Command Type Specific Data
Authenticate
+------+-------+
| 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, thelabelshould be the client UUID and thecontextshould be the raw password.
Connect
+----------+
| ADDR |
+----------+
| Variable |
+----------+
where:
ADDR- target address. See Address
Packet
+----------+--------+------------+---------+------+----------+
| 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
Dissociate
+----------+
| ASSOC_ID |
+----------+
| 2 |
+----------+
where:
ASSOC_ID- UDP relay session ID. See UDP relaying
Heartbeat
+-+
| |
+-+
| |
+-+
Address
Address is a variable-length field that encodes network addresses.
+------+----------+----------+
| 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 are not the first fragment of a UDP packet.
The port number is encoded in 2 bytes after the domain name / IP address.
Protocol Flow
This section describes protocol flow in detail with QUIC as underlying transport.
The protocol itself does not enforce how transport is managed. It can even be integrated into existing services such as HTTP/3.
Here is a typical TUIC flow over a QUIC connection:
Authentication
The client opens a unidirectional_stream and sends an Authenticate command. This can be parallelized with other commands.
The server verifies token. If valid, connection is authenticated and ready for relay tasks.
If server receives other commands before authentication, it should parse command headers and pause. After authentication succeeds, paused tasks should resume.
TCP relaying
Connect initializes a TCP relay.
Client opens a bidirectional_stream and sends Connect. Once command header transmission completes, client may begin relaying immediately without waiting for server response.
Server receives Connect, opens TCP stream to target address, then relays data between TCP stream and QUIC bidirectional stream.
UDP relaying
TUIC achieves 0-RTT full-cone UDP forwarding by syncing UDP session ID (ASSOC_ID) between client and server.
Both sides maintain a UDP session table per QUIC connection, mapping each ASSOC_ID to an associated UDP socket.
ASSOC_ID is a client-generated 16-bit unsigned integer. If client wants server to reuse same UDP socket, it reuses same ASSOC_ID.
When server receives a Packet, it checks whether ASSOC_ID already exists. If not, allocate a UDP socket for that association. The server uses this socket to send requested outbound UDP traffic and to receive inbound UDP packets from any destination, wrap them in Packet headers, and send back to client.
A UDP packet can be fragmented into multiple Packet commands. PKT_ID, FRAG_TOTAL, and FRAG_ID identify and reassemble fragments.
As a client, Packet can be sent via:
- QUIC
unidirectional_stream(UDP relay modequic) - QUIC
datagram(UDP relay modenative)
When server receives the first Packet in an association, it should use the same mode for downstream packets.
A UDP session can be dissociated by client sending Dissociate through QUIC unidirectional_stream. Server then removes session and releases associated UDP socket.
Heartbeat
When any relay task is ongoing, client should periodically send Heartbeat over QUIC datagram to keep connection alive.
Error Handling
No command has a standardized response. If server receives invalid commands or encounters processing errors (for example unreachable target, auth failure), there is no protocol-level standard behavior. Handling is implementation-defined: server may close QUIC connection or ignore command.
For example, if server receives Connect with unreachable target, it may close the corresponding bidirectional_stream as failure signal.
In-depth Protocol Analysis
After deeper analysis, TUIC shows a modern high-performance design that leverages QUIC and TLS effectively. At the same time, its minimal spec places significant responsibility on implementers.
Executive Summary
TUIC is a compact, secure, low-latency-oriented proxy protocol. It uses simple command structures over multiplexed TLS streams (ideally QUIC), and delegates encryption/reliability complexity to lower layers.
Its highlight is efficient 0-RTT full-cone UDP relaying, making it suitable for real-time workloads.
The primary trade-off is non-standardized, implementation-defined error handling. This keeps spec simple but shifts burden to developers for robustness, security, and interoperability.
Key Strengths
-
Outstanding performance
- TCP: client starts data transfer immediately after sending
Connect. - UDP:
ASSOC_IDdesign enables 0-RTT full-cone forwarding. - Efficiency: QUIC datagrams for heartbeat and native UDP mode reduce overhead.
- TCP: client starts data transfer immediately after sending
-
Robust security
- Authentication: token bound to TLS session via TLS Keying Material Exporter (RFC 5705), which helps prevent replay attacks.
- Encryption: all traffic protected by underlying TLS.
-
Design elegance
- protocol stays focused and simple;
- command framing is easy to parse;
- avoids duplicating transport-layer responsibilities.
Critical Implementation Challenges & Recommendations
1. UDP ASSOC_ID lifecycle management (DoS risk)
- Problem: spec defines association creation but not cleanup strategy. Malicious clients can create unlimited associations and exhaust descriptors/memory.
- Recommendation: implement cache with auto-eviction policy.
- use TTL or LRU eviction;
- refresh TTL on use;
- periodic background sweep to close and remove expired associations/sockets;
- a practical default TTL is around 300 seconds.
2. UDP fragment buffer management (DoS risk)
- Problem: attacker can send many first fragments (
FRAG_ID=0) without completion, forcing unbounded memory growth. - Recommendation: strict reassembly buffer governance.
- cap concurrent in-flight reassemblies per
ASSOC_ID(for example 4-8); - short reassembly timeout (for example 1-2 seconds);
- global memory cap across all fragment buffers as final defense.
- cap concurrent in-flight reassemblies per
3. Standardize error handling
- Problem: without error semantics, failures become silent and implementations diverge.
- Recommendation: define explicit transport-aware error policy.
- authentication failure: fatal connection-level error, close QUIC connection with dedicated app error code;
- TCP connection failure: stream-level error, close affected bidirectional stream;
- malformed command: close stream carrying invalid command.
Conclusion
TUIC is a strong and practical protocol for secure low-latency mixed TCP/UDP tunneling. But minimal spec does not mean simple production implementation. Production-grade TUIC requires careful state lifecycle design, resource governance, and explicit failure semantics to close gaps not fully specified by protocol text.