Trojan 协议
Trojan 是一种旨在模仿 HTTPS 流量的代理协议。它使用基于 SHA-224 哈希的密码认证方案,并支持在无法识别流量时回退到真实的 Web 服务器。
源码: protocol/trojan/, transport/trojan/
线路格式
Trojan 协议使用一种简单的、对 TLS 友好的线路格式:
+----------+------+---------+----------+------+----------+
| Password | CRLF | Command | Address | CRLF | Payload |
| (56 hex) | \r\n | (1 byte)| (variable)|\r\n | (variable)|
+----------+------+---------+----------+------+----------+密码推导
密码被转换为 56 字节的十六进制编码 SHA-224 哈希:
const KeyLength = 56
func Key(password string) [KeyLength]byte {
var key [KeyLength]byte
hash := sha256.New224() // SHA-224,非 SHA-256
hash.Write([]byte(password))
hex.Encode(key[:], hash.Sum(nil)) // 28 字节 -> 56 个十六进制字符
return key
}SHA-224 产生 28 字节(224 位),十六进制编码后恰好为 56 个字符。在 handshake 中按原样传输(非 base64)。
命令
const (
CommandTCP = 1 // TCP 连接
CommandUDP = 3 // UDP 关联
CommandMux = 0x7f // Trojan-Go 多路复用
)TCP Handshake
Client -> Server:
[56 bytes: hex SHA224(password)]
[2 bytes: \r\n]
[1 byte: 0x01 (TCP)]
[variable: SOCKS address (type + addr + port)]
[2 bytes: \r\n]
[payload data...]实现中使用了缓冲区合并以提高效率:
func ClientHandshake(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr, payload []byte) error {
headerLen := KeyLength + M.SocksaddrSerializer.AddrPortLen(destination) + 5
header := buf.NewSize(headerLen + len(payload))
header.Write(key[:]) // 56 字节密码哈希
header.Write(CRLF) // \r\n
header.WriteByte(CommandTCP) // 0x01
M.SocksaddrSerializer.WriteAddrPort(header, destination)
header.Write(CRLF) // \r\n
header.Write(payload) // 合并的首个有效载荷
conn.Write(header.Bytes()) // 单次写入系统调用
}UDP 包格式
在初始 handshake(使用 CommandUDP)之后,UDP 包的帧格式为:
+----------+--------+------+----------+
| Address | Length | CRLF | Payload |
| (variable)| (2 BE) | \r\n | (Length) |
+----------+--------+------+----------+func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {
header := buf.With(buffer.ExtendHeader(...))
M.SocksaddrSerializer.WriteAddrPort(header, destination)
binary.Write(header, binary.BigEndian, uint16(bufferLen))
header.Write(CRLF)
conn.Write(buffer.Bytes())
}
func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {
destination := M.SocksaddrSerializer.ReadAddrPort(conn)
var length uint16
binary.Read(conn, binary.BigEndian, &length)
rw.SkipN(conn, 2) // 跳过 CRLF
buffer.ReadFullFrom(conn, int(length))
return destination, nil
}UDP 初始 Handshake
第一个 UDP 包同时包含 Trojan 头部和第一个包的地址/长度:
[56 bytes key][CRLF][0x03 UDP][dest addr][CRLF][dest addr][length][CRLF][payload]
^handshake^ ^first packet^注意目标地址出现了两次:一次在 handshake 中,一次在包帧中。
Trojan 服务层
transport/trojan/service.go 实现了服务端协议处理器:
type Service[K comparable] struct {
users map[K][56]byte // user -> key
keys map[[56]byte]K // key -> user(反向查找)
handler Handler // TCP + UDP 处理器
fallbackHandler N.TCPConnectionHandlerEx
logger logger.ContextLogger
}服务端连接处理
func (s *Service[K]) NewConnection(ctx, conn, source, onClose) error {
// 1. 读取 56 字节密码 key
var key [KeyLength]byte
n, err := conn.Read(key[:])
if n != KeyLength {
return s.fallback(ctx, conn, source, key[:n], ...)
}
// 2. 认证
if user, loaded := s.keys[key]; loaded {
ctx = auth.ContextWithUser(ctx, user)
} else {
return s.fallback(ctx, conn, source, key[:], ...)
}
// 3. 跳过 CRLF,读取命令
rw.SkipN(conn, 2)
binary.Read(conn, binary.BigEndian, &command)
// 4. 读取目标地址,跳过尾部 CRLF
destination := M.SocksaddrSerializer.ReadAddrPort(conn)
rw.SkipN(conn, 2)
// 5. 根据命令分发
switch command {
case CommandTCP:
s.handler.NewConnectionEx(ctx, conn, source, destination, onClose)
case CommandUDP:
s.handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, ...)
default: // CommandMux (0x7f)
HandleMuxConnection(ctx, conn, source, s.handler, s.logger, onClose)
}
}回退机制
当认证失败时,服务支持回退到真实的 Web 服务器:
func (s *Service[K]) fallback(ctx, conn, source, header, err, onClose) error {
if s.fallbackHandler == nil {
return E.Extend(err, "fallback disabled")
}
// 将已读取的字节重新添加到连接中
conn = bufio.NewCachedConn(conn, buf.As(header).ToOwned())
s.fallbackHandler.NewConnectionEx(ctx, conn, source, M.Socksaddr{}, onClose)
return nil
}这对审查抵抗至关重要:如果探测器发送非 Trojan 数据,它会被转发到真实的 Web 服务器,使该服务与正常的 HTTPS 站点无法区分。
Mux 支持(Trojan-Go)
mux 实现使用 smux(Simple Multiplexer)以兼容 Trojan-Go:
func HandleMuxConnection(ctx, conn, source, handler, logger, onClose) error {
session, _ := smux.Server(conn, smuxConfig())
for {
stream, _ := session.AcceptStream()
go newMuxConnection(ctx, stream, source, handler, logger)
}
}每个 mux 流包含自己的命令字节和目标地址:
func newMuxConnection0(ctx, conn, source, handler) error {
reader := bufio.NewReader(conn)
command, _ := reader.ReadByte()
destination, _ := M.SocksaddrSerializer.ReadAddrPort(reader)
switch command {
case CommandTCP:
handler.NewConnectionEx(ctx, conn, source, destination, nil)
case CommandUDP:
handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, ...)
}
}smux 配置禁用了 keepalive:
func smuxConfig() *smux.Config {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
return config
}Inbound 实现
type Inbound struct {
inbound.Adapter
router adapter.ConnectionRouterEx
logger log.ContextLogger
listener *listener.Listener
service *trojan.Service[int]
users []option.TrojanUser
tlsConfig tls.ServerConfig
fallbackAddr M.Socksaddr
fallbackAddrTLSNextProto map[string]M.Socksaddr // 基于 ALPN 的回退
transport adapter.V2RayServerTransport
}基于 ALPN 的回退
Trojan 支持基于 ALPN 的回退目标,允许根据 TLS 协商的协议选择不同的回退目标:
func (h *Inbound) fallbackConnection(ctx, conn, metadata, onClose) {
if len(h.fallbackAddrTLSNextProto) > 0 {
if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded {
negotiatedProtocol := tlsConn.ConnectionState().NegotiatedProtocol
fallbackAddr = h.fallbackAddrTLSNextProto[negotiatedProtocol]
}
}
if !fallbackAddr.IsValid() {
fallbackAddr = h.fallbackAddr // 默认回退
}
metadata.Destination = fallbackAddr
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}kTLS 兼容性
inbound 在满足条件时启用 kTLS(内核 TLS):
tlsConfig, _ := tls.NewServerWithOptions(tls.ServerOptions{
KTLSCompatible: transport.Type == "" && !multiplex.Enabled,
// kTLS 仅在以下条件下启用:无 V2Ray 传输层且未启用多路复用
})Outbound 实现
type Outbound struct {
outbound.Adapter
key [56]byte // 预计算的 SHA224 key
multiplexDialer *mux.Client
tlsConfig tls.Config
tlsDialer tls.Dialer
transport adapter.V2RayClientTransport
}key 在构造时一次性计算:
outbound.key = trojan.Key(options.Password)连接流程
func (h *trojanDialer) DialContext(ctx, network, destination) (net.Conn, error) {
// 1. 建立连接:transport > TLS > 原始 TCP
var conn net.Conn
if h.transport != nil {
conn = h.transport.DialContext(ctx)
} else if h.tlsDialer != nil {
conn = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
} else {
conn = h.dialer.DialContext(ctx, "tcp", h.serverAddr)
}
// 2. 使用 Trojan 协议包装
switch network {
case "tcp":
return trojan.NewClientConn(conn, h.key, destination)
case "udp":
return bufio.NewBindPacketConn(
trojan.NewClientPacketConn(conn, h.key), destination)
}
}Early Data(延迟写入)
ClientConn 实现了 N.EarlyWriter,这意味着 Trojan 头部仅在第一次 Write() 调用时发送,与首个有效载荷合并:
func (c *ClientConn) Write(p []byte) (n int, err error) {
if c.headerWritten {
return c.ExtendedConn.Write(p)
}
err = ClientHandshake(c.ExtendedConn, c.key, c.destination, p)
c.headerWritten = true
n = len(p)
return
}配置示例
{
"type": "trojan",
"tag": "trojan-in",
"listen": "::",
"listen_port": 443,
"users": [
{ "name": "user1", "password": "my-secret-password" }
],
"tls": {
"enabled": true,
"server_name": "example.com",
"certificate_path": "/path/to/cert.pem",
"key_path": "/path/to/key.pem"
},
"fallback": {
"server": "127.0.0.1",
"server_port": 8080
},
"fallback_for_alpn": {
"h2": {
"server": "127.0.0.1",
"server_port": 8081
}
}
}