23/ZMTP
ZeroMQ 消息传输协议
- 状态:稳定
- 编辑:Pieter Hintjens ph@imatix.com
- 贡献者:Martin Hurton mh@imatix.com,Ian Barber ian.barber@gmail.com
ZeroMQ 消息传输协议(ZMTP)是一种传输层协议,用于通过已连接的传输层(例如 TCP)在两个对等方之间交换消息。本文档描述 ZMTP 3.0。此版本的主要变化是增加了安全机制,并从问候语中移除了硬编码的连接元数据(套接字类型和身份)。
前言
版权所有 (c) 2009-2014 iMatix Corporation 及贡献者
本规范是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证(第三版或您选择的任何更高版本)的条款重新分发和/或修改它。分发本规范是希望它有用,但**不附带任何担保**;甚至不包括适销性或特定用途适用性的暗示担保。请参阅 GNU 通用公共许可证以获取更多详细信息。您应该已随程序收到一份 GNU 通用公共许可证的副本;如果没有,请参阅https://gnu.ac.cn/licenses。
本规范是自由开放标准,受数字标准组织 (Consensus-Oriented Specification System) 的共识导向规范系统管辖。
本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照RFC 2119中的描述进行解释。
目标
ZeroMQ 消息传输协议(ZMTP)是一种传输层协议,用于通过已连接的传输层(例如 TCP)在两个对等方之间交换消息。本文档描述 ZMTP 的 3.0 版本。ZMTP 解决了我们在使用 TCP 承载消息时面临的许多问题:
-
TCP 承载的是没有分隔符的字节流,但我们希望发送和接收离散的消息。因此,ZMTP 读取和写入由大小和主体组成的**帧**。
-
我们需要在每个帧上携带元数据(例如,帧是否是多分部消息的一部分)。ZMTP 在每个帧中提供了一个**标志**字段用于元数据。
-
我们需要能够与旧的实现通信,以便我们的帧结构可以在不破坏现有实现的情况下演进。ZMTP 定义了一个**问候语**,它宣布了实现的版本号,并指定了版本协商的方法。
-
我们需要安全性,以便对等方可以确定与其通信的对等方的身份,并且消息不会被第三方篡改或检查。ZMTP 定义了一个**安全握手**,允许对等方建立安全连接。
-
我们需要一系列安全协议,从明文(无安全,但速度快)到完全认证和加密(安全,但速度慢)。此外,随着时间的推移,我们需要自由地添加新的安全协议。ZMTP 定义了一种允许对等方就可扩展的**安全机制**达成一致的方式。
-
我们需要一种在安全握手后携带连接元数据的方式。ZMTP 定义了一组标准的**元数据属性**(套接字类型、身份等),对等方在安全机制后进行交换。
-
我们需要以易于团队在任何平台和任何语言上实现的方式写下这些解决方案。因此,ZMTP 被指定为一个形式化协议(本文档),并以自由许可证提供给团队。
-
我们需要保证人们不会创建 ZMTP 的私有分支,从而破坏互操作性。因此,ZMTP 在 GPLv3 下授权,以便任何派生版本也必须对实现它的软件用户可用。
ZMTP 2.0 的变更
本规范 3.0 版本的变化包括:更简单的版本编号方案、增加了安全机制、从问候语中移除了硬编码的连接元数据(套接字类型和身份)、增加了连接元数据以及增加了命令。
相关规范
- https://rfc.zeromq.cn/spec:24/ZMTP-PLAIN 定义了 ZMTP-PLAIN 安全机制。
- https://rfc.zeromq.cn/spec:25/ZMTP-CURVE 定义了 ZMTP-CURVE 安全机制。
- https://rfc.zeromq.cn/spec:26/CURVEZMQ 定义了 CurveZMQ 认证和加密协议。
- https://rfc.zeromq.cn/spec:27/ZAP 定义了 ZeroMQ 认证协议。
- https://rfc.zeromq.cn/spec:28/REQREP 定义了 REQ、REP、DEALER 和 ROUTER 套接字的语义。
- https://rfc.zeromq.cn/spec:29/PUBSUB 定义了 PUB、XPUB、SUB 和 XSUB 套接字的语义。
- https://rfc.zeromq.cn/spec:30/PIPELINE 定义了 PUSH 和 PULL 套接字的语义。
- https://rfc.zeromq.cn/spec:31/EXPAIR 定义了独占 PAIR 套接字的语义。
- https://rfc.zeromq.cn/spec:37/ZMTP 定义了本规范的 3.1 版本。
实现
整体行为
一个 ZMTP 连接经历以下主要阶段:
-
两个对等方通过互相发送数据来协商连接的版本和安全机制,然后继续讨论或关闭连接。
-
两个对等方通过交换零个或多个命令来执行安全机制握手。如果安全握手成功,对等方继续讨论;否则,一个或两个对等方关闭连接。
-
每个对等方随后作为最终命令发送关于连接的元数据给对方。对等方可以检查元数据,并且每个对等方决定继续或关闭连接。
-
然后,每个对等方能够向对方发送消息。任何对等方可以在任何时候关闭连接。
形式化文法
以下 ABNF 文法定义了该协议:
; The protocol consists of zero or more connections
zmtp = *connection
; A connection is a greeting, a handshake, and traffic
connection = greeting handshake traffic
; The greeting announces the protocol details
greeting = signature version mechanism as-server filler
signature = %xFF padding %x7F
padding = 8OCTET ; Not significant
version = version-major version-minor
version-major = %x03
version-minor = %x00
; The mechanism is a null padded string
mechanism = 20mechanism-char
mechanism-char = "A"-"Z" | DIGIT
| "-" | "_" | "." | "+" | %x0
; Is the peer acting as server?
as-server = %x00 | %x01
; The filler extends the greeting to 64 octets
filler = 31%x00 ; 31 zero octets
; The handshake consists of at least one command
; The actual grammar depends on the security mechanism
handshake = 1*command
; Traffic consists of commands and messages intermixed
traffic = *(command | message)
; A command is a single long or short frame
command = command-size command-body
command-size = %x04 short-size | %x06 long-size
short-size = OCTET ; Body is 0 to 255 octets
long-size = 8OCTET ; Body is 0 to 2^63-1 octets
command-body = command-name command-data
command-name = OCTET 1*255command-name-char
command-name-char = ALPHA
command-data = *OCTET
; A message is one or more frames
message = *message-more message-last
message-more = ( %x01 short-size | %x03 long-size ) message-body
message-last = ( %x00 short-size | %x02 long-size ) message-body
message-body = *OCTET
版本协商
ZMTP 提供非对称版本协商。一个 ZMTP 对等方**可以**尝试检测并使用旧版本的协议。它也**可以**要求其对等方具备 ZMTP 能力。
在第一种情况下,在建立或接收连接后,对等方**必须**向对方发送足以触发版本检测的部分问候语。这是问候语的前 11 个字节(签名和主要版本号)。然后,对等方**必须**读取对方发送的问候语的前十一个字节,并确定是否降级。每种旧 ZMTP 版本的具体启发式方法在“向下兼容性”一节中解释。在这种情况下,对等方**可以**使用填充字段进行旧协议检测(我们在下面解释了已知的具体情况)。
在第二种情况下,在建立或接收连接后,对等方**必须**发送其完整的问候语(64 个字节),并且**必须**期望接收一个匹配的 64 字节问候语。在这种情况下,对等方**应该**将填充字段设置为二进制零。
无论哪种情况,请注意:
-
对等方**不得**赋予填充字段任何重要意义,并且**不得**以任何方式验证或解释它。
-
对等方**必须**接受更高协议版本为有效。也就是说,ZMTP 对等方**必须**接受大于或等于 3.0 的协议版本。这允许未来的实现与当前实现安全地互操作。
-
对等方在与相同或更高协议的对等方通信时,**必须**始终使用自己的协议(包括帧结构)。
-
对等方**可以**降低其协议版本以与更低协议的对等方通信。
-
如果对等方无法降低其协议版本以匹配其对等方,它**必须**关闭连接。
拓扑
ZMTP 默认是一种对等协议,不区分客户端和服务器。
然而,安全机制(作为扩展协议,并在下面解释)**可以**为客户端对等方和服务器对等方定义不同的角色。这种差异反映了将认证集中在服务器上的通用模型。
在传统的 TCP 拓扑中,“服务器”是绑定的对等方,“客户端”是连接的对等方。ZMTP 允许这种模式,但也允许相反的拓扑,即客户端绑定,服务器连接。因此,实际的服务器角色不是由绑定/连接方向定义的,而是在问候语中的as-server字段中指定,服务器对等方该字段设置为 1,客户端对等方设置为 0。
如果选择的安全机制未指定客户端和服务器拓扑,则 as-server 字段**必须**没有意义,并且所有对等方**应该**为零。
认证与保密性
ZMTP 通过使用协商的安全机制提供可扩展的认证和保密性,该机制 loosely 基于 IETF Simple Authentication and Security Layer (SASL)。对等方**可以**支持以下任何或所有机制:
-
“NULL”,本文档后面指定,不实现认证和保密性。
-
“PLAIN”,由 rfc.zeromq.org/spec:24/ZMTP-PLAIN 指定,实现明文的简单用户名和密码认证。
-
“CURVE”,由 rfc.zeromq.org/spec:25/ZMTP-CURVE 指定,使用 CurveZMQ 安全协议实现完整的认证和保密性。
安全机制是一个 ASCII 字符串,必要时填充空字节以适合 20 个字节。实现**可以**定义自己的机制用于实验和内部使用。所有旨在公共互操作性的机制**必须**定义为 0MQ RFC。机制名称**必须**按先到先得的方式分配。机制名称**必须**仅由大写字母 A 到 Z、数字以及嵌入的连字符或下划线组成。
与 SASL 不同,对等方只宣布一种安全机制,SASL 允许服务器宣布多种安全机制。ZMTP 中的安全性是**断言性的**,即给定套接字上的所有对等方都具有相同的、必需的安全级别。这可以防止降级攻击并简化实现。
每种安全机制定义了一个协议,由零个或多个由任一对等方发送给对方的命令组成,直到握手完成或任一对等方拒绝继续并关闭连接。
命令是单帧,由大小字段和主体组成。如果主体为 0-255 字节,命令**应该**使用短大小字段(%x04 后面跟 1 个字节)。如果主体为 256 个或更多字节,命令**应该**使用长大小字段(%x06 后面跟 8 个字节)。
大小字段总是明文。主体可以是部分或完全加密的。ZMTP 不定义命令的语法或语义。这些完全由安全机制协议定义。
支持的机制不被视为敏感信息。读取完整问候语(包括机制)的对等方**必须**也发送包含机制的完整问候语。这避免了两个对等方互相等待对方透露问候语其余部分而发生的死锁。
如果对等方收到的机制与它发送的机制不完全匹配,它**必须**关闭连接。
错误处理
ZMTP 允许在机制握手期间使用 ERROR 命令显式发送致命错误响应。对等方**必须**将收到的 ERROR 命令视为致命错误,并通过关闭连接来处理,并且不要使用相同的安全凭据重新连接。
实现**应该**通过关闭连接来指示任何其他错误,例如过载、暂时拒绝连接等。对等方**必须**将意外的连接关闭视为临时错误,并且**应该**重新连接。
为了避免连接风暴,对等方应在短暂且可能随机的间隔后重新连接。此外,如果对等方重新连接超过一次,它应该增加重新连接之间的延迟。有多种策略可行。
帧结构
在固定大小为 64 个字节的问候语之后,所有后续数据都以**帧**的形式发送,帧携带命令或消息。帧由大小字段、标志字段和主体组成。帧设计旨在对小帧高效,同时也能处理极大的数据。
帧由一个标志字段(1 个字节),随后是一个大小字段(一个字节或八个字节)以及一个大小为 size 的帧主体。大小不包括标志字段或其自身,因此空帧的大小为零。
短帧的主体为 0 到 255 个字节。长帧的主体为 0 到 2^63-1 个字节。
标志字段由一个包含各种控制标志的字节组成。Bit 0 是最低有效位(最右边的位)。
-
Bits 7-3:**保留**。Bits 7-3 保留供将来使用,并且**必须**为零。
-
Bit 2 (COMMAND):**命令帧**。值为 1 表示该帧是命令帧。值为 0 表示该帧是消息帧。
-
Bit 1 (LONG):**长帧**。值为 0 表示帧大小编码为一个字节。值为 1 表示帧大小编码为网络字节序的 64 位无符号整数。
-
Bit 0 (MORE):**后续还有更多帧**。值为 0 表示后续没有更多帧。值为 1 表示后续还有更多帧。在命令帧上,此位**必须**为零。
命令
命令由 ZMTP 实现使用,通常对应用程序不可见,但在某些情况下除外。命令始终由一个帧组成,包含可打印的命令名称、一个空字节分隔符和数据。
这些是本规范定义的命令:
- READY 和 ERROR - 实现 NULL 安全握手,请参见下面的“NULL 安全机制”。
ZMTP 支持可扩展的安全机制,这些机制可以定义自己的命令。安全机制**可以**使用它们所需的任何命令名称。
消息
消息携带应用程序数据,通常不由 ZMTP 实现创建、修改或过滤,但在某些情况下除外。消息由一个或多个帧组成,实现**必须**始终原子地发送和交付消息,即,要么发送或交付消息的所有帧,要么都不发送或交付。
NULL 安全机制
NULL 机制不实现认证和保密性。在没有传输层安全(例如 VPN)的公共基础设施上**不应**使用 NULL 机制。
要完成 NULL 安全握手,两个对等方**必须**互相发送一个 READY 命令。两个对等方**应该**随后解析,并且**可以**验证 READY 命令。一个或两个对等方随后**可以**选择在验证失败时关闭连接。对等方**可以**在其发送元数据后立即开始发送消息。
当对等方使用 NULL 安全机制时,as-server 字段**必须**为零。以下 ABNF 文法定义了 NULL 安全握手:
null = ready *message | error
ready = command-size %d5 "READY" metadata
metadata = *property
property = name value
name = OCTET 1*255name-char
name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
value = 4OCTET *OCTET ; Size in network byte order
error = command-size %d5 "ERROR" error-reason
error-reason = OCTET 0*255VCHAR
消息和命令大小由先前解释的 ZMTP 文法定义。
READY 命令的主体由属性列表组成,每个属性由名称和值组成,以大小指定的字符串形式表示。
名称**必须**为 1 到 255 个字符。零大小的名称无效。名称的大小写(大写或小写)**不得**具有重要意义。
值**必须**为 0 到 2,147,483,647(C/C++ 中的 2^31-1 或 INT32_MAX)个不透明二进制字节。允许零大小的值。值的语义取决于属性。值大小字段**必须**为四个字节,采用网络字节序。请注意,此大小字段在内存中大多不会对齐。
请注意,为了避免死锁,每个对等方**必须**在其尝试从另一对等方接收 READY 之前发送其 READY 命令。在 NULL 机制中,对等方是对称的。以下是两个对等方 P 和 R 的命令流程:
P:greeting R:greeting
P:ready R:ready
P:message... R:message...
ERROR 命令的主体包含可供记录的错误原因。它没有定义的语义值。
连接元数据
安全机制**必须**提供一种方式,使对等方能够以键值字典的形式交换元数据。元数据的具体编码取决于机制。
元数据名称**必须**不区分大小写。
定义了以下元数据属性:
-
“Socket-Type”,指定发送方的套接字类型。参见下面的“套接字类型属性”一节。发送方**应该**指定 Socket-Type。
-
“Identity”,指定发送方的套接字身份。参见下面的“身份属性”一节。发送方**可以**指定一个 Identity。
实现**可以**提供其他元数据属性,例如实现名称、平台名称等。为了互操作性,元数据名称和语义**可以**定义为 RFC。
以“X-”开头的元数据名称**必须**保留供应用程序使用。
套接字类型属性
Socket-Type 声明发送对等方的 ZeroMQ 套接字类型。Socket-Type **必须**匹配此文法:
socket-type = "REQ" | "REP"
| "DEALER" | "ROUTER"
| "PUB" | "XPUB"
| "SUB" | "XSUB"
| "PUSH" | "PULL"
| "PAIR"
对等方**应该**强制要求另一对等方使用有效的套接字类型。此表显示了合法的组合(以“*”表示):
| REQ | REP | DEALER | ROUTER | PUB | XPUB | SUB | XSUB | PUSH | PULL | PAIR |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
REQ | | * | | * | | | | | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
REP | * | | * | | | | | | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
DEALER | | * | * | * | | | | | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
ROUTER | * | | * | * | | | | | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
PUB | | | | | | | * | * | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
XPUB | | | | | | | * | * | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
SUB | | | | | * | * | | | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
XSUB | | | | | * | * | | | | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
PUSH | | | | | | | | | | * | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
PULL | | | | | | | | | * | | |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
PAIR | | | | | | | | | | | * |
-------+-----+-----+--------+--------+-----+------+-----+------+------+------+------+
当对等方验证套接字类型时,它**应该**通过返回一个 ERROR 命令,然后断开与对等方的连接来处理错误。
身份属性
连接到 ROUTER 的 REQ、DEALER 或 ROUTER 对等方**可以**声明其身份,这被 ROUTER 套接字用作寻址机制。对于所有其他套接字类型,Identity 属性将被忽略。
Identity **必须**匹配此文法:
identity = 0*255OCTET
套接字语义
为了确保互操作性,我们旨在定义面向调用应用程序的套接字特定 API 语义,以及对等方之间通过网络通信的行为。
这些规则适用于所有套接字:
-
所有套接字**必须**接受连接(绑定到地址)并建立连接。
-
所有套接字**必须**机会性地建立连接,即:它们异步连接到端点,如果连接断开,**应该**在适当的延迟后重新连接。
-
消息**必须**原子地发送或接收;也就是说,要么发送或接收所有帧,要么都不发送或接收。在发送时,对等方**必须**在内存中排队消息的所有帧,直到发送最后一个帧为止。
-
消息**不得**向任何对等方交付多次。
-
两个直接对等方之间的所有消息**必须**按顺序交付。
请求-应答模式
实现**应该**遵循https://rfc.zeromq.cn/spec:28/REQREP关于 REQ、REP、DEALER 和 ROUTER 套接字的语义。
发布-订阅模式
实现**应该**遵循https://rfc.zeromq.cn/spec:29/PUBSUB关于 PUB、XPUB、SUB 和 XSUB 套接字的语义。
使用 ZMTP 时,消息过滤**必须**发生在发布者端(PUB 或 XPUB 套接字)。要创建订阅,SUB 或 XSUB 对等方**必须**发送一个 SUBSCRIBE 消息,其文法如下:
subscribe = %x00 short-size %d1 subscription
subscription = *OCTET
要取消订阅,SUB 或 XSUB 对等方**必须**发送一个取消订阅消息,其文法如下:
unsubscribe = %x00 short-size %d0 subscription
订阅是一个二进制字符串,指定订阅者想要什么消息。订阅“A”**必须**匹配所有以“A”开头的消息。空订阅**必须**匹配所有消息。
订阅**必须**是累加的,并且**不得**是幂等的。也就是说,订阅“A”和“”与单独订阅“”相同。订阅“A”和“A”算作两次订阅,需要发送两次取消订阅消息才能撤销。
管道模式
实现**应该**遵循https://rfc.zeromq.cn/spec:30/PIPELINE关于 PUSH 和 PULL 套接字的语义。
独占对模式
实现**应该**遵循https://rfc.zeromq.cn/spec:31/EXPAIR关于独占 PAIR 套接字的语义。
向下兼容性
为了检测并使用旧版本的 ZMTP,我们定义了两种策略;一种只检测 2.0 对等方,另一种检测 1.0 和 2.0 对等方。
当对等方不需要向下兼容性时,它**应该**在建立新的对等连接后立即发送其完整的问候语。
检测 ZMTP 2.0 对等方
从 ZMTP 2.0 开始,协议包含紧随签名之后的版本号。为了检测 ZMTP 2.0 对等方并与其互操作,实现**可以**使用此策略:
-
发送 10 字节签名,后跟主要版本号(单个字节 %x03)。
-
等待另一对等方发送其问候语。
-
如果对等方版本号是 1 或 2,则对等方正在使用 ZMTP 2.0,因此发送 ZMTP 2.0 套接字类型和身份,并继续使用 ZMTP 2.0。
-
如果对等方版本号是 3 或更高,则对等方正在使用 ZMTP 3.0,因此发送问候语的其余部分,并继续使用 ZMTP 3.0。
以下是两个 ZMTP 3.0 对等方使用此算法时的命令序列:
C:signature + major-version S:signature + major-version
C:rest of greeting S:rest of greeting
C:ready S:ready
C:message... S:message...
检测 ZMTP 1.0 和 2.0 对等方
ZMTP 1.0 没有任何版本信息。为了检测 ZMTP 1.0 和 2.0 对等方并与其互操作,实现**可以**使用此策略:
-
发送一个 10 字节的伪签名,由“%xFF 大小 %x7F”组成,其中“大小”是发送方身份的字节数(0 或更大)加 1。“大小”**必须**是网络字节序的 8 个字节,并占用填充字段。
-
读取另一对等方的第一个字节。
-
如果第一个字节不是 %FF,则另一对等方正在使用 ZMTP 1.0,并已发送其身份的短帧长度。我们读取该数量的字节。
-
如果第一个字节是 %FF,则我们再读取九个字节,并检查最后一个字节(总共第 10 个)。如果最低有效位是 0,则另一对等方正在使用 ZMTP 1.0,并已发送其身份的长长度。我们读取该数量的字节。
-
如果最低有效位是 1,则对等方正在使用 ZMTP 2.0 或更高版本,并已发送 ZMTP 签名。我们再读取一个字节,指示 ZMTP 版本。如果这个版本是 1 或 2,则我们有一个 ZMTP 2.0 对等方。如果这个版本是 3,则我们有一个 ZMTP 3.0 对等方。
-
如果实现的安全性机制不是 NULL,并且检测到 ZMTP 1.0 或 2.0 对等方,它**必须**立即关闭连接。ZMTP 1.0 或 2.0 对等方只能请求 NULL 安全。
-
当我们检测到 ZMTP 1.0 对等方时,我们已经发送了 10 个字节,另一对等方将其解释为身份帧的开头。我们通过发送身份帧的主体(零个或更多字节)继续。从那时起,我们在该连接上使用 ZMTP 1.0 帧语法编码和解码所有帧。
-
当我们检测到 ZMTP 2.0 对等方时,我们继续进行版本协商,如上文所述,通过发送我们的版本号,然后根据 ZMTP 2.0 规范定义的套接字类型和身份。
-
当我们检测到 ZMTP 3.0 对等方时,我们继续发送问候语的其余部分(字节 10-64),然后像往常一样继续进行安全握手。
示例
一个 DEALER 客户端连接到 ROUTER 服务器。客户端和服务器都运行 ZMTP,并且实现具备向下兼容性检测。对等方将使用 NULL 安全机制互相通信。
- 客户端向服务器发送一个部分问候语(11 个字节),同时(在从客户端接收任何内容之前)服务器也发送一个部分问候语:
signature major
+------+-------------+------+------+
| %xFF | %x00...%x00 | %x7F | %x03 |
+------+-------------+------+------+
0 1 - 8 9 10
- 客户端和服务器读取主要版本号(%x03),并互相发送其问候语的其余部分:
minor mechanism as-server filler
+------+-----------+---------+-------------+
| %x00 | "NULL" | %x00 | %x00...%x00 |
+------+-----------+---------+-------------+
11 12 - 31 32 33 - 63
- 客户端和服务器现在执行 NULL 安全握手。首先,客户端向服务器发送一个 READY 命令,指定“DEALER”Socket-Type 和空的 Identity 属性:
+------+----+
| %x04 | 41 |
+------+----+
0 1
flags size
+------+---+---+---+---+---+
| %x05 | R | E | A | D | Y |
+------+---+---+---+---+---+
2 3 4 5 6 7
Command name "READY"
+----+---+---+---+---+---+---+---+---+---+---+---+
| 11 | S | o | c | k | e | t | - | T | y | p | e |
+----+---+---+---+---+---+---+---+---+---+---+---+
8 9 10 11 12 13 14 15 16 17 18 19
Property name "Socket-Type"
+------+------+------+------+---+---+---+---+---+---+
| %x00 | %x00 | %x00 | %x06 | D | E | A | L | E | R |
+------+------+------+------+---+---+---+---+---+---+
20 21 22 23 24 25 26 27 28 29
Property value "DEALER"
+----+---+---+---+---+---+---+---+---+
| 8 | I | d | e | n | t | i | t | y |
+----+---+---+---+---+---+---+---+---+
30 31 32 33 34 35 36 37 38
Property name "Identity"
+------+------+------+------+
| %x00 | %x00 | %x00 | %x00 |
+------+------+------+------+
39 40 41 42
Property value ""
- 服务器验证套接字类型,接受它,并回复一个只包含 Socket-Type 属性的 READY 命令(ROUTER 套接字不发送身份):
+------+----+
| %x04 | 28 |
+------+----+
+------+---+---+---+---+---+
| %x05 | R | E | A | D | Y |
+------+---+---+---+---+---+
+----+---+---+---+---+---+---+---+---+---+---+---+
| 11 | S | o | c | k | e | t | - | T | y | p | e |
+----+---+---+---+---+---+---+---+---+---+---+---+
+------+------+------+------+---+---+---+---+---+---+
| %x00 | %x00 | %x00 | %x06 | R | O | U | T | E | R |
+------+------+------+------+---+---+---+---+---+---+
一旦服务器发送了自己的 READY 命令,它也可以向客户端发送消息。一旦客户端收到服务器的 READY 命令,它也可以向服务器发送消息。
连接的线路分析
根据安全机制,ZMTP 连接可以是明文或加密的。在明文连接上,消息数据**必须**作为消息帧发送。所有明文机制**必须**使用本规范中定义的消息帧结构,以便线路分析不需要了解特定的机制。
在加密连接上,消息数据**必须**编码为命令,以便线路分析不可能进行。
在所有 ZMTP 连接上,命令名称**必须**可见,并且命令帧**必须**是可打印的。
安全注意事项
-
实现**必须**防范降级攻击,这可能采取伪造 ZMTP 1.0 或 2.0 协议头,或要求比应用程序要求的安全机制级别更低的形式。
-
攻击者可能通过重复连接尝试来淹没对等方。因此,对等方**可以**记录失败的访问,并且**可以**检测和阻止来自特定源 IP 地址的重复失败连接。
-
攻击者可能通过保持连接打开来尝试导致对等方的内存耗尽。因此,对等方**可以**仅在安全握手完成后才为连接分配内存,并且**可以**限制正在进行的握手数量和成本。
-
攻击者可能尝试发出小型请求,从而生成大量响应发回伪造的源地址(攻击的实际目标)。这被称为“放大攻击”。因此,对等方**可以**限制每个源 IP 地址正在进行的握手数量。
-
攻击者可能尝试从实现的元数据中发现易受攻击的版本。因此,ZMTP 在安全握手**之后**发送元数据。
-
攻击者即使不破解加密,也可以利用消息的大小来收集有关其含义的信息。因此,在加密连接上,消息数据**应该**填充到随机的最小大小。
-
攻击者可以利用流量是否存在来收集有关对等方的信息(“人员 X 已上线”)。因此,在加密连接上,当没有其他流量时,对等方**应该**发送随机的垃圾数据(“噪声”)。为了完全掩盖流量,对等方**可以**对消息进行限流,以便噪声和真实数据之间没有可见的区别。