Trojan 协议
Trojan 是一种旨在与正常 TLS/HTTPS 流量无法区分的协议。它完全依赖 TLS 传输层进行加密——协议本身在 TLS 隧道中以明文传输认证信息和命令。当收到无效请求时,服务端可以"回落"到真实的 Web 服务器,使代理无法被检测。
概述
- 方向:入站 + 出站
- 传输:TCP、UNIX 套接字(必须使用 TLS/REALITY 传输)
- 加密:无(委托给传输层 TLS)
- 认证:SHA224(password) 十六进制字符串
- 多路复用:原生不支持(但可通过其他层进行 XUDP 包装)
- 回落:内置多级回落(SNI、ALPN、路径)
线路格式
TCP 请求(客户端到服务端)
+----------------------------------------------+------+-----+----------+------+
| SHA224(password) hex | CRLF | Cmd | Address | CRLF |
| 56 bytes ASCII | 2B | 1B | variable | 2B |
+----------------------------------------------+------+-----+----------+------+
| Payload ... |
+-----------------------------------------------------------------------------+源码:proxy/trojan/protocol.go:64-95
| 字段 | 大小 | 描述 |
|---|---|---|
| Password Hash | 56 字节 | 明文密码的 SHA-224 十六进制编码 |
| CRLF | 2 字节 | \r\n(0x0D 0x0A) |
| Command | 1 字节 | 0x01 = TCP Connect、0x03 = UDP Associate |
| Address | 可变长 | SOCKS5 风格:AddrType(1B) + Address + Port(2B, BE) |
| CRLF | 2 字节 | \r\n(0x0D 0x0A) |
地址类型(与 SOCKS5 相同):
| 字节值 | 类型 |
|---|---|
0x01 | IPv4(4 字节) |
0x03 | 域名(1 字节长度 + 字符串) |
0x04 | IPv6(16 字节) |
源码:proxy/trojan/protocol.go:16-21
头部之后,TCP 流上的剩余数据为原始载荷。
TCP 响应
服务端直接发送原始载荷——无响应头。这是因为该协议被设计为看起来像正常的 TLS 流量。
UDP 帧封装
当命令为 UDP(0x03)时,载荷按数据包封装:
+----------+---------+------+---------+
| Address | Length | CRLF | Payload |
| variable | 2B BE | 2B | Length |
+----------+---------+------+---------+
| next packet ... |
+-------------------------------------+源码:proxy/trojan/protocol.go:125-150
| 字段 | 大小 | 描述 |
|---|---|---|
| Address | 可变长 | SOCKS5 风格:AddrType(1B) + Address + Port(2B, BE) |
| Length | 2 字节 | 大端序 uint16,载荷长度(最大 8192) |
| CRLF | 2 字节 | \r\n |
| Payload | Length 字节 | UDP 数据报 |
服务端的 PacketReader 读取每个封装的数据包,并将目标地址附加为 buffer.UDP:
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
addr, port, err := addrParser.ReadAddressPort(nil, r)
// ...
remain := int(binary.BigEndian.Uint16(lengthBuf[:]))
if remain > maxLength { return nil, errors.New("oversize payload") }
// read CRLF, then read `remain` bytes of payload
}源码:proxy/trojan/protocol.go:220-262
密码认证
密码使用 SHA-224 散列并进行十六进制编码,生成 56 字节的 ASCII 字符串:
func hexSha224(password string) []byte {
buf := make([]byte, 56)
hash := sha256.New224()
hash.Write([]byte(password))
hex.Encode(buf, hash.Sum(nil))
return buf
}源码:proxy/trojan/config.go:43-49
MemoryAccount 同时存储明文密码和预计算的 56 字节十六进制密钥。
验证器
Validator 使用两个 sync.Map 实例:
users:映射十六进制编码的 SHA224 散列 →*protocol.MemoryUseremail:映射小写邮箱 →*protocol.MemoryUser
源码:proxy/trojan/validator.go:12-82
入站处理器(服务端)
文件:proxy/trojan/server.go
连接处理
sequenceDiagram
participant C as 客户端
participant S as 服务端
participant F as 回落
C->>S: TLS ClientHello
S->>C: TLS ServerHello + 证书
C->>S: SHA224(password) + CRLF + 命令 + 地址 + CRLF + 载荷
alt 密码有效
S->>S: 解析命令和地址
alt TCP 命令
S->>C: 原始代理响应
else UDP 命令
S->>C: 封装的 UDP 数据包
end
else 无效或数据不足
S->>F: 将整个连接转发到回落
end服务端读取第一个缓冲区(最多 buf.Size 字节)并进行快速验证:
if firstLen < 58 || first.Byte(56) != '\r' {
// Not trojan protocol - fallback
shouldFallback = true
} else {
user = s.validator.Get(hexString(first.BytesTo(56)))
if user == nil {
shouldFallback = true
}
}源码:proxy/trojan/server.go:176-201
关键洞察:前 56 字节必须是有效的十六进制 SHA224 散列,且位置 56 处必须是 \r。这在完整头部解析之前就进行检查。
回落机制
当配置了回落时,无效连接会被透明地转发到另一个服务器。回落系统支持三级匹配:
- SNI(
name):与 TLS ServerName 匹配 - ALPN(
alpn):与协商的 ALPN 协议匹配 - 路径(
path):与 HTTP 请求路径匹配(从首字节中提取)
type Fallback struct {
Name string // SNI match
Alpn string // ALPN match
Path string // HTTP path match
Dest string // Destination address (e.g., "127.0.0.1:8080")
Type string // Network type ("tcp" or "unix")
Xver uint64 // PROXY protocol version (0=none, 1=v1, 2=v2)
}回落数据结构是三级嵌套映射:map[name]map[alpn]map[path]*Fallback
源码:proxy/trojan/server.go:66-113
回落处理器还支持 PROXY 协议(v1 文本和 v2 二进制),用于将真实客户端 IP 转发给后端:
// PROXY protocol v1
"PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"
// PROXY protocol v2
"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" // signature
"\x21\x11\x00\x0C" // v2 + PROXY + AF_INET + STREAM + 12 bytes源码:proxy/trojan/server.go:493-522
出站处理器(客户端)
文件:proxy/trojan/client.go
客户端处理器的工作流程:
- 通过配置的传输层(通常是 TLS)拨号连接服务器
- 创建
ConnWriter,在首次Write()调用时延迟写入头部 - 对于 UDP,使用
PacketWriter进行逐包封装 - 将首个载荷与头部一起发送,实现 0-RTT 优化
connWriter := &ConnWriter{
Writer: bufferWriter,
Target: destination,
Account: account,
}
// First write triggers header send
buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100)源码:proxy/trojan/client.go:99-128
ConnWriter.writeHeader() 组装如下内容:
buffer.Write(c.Account.Key) // 56-byte SHA224 hex
buffer.Write(crlf) // \r\n
buffer.WriteByte(command) // 0x01 or 0x03
addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port)
buffer.Write(crlf) // \r\n源码:proxy/trojan/protocol.go:64-95
实现说明
TLS 是必需的:Trojan 本身不提供任何加密。没有 TLS 的情况下,密码散列和所有流量都以明文传输。该协议被设计为始终与 TLS 或 REALITY 传输配合使用。
回落对隐蔽性至关重要:当未找到有效的 Trojan 头部时,服务端应将连接转发到真实的 Web 服务器。这意味着端口扫描和探测无法将该服务器与正常的 HTTPS 站点区分开来。
无响应头:与 VMess 不同,不存在服务端到客户端的头部。这简化了实现,但意味着客户端无法在协议级别检测服务端错误。
最大 UDP 载荷:每个 UDP 帧限制为 8192 字节(
maxLength = 8192)。超过此限制的数据包将被拒绝。ConnReader 是有状态的:
ConnReader.ParseHeader()仅调用一次。之后,Read()直接透传到底层读取器。headerParsed标志防止重复解析。网络支持:服务端支持
net.Network_TCP和net.Network_UNIX,但不支持原始 UDP。UDP 流量作为封装数据在 TCP/TLS 连接内进行隧道传输。
源码:proxy/trojan/server.go:144-146