本文是 TINC 源码解读系列的第一篇,重点分析 TINC 的协议设计。TINC 采用分层架构,使用 Meta Protocol 处理控制平面,Packet Protocol 处理数据平面,SPTPS 提供额外的加密保证。

1. 协议概览

TINC 使用两个独立的协议层:

  • Meta Protocol - TCP 连接上的文本协议(连接建立、拓扑交换)
  • Packet Protocol - UDP 连接上的二进制协议(数据包转发)

协议版本

PROT_MAJOR = 17  // 主版本号(不兼容时变化)
PROT_MINOR = 7   // 次版本号(向后兼容)

2. Meta 协议 - 控制平面

消息类型枚举

Meta Protocol 定义了 20 多种消息类型,按功能分类:

// 认证阶段
ID              // 连接标识
METAKEY         // 元数据密钥交换
CHALLENGE       // 密码挑战
CHAL_REPLY      // 挑战回复
ACK             // 确认

// 连接管理
STATUS          // 状态信息
ERROR           // 错误消息
TERMREQ         // 终止请求

// 保活
PING            // 心跳 Ping
PONG            // 心跳 Pong

// 拓扑交换
ADD_SUBNET      // 添加子网
DEL_SUBNET      // 删除子网
ADD_EDGE        // 添加边/链路
DEL_EDGE        // 删除边/链路

// 密钥交换
KEY_CHANGED     // 密钥已更改
REQ_KEY         // 请求加密密钥
ANS_KEY         // 应答加密密钥

// 数据转发
PACKET          // TCP 转发数据包

// Tinc 1.1+ 扩展
REQ_PUBKEY      // 请求公钥
ANS_PUBKEY      // 应答公钥
SPTPS_PACKET    // SPTPS 加密包
UDP_INFO        // UDP 地址信息
MTU_INFO        // MTU 信息

核心消息格式

ID 消息 - 连接起点

格式: ID name version

示例: 0 mynode 17.7

含义:
├─ 0        - 消息类型标识
├─ mynode   - 节点名称
└─ 17.7     - 协议版本 (MAJOR.MINOR)

METAKEY 消息 - 加密参数协商

格式: METAKEY cipher digest keylength [加密的密钥]

示例: 1 aes-256-cbc sha512 32 [128字节加密数据]

含义:
├─ 1              - 消息类型
├─ aes-256-cbc    - 对称加密算法
├─ sha512         - HMAC 摘要算法
└─ 32             - 密钥长度(字节)

特点:
└─ 加密密钥使用对方的公钥进行 RSA 加密

CHALLENGE 消息 - 身份验证

格式: CHALLENGE <hash>

验证过程:
1. 接收方生成随机 challenge (16 字节)
2. 计算 hash = HMAC-SHA256(challenge, shared_key)
3. 发送 CHALLENGE hash
4. 发起方计算相同的 hash
5. 验证 hash 相等

ADD_EDGE 消息 - 拓扑通告

格式: ADD_EDGE node_from node_to address port weight options

示例: ADD_EDGE nodeA nodeB 192.168.1.100 655 100 0

解析:
├─ ADD_EDGE        - 消息类型
├─ nodeA           - 源节点
├─ nodeB           - 目标节点
├─ 192.168.1.100   - 目标地址(实际网络地址)
├─ 655             - UDP 端口
├─ 100             - 权重(用于 Dijkstra)
└─ 0               - 选项标志

选项标志:
├─ 0x0001: OPTION_INDIRECT   (间接连接)
├─ 0x0002: OPTION_TCPONLY    (仅 TCP)
├─ 0x0004: OPTION_PMTU_DISCOVERY
└─ 0x0008: OPTION_CLAMP_MSS

ADD_SUBNET 消息 - 子网通告

格式: ADD_SUBNET owner_node network/prefixlength

IPv4 示例: ADD_SUBNET node1 192.168.1.0/24
IPv6 示例: ADD_SUBNET node1 2001:db8::/32
MAC  示例: ADD_SUBNET node1 ff:ff:ff:ff:ff:ff

用途:
└─ 通告该节点负责路由的子网地址

REQ_KEY 和 ANS_KEY - 数据加密密钥交换

REQ_KEY 格式: REQ_KEY originator destination
含义: originator 请求与 destination 的通信密钥

ANS_KEY 格式: ANS_KEY from to cipher_id digest_id key mac_length
示例: ANS_KEY nodeB nodeA 1 3 4ae0b0a82d6e0078 16

