Skip to content

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 Hash56 字节明文密码的 SHA-224 十六进制编码
CRLF2 字节\r\n(0x0D 0x0A)
Command1 字节0x01 = TCP Connect、0x03 = UDP Associate
Address可变长SOCKS5 风格:AddrType(1B) + Address + Port(2B, BE)
CRLF2 字节\r\n(0x0D 0x0A)

地址类型(与 SOCKS5 相同):

字节值类型
0x01IPv4(4 字节)
0x03域名(1 字节长度 + 字符串)
0x04IPv6(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)
Length2 字节大端序 uint16,载荷长度(最大 8192)
CRLF2 字节\r\n
PayloadLength 字节UDP 数据报

服务端的 PacketReader 读取每个封装的数据包,并将目标地址附加为 buffer.UDP

go
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 字符串:

go
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.MemoryUser
  • email:映射小写邮箱 → *protocol.MemoryUser

源码:proxy/trojan/validator.go:12-82

入站处理器(服务端)

文件:proxy/trojan/server.go

连接处理

mermaid
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 字节)并进行快速验证:

go
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。这在完整头部解析之前就进行检查。

回落机制

当配置了回落时,无效连接会被透明地转发到另一个服务器。回落系统支持三级匹配:

  1. SNIname):与 TLS ServerName 匹配
  2. ALPNalpn):与协商的 ALPN 协议匹配
  3. 路径path):与 HTTP 请求路径匹配(从首字节中提取)
go
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 转发给后端:

go
// 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

客户端处理器的工作流程:

  1. 通过配置的传输层(通常是 TLS)拨号连接服务器
  2. 创建 ConnWriter,在首次 Write() 调用时延迟写入头部
  3. 对于 UDP,使用 PacketWriter 进行逐包封装
  4. 将首个载荷与头部一起发送,实现 0-RTT 优化
go
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() 组装如下内容:

go
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

实现说明

  1. TLS 是必需的:Trojan 本身不提供任何加密。没有 TLS 的情况下,密码散列和所有流量都以明文传输。该协议被设计为始终与 TLS 或 REALITY 传输配合使用。

  2. 回落对隐蔽性至关重要:当未找到有效的 Trojan 头部时,服务端应将连接转发到真实的 Web 服务器。这意味着端口扫描和探测无法将该服务器与正常的 HTTPS 站点区分开来。

  3. 无响应头:与 VMess 不同,不存在服务端到客户端的头部。这简化了实现,但意味着客户端无法在协议级别检测服务端错误。

  4. 最大 UDP 载荷:每个 UDP 帧限制为 8192 字节(maxLength = 8192)。超过此限制的数据包将被拒绝。

  5. ConnReader 是有状态的ConnReader.ParseHeader() 仅调用一次。之后,Read() 直接透传到底层读取器。headerParsed 标志防止重复解析。

  6. 网络支持:服务端支持 net.Network_TCPnet.Network_UNIX,但不支持原始 UDP。UDP 流量作为封装数据在 TCP/TLS 连接内进行隧道传输。

源码:proxy/trojan/server.go:144-146

用于重新实现目的的技术分析。