解析:
├─ nodeB         - 密钥所有者
├─ nodeA         - 请求方
├─ 1             - 对称加密算法 ID
├─ 3             - HMAC 摘要算法 ID
├─ 4ae0...78     - 128 位加密密钥
└─ 16            - MAC 长度

PING 和 PONG - 保活消息

PING 格式: PING [随机数]
PONG 格式: PONG [随机数]

用途:
├─ 检测连接是否仍然活跃
├─ 防止连接因不活动而被关闭
├─ 检测单向连接失败
└─ 通过随机数防止已知明文攻击

3. Meta 协议握手流程

完整的连接建立过程

发起方                      响应方
─────────────────────────────────────

TCP 连接 ─────────────→

                  ← 接受连接

发送 ID ──────────→
                  
                  ✓ 验证版本

                  ← 发送 ID

✓ 验证版本

发送 METAKEY ─────→
(加密的元密钥)

                  ✓ 解密元密钥

                  ← 发送 METAKEY

✓ 解密元密钥

从此以后,所有通信使用
协商的加密密钥加密

发送 CHALLENGE ────→
(hash = HMAC(...))

                  ✓ 验证 hash

                  ← CHAL_REPLY

✓ 双向认证成功

发送 ACK ─────────→

                  ← ACK

✓ 连接完全建立

握手状态机

[TCP Connected]
    ↓
[ID Exchanged]
├─ 校验版本号
├─ 确定协议版本
└─ 准备加密
    ↓
[METAKEY Exchanged]
├─ 选择加密算法
├─ 交换元数据密钥
└─ 开始加密通信
    ↓
[CHALLENGE Issued]
├─ 单向认证
├─ 验证对方公钥
└─ 生成共享密钥
    ↓
[CHAL_REPLY Received]
├─ 双向认证完成
├─ 确认对方身份
└─ 建立信任
    ↓
[ACK Sent/Received]
├─ 连接活跃
├─ 准备数据交换
└─ 拓扑同步
    ↓
[ACTIVE]
├─ 双向 PING/PONG
├─ 转发 ADD_EDGE
├─ 转发 ADD_SUBNET
├─ 转发 REQ_KEY/ANS_KEY
└─ 转发数据包

4. Packet Protocol - 数据平面

UDP 数据包格式

加密前的包结构

Byte:  0-3         4-...           ...      ...+16
      +----------+---------------+--------+--------+
      | seqno    | payload       | padding| MAC    |
      | (32 bit) | (VPN data)    |        |(256bit)|
      +----------+---------------+--------+--------+
      +-----------------------------------+--------+
              Covered by HMAC

加密后的完整 UDP 包

+-------------------------------------------------+
| UDP Header (20 bytes)                           |
| +--- Source IP                                  |
| +--- Dest IP                                    |
| +--- Source Port (1194 or configured)           |
| +--- Dest Port                                  |
+-------------------------------------------------+
| Encrypted Payload (variable length)             |
| +--- Nonce/IV (optional)                        |
| +--- Ciphertext:                                |
| |    +--- seqno (sequence number)               |
| |    +--- VPN data (virtual network packet)     |
| |    +--- HMAC (message authentication code)    |
| +--- Authentication Tag (AEAD encryption)       |
+-------------------------------------------------+

VPN 数据包结构

虚拟网络上传输的是标准 Ethernet Frame:

+----------+----------+----------+---------+-----+
| Dest MAC | Src MAC  | EtherType| Payload | CRC |
| (6 Byte) | (6 Byte) | (2 Byte) |(var len)| (4) |
+----------+----------+----------+---------+-----+

EtherType:
+-- 0x0800: IPv4
+-- 0x86DD: IPv6
+-- 0x0806: ARP

支持的加密算法

对称加密:
├─ ChaCha20-Poly1305  (推荐,AEAD)
├─ AES-256-GCM        (AEAD)
├─ AES-256-CBC        (需要 HMAC)
└─ null cipher        (调试用,无加密)

消息摘要:
├─ SHA512             (推荐)
├─ SHA256
├─ SHA1
└─ MD5 (遗留,不安全)

密钥长度:
├─ 256 bit (标准)
└─ 128 bit (弱密钥,不推荐)

5. SPTPS 协议 - 安全的点对点通信

SPTPS 设计目的

SPTPS(Simple Peer-to-Peer Security)是一个轻量级的加密层,用于在 UDP 上建立直接的加密通道,不需要依赖 Meta Protocol 的握手。

与 Meta Protocol 的区别

  • Meta Protocol: 文本协议,TCP,用于控制平面
  • SPTPS: 二进制协议,UDP,用于数据平面持久化会话

SPTPS 记录类型

#define SPTPS_HANDSHAKE 128   // 密钥交换和认证
#define SPTPS_ALERT 129       // 警告或错误消息
#define SPTPS_CLOSE 130       // 应用程序关闭连接

SPTPS 握手状态

typedef enum sptps_state_t {
    SPTPS_KEX = 1,           // 等待第一个密钥交换记录
    SPTPS_SECONDARY_KEX = 2, // 准备接收二次密钥交换
    SPTPS_SIG = 3,           // 等待签名记录
    SPTPS_ACK = 4,           // 等待确认记录
} sptps_state_t;

SPTPS 密钥交换结构

struct sptps_kex_t {
    uint8_t version;           // 版本号
    uint8_t nonce[32];         // 随机数
    uint8_t pubkey[32];        // 临时 ECDH 公钥 (Curve25519)
}
// 总大小: 65 字节

SPTPS 握手流程

Initiator                   Responder
─────────────────────────────────────

生成临时密钥对
(Curve25519 ECDH)

发送 HANDSHAKE ────────────→
  payload: kex1
  (version, nonce, pubkey)

                           收到 kex1
                           生成临时密钥对
                           计算共享密钥

                           ← 发送 HANDSHAKE
                             payload: kex2

收到 kex2
计算共享密钥

验证: ECDH(A_priv, B_pub)
    == ECDH(B_priv, A_pub) ✓

使用 PRF 派生会话密钥

发送签名记录 ───────────────→
  签署握手数据

                           验证签名

                           ← 发送 ACK 记录

验证 ACK
握手完成 ✓

双向 AEAD 加密通信开始

密钥派生

Shared_Secret = ECDH(my_private_key, peer_public_key)

Session_Keys = PRF-SHA512(
    Shared_Secret,
    label = "SPTPS key expansion" 
          + initiator_pubkey 
          + responder_pubkey
)

派生密钥 (共 64 字节):
├─ Key_A (32 字节): Initiator → Responder
├─ Key_B (32 字节): Responder → Initiator
└─ 每个方向使用不同密钥 (前向保密)

6. 身份验证与安全机制

签名与验证

每个节点都有持久的 ECDSA/EdDSA 密钥对:

公钥位置: hosts 目录 ($TINC_HOME/hosts/nodename)
私钥位置: 配置目录 ($TINC_HOME/rsa_key.priv, ed25519_key.priv)

认证流程:
1. 交换 ID → 校验版本
2. 交换 METAKEY → 协商加密算法
3. 计算 challenge hash
4. 发送 CHALLENGE + CHAL_REPLY
5. 验证签名:数据已被对方的私钥签署
6. 发送 ACK

验证公钥:
├─ 从 hosts 目录读取对方的公钥
├─ 验证签名: Verify(signature, data, peer_pubkey)
├─ 若验证失败,拒绝连接
└─ 若验证成功,建立信任

重放保护

Meta Protocol 重放保护

├─ past_request_tree 跟踪已处理的请求
├─ 相同请求在一定时间内被忽略
└─ 防止攻击者重复发送拓扑更新

数据包重放保护

├─ 序列号 (seqno): 递增计数器
├─ 验证 seqno > last_seqno
├─ 丢弃重复包
└─ 使用滑动窗口防止乱序问题

7. 拓扑同步协议

完整的拓扑交换过程

当两个节点建立连接后,会进行完整的拓扑交换:

Node A ──────TCP──────→ Node B

1. 身份验证完成
   (ID, METAKEY, CHALLENGE, ACK)

2. Node A 发送其知道的拓扑:
   
   ADD_EDGE nodeA nodeC 10.0.0.3 655 50 0
   ADD_EDGE nodeA nodeD 10.0.0.4 655 100 0
   ADD_SUBNET nodeA 172.16.1.0/24
   ADD_SUBNET nodeC 172.16.2.0/24

3. Node B 发送其知道的拓扑:
   
   ADD_EDGE nodeB nodeE 10.0.0.5 655 50 0
   ADD_SUBNET nodeB 172.16.4.0/24
   ADD_SUBNET nodeE 172.16.5.0/24

4. 每当拓扑变化时:
   └─ 通过 ADD_EDGE/ADD_SUBNET 发布增量更新

5. 当边或节点失败时:
   └─ 广播 DEL_EDGE/DEL_SUBNET

拓扑维护机制

变化检测:
├─ 新节点加入 → ADD_EDGE + ADD_SUBNET
├─ 节点离开 → DEL_EDGE + DEL_SUBNET
├─ 链路权重变化 → ADD_EDGE (新权重)
└─ 密钥更新 → KEY_CHANGED

广播机制:
├─ 每个连接都转发其收到的拓扑消息
├─ 防环:记录消息源,不回复给源
└─ 定期同步:每个连接建立时交换完整拓扑

8. 协议扩展 (Tinc 1.1+)

新增消息类型

REQ_PUBKEY / ANS_PUBKEY

格式: REQ_PUBKEY from to [timestamp]
      ANS_PUBKEY from to [key_data]

用途:
├─ 请求/应答节点的公钥
├─ 用于拓展的密钥交换
└─ 支持在线密钥更新

UDP_INFO

格式: UDP_INFO originator address:port

用途:
├─ 通告节点可用的 UDP 地址
├─ 支持多地址(IPv4, IPv6)
└─ 加速 UDP 直连建立

MTU_INFO

格式: MTU_INFO node mtu

用途:
├─ 通告路径的最大传输单元
├─ 支持 PMTU 发现
└─ 优化包大小

SPTPS_PACKET

用途:
├─ 使用 SPTPS 加密的 TCP 包
├─ 在 meta 协议上层的加密
└─ 增强安全性

9. 安全性分析

已实现的安全特性

特性 实现 说明
前向保密 (PFS) ECDH 临时密钥,会话独立
数据完整性 HMAC/AEAD 认证
数据机密性 对称加密 (AES/ChaCha20)
重放保护 序列号 + 滑动窗口
身份认证 ECDSA/EdDSA 签名
密钥交换 ECDH (P-256/Curve25519)
已知明文保护 Ping/Pong 随机值

潜在的风险和对策

风险 严重度 现状
密钥重用 支持密钥轮换 (KEY_CHANGED)
时钟偏差 支持时钟偏差检测
实现漏洞 定期安全审计
算法选择 支持现代算法 (ChaCha20)
密钥缓存 支持定期过期

10. 连接到完整通信的时间线

时间   事件
──────────────────────────────────────────────
0ms    TCP 连接建立
       ↓
10ms   交换 ID (版本协商)
       ↓
20ms   交换 METAKEY (加密参数)
       ↓
30ms   发送 CHALLENGE (单向认证)
       ↓
40ms   接收 CHAL_REPLY (双向认证)
       ↓
50ms   从此以后所有通信加密
       ↓
60ms   交换 ADD_EDGE (拓扑)
       ↓
100ms  交换 ADD_SUBNET (子网)
       ↓
150ms  定期 PING/PONG (保活)
       ↓
∞      数据转发 & 拓扑维护

11. 协议调试

启用协议日志

# 启用详细的协议调试输出
tincd -n myvpn -d DEBUG

# 典型输出示例:
[DEBUG] Sending ID to node1: "0 mynode 17.7"
[DEBUG] Sending METAKEY to node1: cipher=1 digest=3 maclength=16
[DEBUG] Sending CHALLENGE to node1
[DEBUG] Sending ACK to node1
[DEBUG] Sending ADD_EDGE node1 node2 192.168.1.2 655 100 0
[DEBUG] Sending ADD_SUBNET node2 172.16.2.0/24
[DEBUG] Sending PING to node1
[DEBUG] Received PONG from node1

网络包捕获

# 捕获 Meta Protocol (TCP 655)
tcpdump -i eth0 -n tcp port 655

# 捕获 Packet Protocol (UDP 1194)
tcpdump -i eth0 -n udp port 1194

# 使用 Wireshark 分析
wireshark -i eth0

总结

TINC 协议是一个分层的、设计精良的 VPN 协议体系:

核心设计

Meta Protocol (控制平面):

  • TCP 文本协议,用于控制和管理
  • 握手、认证、拓扑交换
  • 可靠但相对低频

Packet Protocol (数据平面):

  • UDP 二进制协议,用于数据转发
  • 高效、低延迟
  • 支持快速加密/解密

SPTPS (加密层):

  • 额外的 UDP 加密层
  • 前向保密和轻量级握手
  • 支持直接 P2P 加密通信

安全特性

✅ 现代密钥交换 (ECDH)
✅ 强力数字签名 (ECDSA/EdDSA)
✅ 前向保密 (PFS)
✅ 重放保护
✅ 完整性认证 (HMAC/AEAD)
✅ 灵活的算法选择

性能考虑

  • Meta Protocol 低频交互,控制开销小
  • Packet Protocol 用 UDP,转发延迟低
  • 数据密钥缓存,避免频繁协商
  • 支持直接 P2P(避免中继)

这种精心设计的两层协议架构使 TINC 成为一个既可靠又高效的 VPN 解决方案,是现代 VPN 软件的最佳实践。


系列预告